all repos — Legends-RPG @ 145b437cf4e619fc52e081dd3dba9935239867bf

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) > 800:
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                enemy.kill()
270                self.enemy_list.pop(enemy.index)
271                self.arrow.remove_pos(self.player.attacked_enemy)
272                posx = enemy.rect.x - 32
273                posy = enemy.rect.y - 64
274                fire_sprite = attack.Fire(posx, posy)
275                self.attack_animations.add(fire_sprite)
276            self.enemy_index = 0
277            self.player.attacked_enemy = None
278
279    def set_enemy_indices(self):
280        for i, enemy in enumerate(self.enemy_list):
281            enemy.index = i
282
283    def draw_battle(self, surface):
284        """Draw all elements of battle state"""
285        self.background.draw(surface)
286        self.enemy_group.draw(surface)
287        self.attack_animations.draw(surface)
288        self.sword.draw(surface)
289        surface.blit(self.player.image, self.player.rect)
290        surface.blit(self.info_box.image, self.info_box.rect)
291        surface.blit(self.select_box.image, self.select_box.rect)
292        surface.blit(self.arrow.image, self.arrow.rect)
293        self.player_health_box.draw(surface)
294        self.damage_points.draw(surface)
295
296
297    def player_damaged(self, damage):
298        self.game_data['player stats']['health']['current'] -= damage
299
300    def player_healed(self, heal, magic_points=0):
301        """
302        Add health from potion to game data.
303        """
304        health = self.game_data['player stats']['health']
305
306        health['current'] += heal
307        if health['current'] > health['maximum']:
308            health['current'] = health['maximum']
309
310        if self.state == c.DRINK_HEALING_POTION:
311            self.game_data['player inventory']['Healing Potion']['quantity'] -= 1
312            if self.game_data['player inventory']['Healing Potion']['quantity'] == 0:
313                del self.game_data['player inventory']['Healing Potion']
314        elif self.state == c.CURE_SPELL:
315            self.game_data['player stats']['magic points']['current'] -= magic_points
316
317    def magic_boost(self, magic_points):
318        """
319        Add magic from ether to game data.
320        """
321        magic = self.game_data['player stats']['magic points']
322        magic['current'] += magic_points
323        if magic['current'] > magic['maximum']:
324            magic['current'] = magic['maximum']
325
326        self.game_data['player inventory']['Ether Potion']['quantity'] -= 1
327        if not self.game_data['player inventory']['Ether Potion']['quantity']:
328            del self.game_data['player inventory']['Ether Potion']
329
330    def set_timer_to_current_time(self):
331        """Set the timer to the current time."""
332        self.timer = self.current_time
333
334    def cast_fire_blast(self):
335        """
336        Cast fire blast on all enemies.
337        """
338        POWER = self.inventory['Fire Blast']['power']
339        MAGIC_POINTS = self.inventory['Fire Blast']['magic points']
340        self.game_data['player stats']['magic points']['current'] -= MAGIC_POINTS
341        for enemy in self.enemy_list:
342            DAMAGE = random.randint(POWER//2, POWER)
343            self.damage_points.add(
344                attackitems.HealthPoints(DAMAGE, enemy.rect.topright))
345            enemy.health -= DAMAGE
346            posx = enemy.rect.x - 32
347            posy = enemy.rect.y - 64
348            fire_sprite = attack.Fire(posx, posy)
349            self.attack_animations.add(fire_sprite)
350            if enemy.health <= 0:
351                enemy.kill()
352                self.arrow.remove_pos(enemy)
353            else:
354                enemy.enter_knock_back_state()
355        self.enemy_list = [enemy for enemy in self.enemy_list if enemy.health > 0]
356        self.enemy_index = 0
357        self.arrow.index = 0
358        self.arrow.become_invisible_surface()
359        self.arrow.state = c.SELECT_ACTION
360        self.state = c.FIRE_SPELL
361        self.set_timer_to_current_time()
362        self.info_box.state = c.FIRE_SPELL
363
364    def cast_cure(self):
365        """
366        Cast cure spell on player.
367        """
368        HEAL_AMOUNT = self.inventory['Cure']['power']
369        MAGIC_POINTS = self.inventory['Cure']['magic points']
370        self.player.healing = True
371        self.set_timer_to_current_time()
372        self.state = c.CURE_SPELL
373        self.arrow.become_invisible_surface()
374        self.enemy_index = 0
375        self.damage_points.add(
376            attackitems.HealthPoints(HEAL_AMOUNT, self.player.rect.topright, False))
377        self.player_healed(HEAL_AMOUNT, MAGIC_POINTS)
378        self.info_box.state = c.DRINK_HEALING_POTION
379
380    def drink_ether(self):
381        """
382        Drink ether potion.
383        """
384        self.player.healing = True
385        self.set_timer_to_current_time()
386        self.state = c.DRINK_ETHER_POTION
387        self.arrow.become_invisible_surface()
388        self.enemy_index = 0
389        self.damage_points.add(
390            attackitems.HealthPoints(30,
391                                     self.player.rect.topright,
392                                     False,
393                                     True))
394        self.magic_boost(30)
395        self.info_box.state = c.DRINK_ETHER_POTION
396
397    def drink_healing_potion(self):
398        """
399        Drink Healing Potion.
400        """
401        self.player.healing = True
402        self.set_timer_to_current_time()
403        self.state = c.DRINK_HEALING_POTION
404        self.arrow.become_invisible_surface()
405        self.enemy_index = 0
406        self.damage_points.add(
407            attackitems.HealthPoints(30,
408                                     self.player.rect.topright,
409                                     False))
410        self.player_healed(30)
411        self.info_box.state = c.DRINK_HEALING_POTION
412
413
414
415
416