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