Began working on context-sensitive dialogues
[butaba-adventures.git] / utility.py
index 5d8c45d..f115374 100644 (file)
 # utility functions for the game
 import pygame
+import sys
 import os.path
+import xml.etree.cElementTree as ET
+import textwrap
+
+# function to run through an interactive dialogue and return the response ID
+def dialogue_play (screen, bgscreen, npc, responder_portrait=None,
+                                       marginleft=0, margintop=0, marginright=0, marginbottom=0):
+
+       # first check if NPC has a dialogue at all
+       if len (npc.dialogues) == 0 or npc.currentdialog >= len (npc.dialogues):
+               return None
+
+       # get the conversation as a dictionary
+       convtree = xml_to_dict (npc.dialogues[npc.currentdialog])
+
+       scrwidth = screen.get_width ()
+       scrheight = screen.get_height ()
+       bgwidth = bgscreen.get_width ()
+       bgheight = bgscreen.get_height ()
+       leftedge = scrwidth/2 - bgwidth/2
+       topedge = scrheight/2 - bgheight/2
+
+       # now initiate the conversation
+       dlg_id = "1"
+       reply_mode = False
+       current_resp = convtree[dlg_id][1][0][1]
+
+       while 1:
+               screen.blit (bgscreen, (leftedge, topedge))
+
+               # a dialgoue ID of 0 always exits the conversation
+               # return the unique response ID
+               if dlg_id == "0":
+                       return current_resp
+
+               # not reply mode
+               if reply_mode is False:
+                       if npc.portrait is not None:
+                               screen.blit (npc.portrait, (leftedge, topedge))
+
+                       # get the lines to display wrapped using textwrap module
+                       lines = []
+                       textnpc = textwrap.wrap (convtree[dlg_id][0], 40)
+                       for text in textnpc:
+                               lines.append ((10, 0, 0, 0, text))
+
+                       put_lines (screen, lines,
+                                                       pygame.Rect(leftedge+marginleft, topedge+margintop,
+                                                               bgwidth-marginleft-marginright, bgheight-margintop-marginbottom))
+                       pygame.display.update ()
+
+                       for event in pygame.event.get ():
+                               if event.type == pygame.QUIT:
+                                       sys.exit (0)
+                               elif event.type == pygame.KEYDOWN:
+                                       # now continue the dialog
+                                       if event.key == pygame.K_RETURN or event.key == pygame.K_SPACE:
+                                               reply_mode = True
+                                               current_resp = convtree[dlg_id][1][0][1]
+                                               sel = 0
+               # reply mode
+               else:
+                       if responder_portrait is not None:
+                               screen.blit (responder_portrait, (leftedge, topedge))
+
+                       lines = []
+                       for resptext, respid, nextdlgid in convtree[dlg_id][1]:
+                               if respid == current_resp:
+                                       lines.append ((10, 255, 0, 0, resptext))
+                               else:
+                                       lines.append ((10, 0, 0, 0, resptext))
+
+                       put_lines (screen, lines,
+                                               pygame.Rect (leftedge+marginleft, topedge+margintop,
+                                                                       bgwidth-marginleft-marginright, bgheight-margintop-marginbottom))
+
+                       pygame.display.update ()
+                       for event in pygame.event.get ():
+                               if event.type == pygame.QUIT:
+                                       sys.exit (0)
+                               elif event.type == pygame.KEYDOWN:
+                                       if event.key == pygame.K_UP or event.key == pygame.K_LEFT:
+                                               sel -= 1
+                                               if sel < 0:
+                                                       sel = 0
+                                               current_resp = convtree[dlg_id][1][sel][1]
+                                       elif event.key == pygame.K_DOWN or event.key == pygame.K_RIGHT:
+                                               sel += 1
+                                               if sel >= len (convtree[dlg_id][1]):
+                                                       sel = len(convtree[dlg_id][1]) - 1
+                                               current_resp = convtree[dlg_id][1][sel][1]
+
+                                       elif event.key == pygame.K_RETURN or event.key == pygame.K_SPACE:
+                                               dlg_id = convtree[dlg_id][1][sel][2]
+                                               reply_mode = False
+
+# function to parse a conversation XML into a python dictionary
+def xml_to_dict (file):
+       # parse the dialogue XML file
+       dlgtree = ET.parse (file)
+       # get the root element
+       conversation = dlgtree.getroot ()
+
+       # build the conversation tree as a dictionary
+       convtree = dict ()
+       for dlg in conversation.findall ("dialogue"):
+               id = dlg.get ("id")
+               speech = dlg.find ("speech").text
+               responses = []
+               for resp in dlg.findall ("response"):
+                       responses.append ((resp.text, resp.get ("id"), resp.get ("nextdialogue")))
+
+               convtree[id] = (speech, responses)
+
+
+       return convtree
+
 
 # function to draw text on surface
 def put_text (surface,  x, y, size, (r,g,b), text):
-       harisfont = os.path.join ("font", "HarisComic-2.ttf")
+       harisfont = os.path.join ("font", "harisgamefont.ttf")
        textsurf = pygame.font.Font (harisfont, size).render (text, True, pygame.Color (r,g,b))
        surface.blit (textsurf, (x, y))
 
+# function to draw several lines of text, centered horizontally and vertically on screen
+# or drawn centered on a rectangle
+def put_lines (surface, text_lines, rect=None):
+       textsurfs = []
+       height = 0
+       harisfont = os.path.join ("font", "harisgamefont.ttf")
 
