all repos — Legends-RPG @ 8a2cd71823b2d268cca5ad5b0a908c0474c17d52

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