all repos — Legends-RPG @ 454d72914a5fcab90211d6ec794243eecdad4aed

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, copy
  4import pygame as pg
  5from .. import tools, battlegui, observer
  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.game_data = {}
 14        self.current_time = 0.0
 15        self.timer = 0.0
 16        self.allow_input = False
 17        self.allow_info_box_change = False
 18        self.name = 'battle'
 19        self.state = None
 20
 21        self.player = None
 22        self.attack_animations = None
 23        self.sword = None
 24        self.enemy_index = 0
 25        self.attacked_enemy = None
 26        self.attacking_enemy = None
 27        self.enemy_group = None
 28        self.enemy_pos_list = []
 29        self.enemy_list = []
 30
 31        self.background = None
 32        self.info_box = None
 33        self.arrow = None
 34        self.select_box = None
 35        self.player_health_box = None
 36        self.select_action_state_dict = {}
 37        self.next = None
 38        self.observers = []
 39        self.damage_points = pg.sprite.Group()
 40
 41    def startup(self, current_time, game_data):
 42        """Initialize state attributes"""
 43        self.current_time = current_time
 44        self.timer = current_time
 45        self.game_data = game_data
 46        self.inventory = game_data['player inventory']
 47        self.state = c.SELECT_ACTION
 48        self.next = game_data['last state']
 49        self.run_away = False
 50
 51        self.player = self.make_player()
 52        self.attack_animations = pg.sprite.Group()
 53        self.sword = attackitems.Sword(self.player)
 54        self.enemy_group, self.enemy_pos_list, self.enemy_list = self.make_enemies()
 55        self.background = self.make_background()
 56        self.info_box = battlegui.InfoBox(game_data)
 57        self.arrow = battlegui.SelectArrow(self.enemy_pos_list,
 58                                           self.info_box)
 59        self.select_box = battlegui.SelectBox()
 60        self.player_health_box = battlegui.PlayerHealth(self.select_box.rect,
 61                                                        self.game_data)
 62
 63        self.select_action_state_dict = self.make_selection_state_dict()
 64        self.observers = [observer.Battle(self)]
 65        self.player.observers.extend(self.observers)
 66        self.damage_points = pg.sprite.Group()
 67
 68    @staticmethod
 69    def make_background():
 70        """Make the blue/black background"""
 71        background = pg.sprite.Sprite()
 72        surface = pg.Surface(c.SCREEN_SIZE).convert()
 73        surface.fill(c.BLACK_BLUE)
 74        background.image = surface
 75        background.rect = background.image.get_rect()
 76        background_group = pg.sprite.Group(background)
 77
 78        return background_group
 79
 80    @staticmethod
 81    def make_enemies():
 82        """Make the enemies for the battle. Return sprite group"""
 83        pos_list = []
 84
 85        for column in range(3):
 86            for row in range(3):
 87                x = (column * 100) + 100
 88                y = (row * 100) + 100
 89                pos_list.append([x, y])
 90
 91        enemy_group = pg.sprite.Group()
 92
 93        for enemy in range(random.randint(1, 6)):
 94            enemy_group.add(person.Person('devil', 0, 0,
 95                                          'down', 'battle resting'))
 96
 97        for i, enemy in enumerate(enemy_group):
 98            enemy.rect.topleft = pos_list[i]
 99            enemy.image = pg.transform.scale2x(enemy.image)
