Added the Conversation Interaction
[butaba-adventures.git] / maingame.py
1 import pygame
2 import sys
3 import random
4 import os.path
5 import cPickle
6
7 import level
8 import butaba
9 import utility
10 import gameobjects
11 import constants
12 import npcs
13
14 class MainGame:
15
16 # initialize the game
17 def __init__ (self):
18 pygame.init ()
19 self.screen = pygame.display.set_mode ((720, 512))
20 pygame.display.set_caption ("The Adventures of Butaba")
21
22 # initalize background graphics
23 self.img_tileset = pygame.image.load (os.path.join ("background", "tileset.png")).convert ()
24
25 self.img_menu = pygame.image.load (os.path.join ("background", "menu_screen.png")).convert ()
26
27 self.img_inventory = pygame.image.load (os.path.join ("background", "inventory.png")).convert ()
28
29 self.img_chestbg = pygame.image.load (os.path.join ("background", "chestcontent.png")).convert ()
30
31 self.img_dialogue = pygame.image.load (os.path.join ("background", "dialog_screen.png")).convert ()
32 self.img_dialogue.set_colorkey (pygame.Color (0, 255, 0))
33
34
35 # initialize object graphics
36 self.img_redpotion = pygame.image.load (os.path.join ("objects", "red-potion.png")).convert ()
37 self.img_redpotion.set_colorkey (pygame.Color (0, 255, 0))
38 self.img_goldcoins = pygame.image.load (os.path.join ("objects", "gold-coins.png")).convert ()
39 self.img_goldcoins.set_colorkey (pygame.Color (0, 255, 0))
40 self.img_wand = pygame.image.load (os.path.join ("objects", "wand.png")).convert ()
41 self.img_wand.set_colorkey (pygame.Color (0, 255, 0))
42 self.img_bulb = pygame.image.load (os.path.join ("objects", "bulb.png")).convert ()
43 self.img_bulb.set_colorkey (pygame.Color (0, 255, 0))
44 self.img_lightning = pygame.image.load (os.path.join ("objects", "lightning.png")).convert ()
45 self.img_lightning.set_colorkey (pygame.Color (0, 255, 0))
46 self.img_key = pygame.image.load (os.path.join ("objects", "key.png")).convert ()
47 self.img_key.set_colorkey (pygame.Color (0, 255, 0))
48 self.img_key2 = pygame.image.load (os.path.join ("objects", "key2.png")).convert ()
49 self.img_key2.set_colorkey (pygame.Color (0, 255, 0))
50 self.img_chest = pygame.image.load (os.path.join ("objects", "chest.png")).convert ()
51 self.img_chest.set_colorkey (pygame.Color (0, 255, 0))
52
53 # initialize player graphics
54 self.img_butabafront = pygame.image.load (os.path.join ("sprite", "butaba-front.png")).convert ()
55 self.img_butabafront.set_colorkey (pygame.Color (0, 255, 0))
56 self.img_butababack = pygame.image.load (os.path.join ("sprite", "butaba-back.png")).convert ()
57 self.img_butababack.set_colorkey (pygame.Color (0, 255, 0))
58 self.img_butabaleft = pygame.image.load (os.path.join ("sprite", "butaba-left.png")).convert ()
59 self.img_butabaleft.set_colorkey (pygame.Color (0, 255, 0))
60 self.img_butabaright = pygame.image.load (os.path.join ("sprite", "butaba-right.png")).convert ()
61 self.img_butabaright.set_colorkey (pygame.Color (0, 255, 0))
62
63 # initialize NPC graphics
64 self.img_bulisa = pygame.image.load (os.path.join ("sprite", "bulisa.png")).convert ()
65 self.img_bulisa.set_colorkey (pygame.Color (0, 255, 0))
66
67 # initialize portraits
68 self.img_butaba_portrait = pygame.image.load (os.path.join ("portraits", "butaba.png")).convert ()
69 self.img_bulisa_portrait = pygame.image.load (os.path.join ("portraits", "bulisa.png")).convert ()
70
71 # set level data
72 self.setup_levels ()
73 # set current level and position of our character
74 self.currentlevel = self.level1
75
76 # set the status message
77 self.status_message = "Game started"
78
79 self.butaba = butaba.Butaba (5,0, butaba.Butaba.RIGHT)
80
81 # set up the levels and their interactions
82 def setup_levels (self):
83 # set up the objects first
84 chest1 = gameobjects.Chest (2, 6, "chest", self.img_chest, constants.KEY_CHEST1, True)
85 chest2 = gameobjects.Chest (6, 6, "chest", self.img_chest, constants.KEY_CHEST2, True)
86 key1 = gameobjects.Key (5, 3, "a chest key", self.img_key2, constants.KEY_CHEST1)
87 key2 = gameobjects.Key (5, 3, "a chest key", self.img_key, constants.KEY_CHEST2)
88 potion = gameobjects.HealthPotion (5, 2, self.img_redpotion)
89 gold50 = gameobjects.GoldCoins (6, 2, self.img_goldcoins, 50)
90 gold25 = gameobjects.GoldCoins (6, 2, self.img_goldcoins, 25)
91 gold10 = gameobjects.GoldCoins (6, 2, self.img_goldcoins, 10)
92 potion2 = gameobjects.HealthPotion (5, 2, self.img_redpotion)
93 potion3 = gameobjects.HealthPotion (5, 2, self.img_redpotion)
94
95 npc_bulisa = npcs.Bulisa (4, 3, self.img_bulisa, self.img_bulisa_portrait,
96 [ os.path.join ("dialogues", "bulisa1.dlg") ])
97
98 chest1.objects = [ gold50, gold25, potion2, potion3, key2, gold10 ]
99
100 # create the levels
101 self.level1 = level.Level (cPickle.load (file ("levels/level1.dat")),
102 objects = [ chest1 ] )
103
104 self.level1w = level.Level (cPickle.load (file ("levels/level1w.dat")))
105
106 self.level1e = level.Level (cPickle.load (file ("levels/level1e.dat")),
107 objects = [ key1, potion, chest2 ], npcs = [ npc_bulisa ])
108
109 # set up the interaction between levels
110 self.level1.levelright = self.level1e
111 self.level1.levelleft = self.level1w
112 self.level1e.levelleft = self.level1
113 self.level1w.levelright = self.level1
114
115 def main_loop (self):
116 # main game loop
117 while 1:
118 # clear screen
119 self.screen.fill (pygame.Color (0,0,0))
120 # draw the level
121 self.draw_level_background (self.currentlevel)
122 # draw level objects
123 self.draw_level_objects (self.currentlevel)
124 # draw the NPCs in the level
125 self.draw_level_npcs (self.currentlevel)
126 # draw our character
127 self.draw_butaba ()
128 # display the character's inventory
129 self.draw_inventory ()
130 # draw the status info
131 self.draw_status ()
132 # update the display
133 pygame.display.update ()
134
135 # get keyboard events
136 for event in pygame.event.get ():
137 if event.type == pygame.QUIT:
138 sys.exit (0)
139 # if keyboard event
140 if event.type == pygame.KEYDOWN:
141 if event.key == pygame.K_UP:
142 self.move_butaba_up ()
143 elif event.key == pygame.K_DOWN:
144 self.move_butaba_down ()
145 elif event.key == pygame.K_LEFT:
146 self.move_butaba_left ()
147 elif event.key == pygame.K_RIGHT:
148 self.move_butaba_right ()
149 # drinking health potion in inventory
150 elif event.key == ord ("h") or event.key == ord ("H"):
151 self.inventory_drink_health_potion ()
152 # quit the game
153 elif event.key == ord ("q") or event.key == ord ("Q"):
154 sys.exit (0)
155
156 # drink a health potion if it is in the player's inventory
157 def inventory_drink_health_potion (self):
158 # look for a health potion
159 for item in self.butaba.objects:
160 if isinstance (item, gameobjects.HealthPotion) is True:
161 self.use_object (self.butaba, item)
162 break
163
164 def move_butaba_up (self):
165 # clear any status messages
166 self.status_message = None
167 # first if butaba is not facing up, make him face up
168 if self.butaba.position <> butaba.Butaba.BACK:
169 self.butaba.position = butaba.Butaba.BACK
170 return
171
172 # if butaba is trying to move off the top of the screen
173 if self.butaba.row <= 0:
174 # if there is a level above set current level to that one
175 if self.currentlevel.leveltop is not None:
176 lastrow = len (self.currentlevel.leveltop.background) - 1
177 # interact with objects
178 if self.level_interact (self.currentlevel.leveltop, lastrow, self.butaba.col) is False:
179 return
180
181 # make sure there is no obstacle
182 if self.check_background_obstacle (self.currentlevel.leveltop, lastrow, self.butaba.col) is False:
183 self.currentlevel = self.currentlevel.leveltop
184 self.butaba.row = lastrow
185 # normal upward movement
186 else:
187 # if there is any object in that place interact with it
188 # if any object is a blocking object then avoid movement
189 if self.level_interact (self.currentlevel, self.butaba.row-1, self.butaba.col) is False:
190 return
191
192 if self.check_background_obstacle (self.currentlevel, self.butaba.row-1, self.butaba.col) is False:
193 self.butaba.row -= 1
194
195 def move_butaba_down (self):
196 # clear any status messages
197 self.status_message = None
198 # first if butaba is not facing forward, make him face forward/down
199 if self.butaba.position <> butaba.Butaba.FRONT:
200 self.butaba.position = butaba.Butaba.FRONT
201 return
202
203 # if butaba is trying to move off the bottom of the screen
204 if self.butaba.row >= len (self.currentlevel.background)-1:
205 # if there is a level below set current level to that one
206 if self.currentlevel.levelbottom is not None:
207 # interact with objects if any
208 # if any object is a blocking object then avoid movement
209 if self.level_interact (self.currentlevel.levelbottom, 0, self.butaba.col) is False:
210 return
211 # make sure there is no obstacle at that position
212 if self.check_background_obstacle (self.currentlevel.levelbottom, 0, self.butaba.col) is False:
213 self.currentlevel = self.currentlevel.levelbottom
214 self.butaba.row = 0
215 # normal downward movement
216 else:
217 # interact with objects if any
218 # if any object is a blocking object then avoid movement
219 if self.level_interact (self.currentlevel, self.butaba.row+1, self.butaba.col) is False:
220 return
221 if self.check_background_obstacle (self.currentlevel, self.butaba.row+1, self.butaba.col) is False:
222 self.butaba.row += 1
223
224 # check if a background tile is an obstacle
225 def check_background_obstacle (self, level, row, col):
226 if (level.background[row][col][2] == 1):
227 return True
228 else:
229 return False
230
231 # get and interact with objects and characters if present in a particular row/col
232 def level_interact (self, level, row, col):
233 objs = []
234 # get list of objects at current location
235 for obj in level.objects:
236 if obj.row == row and obj.col == col:
237 objs.append (obj)
238
239 notblock = self.interact_objects (level, objs)
240
241 # get npc at current location
242 current_npc = None
243 for npc in level.npcs:
244 if npc.row == row and npc.col == col:
245 current_npc = npc
246 break
247
248 # npcs always block the tile. So return false if there is an NPC
249 # at the location
250 if current_npc is not None:
251 self.interact_npc (current_npc)
252 return False
253
254 return notblock
255
256 # interaction with npcs
257 def interact_npc (self, npc):
258 # interact with NPC and get the response ID
259 resp_id = utility.dialogue_play (self.screen, self.img_dialogue, npc, self.img_butaba_portrait, 0, 90)
260
261 # if none
262 if resp_id is None:
263 self.status_message = "You cannot initiate a conversation with %s" % npc.charname
264
265 # interaction with objects
266 def interact_objects (self, container, objs):
267 # overall flag for blocking/non-blocking objects
268 notblock = True
269
270 # now perform interaction
271 for obj in objs:
272 # run the object interact function
273 if obj.interact () is False:
274 notblock = False
275 # if object can be picked up ask
276 if obj.can_pickup is True:
277 ans = utility.ask_question (self.screen, "Found %s." % obj.text, ["Pick up", obj.use_str, "Ignore"], self.img_menu)
278 # if the answer is "pick up"
279 if ans == 1:
280 self.pickup_object (container, obj)
281 elif ans == 2:
282 # use the object according to its type
283 self.use_object (container, obj)
284 # if it cannot be picked up, try to use it anyway
285 else:
286 ans = utility.ask_question (self.screen, "Found %s." % obj.text, [obj.use_str, "Ignore"], self.img_menu)
287 if ans == 1:
288 self.use_object (container, obj)
289
290 return notblock
291
292 # transfer an object from one container to another
293 # container must have an objects list
294 def transfer_object (self, source, obj, dest):
295 # remove object from source
296 source.objects.remove (obj)
297 # add object to destination
298 dest.objects.append (obj)
299
300 # picking up an object
301 def pickup_object (self, container, obj):
302 # only if object can be picked up, pick it up or use it
303 if obj.can_pickup is True:
304 # check if the inventory is full
305 if len (self.butaba.objects) >= butaba.Butaba.MAXITEMS:
306 self.status_message = "Cannot pick up item. Inventory full"
307 else:
308 # add item to inventory
309 self.transfer_object (container, obj, self.butaba)
310
311 self.status_message = "You picked up %s" % obj.text
312
313 # this method uses the object first by calling the object use () method
314 # and then performing specific actions as necessary
315 def use_object (self, container, obj):
316 # if the object is a health potion
317 if isinstance (obj, gameobjects.HealthPotion) is True:
318 if self.butaba.health < butaba.Butaba.MAXHEALTH:
319 obj.use (self.butaba)
320 container.objects.remove (obj)
321 self.status_message = "You gained health"
322 else:
323 self.status_message = "You already have maximum health!"
324 # if the object is a chest
325 elif isinstance (obj, gameobjects.Chest) is True:
326 # if chest is locked, try to open it
327 if obj.locked is True:
328 # try opening the chest with every item 9the use () function
329 # of the chest determines if item is a key anyway
330 fittedkey = None
331 for invobj in self.butaba.objects:
332 fittedkey = obj.use (invobj)
333 # if a key fits
334 if fittedkey is not None:
335 break
336 # if no key found
337 if fittedkey is None:
338 self.status_message = "No key found to open %s" % obj.text
339 # chest successfully unlocked
340 else:
341 self.status_message = "You unlocked the %s" % obj.text
342 # remove the key from Butaba
343 self.butaba.objects.remove (fittedkey)
344 # add an experience point for unlocking chest subject
345 # to a limit of KNOWLEDGEMAX_CHEST_UNLOCK
346 if self.butaba.experience < constants.KNOWLEDGEMAX_CHEST_UNLOCK:
347 self.butaba.experience += 1
348 self.status_message += " and gained experience!"
349 # display the contents of the chest
350 else:
351 item = utility.get_container_object (self.screen, obj, self.img_chestbg, 30)
352 if item is not None:
353 self.interact_objects (obj, [ item, ])
354
355 # if the object is gold coins
356 elif isinstance (obj, gameobjects.GoldCoins) is True:
357 obj.use (self.butaba)
358 self.status_message = "You picked up %d gold." % obj.value
359 # remove the gold coins after adding it to Butaba's gold
360 container.objects.remove (obj)
361
362 def move_butaba_left (self):
363 # clear any status messages
364 self.status_message = None
365
366 # first if Butaba is not facing left, make him face left
367 if self.butaba.position <> butaba.Butaba.LEFT:
368 self.butaba.position = butaba.Butaba.LEFT
369 return
370
371 # if butaba is trying to move off the left edge
372 if self.butaba.col <= 0:
373 # if there is a level to the right set current level to that one
374 if self.currentlevel.levelleft is not None:
375 # get the last column of the previous level
376 lastcol = len (self.currentlevel.levelleft.background[0]) - 1
377 # interact with objects if any
378 # if any object is a blocking object then avoid movement
379 if self.level_interact (self.currentlevel.levelleft, self.butaba.row, lastcol) is False:
380 return
381 # make sure there is no obstacle at that position of movement
382 if self.check_background_obstacle (self.currentlevel.levelleft, self.butaba.row, lastcol) is False:
383 self.currentlevel = self.currentlevel.levelleft
384 self.butaba.col = lastcol
385 # normal left movement
386 else:
387 # interact with objects if any
388 # if any object is a blocking object then avoid movement
389 if self.level_interact (self.currentlevel, self.butaba.row, self.butaba.col-1) is False:
390 return
391 if self.check_background_obstacle (self.currentlevel, self.butaba.row, self.butaba.col-1) is False:
392 self.butaba.col -= 1
393
394 def move_butaba_right (self):
395 # clear any status messages
396 self.status_message = None
397
398 # First if Butaba is not facing right make him face right
399 if self.butaba.position <> butaba.Butaba.RIGHT:
400 self.butaba.position = butaba.Butaba.RIGHT
401 return
402
403 # if butaba is trying to move off the right edge
404 if self.butaba.col >= len (self.currentlevel.background[0])-1:
405 # if there is a level to the right swap current level with that one
406 if self.currentlevel.levelright is not None:
407 # interact with objects if any
408 # if any object is a blocking object then avoid movement
409 if self.level_interact (self.currentlevel.levelright, self.butaba.row, 0) is False:
410 return
411
412 # make sure there is no obstacle at that position of movement
413 # get the last column of the previous level
414 if self.check_background_obstacle (self.currentlevel.levelright, self.butaba.row, 0) is False:
415 self.currentlevel = self.currentlevel.levelright
416 self.butaba.col = 0
417 # normal right movement
418 else:
419 # interact with objects if any
420 # if any object is a blocking object then avoid moving
421 if self.level_interact (self.currentlevel, self.butaba.row, self.butaba.col + 1) is False:
422 return
423 if self.check_background_obstacle (self.currentlevel, self.butaba.row, self.butaba.col + 1) is False:
424 self.butaba.col += 1
425
426 def draw_butaba (self):
427 if self.butaba.position == butaba.Butaba.FRONT:
428 self.screen.blit (self.img_butabafront, (self.butaba.col*48, self.butaba.row*48))
429 elif self.butaba.position == butaba.Butaba.BACK:
430 self.screen.blit (self.img_butababack, (self.butaba.col*48, self.butaba.row*48))
431 elif self.butaba.position == butaba.Butaba.LEFT:
432 self.screen.blit (self.img_butabaleft, (self.butaba.col*48, self.butaba.row*48))
433 elif self.butaba.position == butaba.Butaba.RIGHT:
434 self.screen.blit (self.img_butabaright, (self.butaba.col*48, self.butaba.row*48))
435
436
437 # Draw the status infodisplay
438 def draw_status (self):
439 self.screen.blit (self.img_redpotion, (485, 10))
440 utility.put_text (self.screen, 550, 25, 20, (255, 0, 0), "%d" % self.butaba.health)
441
442 self.screen.blit (self.img_lightning, (620, 10))
443 utility.put_text (self.screen, 660, 25, 20, (255,255,255), "%d" % self.butaba.strength)
444
445 self.screen.blit (self.img_wand, (485, 65))
446 utility.put_text (self.screen, 550, 75, 20, (0, 0, 255), "%d" % self.butaba.magic)
447
448 self.screen.blit (self.img_bulb, (620, 65))
449 utility.put_text (self.screen, 660, 75, 20, (0, 255, 0), "%d" % self.butaba.experience)
450
451 self.screen.blit (self.img_goldcoins, (485, 110))
452 utility.put_text (self.screen, 550, 130, 20, (255, 255, 0), "%d" % self.butaba.gold)
453
454 if self.status_message is not None:
455 utility.put_text (self.screen, 10, 485, 10, (255,255, 0), "%s" % self.status_message)
456
457 # display the inventory of the player
458 def draw_inventory (self):
459 # draw the inventory slots
460 r = 1
461 c = 1
462 utility.put_text (self.screen, 490, 170, 16, (255,255 , 0), "Inventory")
463 for i in range (butaba.Butaba.MAXITEMS):
464 self.screen.blit (self.img_inventory, (440+c*54, 150+r*54))
465 if c % 4 == 0:
466 r += 1
467 c = 1
468 else:
469 c += 1
470
471 r = 1
472 c = 1
473 for obj in self.butaba.objects:
474 self.screen.blit (obj.image, (440+c*54+2, 150+r*54+2))
475 if c % 4 == 0:
476 r += 1
477 c = 1
478 else:
479 c += 1
480
481 # Draw the level background tiles on surface
482 def draw_level_background (self, level):
483 i = 0
484 for row in level.background:
485 j = 0
486 for tilerow, tilecol, is_solid in row:
487 tilex = tilecol * 48
488 tiley = tilerow * 48
489 self.screen.blit (self.img_tileset, (j*48, i*48), pygame.Rect (tilex, tiley, 48, 48))
490
491 j += 1
492 i += 1
493
494 # Draw the level objects
495 def draw_level_objects (self, level):
496 for obj in level.objects:
497 if obj.image is not None:
498 self.screen.blit (obj.image, (obj.col*48, obj.row*48))
499
500
501 # Draw the NPCs in the level
502 def draw_level_npcs (self, level):
503 for npc in level.npcs:
504 if npc.image is not None:
505 self.screen.blit (npc.image, (npc.col*48, npc.row*48))