data/states/battle.py (view raw)
1"""This is the state that handles battles against
2monsters"""
3
4import random
5import pygame as pg
6from .. import tools, setup
7from .. components import person, attack
8from .. import constants as c
9
10#STATES
11
12SELECT_ACTION = 'select action'
13SELECT_ENEMY = 'select enemy'
14ENEMY_ATTACK = 'enemy attack'
15PLAYER_ATTACK = 'player attack'
16SELECT_ITEM = 'select item'
17SELECT_MAGIC = 'select magic'
18RUN_AWAY = 'run away'
19ATTACK_ANIMATION = 'attack animation'
20
21#EVENTS
22
23END_BATTLE = 'end battle'
24
25
26class Battle(tools._State):
27 def __init__(self):
28 super(Battle, self).__init__()
29
30 def startup(self, current_time, game_data):
31 """Initialize state attributes"""
32 self.current_time = current_time
33 self.allow_input = False
34 self.game_data = game_data
35 self.background = self.make_background()
36 self.enemy_group, self.enemy_pos_list = self.make_enemies()
37 self.player = self.make_player()
38 self.attack_animations = pg.sprite.Group()
39 self.info_box = InfoBox(game_data)
40 self.arrow = SelectArrow(self.enemy_pos_list)
41 self.attacked_enemy = None
42 self.attacking_enemy = None
43 self.select_box = SelectBox()
44 self.state = SELECT_ACTION
45 self.select_action_state_dict = self.make_selection_state_dict()
46 self.name = 'battle'
47 self.next = game_data['last state']
48 self.observer = Observer(self)
49 self.player.observer = self.observer
50
51 def make_background(self):
52 """Make the blue/black background"""
53 background = pg.sprite.Sprite()
54 surface = pg.Surface(c.SCREEN_SIZE).convert()
55 surface.fill(c.BLACK_BLUE)
56 background.image = surface
57 background.rect = background.image.get_rect()
58 background_group = pg.sprite.Group(background)
59
60 return background_group
61
62 def make_enemies(self):
63 """Make the enemies for the battle. Return sprite group"""
64 pos_list = []
65
66 for column in range(3):
67 for row in range(3):
68 x = (column * 100) + 100
69 y = (row * 100) + 100
70 pos_list.append([x,y])
71
72 enemy_group = pg.sprite.Group()
73
74 for enemy in range(random.randint(1, 6)):
75 enemy_group.add(person.Person('devil', 0, 0,
76 'down', 'battle resting'))
77
78 for i, enemy in enumerate(enemy_group):
79 enemy.rect.topleft = pos_list[i]
80 enemy.image = pg.transform.scale2x(enemy.image)
81
82 return enemy_group, pos_list[0:len(enemy_group)]
83
84 def make_player(self):
85 """Make the sprite for the player's character"""
86 player = person.Player('left', 630, 220, 'battle resting', 1)
87 player.image = pg.transform.scale2x(player.image)
88
89 return player
90
91 def make_selection_state_dict(self):
92 """
93 Make a dictionary of states with arrow coordinates as keys.
94 """
95 pos_list = self.arrow.make_select_action_pos_list()
96 state_list = [SELECT_ENEMY, SELECT_ITEM, SELECT_MAGIC, RUN_AWAY]
97 return dict(zip(pos_list, state_list))
98
99 def update(self, surface, keys, current_time):
100 """Update the battle state"""
101 self.check_input(keys)
102 self.enemy_group.update(current_time)
103 self.player.update(keys, current_time)
104 self.attack_animations.update()
105 self.arrow.update(keys)
106
107 self.draw_battle(surface)
108
109 def check_input(self, keys):
110 """
111 Check user input to navigate GUI.
112 """
113 if self.allow_input:
114 if keys[pg.K_RETURN]:
115 self.notify(END_BATTLE)
116
117 elif keys[pg.K_SPACE]:
118 if self.state == SELECT_ACTION:
119 self.state = self.select_action_state_dict[
120 self.arrow.rect.topleft]
121 self.notify(self.state)
122
123 elif self.state == SELECT_ENEMY:
124 self.state = PLAYER_ATTACK
125 self.notify(self.state)
126
127 self.allow_input = False
128
129 if keys[pg.K_RETURN] == False and keys[pg.K_SPACE] == False:
130 self.allow_input = True
131
132 def notify(self, event):
133 """
134 Notify observer of event.
135 """
136 self.observer.on_notify(event)
137
138 def end_battle(self):
139 """
140 End battle and flip back to previous state.
141 """
142 self.game_data['last state'] = self.name
143 self.done = True
144
145 def draw_battle(self, surface):
146 """Draw all elements of battle state"""
147 self.background.draw(surface)
148 self.enemy_group.draw(surface)
149 self.attack_animations.draw(surface)
150 surface.blit(self.player.image, self.player.rect)
151 surface.blit(self.info_box.image, self.info_box.rect)
152 surface.blit(self.select_box.image, self.select_box.rect)
153 surface.blit(self.arrow.image, self.arrow.rect)
154
155
156class Observer(object):
157 """
158 Observes events of battle and passes info to components.
159 """
160 def __init__(self, level):
161 self.level = level
162 self.info_box = level.info_box
163 self.select_box = level.info_box
164 self.arrow = level.arrow
165 self.player = level.player
166 self.enemies = level.enemy_group
167 self.event_dict = self.make_event_dict()
168
169 def make_event_dict(self):
170 """
171 Make a dictionary of events the Observer can
172 receive.
173 """
174 event_dict = {END_BATTLE: self.end_battle,
175 SELECT_ACTION: self.select_action,
176 SELECT_ITEM: self.select_item,
177 SELECT_ENEMY: self.select_enemy,
178 ENEMY_ATTACK: self.enemy_attack,
179 PLAYER_ATTACK: self.player_attack,
180 ATTACK_ANIMATION: self.attack_animation,
181 RUN_AWAY: self.run_away}
182
183 return event_dict
184
185 def on_notify(self, event):
186 """
187 Notify Observer of event.
188 """
189 if event in self.event_dict:
190 self.event_dict[event]()
191
192 def end_battle(self):
193 """
194 End Battle and flip to previous state.
195 """
196 self.level.end_battle()
197
198 def select_action(self):
199 """
200 Set components to select action.
201 """
202 self.level.state = SELECT_ACTION
203 self.arrow.index = 0
204 self.arrow.state = SELECT_ACTION
205 self.arrow.image = setup.GFX['smallarrow']
206
207 def select_enemy(self):
208 self.level.state = SELECT_ENEMY
209 self.arrow.state = SELECT_ENEMY
210
211 def select_item(self):
212 self.level.state = SELECT_ITEM
213 self.info_box.image = self.info_box.make_image(SELECT_ITEM)
214
215 def enemy_attack(self):
216 pass
217
218 def player_attack(self):
219 enemy_posx = self.arrow.rect.x + 60
220 enemy_posy = self.arrow.rect.y - 20
221 enemy_pos = (enemy_posx, enemy_posy)
222 enemy_to_attack = None
223
224 for enemy in self.enemies:
225 if enemy.rect.topleft == enemy_pos:
226 enemy_to_attack = enemy
227
228 self.player.enter_attack_state(enemy_to_attack)
229 self.arrow.become_invisible_surface()
230
231 def attack_animation(self):
232 """
233 Make an attack animation over attacked enemy.
234 """
235 enemy = self.player.attacked_enemy
236 if enemy:
237 enemy.kill()
238 posx = enemy.rect.x - 32
239 posy = enemy.rect.y - 64
240 fire_sprite = attack.Fire(posx, posy)
241 self.level.attack_animations.add(fire_sprite)
242
243 def run_away(self):
244 self.level.end_battle()
245
246
247class InfoBox(object):
248 """
249 Info box that describes attack damage and other battle
250 related information.
251 """
252 def __init__(self, game_data):
253 self.game_data = game_data
254 self.state = SELECT_ACTION
255 self.title_font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 22)
256 self.title_font.set_underline(True)
257 self.font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 18)
258 self.message_dict = self.make_message_dict()
259 self.image = self.make_image(SELECT_ACTION)
260 self.rect = self.image.get_rect(bottom=608)
261
262 def make_message_dict(self):
263 """
264 Make dictionary of states Battle info can be in.
265 """
266 message_dict = {SELECT_ACTION: 'Select an action.',
267 SELECT_MAGIC: 'Select a magic spell.',
268 SELECT_ITEM: 'Select an item.',
269 SELECT_ENEMY: 'Select an enemy.',
270 ENEMY_ATTACK: 'The enemy attacks player!',
271 PLAYER_ATTACK: 'Player attacks enemy.',
272 RUN_AWAY: 'Run away'}
273
274 return message_dict
275
276 def make_item_text(self):
277 """
278 Make the text for when the player selects items.
279 """
280 inventory = self.game_data['player inventory']
281 allowed_item_list = ['Healing Potion']
282 title = 'SELECT ITEM'
283 item_text_list = [title]
284
285 for item in inventory:
286 if item in allowed_item_list:
287 text = item + ": " + str(inventory[item]['quantity'])
288 item_text_list.append(text)
289
290 item_text_list.append('BACK')
291
292 return item_text_list
293
294 def make_text_sprites(self, text_list):
295 """
296 Make sprites out of text.
297 """
298 sprite_group = pg.sprite.Group()
299
300 for i, text in enumerate(text_list):
301 sprite = pg.sprite.Sprite()
302
303 if i == 0:
304 x = 195
305 y = 10
306 surface = self.title_font.render(text, True, c.NEAR_BLACK)
307 rect = surface.get_rect(x=x, y=y)
308 else:
309 x = 100
310 y = (i * 30) + 20
311 surface = self.font.render(text, True, c.NEAR_BLACK)
312 rect = surface.get_rect(x=x, y=y)
313 sprite.image = surface
314 sprite.rect = rect
315 sprite_group.add(sprite)
316
317 return sprite_group
318
319 def make_image(self, state):
320 """
321 Make image out of box and message.
322 """
323 image = setup.GFX['shopbox']
324 rect = image.get_rect(bottom=608)
325 surface = pg.Surface(rect.size)
326 surface.set_colorkey(c.BLACK)
327 surface.blit(image, (0, 0))
328
329 if state == SELECT_ITEM:
330 text_sprites = self.make_text_sprites(self.make_item_text())
331 text_sprites.draw(surface)
332 else:
333 text_surface = self.font.render(self.message_dict[state], True, c.NEAR_BLACK)
334 text_rect = text_surface.get_rect(x=50, y=50)
335 surface.blit(text_surface, text_rect)
336
337 return surface
338
339
340class SelectBox(object):
341 """
342 Box to select whether to attack, use item, use magic or run away.
343 """
344 def __init__(self):
345 self.font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 22)
346 self.slots = self.make_slots()
347 self.image = self.make_image()
348 self.rect = self.image.get_rect(bottom=608,
349 right=800)
350
351 def make_image(self):
352 """
353 Make the box image for
354 """
355 image = setup.GFX['goldbox']
356 rect = image.get_rect(bottom=608)
357 surface = pg.Surface(rect.size)
358 surface.set_colorkey(c.BLACK)
359 surface.blit(image, (0, 0))
360
361 for text in self.slots:
362 text_surface = self.font.render(text, True, c.NEAR_BLACK)
363 text_rect = text_surface.get_rect(x=self.slots[text]['x'],
364 y=self.slots[text]['y'])
365 surface.blit(text_surface, text_rect)
366
367 return surface
368
369 def make_slots(self):
370 """
371 Make the slots that hold the text selections, and locations.
372 """
373 slot_dict = {}
374 selections = ['Attack', 'Items', 'Magic', 'Run']
375
376 for i, text in enumerate(selections):
377 slot_dict[text] = {'x': 150,
378 'y': (i*34)+10}
379
380 return slot_dict
381
382
383
384class SelectArrow(object):
385 """Small arrow for menu"""
386 def __init__(self, enemy_pos_list):
387 self.image = setup.GFX['smallarrow']
388 self.rect = self.image.get_rect()
389 self.state = 'select action'
390 self.state_dict = self.make_state_dict()
391 self.pos_list = self.make_select_action_pos_list()
392 self.index = 0
393 self.rect.topleft = self.pos_list[self.index]
394 self.allow_input = False
395 self.enemy_pos_list = enemy_pos_list
396
397 def make_state_dict(self):
398 """Make state dictionary"""
399 state_dict = {'select action': self.select_action,
400 'select enemy': self.select_enemy}
401
402 return state_dict
403
404 def select_action(self, keys):
405 """
406 Select what action the player should take.
407 """
408 self.pos_list = self.make_select_action_pos_list()
409 self.rect.topleft = self.pos_list[self.index]
410
411 if self.allow_input:
412 if keys[pg.K_DOWN] and self.index < (len(self.pos_list) - 1):
413 self.index += 1
414 self.allow_input = False
415 elif keys[pg.K_UP] and self.index > 0:
416 self.index -= 1
417 self.allow_input = False
418
419 if keys[pg.K_DOWN] == False and keys[pg.K_UP] == False:
420 self.allow_input = True
421
422
423 def make_select_action_pos_list(self):
424 """
425 Make the list of positions the arrow can be in.
426 """
427 pos_list = []
428
429 for i in range(4):
430 x = 590
431 y = (i * 34) + 472
432 pos_list.append((x, y))
433
434 return pos_list
435
436 def select_enemy(self, keys):
437 """
438 Select what enemy you want to take action on.
439 """
440 self.pos_list = self.enemy_pos_list
441 pos = self.pos_list[self.index]
442 self.rect.x = pos[0] - 60
443 self.rect.y = pos[1] + 20
444
445 if self.allow_input:
446 if keys[pg.K_DOWN] and self.index < (len(self.pos_list) - 1):
447 self.index += 1
448 self.allow_input = False
449 elif keys[pg.K_UP] and self.index > 0:
450 self.index -= 1
451 self.allow_input = False
452
453
454 if keys[pg.K_DOWN] == False and keys[pg.K_UP] == False \
455 and keys[pg.K_RIGHT] == False and keys[pg.K_LEFT] == False:
456 self.allow_input = True
457
458 def become_invisible_surface(self):
459 """
460 Make image attribute an invisible surface.
461 """
462 self.image = pg.Surface((32, 32))
463 self.image.set_colorkey(c.BLACK)
464
465
466 def enter_select_action(self):
467 """
468 Assign values for the select action state.
469 """
470 pass
471
472 def enter_select_enemy(self):
473 """
474 Assign values for the select enemy state.
475 """
476 pass
477
478 def update(self, keys):
479 """
480 Update arrow position.
481 """
482 state_function = self.state_dict[self.state]
483 state_function(keys)
484
485 def draw(self, surface):
486 """
487 Draw to surface.
488 """
489 surface.blit(self.image, self.rect)
490