""" A fixed-size dialog box for displaying a narrator's words, or a conversation. - Draw text gradually across the page, filling it all in very fast if the player clicks the mouse or hits the action button. - Optionally, draw a throbber (animated gif) on the side of the page. - Font face and color can be specified """ import wx import os import xml.dom.minidom import JoyTracker import UIUtils import UIImages import Config from Utils import * from Global import Log, LogException class ConversationFrame: def __init__(self, Speaker = "", ThrobberName = None, Text = ""): self.Text = Text self.ThrobberName = ThrobberName self.Speaker = Speaker self.ThrobberDelay = 0.4 def ParseFromXML(self, Node): self.Speaker = Node.getAttribute("name") self.ThrobberName = Node.getAttribute("image") self.Text = GetXMLNodeText(Node) try: self.ThrobberDelay = float(Node.getAttribute("delay")) except: pass class ConversationClass: """ A conversation represents one of the "plot points" of the game; these constitute the story script. It consists of one or more frames. A frame has some text associated with it, font info, a speaker (optional) and a throbber name. The throbber (if any) appears to the left of the text. """ def __init__(self): self.Name = "" self.Frames = [] def ParseFromXML(self, Node): self.Name = Node.getAttribute("name") FrameNodes = Node.getElementsByTagName("frame") for FrameNode in FrameNodes: Frame = ConversationFrame() Frame.ParseFromXML(FrameNode) self.Frames.append(Frame) AllConversations = None def LoadConversations(ConversationFilePath): global AllConversations AllConversations = {} File = open(ConversationFilePath, "rb") XML = File.read() File.close() DOMRoot = xml.dom.minidom.parseString(XML) ConversationNodes = DOMRoot.getElementsByTagName("conversation") for Node in ConversationNodes: Conversation = ConversationClass() Conversation.ParseFromXML(Node) if not Conversation.Name or AllConversations.has_key(Conversation.Name): Log("* Warning: Conversation file %s has bogus name %s"%(ConversationFilePath, Conversation.Name)) AllConversations[Conversation.Name] = Conversation def ShowConversation(Parent, Name): if AllConversations == None: LoadConversations(os.path.join("quests", "Dot", "Story.xml")) Conversation = AllConversations.get(Name, None) if not Conversation: Log("* Error: Bogus conversation '%s' requested!"%Name) return Dialog = TalkingDialog(Parent, Conversation) Dialog.ShowModal() ##TestConversation = ConversationClass() ##TestFrame = ConversationFrame("Speaker", "Question", "Abokestok seuiobt osebuit\nse q90enf qiopnqe pgnwoeingwe ogiwegwe") ##TestConversation.Frames.append(TestFrame) ##TestFrame = ConversationFrame("Speaker2", None, "2462i4 462- 6234ni 02egn 204gh20 i230ibg230bgi230bgi23\n023i 03 i02i23\n ...\nqobqoj") ##TestConversation.Frames.append(TestFrame) ##TestFrame = ConversationFrame("Speaker", "Question", "Abokestok seuiobt osebuit\nse q90enf qiopnqe pgnwoeingwe ogiwegwe") ##TestConversation.Frames.append(TestFrame) class TalkingDialog(wx.Dialog, JoyTracker.JoyTracker): SlowTextSpeed = 50 FastTextSpeed = 7 def __init__(self, Parent, Conversation, *args, **kw): try: wx.Dialog.__init__(self, Parent, title = "Retromancer", style = wx.CAPTION | wx.THICK_FRAME | wx.CLIP_CHILDREN | wx.WANTS_CHARS, size = (550, 300), *args, **kw) self.Panel = None JoyTracker.JoyTracker.__init__(self) self.BackgroundColor = "black" self.ForegroundColor = "white" self.SetBackgroundColour(self.BackgroundColor) self.BigFont = wx.Font(14, wx.SWISS, wx.NORMAL, wx.NORMAL) self.FocusGamepadState = self.GetRawGamepad() self.Conversation = Conversation self.BuildWidgets() self.TextSpeed = self.SlowTextSpeed self.Timer = wx.CallLater(self.SlowTextSpeed, self.OnTimer) self.StartFrame(0) except: LogException() def BuildWidgets(self): self.LeftPanel = wx.Panel(self, size = (100, 500)) self.LeftPanel.SetBackgroundColour("green") self.RightPanel = wx.Panel(self, size = (450, 500)) self.RightPanel.SetBackgroundColour("black") Box = wx.BoxSizer(wx.HORIZONTAL) Box.Add(self.LeftPanel, 10, wx.EXPAND) Box.Add(self.RightPanel, 60, wx.EXPAND) self.Eater = UIUtils.KeyEater(self) Box.Add(self.Eater, 1) #%%% self.SetAutoLayout(True) self.SetSizer(Box) #self.Layout() def BuildFrameWidgets(self, Frame): # Kill old widgets before creating new ones: Widgets = self.LeftPanel.GetChildren() for Widget in Widgets: Widget.Destroy() Widgets = self.RightPanel.GetChildren() for Widget in Widgets: Widget.Destroy() ################################## # Left side: SpeakerSizer holds a throbber and a label self.LeftPanel.SetBackgroundColour("black") self.RightPanel.SetBackgroundColour("black") self.SpeakerSizer = wx.BoxSizer(wx.VERTICAL) if Frame.ThrobberName: self.SpeakerThrobber = UIImages.Throbber(self.LeftPanel, Frame.ThrobberName, Delay = Frame.ThrobberDelay) self.SpeakerThrobber.Start() self.SpeakerSizer.Add(self.SpeakerThrobber, 2, wx.CENTER | wx.BORDER | wx.LEFT | wx.RIGHT | wx.TOP, 5) else: DummyPanel = wx.Panel(self.LeftPanel, -1) #, Size = (40, 40)) self.SpeakerSizer.Add(DummyPanel, 2, wx.CENTER, 5) #self.SpeakerSizer.Add(wx.Size(1, 1), 2, wx.CENTER, 5) self.SpeakerText = wx.StaticText(self.LeftPanel, -1, Frame.Speaker) self.SpeakerText.SetFont(self.BigFont) self.SpeakerText.SetForegroundColour(self.ForegroundColor) self.SpeakerSizer.Add(self.SpeakerText, 1, wx.CENTER | wx.BORDER | wx.LEFT | wx.RIGHT | wx.TOP, 5) DummyPanel = wx.Panel(self.LeftPanel, -1) # Size = (1, 1)) self.SpeakerSizer.Add(DummyPanel, 8, wx.EXPAND) #self.SpeakerSizer.Add(wx.Size(1, 1), 1, wx.EXPAND) self.LeftPanel.SetAutoLayout(True) self.LeftPanel.SetSizer(self.SpeakerSizer) self.LeftPanel.Layout() ################################## # Right side: Text! Create several one-line text boxes, because a multi-line text box # gets a scrollbar, and scrollbars aren't the look we want. self.RightSizer = wx.BoxSizer(wx.VERTICAL) self.TextBoxes = [] self.DialogText = wx.TextCtrl(self.RightPanel, -1, "", style = wx.TE_READONLY | wx.TE_MULTILINE | wx.TE_RICH | wx.NO_BORDER) self.DialogText.Font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL) self.DialogText.SetBackgroundColour("black") self.DialogText.SetForegroundColour("white") self.DialogText.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) self.DialogText.Bind(wx.EVT_KEY_UP, self.OnKeyUp) self.DialogText.Bind(wx.EVT_LEFT_DOWN, self.OnDialogClick) self.RightSizer.Add(self.DialogText, 8, wx.EXPAND | wx.BORDER | wx.LEFT | wx.RIGHT | wx.TOP | wx.BOTTOM, 10) self.NextText = wx.TextCtrl(self.RightPanel, -1, "", style = wx.TE_READONLY | wx.NO_BORDER) self.NextText.Font = wx.Font(12, wx.SWISS, wx.NORMAL, wx.NORMAL) self.NextText.SetBackgroundColour("black") self.NextText.SetForegroundColour("white") self.NextText.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) self.NextText.Bind(wx.EVT_KEY_UP, self.OnKeyUp) self.NextText.Bind(wx.EVT_LEFT_DOWN, self.OnDialogClick) self.RightSizer.Add(self.NextText, 1, wx.ALIGN_RIGHT) self.RightPanel.SetSizer(self.RightSizer) self.RightPanel.Layout() #self.Layout() def StartFrame(self, FrameIndex): self.CurrentFrameIndex = FrameIndex if self.CurrentFrameIndex < 0 or self.CurrentFrameIndex >= len(self.Conversation.Frames): self.Destroy() return self.Frame = self.Conversation.Frames[self.CurrentFrameIndex] self.TextPosition = 0 self.LineNumber = 0 self.LineStart = 0 Log("Build widgets for frame: %s"%self.CurrentFrameIndex) try: self.BuildFrameWidgets(self.Frame) except: LogException() self.ResetInputs() self.Eater.SetFocus() def OnDialogClick(self, Event): Log("CLICK!") if self.TextPosition < len(self.Frame.Text): self.TextSpeed = self.FastTextSpeed else: self.StartFrame(self.CurrentFrameIndex + 1) def OnTimer(self, *args, **kw): #Log("Timer: Character %s of %s"%(self.TextPosition, len(self.Frame.Text))) #Log("Button A and B: %s, %s"%(self.ButtonState[Config.KeyConfig.ButtonA], self.ButtonState[Config.KeyConfig.ButtonB])) self.SetGamepad() try: if self.TextPosition < len(self.Frame.Text): Char = self.Frame.Text[self.TextPosition] self.TextPosition += 2 ## if (Char == '\n'): ## self.LineNumber = min(self.LineNumber + 1, len(self.TextBoxes) - 1) ## self.LineStart = self.TextPosition + 1 ## else: ## self.TextBoxes[self.LineNumber] = self.Frame.Text[self.LineStart:self.TextPosition] self.DialogText.Value = self.Frame.Text[:self.TextPosition] if self.TextPosition >= len(self.Frame.Text): self.TextSpeed = self.SlowTextSpeed if self.CurrentFrameIndex < len(self.Conversation.Frames) - 1: self.NextText.Value = "next" else: self.NextText.Value = "done" self.FocusGamepadState = self.GetRawGamepad() self.Timer.Restart(250) return else: self.Timer.Restart(self.TextSpeed) #self.TextSpeed # Process keypresses during text display: self.SetGamepad() if self.ButtonState[Config.KeyConfig.ButtonB] or self.ButtonState[Config.KeyConfig.ButtonA] or self.KeyState.get(wx.WXK_RETURN, 0) \ or self.KeyState.get(wx.WXK_NUMPAD_ENTER, 0): self.TextSpeed = self.FastTextSpeed else: # Process keypresses after text display: self.SetGamepad() if self.ButtonState[Config.KeyConfig.ButtonB] or self.ButtonState[Config.KeyConfig.ButtonA] or self.KeyState.get(wx.WXK_RETURN, 0) \ or self.KeyState.get(wx.WXK_NUMPAD_ENTER, 0): self.StartFrame(self.CurrentFrameIndex + 1) self.Timer.Restart(self.FastTextSpeed) except: LogException() if __name__ == "__main__": LoadConversations(os.path.join("quests", "Dot", "Story.xml"))