all repos — Legends-RPG @ c6b5b5cb031ab81893532d4514cedf0cb3d0d70e

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                    elif self.info_box.item_text_list[self.arrow.index][:5] == 'Ether':
165                        self.state = c.DRINK_ETHER_POTION
166                        self.notify(self.state)
167                elif self.state == c.SELECT_MAGIC:
168                    if self.arrow.index == (len(self.arrow.pos_list) - 1):
169                        self.state = c.SELECT_ACTION
170                        self.notify(self.state)
171                    elif self.info_box.magic_text_list[self.arrow.index] == 'Cure':
172                        if self.game_data['player stats']['magic points']['current'] >= 25:
173                            self.state = c.CURE_SPELL
174                            self.notify(self.state)
175                    elif self.info_box.magic_text_list[self.arrow.index] == 'Fire Blast':
176                        if self.game_data['player stats']['magic points']['current'] >= 25:
177                            self.state = c.FIRE_SPELL
178                            self.notify(self.state)
179
180            self.allow_input = False
181
182        if keys[pg.K_RETURN] == False and keys[pg.K_SPACE] == False:
183            self.allow_input = True
184
185    def check_timed_events(self):
186        """
187        Check if amount of time has passed for timed events.
188        """
189        timed_states = [c.DISPLAY_ENEMY_ATTACK_DAMAGE,
190                        c.ENEMY_HIT,
191                        c.ENEMY_DEAD,
192                        c.DRINK_HEALING_POTION,
193                        c.DRINK_ETHER_POTION]
194        long_delay = timed_states[1:]
195
196        if self.state in long_delay:
197            if (self.current_time - self.timer) > 1000:
198                if self.state == c.ENEMY_HIT:
199                    if len(self.enemy_list):
200                        self.state = c.ENEMY_ATTACK
201                    else:
202                        self.state = c.BATTLE_WON
203                elif (self.state == c.DRINK_HEALING_POTION or
204                      self.state == c.CURE_SPELL or
205                      self.state == c.DRINK_ETHER_POTION):
206                    if len(self.enemy_list):
207                        self.state = c.ENEMY_ATTACK
208                    else:
209                        self.state = c.BATTLE_WON
210                self.timer = self.current_time
211                self.notify(self.state)
212
213        elif self.state == c.FIRE_SPELL or self.state == c.CURE_SPELL:
214            if (self.current_time - self.timer) > 1500:
215                if len(self.enemy_list):
216                    self.state = c.ENEMY_ATTACK
217                else:
218                    self.state = c.BATTLE_WON
219                self.timer = self.current_time
220                self.notify(self.state)
221
222        elif self.state == c.FLEE or self.state == c.BATTLE_WON:
223            if (self.current_time - self.timer) > 1500:
224                self.end_battle()
225
226        elif self.state == c.DISPLAY_ENEMY_ATTACK_DAMAGE:
227            if (self.current_time - self.timer) > 600:
228                if self.enemy_index == (len(self.enemy_list) - 1):
229                    if self.run_away:
230                        self.state = c.FLEE
231                    else:
232                        self.state = c.SELECT_ACTION
233                else:
234                    self.state = c.SWITCH_ENEMY
235                self.timer = self.current_time
236                self.notify(self.state)
237
238    def check_if_battle_won(self):
239        """
240        Check if state is SELECT_ACTION and there are no enemies left.
241        """
242        if self.state == c.SELECT_ACTION:
243            if len(self.enemy_group) == 0:
244                self.notify(c.BATTLE_WON)
245
246    def notify(self, event):
247        """
248        Notify observer of event.
249        """
250        for new_observer in self.observers:
251            new_observer.on_notify(event)
252
253    def end_battle(self):
254        """
255        End battle and flip back to previous state.
256        """
257        self.game_data['last state'] = self.name
258        self.game_data['battle counter'] = random.randint(50, 255)
259        self.done = True
260
261    def attack_enemy(self, enemy_damage):
262        enemy = self.player.attacked_enemy
263        enemy.health -= enemy_damage
264        self.set_enemy_indices()
265
266        if enemy:
267            enemy.enter_knock_back_state()
268            if enemy.health <= 0:
269                self.enemy_list.pop(enemy.index)
270                enemy.state = c.FADE_DEATH
271                self.notify(c.FADE_DEATH)
272                self.arrow.remove_pos(self.player.attacked_enemy)
273            self.enemy_index = 0
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        """
298        Add health from potion to game data.
299        """
300        health = self.game_data['player stats']['health']
301
302        health['current'] += heal
303        if health['current'] > health['maximum']:
304            health['current'] = health['maximum']
305
306        if self.state == c.DRINK_HEALING_POTION:
307            self.game_data['player inventory']['Healing Potion']['quantity'] -= 1
308            if self.game_data['player inventory']['Healing Potion']['quantity'] == 0:
309                del self.game_data['player inventory']['Healing Potion']
310        elif self.state == c.CURE_SPELL:
311            self.game_data['player stats']['magic points']['current'] -= magic_points
312
313    def magic_boost(self, magic_points):
314        """
315        Add magic from ether to game data.
316        """
317        magic = self.game_data['player stats']['magic points']
318        magic['current'] += magic_points
319        if magic['current'] > magic['maximum']:
320            magic['current'] = magic['maximum']
321
322        self.game_data['player inventory']['Ether Potion']['quantity'] -= 1
323        if not self.game_data['player inventory']['Ether Potion']['quantity']:
324            del self.game_data['player inventory']['Ether Potion']
325
326    def set_timer_to_current_time(self):
327        """Set the timer to the current time."""
328        self.timer = self.current_time
329
330    def cast_fire_blast(self):
331        """
332        Cast fire blast on all enemies.
333        """
334        POWER = self.inventory['Fire Blast']['power']
335        MAGIC_POINTS = self.inventory['Fire Blast']['magic points']
336        self.game_data['player stats']['magic points']['current'] -= MAGIC_POINTS
337        for enemy in self.enemy_list:
338            DAMAGE = random.randint(POWER//2, POWER)
339            self.damage_points.add(
340                attackitems.HealthPoints(DAMAGE, enemy.rect.topright))
341            enemy.health -= DAMAGE
342            posx = enemy.rect.x - 32
343            posy = enemy.rect.y - 64
344            fire_sprite = attack.Fire(posx, posy)
345            self.attack_animations.add(fire_sprite)
346            if enemy.health <= 0:
347                enemy.kill()
348                self.arrow.remove_pos(enemy)
349            else:
350                enemy.enter_knock_back_state()
351        self.enemy_list = [enemy for enemy in self.enemy_list if enemy.health > 0]
352        self.enemy_index = 0
353        self.arrow.index = 0
354        self.arrow.become_invisible_surface()
355        self.arrow.state = c.SELECT_ACTION
356        self.state = c.FIRE_SPELL
357        self.set_timer_to_current_time()
358        self.info_box.state = c.FIRE_SPELL
359
360    def cast_cure(self):
361        """
362        Cast cure spell on player.
363        """
364        HEAL_AMOUNT = self.inventory['Cure']['power']
365        MAGIC_POINTS = self.inventory['Cure']['magic points']
366        self.player.healing = True
367        self.set_timer_to_current_time()
368        self.state = c.CURE_SPELL
369        self.arrow.become_invisible_surface()
370        self.enemy_index = 0
371        self.damage_points.add(
372            attackitems.HealthPoints(HEAL_AMOUNT, self.player.rect.topright, False))
373        self.player_healed(HEAL_AMOUNT, MAGIC_POINTS)
374        self.info_box.state = c.DRINK_HEALING_POTION
375
376    def drink_ether(self):
377        """
378        Drink ether potion.
379        """
380        self.player.healing = True
381        self.set_timer_to_current_time()
382        self.state = c.DRINK_ETHER_POTION
383        self.arrow.become_invisible_surface()
384        self.enemy_index = 0
385        self.damage_points.add(
386            attackitems.HealthPoints(30,
387                                     self.player.rect.topright,
388                                     False,
389                                     True))
390        self.magic_boost(30)
391        self.info_box.state = c.DRINK_ETHER_POTION
392
393    def drink_healing_potion(self):
394        """
395        Drink Healing Potion.
396        """
397        self.player.healing = True
398        self.set_timer_to_current_time()
399        self.state = c.DRINK_HEALING_POTION
400        self.arrow.become_invisible_surface()
401        self.enemy_index = 0
402        self.damage_points.add(
403            attackitems.HealthPoints(30,
404                                     self.player.rect.topright,
405                                     False))
406        self.player_healed(30)
407        self.info_box.state = c.DRINK_HEALING_POTION
408
409
410
411
412