all repos — Legends-RPG @ c00a96d3b62fa0d580bba601b8f00a621b26947a

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                        if self.game_data['player stats']['magic']['current'] >= 25:
198                            self.player_actions.append(c.CURE_SPELL)
199                            self.action_selected = True
200                    elif self.info_box.magic_text_list[self.arrow.index] == 'Fire Blast':
201                        if self.game_data['player stats']['magic']['current'] >= 25:
202                            self.player_actions.append(c.FIRE_SPELL)
203                            self.action_selected = True
204
205            self.allow_input = False
206
207        if keys[pg.K_RETURN] == False and keys[pg.K_SPACE] == False:
208            self.allow_input = True
209
210    def check_timed_events(self):
211        """
212        Check if amount of time has passed for timed events.
213        """
214        timed_states = [c.PLAYER_DAMAGED,
215                        c.ENEMY_DAMAGED,
216                        c.ENEMY_DEAD,
217                        c.DRINK_HEALING_POTION,
218                        c.DRINK_ETHER_POTION]
219        long_delay = timed_states[1:]
220
221        if self.state in long_delay:
222            if (self.current_time - self.timer) > 1000:
223                if self.state == c.ENEMY_DAMAGED:
224                    if self.player_actions:
225                        self.player_action_dict[self.player_actions[0]]()
226                        self.player_actions.pop(0)
227                    else:
228                        if len(self.enemy_list):
229                            self.enter_enemy_attack_state()
230                        else:
231                            self.enter_battle_won_state()
232                elif (self.state == c.DRINK_HEALING_POTION or
233                      self.state == c.CURE_SPELL or
234                      self.state == c.DRINK_ETHER_POTION):
235                    if self.player_actions:
236                        self.player_action_dict[self.player_actions[0]]()
237                        self.player_actions.pop(0)
238                    else:
239                        if len(self.enemy_list):
240                            self.enter_enemy_attack_state()
241                        else:
242                            self.enter_battle_won_state()
243                self.timer = self.current_time
244
245        elif self.state == c.FIRE_SPELL or self.state == c.CURE_SPELL:
246            if (self.current_time - self.timer) > 1500:
247                if self.player_actions:
248                    self.player_action_dict[self.player_actions[0]]()
249                    self.player_actions.pop(0)
250                else:
251                    if len(self.enemy_list):
252                        self.enter_enemy_attack_state()
253                    else:
254                        self.enter_battle_won_state()
255                self.timer = self.current_time
256
257        elif self.state == c.RUN_AWAY:
258            if (self.current_time - self.timer) > 1500:
259                self.end_battle()
260
261        elif self.state == c.BATTLE_WON:
262            if (self.current_time - self.timer) > 1800:
263                self.enter_show_experience_state()
264
265        elif self.state == c.LEVEL_UP:
266            if (self.current_time - self.timer) > 2200:
267                if self.game_data['player stats']['Level'] == 3:
268                    self.enter_two_actions_per_turn_state()
269                else:
270                    self.end_battle()
271
272        elif self.state == c.TWO_ACTIONS:
273            if (self.current_time - self.timer) > 3000:
274                self.end_battle()
275
276        elif self.state == c.SHOW_EXPERIENCE:
277            if (self.current_time - self.timer) > 2200:
278                player_stats = self.game_data['player stats']
279                player_stats['experience to next level'] -= self.experience_points
280                if player_stats['experience to next level'] <= 0:
281                    player_stats['Level'] += 1
282                    player_stats['health']['maximum'] += int(player_stats['health']['maximum']*.25)
283                    player_stats['magic']['maximum'] += int(player_stats['magic']['maximum']*.20)
284                    new_experience = int((player_stats['Level'] * 100) * .75)
285                    player_stats['experience to next level'] = new_experience
286                    self.enter_level_up_state()
287                    self.just_leveled_up = True
288                else:
289                    self.end_battle()
290
291        elif self.state == c.PLAYER_DAMAGED:
292            if (self.current_time - self.timer) > 600:
293                if self.enemy_index == (len(self.enemy_list) - 1):
294                    if self.run_away:
295                        self.enter_run_away_state()
296                    else:
297                        self.enter_select_action_state()
298                else:
299                    self.switch_enemy()
300                self.timer = self.current_time
301
302    def check_if_battle_won(self):
303        """
304        Check if state is SELECT_ACTION and there are no enemies left.
305        """
306        if self.state == c.SELECT_ACTION:
307            if len(self.enemy_group) == 0:
308                self.enter_battle_won_state()
309
310    def notify(self, event):
311        """
312        Notify observer of event.
313        """
314        for new_observer in self.observers:
315            new_observer.on_notify(event)
316
317    def end_battle(self):
318        """
319        End battle and flip back to previous state.
320        """
321        if self.game_data['battle type'] == 'evilwizard':
322            self.game_data['crown quest'] = True
323        self.game_data['last state'] = self.name
324        self.game_data['battle counter'] = random.randint(50, 255)
325        self.game_data['battle type'] = None
326        self.done = True
327
328    def attack_enemy(self, enemy_damage):
329        enemy = self.player.attacked_enemy
330        enemy.health -= enemy_damage
331        self.set_enemy_indices()
332
333        if enemy:
334            enemy.enter_knock_back_state()
335            if enemy.health <= 0:
336                self.enemy_list.pop(enemy.index)
337                enemy.state = c.FADE_DEATH
338                self.arrow.remove_pos(self.player.attacked_enemy)
339            self.enemy_index = 0
340
341    def set_enemy_indices(self):
342        for i, enemy in enumerate(self.enemy_list):
343            enemy.index = i
344
345    def draw_battle(self, surface):
346        """Draw all elements of battle state"""
347        self.background.draw(surface)
348        self.enemy_group.draw(surface)
349        self.attack_animations.draw(surface)
350        self.sword.draw(surface)
351        surface.blit(self.player.image, self.player.rect)
352        surface.blit(self.info_box.image, self.info_box.rect)
353        surface.blit(self.select_box.image, self.select_box.rect)
354        surface.blit(self.arrow.image, self.arrow.rect)
355        self.player_health_box.draw(surface)
356        self.damage_points.draw(surface)
357
358    def player_damaged(self, damage):
359        self.game_data['player stats']['health']['current'] -= damage
360
361    def player_healed(self, heal, magic_points=0):
362        """
363        Add health from potion to game data.
364        """
365        health = self.game_data['player stats']['health']
366
367        health['current'] += heal
368        if health['current'] > health['maximum']:
369            health['current'] = health['maximum']
370
371        if self.state == c.DRINK_HEALING_POTION:
372            self.game_data['player inventory']['Healing Potion']['quantity'] -= 1
373            if self.game_data['player inventory']['Healing Potion']['quantity'] == 0:
374                del self.game_data['player inventory']['Healing Potion']
375        elif self.state == c.CURE_SPELL:
376            self.game_data['player stats']['magic']['current'] -= magic_points
377
378    def magic_boost(self, magic_points):
379        """
380        Add magic from ether to game data.
381        """
382        magic = self.game_data['player stats']['magic']
383        magic['current'] += magic_points
384        if magic['current'] > magic['maximum']:
385            magic['current'] = magic['maximum']
386
387        self.game_data['player inventory']['Ether Potion']['quantity'] -= 1
388        if not self.game_data['player inventory']['Ether Potion']['quantity']:
389            del self.game_data['player inventory']['Ether Potion']
390
391    def set_timer_to_current_time(self):
392        """Set the timer to the current time."""
393        self.timer = self.current_time
394
395    def cast_fire_blast(self):
396        """
397        Cast fire blast on all enemies.
398        """
399        self.state = self.info_box.state = c.FIRE_SPELL
400        POWER = self.inventory['Fire Blast']['power']
401        MAGIC_POINTS = self.inventory['Fire Blast']['magic']
402        self.game_data['player stats']['magic']['current'] -= MAGIC_POINTS
403        for enemy in self.enemy_list:
404            DAMAGE = random.randint(POWER//2, POWER)
405            self.damage_points.add(
406                attackitems.HealthPoints(DAMAGE, enemy.rect.topright))
407            enemy.health -= DAMAGE
408            posx = enemy.rect.x - 32
409            posy = enemy.rect.y - 64
410            fire_sprite = attack.Fire(posx, posy)
411            self.attack_animations.add(fire_sprite)
412            if enemy.health <= 0:
413                enemy.kill()
414                self.arrow.remove_pos(enemy)
415            else:
416                enemy.enter_knock_back_state()
417        self.enemy_list = [enemy for enemy in self.enemy_list if enemy.health > 0]
418        self.enemy_index = 0
419        self.arrow.index = 0
420        self.arrow.state = 'invisible'
421        self.set_timer_to_current_time()
422
423    def cast_cure(self):
424        """
425        Cast cure spell on player.
426        """
427        self.state = c.CURE_SPELL
428        HEAL_AMOUNT = self.inventory['Cure']['power']
429        MAGIC_POINTS = self.inventory['Cure']['magic points']
430        self.player.healing = True
431        self.set_timer_to_current_time()
432        self.arrow.state = 'invisible'
433        self.enemy_index = 0
434        self.damage_points.add(
435            attackitems.HealthPoints(HEAL_AMOUNT, self.player.rect.topright, False))
436        self.player_healed(HEAL_AMOUNT, MAGIC_POINTS)
437        self.info_box.state = c.DRINK_HEALING_POTION
438
439    def drink_ether(self):
440        """
441        Drink ether potion.
442        """
443        self.state = self.info_box.state = c.DRINK_ETHER_POTION
444        self.player.healing = True
445        self.set_timer_to_current_time()
446        self.arrow.state = 'invisible'
447        self.enemy_index = 0
448        self.damage_points.add(
449            attackitems.HealthPoints(30,
450                                     self.player.rect.topright,
451                                     False,
452                                     True))
453        self.magic_boost(30)
454
455    def drink_healing_potion(self):
456        """
457        Drink Healing Potion.
458        """
459        self.state = self.info_box.state = c.DRINK_HEALING_POTION
460        self.player.healing = True
461        self.arrow.state = 'invisible'
462        self.enemy_index = 0
463        self.damage_points.add(
464            attackitems.HealthPoints(30,
465                                     self.player.rect.topright,
466                                     False))
467        self.player_healed(30)
468        self.set_timer_to_current_time()
469
470    def enter_select_enemy_state(self):
471        """
472        Transition battle into the select enemy state.
473        """
474        self.state = self.arrow.state = c.SELECT_ENEMY
475        self.arrow.index = 0
476
477    def enter_select_item_state(self):
478        """
479        Transition battle into the select item state.
480        """
481        self.state = self.info_box.state = c.SELECT_ITEM
482        self.arrow.become_select_item_state()
483
484    def enter_select_magic_state(self):
485        """
486        Transition battle into the select magic state.
487        """
488        self.state = self.info_box.state = c.SELECT_MAGIC
489        self.arrow.become_select_magic_state()
490
491    def try_to_run_away(self):
492        """
493        Transition battle into the run away state.
494        """
495        self.run_away = True
496        self.arrow.state = 'invisible'
497        self.enemy_index = 0
498        self.enter_enemy_attack_state()
499
500    def enter_enemy_attack_state(self):
501        """
502        Transition battle into the Enemy attack state.
503        """
504        self.state = self.info_box.state = c.ENEMY_ATTACK
505        enemy = self.enemy_list[self.enemy_index]
506        enemy.enter_enemy_attack_state()
507
508    def enter_player_attack_state(self):
509        """
510        Transition battle into the Player attack state.
511        """
512        self.state = self.info_box.state = c.PLAYER_ATTACK
513        enemy_to_attack = self.enemies_to_attack.pop(0)
514        if enemy_to_attack in self.enemy_list:
515            self.player.enter_attack_state(enemy_to_attack)
516        else:
517            if self.enemy_list:
518                self.player.enter_attack_state(self.enemy_list[0])
519            else:
520                self.enter_battle_won_state()
521        self.arrow.state = 'invisible'
522
523    def get_enemy_to_attack(self):
524        """
525        Get enemy for player to attack by arrow position.
526        """
527        enemy_posx = self.arrow.rect.x + 60
528        enemy_posy = self.arrow.rect.y - 20
529        enemy_pos = (enemy_posx, enemy_posy)
530        enemy_to_attack = None
531
532        for enemy in self.enemy_list:
533            if enemy.rect.topleft == enemy_pos:
534                enemy_to_attack = enemy
535
536        return enemy_to_attack
537
538
539    def enter_drink_healing_potion_state(self):
540        """
541        Transition battle into the Drink Healing Potion state.
542        """
543        self.state = self.info_box.state = c.DRINK_HEALING_POTION
544        self.player.healing = True
545        self.set_timer_to_current_time()
546        self.arrow.state = 'invisible'
547        self.enemy_index = 0
548        self.damage_points.add(
549            attackitems.HealthPoints(30,
550                                     self.player.rect.topright,
551                                     False))
552        self.player_healed(30)
553
554    def enter_drink_ether_potion_state(self):
555        """
556        Transition battle into the Drink Ether Potion state.
557        """
558        self.state = self.info_box.state = c.DRINK_ETHER_POTION
559        self.player.healing = True
560        self.arrow.state = 'invisible'
561        self.enemy_index = 0
562        self.damage_points.add(
563            attackitems.HealthPoints(30,
564                                     self.player.rect.topright,
565                                     False,
566                                     True))
567        self.magic_boost(30)
568        self.set_timer_to_current_time()
569
570    def enter_select_action_state(self):
571        """
572        Transition battle into the select action state
573        """
574        self.state = self.info_box.state = c.SELECT_ACTION
575        self.arrow.index = 0
576        self.arrow.state = self.state
577
578    def enter_player_damaged_state(self):
579        """
580        Transition battle into the player damaged state.
581        """
582        self.state = self.info_box.state = c.PLAYER_DAMAGED
583        if self.enemy_index > len(self.enemy_list) - 1:
584            self.enemy_index = 0
585        enemy = self.enemy_list[self.enemy_index]
586        player_damage = enemy.calculate_hit(self.inventory['equipped armor'],
587                                            self.inventory)
588        self.damage_points.add(
589            attackitems.HealthPoints(player_damage,
590                                     self.player.rect.topright))
591        self.info_box.set_player_damage(player_damage)
592        self.set_timer_to_current_time()
593        self.player_damaged(player_damage)
594        if player_damage:
595            self.player.damaged = True
596            self.player.enter_knock_back_state()
597
598    def enter_enemy_damaged_state(self):
599        """
600        Transition battle into the enemy damaged state.
601        """
602        self.state = self.info_box.state = c.ENEMY_DAMAGED
603        enemy_damage = self.player.calculate_hit()
604        self.damage_points.add(
605            attackitems.HealthPoints(enemy_damage,
606                                     self.player.attacked_enemy.rect.topright))
607
608        self.info_box.set_enemy_damage(enemy_damage)
609
610        self.arrow.index = 0
611        self.attack_enemy(enemy_damage)
612        self.set_timer_to_current_time()
613
614    def switch_enemy(self):
615        """
616        Switch which enemy the player is attacking.
617        """
618        if self.enemy_index < len(self.enemy_list) - 1:
619            self.enemy_index += 1
620            self.enter_enemy_attack_state()
621
622    def enter_run_away_state(self):
623        """
624        Transition battle into the run away state.
625        """
626        self.state = self.info_box.state = c.RUN_AWAY
627        self.arrow.state = 'invisible'
628        self.player.state = c.RUN_AWAY
629        self.set_timer_to_current_time()
630
631    def enter_battle_won_state(self):
632        """
633        Transition battle into the battle won state.
634        """
635        self.state = self.info_box.state = c.BATTLE_WON
636        self.player.state = c.VICTORY_DANCE
637        self.set_timer_to_current_time()
638
639    def enter_show_experience_state(self):
640        """
641        Transition battle into the show experience state.
642        """
643        self.state = self.info_box.state = c.SHOW_EXPERIENCE
644        self.set_timer_to_current_time()
645
646    def enter_level_up_state(self):
647        """
648        Transition battle into the LEVEL UP state.
649        """
650        self.state = self.info_box.state = c.LEVEL_UP
651        self.info_box.reset_level_up_message()
652        self.set_timer_to_current_time()
653
654    def enter_two_actions_per_turn_state(self):
655        self.state = self.info_box.state = c.TWO_ACTIONS
656        self.set_timer_to_current_time()
657
658    def execute_player_actions(self):
659        """
660        Execute the player actions.
661        """
662        if self.player_level < 3:
663            if self.player_actions:
664                enter_state = self.player_action_dict[self.player_actions[0]]
665                enter_state()
666                self.player_actions.pop(0)
667        else:
668            if len(self.player_actions) == 2:
669                enter_state = self.player_action_dict[self.player_actions[0]]
670                enter_state()
671                self.player_actions.pop(0)
672                self.action_selected = False
673            else:
674                if self.action_selected:
675                    self.enter_select_action_state()
676                    self.action_selected = False
677
678
679
680
681
682
683
684
685