all repos — Legends-RPG @ 6dbe415b4b27d5c61b98694551e99bbf3bedf8d1

A fantasy mini-RPG built with Python and Pygame.

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