100            enemy.index = i
101            enemy.level = 1
102            enemy.health = enemy.level * 7
103
104        enemy_list = [enemy for enemy in enemy_group]
105
106        return enemy_group, pos_list[0:len(enemy_group)], enemy_list
107
108    @staticmethod
109    def make_player():
110        """Make the sprite for the player's character"""
111        player = person.Player('left', 630, 220, 'battle resting', 1)
112        player.image = pg.transform.scale2x(player.image)
113        return player
114
115    def make_selection_state_dict(self):
116        """
117        Make a dictionary of states with arrow coordinates as keys.
118        """
119        pos_list = self.arrow.make_select_action_pos_list()
120        state_list = [c.SELECT_ENEMY, c.SELECT_ITEM, c.SELECT_MAGIC, c.RUN_AWAY]
121        return dict(zip(pos_list, state_list))
122
123    def update(self, surface, keys, current_time):
124        """Update the battle state"""
125        self.current_time = current_time
126        self.check_input(keys)
127        self.check_timed_events()
128        self.check_if_battle_won()
129        self.enemy_group.update(current_time)
130        self.player.update(keys, current_time)
131        self.attack_animations.update()
132        self.info_box.update()
133        self.arrow.update(keys)
134        self.sword.update(current_time)
135        self.damage_points.update()
136
137        self.draw_battle(surface)
138
139    def check_input(self, keys):
140        """
141        Check user input to navigate GUI.
142        """
143        if self.allow_input:
144            if keys[pg.K_RETURN]:
145                self.notify(c.END_BATTLE)
146
147            elif keys[pg.K_SPACE]:
148                if self.state == c.SELECT_ACTION:
149                    self.state = self.select_action_state_dict[
150                        self.arrow.rect.topleft]
151                    self.notify(self.state)
152
153                elif self.state == c.SELECT_ENEMY:
154                    self.state = c.PLAYER_ATTACK
155                    self.notify(self.state)
156
157                elif self.state == c.SELECT_ITEM:
158                    if self.arrow.index == (len(self.arrow.pos_list) - 1):
159                        self.state = c.SELECT_ACTION
160                        self.notify(self.state)
161                    elif self.info_box.item_text_list[self.arrow.index][:14] == 'Healing Potion':
162                        self.state = c.DRINK_HEALING_POTION
163                        self.notify(self.state)
164
165                elif self.state == c.SELECT_MAGIC:
166                    if self.arrow.index == (len(self.arrow.pos_list) - 1):
167                        self.state = c.SELECT_ACTION
168                        self.notify(self.state)
169                    elif self.info_box.magic_text_list[self.arrow.index] == 'Cure':
170                        if self.game_data['player stats']['magic points']['current'] >= 25:
171                            self.state = c.CURE_SPELL
172                            self.notify(self.state)
173                    elif self.info_box.magic_text_list[self.arrow.index] == 'Fire Blast':
174                        if self.game_data['player stats']['magic points']['current'] >= 25:
175                            self.state = c.FIRE_SPELL
176                            self.notify(self.state)
177
178            self.allow_input = False
179
180        if keys[pg.K_RETURN] == False and keys[pg.K_SPACE] == False:
181            self.allow_input = True
182
183    def check_timed_events(self):
184        """
185        Check if amount of time has passed for timed events.
186        """
187        timed_states = [c.DISPLAY_ENEMY_ATTACK_DAMAGE,
188                        c.ENEMY_HIT,
189                        c.ENEMY_DEAD,
190                        c.DRINK_HEALING_POTION]
191        long_delay = timed_states[1:]
192
193        if self.state in long_delay:
194            if (self.current_time - self.timer) > 800:
195                if self.state == c.ENEMY_HIT:
196                    if len(self.enemy_list):
197                        self.state = c.ENEMY_ATTACK
198                    else:
199                        self.state = c.BATTLE_WON
200                elif (self.state == c.DRINK_HEALING_POTION or
201                      self.state == c.CURE_SPELL):
202                    if len(self.enemy_list):
203                        self.state = c.ENEMY_ATTACK
204                    else:
205                        self.state = c.BATTLE_WON
206                self.timer = self.current_time
207                self.notify(self.state)
208
209        elif self.state == c.FIRE_SPELL or self.state == c.CURE_SPELL:
210            if (self.current_time - self.timer) > 1500:
211                if len(self.enemy_list):
212                    self.state = c.ENEMY_ATTACK
213                else:
214                    self.state = c.BATTLE_WON
215                self.timer = self.current_time
216                self.notify(self.state)
217
218        elif self.state == c.FLEE or self.state == c.BATTLE_WON:
219            if (self.current_time - self.timer) > 1500:
220                self.end_battle()
221
222        elif self.state == c.DISPLAY_ENEMY_ATTACK_DAMAGE:
223            if (self.current_time - self.timer) > 600:
224                if self.enemy_index == (len(self.enemy_list) - 1):
225                    if self.run_away:
226                        self.state = c.FLEE
227                    else:
228                        self.state = c.SELECT_ACTION
229                else:
230                    self.state = c.SWITCH_ENEMY
231                self.timer = self.current_time
232                self.notify(self.state)
233
234    def check_if_battle_won(self):
235        """
236        Check if state is SELECT_ACTION and there are no enemies left.
237        """
238        if self.state == c.SELECT_ACTION:
239            if len(self.enemy_group) == 0:
240                self.notify(c.BATTLE_WON)
241
242    def notify(self, event):
243        """
244        Notify observer of event.
245        """
246        for new_observer in self.observers:
247            new_observer.on_notify(event)
248
249    def end_battle(self):
250        """
251        End battle and flip back to previous state.
252        """
253        self.game_data['last state'] = self.name
254        self.game_data['battle counter'] = random.randint(50, 255)
255        self.done = True
256
257    def attack_enemy(self, enemy_damage):
258        enemy = self.player.attacked_enemy
259        enemy.health -= enemy_damage
260        self.set_enemy_indices()
261
262        if enemy:
263            enemy.enter_knock_back_state()
264            if enemy.health <= 0:
265                enemy.kill()
266                self.enemy_list.pop(enemy.index)
267                self.arrow.remove_pos(self.player.attacked_enemy)
268                posx = enemy.rect.x - 32
269                posy = enemy.rect.y - 64
270                fire_sprite = attack.Fire(posx, posy)
271                self.attack_animations.add(fire_sprite)
272            self.enemy_index = 0
273            self.player.attacked_enemy = None
274
275    def set_enemy_indices(self):
276        for i, enemy in enumerate(self.enemy_list):
277            enemy.index = i
278
279    def draw_battle(self, surface):
280        """Draw all elements of battle state"""
281        self.background.draw(surface)
282        self.enemy_group.draw(surface)
283        self.attack_animations.draw(surface)
284        self.sword.draw(surface)
285        surface.blit(self.player.image, self.player.rect)
286        surface.blit(self.info_box.image, self.info_box.rect)
287        surface.blit(self.select_box.image, self.select_box.rect)
288        surface.blit(self.arrow.image, self.arrow.rect)
289        self.player_health_box.draw(surface)
290        self.damage_points.draw(surface)
291
292
293    def player_damaged(self, damage):
294        self.game_data['player stats']['health']['current'] -= damage
295
296    def player_healed(self, heal, magic_points=0):
297        health = self.game_data['player stats']['health']
298
299        health['current'] += heal
300        if health['current'] > health['maximum']:
301            health['current'] = health['maximum']
302
303        if self.state == c.DRINK_HEALING_POTION:
304            self.game_data['player inventory']['Healing Potion']['quantity'] -= 1
305            if self.game_data['player inventory']['Healing Potion']['quantity'] == 0:
306                del self.game_data['player inventory']['Healing Potion']
307        elif self.state == c.CURE_SPELL:
308            self.game_data['player stats']['magic points']['current'] -= magic_points
309
310    def set_timer_to_current_time(self):
311        """Set the timer to the current time."""
312        self.timer = self.current_time
313
314    def cast_fire_blast(self):
315        """
316        Cast fire blast on all enemies.
317        """
318        POWER = self.inventory['Fire Blast']['power']
319        MAGIC_POINTS = self.inventory['Fire Blast']['magic points']
320        self.game_data['player stats']['magic points']['current'] -= MAGIC_POINTS
321        for enemy in self.enemy_list:
322            DAMAGE = random.randint(POWER//2, POWER)
323            self.damage_points.add(
324                attackitems.HealthPoints(DAMAGE, enemy.rect.topright))
325            enemy.health -= DAMAGE
326            posx = enemy.rect.x - 32
327            posy = enemy.rect.y - 64
328            fire_sprite = attack.Fire(posx, posy)
329            self.attack_animations.add(fire_sprite)
330            if enemy.health <= 0:
331                enemy.kill()
332                self.arrow.remove_pos(enemy)
333            else:
334                enemy.enter_knock_back_state()
335        self.enemy_list = [enemy for enemy in self.enemy_list if enemy.health > 0]
336        self.enemy_index = 0
337        self.arrow.index = 0
338        self.arrow.become_invisible_surface()
339        self.arrow.state = c.SELECT_ACTION
340        self.state = c.FIRE_SPELL
341        self.set_timer_to_current_time()
342        self.info_box.state = c.FIRE_SPELL
343
344    def cast_cure(self):
345        """
346        Cast cure spell on player.
347        """
348        HEAL_AMOUNT = self.inventory['Cure']['power']
349        MAGIC_POINTS = self.inventory['Cure']['magic points']
350        self.player.healing = True
351        self.set_timer_to_current_time()
352        self.state = c.CURE_SPELL
353        self.arrow.become_invisible_surface()
354        self.enemy_index = 0
355        self.damage_points.add(
356            attackitems.HealthPoints(HEAL_AMOUNT, self.player.rect.topright, False))
357        self.player_healed(HEAL_AMOUNT, MAGIC_POINTS)
358        self.info_box.state = c.DRINK_HEALING_POTION
359
360
361
362
363