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