all repos — Legends-RPG @ 29d6707d48fd9a25b37b8dd03c3031896d11dc36

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