data/states/battle.py (view raw)
1"""This is the state that handles battles against
2monsters"""
3import random
4import pygame as pg
5from .. import tools, battlegui, observer, setup
6from .. components import person, attack, attackitems
7from .. import constants as c
8
9
10class Battle(tools._State):
11 def __init__(self):
12 super(Battle, self).__init__()
13 self.name = 'battle'
14 self.music = setup.MUSIC['high_action']
15
16 def startup(self, current_time, game_data):
17 """Initialize state attributes"""
18 self.current_time = current_time
19 self.timer = current_time
20 self.allow_input = False
21 self.game_data = game_data
22 self.inventory = game_data['player inventory']
23 self.state = c.SELECT_ACTION
24 self.next = game_data['last state']
25 self.run_away = False
26
27 self.player = self.make_player()
28 self.attack_animations = pg.sprite.Group()
29 self.sword = attackitems.Sword(self.player)
30 self.enemy_group, self.enemy_pos_list, self.enemy_list = self.make_enemies()
31 self.experience_points = self.get_experience_points()
32 self.new_gold = self.get_new_gold()
33 self.background = self.make_background()
34 self.info_box = battlegui.InfoBox(game_data,
35 self.experience_points,
36 self.new_gold)
37 self.arrow = battlegui.SelectArrow(self.enemy_pos_list,
38 self.info_box)
39 self.select_box = battlegui.SelectBox()
40 self.player_health_box = battlegui.PlayerHealth(self.select_box.rect,
41 self.game_data)
42
43 self.select_action_state_dict = self.make_selection_state_dict()
44 self.observers = [observer.Battle(self)]
45 self.player.observers.extend(self.observers)
46 self.damage_points = pg.sprite.Group()
47 self.player_actions = []
48 self.player_action_dict = self.make_player_action_dict()
49 self.player_level = self.game_data['player stats']['Level']
50 self.enemies_to_attack = []
51 self.action_selected = False
52 self.just_leveled_up = False
53
54 def make_player_action_dict(self):
55 """
56 Make the dict to execute player actions.
57 """
58 action_dict = {c.PLAYER_ATTACK: self.enter_player_attack_state,
59 c.CURE_SPELL: self.cast_cure,
60 c.FIRE_SPELL: self.cast_fire_blast,
61 c.DRINK_HEALING_POTION: self.enter_drink_healing_potion_state,
62 c.DRINK_ETHER_POTION: self.enter_drink_ether_potion_state}
63
64 return action_dict
65
66 def make_enemy_level_dict(self):
67 new_dict = {c.OVERWORLD: 1,
68 c.DUNGEON: 2,
69 c.DUNGEON2: 2,
70 c.DUNGEON3: 3,
71 c.DUNGEON4: 2,
72 c.DUNGEON5: 5}
73
74 return new_dict
75
76 def set_enemy_level(self, enemy_list):
77 dungeon_level_dict = self.make_enemy_level_dict()
78
79 for enemy in enemy_list:
80 enemy.level = dungeon_level_dict[self.previous]
81
82 def get_experience_points(self):
83 """
84 Calculate experience points based on number of enemies
85 and their levels.
86 """
87 experience_total = 0
88
89 for enemy in self.enemy_list:
90 experience_total += (random.randint(5,10)*enemy.level)
91
92 return experience_total
93
94 def get_new_gold(self):
95 """
96 Calculate the gold collected at the end of the battle.
97 """
98 gold = 0
99
100 for enemy in self.enemy_list:
101 max_gold = enemy.level * 10
102 gold += (random.randint(1, max_gold))
103
104 return gold
105
106 def make_background(self):
107 """Make the blue/black background"""
108 background = pg.sprite.Sprite()
109 surface = pg.Surface(c.SCREEN_SIZE).convert()
110 surface.fill(c.BLACK_BLUE)
111 background.image = surface
112 background.rect = background.image.get_rect()
113 background_group = pg.sprite.Group(background)
114
115 return background_group
116
117 def make_enemies(self):
118 """Make the enemies for the battle. Return sprite group"""
119 pos_list = []
120
121 for column in range(3):
122 for row in range(3):
123 x = (column * 100) + 100
124 y = (row * 100) + 100
125 pos_list.append([x, y])
126
127 enemy_group = pg.sprite.Group()
128
129 if self.game_data['battle type']:
130 enemy = person.Enemy('evilwizard', 0, 0,
131 'down', 'battle resting')
132 enemy_group.add(enemy)
133 else:
134 for enemy in range(random.randint(1, 6)):
135 enemy_group.add(person.Enemy('devil', 0, 0,
136 'down', 'battle resting'))
137
138 for i, enemy in enumerate(enemy_group):
139 enemy.rect.topleft = pos_list[i]
140 enemy.image = pg.transform.scale2x(enemy.image)
141 enemy.index = i
142 enemy.level = self.make_enemy_level_dict()[self.previous]
143 enemy.health = enemy.level * 7
144
145 enemy_list = [enemy for enemy in enemy_group]
146
147 return enemy_group, pos_list[0:len(enemy_group)], enemy_list
148
149 def make_player(self):
150 """Make the sprite for the player's character"""
151 player = person.Player('left', self.game_data, 630, 220, 'battle resting', 1)
152 player.image = pg.transform.scale2x(player.image)
153 return player
154
155 def make_selection_state_dict(self):
156 """
157 Make a dictionary of states with arrow coordinates as keys.
158 """
159 pos_list = self.arrow.make_select_action_pos_list()
160 state_list = [self.enter_select_enemy_state, self.enter_select_item_state,
161 self.enter_select_magic_state, self.try_to_run_away]
162 return dict(zip(pos_list, state_list))
163
164 def update(self, surface, keys, current_time):
165 """Update the battle state"""
166 self.current_time = current_time
167 self.check_input(keys)
168 self.check_timed_events()
169 self.check_if_battle_won()
170 self.enemy_group.update(current_time)
171 self.player.update(keys, current_time)
172 self.attack_animations.update()
173 self.info_box.update()
174 self.arrow.update(keys)
175 self.sword.update(current_time)
176 self.damage_points.update()
177 self.execute_player_actions()
178
179 self.draw_battle(surface)
180
181 def check_input(self, keys):
182 """
183 Check user input to navigate GUI.
184 """
185 if self.allow_input:
186 if keys[pg.K_RETURN]:
187 self.end_battle()
188
189 elif keys[pg.K_SPACE]:
190 if self.state == c.SELECT_ACTION:
191 enter_state_function = self.select_action_state_dict[
192 self.arrow.rect.topleft]
193 enter_state_function()
194
195 elif self.state == c.SELECT_ENEMY:
196 self.player_actions.append(c.PLAYER_ATTACK)
197 self.enemies_to_attack.append(self.get_enemy_to_attack())
198 self.action_selected = True
199
200 elif self.state == c.SELECT_ITEM:
201 if self.arrow.index == (len(self.arrow.pos_list) - 1):
202 self.enter_select_action_state()
203 elif self.info_box.item_text_list[self.arrow.index][:14] == 'Healing Potion':
204 self.player_actions.append(c.DRINK_HEALING_POTION)
205 self.action_selected = True
206 elif self.info_box.item_text_list[self.arrow.index][:5] == 'Ether':
207 self.player_actions.append(c.DRINK_ETHER_POTION)
208 self.action_selected = True
209 elif self.state == c.SELECT_MAGIC:
210 if self.arrow.index == (len(self.arrow.pos_list) - 1):
211 self.enter_select_action_state()
212 elif self.info_box.magic_text_list[self.arrow.index] == 'Cure':
213 magic_points = self.game_data['player inventory']['Cure']['magic points']
214 if self.game_data['player stats']['magic']['current'] >= magic_points:
215 self.player_actions.append(c.CURE_SPELL)
216 self.action_selected = True
217 elif self.info_box.magic_text_list[self.arrow.index] == 'Fire Blast':
218 magic_points = self.game_data['player inventory']['Fire Blast']['magic points']
219 if self.game_data['player stats']['magic']['current'] >= magic_points:
220 self.player_actions.append(c.FIRE_SPELL)
221 self.action_selected = True
222
223 self.allow_input = False
224
225 if keys[pg.K_RETURN] == False and keys[pg.K_SPACE] == False:
226 self.allow_input = True
227
228 def check_timed_events(self):
229 """
230 Check if amount of time has passed for timed events.
231 """
232 timed_states = [c.PLAYER_DAMAGED,
233 c.ENEMY_DAMAGED,
234 c.ENEMY_DEAD,
235 c.DRINK_HEALING_POTION,
236 c.DRINK_ETHER_POTION]
237 long_delay = timed_states[1:]
238
239 if self.state in long_delay:
240 if (self.current_time - self.timer) > 1000:
241 if self.state == c.ENEMY_DAMAGED:
242 if self.player_actions:
243 self.player_action_dict[self.player_actions[0]]()
244 self.player_actions.pop(0)
245 else:
246 if len(self.enemy_list):
247 self.enter_enemy_attack_state()
248 else:
249 self.enter_battle_won_state()
250 elif (self.state == c.DRINK_HEALING_POTION or
251 self.state == c.CURE_SPELL or
252 self.state == c.DRINK_ETHER_POTION):
253 if self.player_actions:
254 self.player_action_dict[self.player_actions[0]]()
255 self.player_actions.pop(0)
256 else:
257 if len(self.enemy_list):
258 self.enter_enemy_attack_state()
259 else:
260 self.enter_battle_won_state()
261 self.timer = self.current_time
262
263 elif self.state == c.FIRE_SPELL or self.state == c.CURE_SPELL:
264 if (self.current_time - self.timer) > 1500:
265 if self.player_actions:
266 self.player_action_dict[self.player_actions[0]]()
267 self.player_actions.pop(0)
268 else:
269 if len(self.enemy_list):
270 self.enter_enemy_attack_state()
271 else:
272 self.enter_battle_won_state()
273 self.timer = self.current_time
274
275 elif self.state == c.RUN_AWAY:
276 if (self.current_time - self.timer) > 1500:
277 self.end_battle()
278
279 elif self.state == c.BATTLE_WON:
280 if (self.current_time - self.timer) > 1800:
281 self.enter_show_gold_state()
282
283 elif self.state == c.SHOW_GOLD:
284 if (self.current_time - self.timer) > 1800:
285 self.enter_show_experience_state()
286
287 elif self.state == c.LEVEL_UP:
288 if (self.current_time - self.timer) > 2200:
289 if self.game_data['player stats']['Level'] == 3:
290 self.enter_two_actions_per_turn_state()
291 else:
292 self.end_battle()
293
294 elif self.state == c.TWO_ACTIONS:
295 if (self.current_time - self.timer) > 3000:
296 self.end_battle()
297
298 elif self.state == c.SHOW_EXPERIENCE:
299 if (self.current_time - self.timer) > 2200:
300 player_stats = self.game_data['player stats']
301 player_stats['experience to next level'] -= self.experience_points
302 if player_stats['experience to next level'] <= 0:
303 player_stats['Level'] += 1
304 player_stats['health']['maximum'] += int(player_stats['health']['maximum']*.25)
305 player_stats['magic']['maximum'] += int(player_stats['magic']['maximum']*.20)
306 new_experience = int((player_stats['Level'] * 100) * .75)
307 player_stats['experience to next level'] = new_experience
308 self.enter_level_up_state()
309 self.just_leveled_up = True
310 else:
311 self.end_battle()
312
313 elif self.state == c.PLAYER_DAMAGED:
314 if (self.current_time - self.timer) > 600:
315 if self.enemy_index == (len(self.enemy_list) - 1):
316 if self.run_away:
317 self.enter_run_away_state()
318 else:
319 self.enter_select_action_state()
320 else:
321 self.switch_enemy()
322 self.timer = self.current_time
323
324 def check_if_battle_won(self):
325 """
326 Check if state is SELECT_ACTION and there are no enemies left.
327 """
328 if self.state == c.SELECT_ACTION:
329 if len(self.enemy_group) == 0:
330 self.enter_battle_won_state()
331
332 def notify(self, event):
333 """
334 Notify observer of event.
335 """
336 for new_observer in self.observers:
337 new_observer.on_notify(event)
338
339 def end_battle(self):
340 """
341 End battle and flip back to previous state.
342 """
343 if self.game_data['battle type'] == 'evilwizard':
344 self.game_data['crown quest'] = True
345 self.game_data['last state'] = self.name
346 self.game_data['battle counter'] = random.randint(50, 255)
347 self.game_data['battle type'] = None
348 self.done = True
349
350 def attack_enemy(self, enemy_damage):
351 enemy = self.player.attacked_enemy
352 enemy.health -= enemy_damage
353 self.set_enemy_indices()
354
355 if enemy:
356 enemy.enter_knock_back_state()
357 if enemy.health <= 0:
358 self.enemy_list.pop(enemy.index)
359 enemy.state = c.FADE_DEATH
360 self.arrow.remove_pos(self.player.attacked_enemy)
361 self.enemy_index = 0
362
363 def set_enemy_indices(self):
364 for i, enemy in enumerate(self.enemy_list):
365 enemy.index = i
366
367 def draw_battle(self, surface):
368 """Draw all elements of battle state"""
369 self.background.draw(surface)
370 self.enemy_group.draw(surface)
371 self.attack_animations.draw(surface)
372 self.sword.draw(surface)
373 surface.blit(self.player.image, self.player.rect)
374 surface.blit(self.info_box.image, self.info_box.rect)
375 surface.blit(self.select_box.image, self.select_box.rect)
376 surface.blit(self.arrow.image, self.arrow.rect)
377 self.player_health_box.draw(surface)
378 self.damage_points.draw(surface)
379
380 def player_damaged(self, damage):
381 self.game_data['player stats']['health']['current'] -= damage
382
383 def player_healed(self, heal, magic_points=0):
384 """
385 Add health from potion to game data.
386 """
387 health = self.game_data['player stats']['health']
388
389 health['current'] += heal
390 if health['current'] > health['maximum']:
391 health['current'] = health['maximum']
392
393 if self.state == c.DRINK_HEALING_POTION:
394 self.game_data['player inventory']['Healing Potion']['quantity'] -= 1
395 if self.game_data['player inventory']['Healing Potion']['quantity'] == 0:
396 del self.game_data['player inventory']['Healing Potion']
397 elif self.state == c.CURE_SPELL:
398 self.game_data['player stats']['magic']['current'] -= magic_points
399
400 def magic_boost(self, magic_points):
401 """
402 Add magic from ether to game data.
403 """
404 magic = self.game_data['player stats']['magic']
405 magic['current'] += magic_points
406 if magic['current'] > magic['maximum']:
407 magic['current'] = magic['maximum']
408
409 self.game_data['player inventory']['Ether Potion']['quantity'] -= 1
410 if not self.game_data['player inventory']['Ether Potion']['quantity']:
411 del self.game_data['player inventory']['Ether Potion']
412
413 def set_timer_to_current_time(self):
414 """Set the timer to the current time."""
415 self.timer = self.current_time
416
417 def cast_fire_blast(self):
418 """
419 Cast fire blast on all enemies.
420 """
421 self.state = self.info_box.state = c.FIRE_SPELL
422 POWER = self.inventory['Fire Blast']['power']
423 MAGIC_POINTS = self.inventory['Fire Blast']['magic points']
424 self.game_data['player stats']['magic']['current'] -= MAGIC_POINTS
425 for enemy in self.enemy_list:
426 DAMAGE = random.randint(POWER//2, POWER)
427 self.damage_points.add(
428 attackitems.HealthPoints(DAMAGE, enemy.rect.topright))
429 enemy.health -= DAMAGE
430 posx = enemy.rect.x - 32
431 posy = enemy.rect.y - 64
432 fire_sprite = attack.Fire(posx, posy)
433 self.attack_animations.add(fire_sprite)
434 if enemy.health <= 0:
435 enemy.kill()
436 self.arrow.remove_pos(enemy)
437 else:
438 enemy.enter_knock_back_state()
439 self.enemy_list = [enemy for enemy in self.enemy_list if enemy.health > 0]
440 self.enemy_index = 0
441 self.arrow.index = 0
442 self.arrow.state = 'invisible'
443 self.set_timer_to_current_time()
444
445 def cast_cure(self):
446 """
447 Cast cure spell on player.
448 """
449 self.state = c.CURE_SPELL
450 HEAL_AMOUNT = self.inventory['Cure']['power']
451 MAGIC_POINTS = self.inventory['Cure']['magic points']
452 self.player.healing = True
453 self.set_timer_to_current_time()
454 self.arrow.state = 'invisible'
455 self.enemy_index = 0
456 self.damage_points.add(
457 attackitems.HealthPoints(HEAL_AMOUNT, self.player.rect.topright, False))
458 self.player_healed(HEAL_AMOUNT, MAGIC_POINTS)
459 self.info_box.state = c.DRINK_HEALING_POTION
460
461 def drink_ether(self):
462 """
463 Drink ether potion.
464 """
465 self.state = self.info_box.state = c.DRINK_ETHER_POTION
466 self.player.healing = True
467 self.set_timer_to_current_time()
468 self.arrow.state = 'invisible'
469 self.enemy_index = 0
470 self.damage_points.add(
471 attackitems.HealthPoints(30,
472 self.player.rect.topright,
473 False,
474 True))
475 self.magic_boost(30)
476
477 def drink_healing_potion(self):
478 """
479 Drink Healing Potion.
480 """
481 self.state = self.info_box.state = c.DRINK_HEALING_POTION
482 self.player.healing = True
483 self.arrow.state = 'invisible'
484 self.enemy_index = 0
485 self.damage_points.add(
486 attackitems.HealthPoints(30,
487 self.player.rect.topright,
488 False))
489 self.player_healed(30)
490 self.set_timer_to_current_time()
491
492 def enter_select_enemy_state(self):
493 """
494 Transition battle into the select enemy state.
495 """
496 self.state = self.arrow.state = c.SELECT_ENEMY
497 self.arrow.index = 0
498
499 def enter_select_item_state(self):
500 """
501 Transition battle into the select item state.
502 """
503 self.state = self.info_box.state = c.SELECT_ITEM
504 self.arrow.become_select_item_state()
505
506 def enter_select_magic_state(self):
507 """
508 Transition battle into the select magic state.
509 """
510 self.state = self.info_box.state = c.SELECT_MAGIC
511 self.arrow.become_select_magic_state()
512
513 def try_to_run_away(self):
514 """
515 Transition battle into the run away state.
516 """
517 self.run_away = True
518 self.arrow.state = 'invisible'
519 self.enemy_index = 0
520 self.enter_enemy_attack_state()
521
522 def enter_enemy_attack_state(self):
523 """
524 Transition battle into the Enemy attack state.
525 """
526 self.state = self.info_box.state = c.ENEMY_ATTACK
527 enemy = self.enemy_list[self.enemy_index]
528 enemy.enter_enemy_attack_state()
529
530 def enter_player_attack_state(self):
531 """
532 Transition battle into the Player attack state.
533 """
534 self.state = self.info_box.state = c.PLAYER_ATTACK
535 enemy_to_attack = self.enemies_to_attack.pop(0)
536 if enemy_to_attack in self.enemy_list:
537 self.player.enter_attack_state(enemy_to_attack)
538 else:
539 if self.enemy_list:
540 self.player.enter_attack_state(self.enemy_list[0])
541 else:
542 self.enter_battle_won_state()
543 self.arrow.state = 'invisible'
544
545 def get_enemy_to_attack(self):
546 """
547 Get enemy for player to attack by arrow position.
548 """
549 enemy_posx = self.arrow.rect.x + 60
550 enemy_posy = self.arrow.rect.y - 20
551 enemy_pos = (enemy_posx, enemy_posy)
552 enemy_to_attack = None
553
554 for enemy in self.enemy_list:
555 if enemy.rect.topleft == enemy_pos:
556 enemy_to_attack = enemy
557
558 return enemy_to_attack
559
560
561 def enter_drink_healing_potion_state(self):
562 """
563 Transition battle into the Drink Healing Potion state.
564 """
565 self.state = self.info_box.state = c.DRINK_HEALING_POTION
566 self.player.healing = True
567 self.set_timer_to_current_time()
568 self.arrow.state = 'invisible'
569 self.enemy_index = 0
570 self.damage_points.add(
571 attackitems.HealthPoints(30,
572 self.player.rect.topright,
573 False))
574 self.player_healed(30)
575
576 def enter_drink_ether_potion_state(self):
577 """
578 Transition battle into the Drink Ether Potion state.
579 """
580 self.state = self.info_box.state = c.DRINK_ETHER_POTION
581 self.player.healing = True
582 self.arrow.state = 'invisible'
583 self.enemy_index = 0
584 self.damage_points.add(
585 attackitems.HealthPoints(30,
586 self.player.rect.topright,
587 False,
588 True))
589 self.magic_boost(30)
590 self.set_timer_to_current_time()
591
592 def enter_select_action_state(self):
593 """
594 Transition battle into the select action state
595 """
596 self.state = self.info_box.state = c.SELECT_ACTION
597 self.arrow.index = 0
598 self.arrow.state = self.state
599
600 def enter_player_damaged_state(self):
601 """
602 Transition battle into the player damaged state.
603 """
604 self.state = self.info_box.state = c.PLAYER_DAMAGED
605 if self.enemy_index > len(self.enemy_list) - 1:
606 self.enemy_index = 0
607 enemy = self.enemy_list[self.enemy_index]
608 player_damage = enemy.calculate_hit(self.inventory['equipped armor'],
609 self.inventory)
610 self.damage_points.add(
611 attackitems.HealthPoints(player_damage,
612 self.player.rect.topright))
613 self.info_box.set_player_damage(player_damage)
614 self.set_timer_to_current_time()
615 self.player_damaged(player_damage)
616 if player_damage:
617 self.player.damaged = True
618 self.player.enter_knock_back_state()
619
620 def enter_enemy_damaged_state(self):
621 """
622 Transition battle into the enemy damaged state.
623 """
624 self.state = self.info_box.state = c.ENEMY_DAMAGED
625 enemy_damage = self.player.calculate_hit()
626 self.damage_points.add(
627 attackitems.HealthPoints(enemy_damage,
628 self.player.attacked_enemy.rect.topright))
629
630 self.info_box.set_enemy_damage(enemy_damage)
631
632 self.arrow.index = 0
633 self.attack_enemy(enemy_damage)
634 self.set_timer_to_current_time()
635
636 def switch_enemy(self):
637 """
638 Switch which enemy the player is attacking.
639 """
640 if self.enemy_index < len(self.enemy_list) - 1:
641 self.enemy_index += 1
642 self.enter_enemy_attack_state()
643
644 def enter_run_away_state(self):
645 """
646 Transition battle into the run away state.
647 """
648 self.state = self.info_box.state = c.RUN_AWAY
649 self.arrow.state = 'invisible'
650 self.player.state = c.RUN_AWAY
651 self.set_timer_to_current_time()
652
653 def enter_battle_won_state(self):
654 """
655 Transition battle into the battle won state.
656 """
657 self.state = self.info_box.state = c.BATTLE_WON
658 self.player.state = c.VICTORY_DANCE
659 self.set_timer_to_current_time()
660
661 def enter_show_gold_state(self):
662 """
663 Transition battle into the show gold state.
664 """
665 self.inventory['GOLD']['quantity'] += self.new_gold
666 self.state = self.info_box.state = c.SHOW_GOLD
667 self.set_timer_to_current_time()
668
669 def enter_show_experience_state(self):
670 """
671 Transition battle into the show experience state.
672 """
673 self.state = self.info_box.state = c.SHOW_EXPERIENCE
674 self.set_timer_to_current_time()
675
676 def enter_level_up_state(self):
677 """
678 Transition battle into the LEVEL UP state.
679 """
680 self.state = self.info_box.state = c.LEVEL_UP
681 self.info_box.reset_level_up_message()
682 self.set_timer_to_current_time()
683
684 def enter_two_actions_per_turn_state(self):
685 self.state = self.info_box.state = c.TWO_ACTIONS
686 self.set_timer_to_current_time()
687
688 def execute_player_actions(self):
689 """
690 Execute the player actions.
691 """
692 if self.player_level < 3:
693 if self.player_actions:
694 enter_state = self.player_action_dict[self.player_actions[0]]
695 enter_state()
696 self.player_actions.pop(0)
697 else:
698 if len(self.player_actions) == 2:
699 enter_state = self.player_action_dict[self.player_actions[0]]
700 enter_state()
701 self.player_actions.pop(0)
702 self.action_selected = False
703 else:
704 if self.action_selected:
705 self.enter_select_action_state()
706 self.action_selected = False
707
708
709
710
711
712
713
714
715