all repos — Legends-RPG @ aef47eaae27734e81f7d2804e5782652e20a6bbc

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