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