+       for size, r, g, b, text in text_lines:
+               s = pygame.font.Font (harisfont, size).render (text, True, pygame.Color (r,g,b))
+               # add spacing
+               height = height + s.get_height()*1.5
+               textsurfs.append (s)
+
+       i = 0
+       # if no rectangle specified, center in screen
+       if rect is None:
+               scrwidth = surface.get_width ()
+               scrheight = surface.get_height ()
+
+               for s in textsurfs:
+                       surface.blit (s, (scrwidth/2 - s.get_width()/2, scrheight/2 - height/2+ i* (s.get_height()*1.5)))
+                       i += 1
+       # center on specified rectangular region
+       else:
+               midx = (rect.left + rect.right)/2
+               midy = (rect.top + rect.bottom)/2
+               num = len (textsurfs) / 2
+               for s in textsurfs:
+                       surface.blit (s, (midx - s.get_width()/2, midy - (num-i) * s.get_height() * 3/2))
+                       i += 1
 
 # function to ask a question and return answer
-def ask_question (surface, question, answers):
-       pygame.draw.rect (surface, pygame.Color (255, 255, 128), pygame.Rect (40, 40, 480-40, 480-40))
+def ask_question (surface, question, answers, bgscreen):
+
+       sel_answer = 1
+
+       while 1:
+               textarray = [ [ 10, 128, 0, 0, question ] ]
+
+               i = 1
+               for answer in answers:
+                       if sel_answer == i:
+                               r, g, b = 255, 0, 0
+                       else:
+                               r, g, b = 0, 0, 0
+                       textarray.append ( [10, r, g, b, answer] )
 
-       put_text (surface, 60, 60, 22, (0, 0, 192), question)
+                       i += 1
 
-       i = 1
-       for answer in answers:
-               put_text (surface, 60, i*20+60, 22, (0, 0, 128), "%d" % i)
-               put_text (surface, 80, i*20+60, 22, (0, 0, 0), answer)
+               surface.blit (bgscreen, (surface.get_width()/2 - bgscreen.get_width()/2,
+                                                                       surface.get_height()/2 - bgscreen.get_height()/2))
+               put_lines (surface, textarray, pygame.Rect (surface.get_width()/2 - bgscreen.get_width()/2,
+                                                                                               surface.get_height()/2 - bgscreen.get_height()/2,
+                                                                                               bgscreen.get_width(), bgscreen.get_height()))
+
+               pygame.display.update ()
+
+               for event in pygame.event.get ():
+                       if event.type == pygame.QUIT:
+                               sys.exit (0)
+                       elif event.type == pygame.KEYDOWN:
+                               if event.key == pygame.K_UP:
+                                       sel_answer -= 1
+                                       if sel_answer < 1:
+                                               sel_answer = 1
+                               elif event.key == pygame.K_DOWN:
+                                       sel_answer += 1
+                                       if sel_answer > len(answers):
+                                               sel_answer = len(answers)
+                               elif event.key == pygame.K_RETURN:
+                                       return sel_answer
+
+# function displaying the contents of a container. Object must contain
+# items list
+# edgewidth - container edges to avoid drawing items in
+def get_container_object (surface, obj, bgimage, edgewidth=0):
+
+       # get the number of items
+       numitems = len (obj.objects)
+       # number of rows
+       num_rows = (bgimage.get_height () - edgewidth*2) / 48
+       # number of cols
+       num_cols = (bgimage.get_width () - edgewidth*2) / 48
+
+       objposx = surface.get_width()/2 - bgimage.get_width()/2
+       objposy = surface.get_height()/2 - bgimage.get_height()/2
+
+       selitem = 0
+       selrow = 0
+       selcol = 0
 
        while 1:
+               # display the background for the container
+               surface.blit (bgimage, (objposx, objposy))
+
+               # display each item in the container
+               i = 0
+               j = 0
+
+               # display all the items in container
+               for item in obj.objects:
+                       surface.blit (item.image, (objposx + edgewidth+ j*48, objposy + edgewidth + i*48))
+                       j += 1
+                       if j >= num_cols:
+                               j = 0
+                               i += 1
+               # only draw selector if there is at least one item
+               if numitems > 0:
+                       pygame.draw.rect (surface, pygame.Color (255,255,255),
+                                       pygame.Rect(objposx + edgewidth + selcol*48, objposy + edgewidth + selrow*48, 48, 48), 1)
+
+               pygame.display.update ()
+
+               # get events
                for event in pygame.event.get ():
                        if event.type == pygame.QUIT:
                                sys.exit (0)
                        elif event.type == pygame.KEYDOWN:
-                               print event.key
+                               if event.key == pygame.K_ESCAPE:
+                                       return None
+                               elif event.key == pygame.K_RETURN:
+                                       if numitems > 0 and selitem >= 0 and selitem < numitems:
+                                               return obj.objects[selitem]
+                                       else:
+                                               return None
+                               elif event.key == pygame.K_UP or event.key == pygame.K_LEFT:
+                                       # go to the prev item
+                                       selitem -= 1
+                                       if selitem < 0:
+                                               selitem = numitems - 1
+                                       selrow = selitem / num_cols
+                                       selcol = selitem % num_cols
+
+                               elif event.key == pygame.K_DOWN or event.key == pygame.K_RIGHT:
+                                       # go to the next item
+                                       selitem += 1
+                                       if selitem > numitems - 1:
+                                               selitem = 0
+                                       selrow = selitem / num_cols
+                                       selcol = selitem % num_cols