Began working on context-sensitive dialogues
[butaba-adventures.git] / utility.py
1 # utility functions for the game
2 import pygame
3 import sys
4 import os.path
5 import xml.etree.cElementTree as ET
6 import textwrap
7
8 # function to run through an interactive dialogue and return the response ID
9 def dialogue_play (screen, bgscreen, npc, responder_portrait=None,
10 marginleft=0, margintop=0, marginright=0, marginbottom=0):
11
12 # first check if NPC has a dialogue at all
13 if len (npc.dialogues) == 0 or npc.currentdialog >= len (npc.dialogues):
14 return None
15
16 # get the conversation as a dictionary
17 convtree = xml_to_dict (npc.dialogues[npc.currentdialog])
18
19 scrwidth = screen.get_width ()
20 scrheight = screen.get_height ()
21 bgwidth = bgscreen.get_width ()
22 bgheight = bgscreen.get_height ()
23 leftedge = scrwidth/2 - bgwidth/2
24 topedge = scrheight/2 - bgheight/2
25
26 # now initiate the conversation
27 dlg_id = "1"
28 reply_mode = False
29 current_resp = convtree[dlg_id][1][0][1]
30
31 while 1:
32 screen.blit (bgscreen, (leftedge, topedge))
33
34 # a dialgoue ID of 0 always exits the conversation
35 # return the unique response ID
36 if dlg_id == "0":
37 return current_resp
38
39 # not reply mode
40 if reply_mode is False:
41 if npc.portrait is not None:
42 screen.blit (npc.portrait, (leftedge, topedge))
43
44 # get the lines to display wrapped using textwrap module
45 lines = []
46 textnpc = textwrap.wrap (convtree[dlg_id][0], 40)
47 for text in textnpc:
48 lines.append ((10, 0, 0, 0, text))
49
50 put_lines (screen, lines,
51 pygame.Rect(leftedge+marginleft, topedge+margintop,
52 bgwidth-marginleft-marginright, bgheight-margintop-marginbottom))
53 pygame.display.update ()
54
55 for event in pygame.event.get ():
56 if event.type == pygame.QUIT:
57 sys.exit (0)
58 elif event.type == pygame.KEYDOWN:
59 # now continue the dialog
60 if event.key == pygame.K_RETURN or event.key == pygame.K_SPACE:
61 reply_mode = True
62 current_resp = convtree[dlg_id][1][0][1]
63 sel = 0
64 # reply mode
65 else:
66 if responder_portrait is not None:
67 screen.blit (responder_portrait, (leftedge, topedge))
68
69 lines = []
70 for resptext, respid, nextdlgid in convtree[dlg_id][1]:
71 if respid == current_resp:
72 lines.append ((10, 255, 0, 0, resptext))
73 else:
74 lines.append ((10, 0, 0, 0, resptext))
75
76 put_lines (screen, lines,
77 pygame.Rect (leftedge+marginleft, topedge+margintop,
78 bgwidth-marginleft-marginright, bgheight-margintop-marginbottom))
79
80 pygame.display.update ()
81 for event in pygame.event.get ():
82 if event.type == pygame.QUIT:
83 sys.exit (0)
84 elif event.type == pygame.KEYDOWN:
85 if event.key == pygame.K_UP or event.key == pygame.K_LEFT:
86 sel -= 1
87 if sel < 0:
88 sel = 0
89 current_resp = convtree[dlg_id][1][sel][1]
90 elif event.key == pygame.K_DOWN or event.key == pygame.K_RIGHT:
91 sel += 1
92 if sel >= len (convtree[dlg_id][1]):
93 sel = len(convtree[dlg_id][1]) - 1
94 current_resp = convtree[dlg_id][1][sel][1]
95
96 elif event.key == pygame.K_RETURN or event.key == pygame.K_SPACE:
97 dlg_id = convtree[dlg_id][1][sel][2]
98 reply_mode = False
99
100 # function to parse a conversation XML into a python dictionary
101 def xml_to_dict (file):
102 # parse the dialogue XML file
103 dlgtree = ET.parse (file)
104 # get the root element
105 conversation = dlgtree.getroot ()
106
107 # build the conversation tree as a dictionary
108 convtree = dict ()
109 for dlg in conversation.findall ("dialogue"):
110 id = dlg.get ("id")
111 speech = dlg.find ("speech").text
112 responses = []
113 for resp in dlg.findall ("response"):
114 responses.append ((resp.text, resp.get ("id"), resp.get ("nextdialogue")))
115
116 convtree[id] = (speech, responses)
117
118
119 return convtree
120
121
122 # function to draw text on surface
123 def put_text (surface, x, y, size, (r,g,b), text):
124 harisfont = os.path.join ("font", "harisgamefont.ttf")
125 textsurf = pygame.font.Font (harisfont, size).render (text, True, pygame.Color (r,g,b))
126 surface.blit (textsurf, (x, y))
127
128 # function to draw several lines of text, centered horizontally and vertically on screen
129 # or drawn centered on a rectangle
130 def put_lines (surface, text_lines, rect=None):
131 textsurfs = []
132 height = 0
133 harisfont = os.path.join ("font", "harisgamefont.ttf")
134
135 for size, r, g, b, text in text_lines:
136 s = pygame.font.Font (harisfont, size).render (text, True, pygame.Color (r,g,b))
137 # add spacing
138 height = height + s.get_height()*1.5
139 textsurfs.append (s)
140
141 i = 0
142 # if no rectangle specified, center in screen
143 if rect is None:
144 scrwidth = surface.get_width ()
145 scrheight = surface.get_height ()
146
147 for s in textsurfs:
148 surface.blit (s, (scrwidth/2 - s.get_width()/2, scrheight/2 - height/2+ i* (s.get_height()*1.5)))
149 i += 1
150 # center on specified rectangular region
151 else:
152 midx = (rect.left + rect.right)/2
153 midy = (rect.top + rect.bottom)/2
154 num = len (textsurfs) / 2
155 for s in textsurfs:
156 surface.blit (s, (midx - s.get_width()/2, midy - (num-i) * s.get_height() * 3/2))
157 i += 1
158
159 # function to ask a question and return answer
160 def ask_question (surface, question, answers, bgscreen):
161
162 sel_answer = 1
163
164 while 1:
165 textarray = [ [ 10, 128, 0, 0, question ] ]
166
167 i = 1
168 for answer in answers:
169 if sel_answer == i:
170 r, g, b = 255, 0, 0
171 else:
172 r, g, b = 0, 0, 0
173 textarray.append ( [10, r, g, b, answer] )
174
175 i += 1
176
177 surface.blit (bgscreen, (surface.get_width()/2 - bgscreen.get_width()/2,
178 surface.get_height()/2 - bgscreen.get_height()/2))
179 put_lines (surface, textarray, pygame.Rect (surface.get_width()/2 - bgscreen.get_width()/2,
180 surface.get_height()/2 - bgscreen.get_height()/2,
181 bgscreen.get_width(), bgscreen.get_height()))
182
183 pygame.display.update ()
184
185 for event in pygame.event.get ():
186 if event.type == pygame.QUIT:
187 sys.exit (0)
188 elif event.type == pygame.KEYDOWN:
189 if event.key == pygame.K_UP:
190 sel_answer -= 1
191 if sel_answer < 1:
192 sel_answer = 1
193 elif event.key == pygame.K_DOWN:
194 sel_answer += 1
195 if sel_answer > len(answers):
196 sel_answer = len(answers)
197 elif event.key == pygame.K_RETURN:
198 return sel_answer
199
200 # function displaying the contents of a container. Object must contain
201 # items list
202 # edgewidth - container edges to avoid drawing items in
203 def get_container_object (surface, obj, bgimage, edgewidth=0):
204
205 # get the number of items
206 numitems = len (obj.objects)
207 # number of rows
208 num_rows = (bgimage.get_height () - edgewidth*2) / 48
209 # number of cols
210 num_cols = (bgimage.get_width () - edgewidth*2) / 48
211
212 objposx = surface.get_width()/2 - bgimage.get_width()/2
213 objposy = surface.get_height()/2 - bgimage.get_height()/2
214
215 selitem = 0
216 selrow = 0
217 selcol = 0
218
219 while 1:
220 # display the background for the container
221 surface.blit (bgimage, (objposx, objposy))
222
223 # display each item in the container
224 i = 0
225 j = 0
226
227 # display all the items in container
228 for item in obj.objects:
229 surface.blit (item.image, (objposx + edgewidth+ j*48, objposy + edgewidth + i*48))
230 j += 1
231 if j >= num_cols:
232 j = 0
233 i += 1
234 # only draw selector if there is at least one item
235 if numitems > 0:
236 pygame.draw.rect (surface, pygame.Color (255,255,255),
237 pygame.Rect(objposx + edgewidth + selcol*48, objposy + edgewidth + selrow*48, 48, 48), 1)
238
239 pygame.display.update ()
240
241 # get events
242 for event in pygame.event.get ():
243 if event.type == pygame.QUIT:
244 sys.exit (0)
245 elif event.type == pygame.KEYDOWN:
246 if event.key == pygame.K_ESCAPE:
247 return None
248 elif event.key == pygame.K_RETURN:
249 if numitems > 0 and selitem >= 0 and selitem < numitems:
250 return obj.objects[selitem]
251 else:
252 return None
253 elif event.key == pygame.K_UP or event.key == pygame.K_LEFT:
254 # go to the prev item
255 selitem -= 1
256 if selitem < 0:
257 selitem = numitems - 1
258 selrow = selitem / num_cols
259 selcol = selitem % num_cols
260
261 elif event.key == pygame.K_DOWN or event.key == pygame.K_RIGHT:
262 # go to the next item
263 selitem += 1
264 if selitem > numitems - 1:
265 selitem = 0
266 selrow = selitem / num_cols
267 selcol = selitem % num_cols