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