all repos — Legends-RPG @ 29279f0d4ae0de678743f8baccb7574024b042e1

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"""
  3
  4import random
  5import pygame as pg
  6from .. import tools, setup
  7from .. components import person, attack
  8from .. import constants as c
  9
 10#STATES
 11
 12SELECT_ACTION = 'select action'
 13SELECT_ENEMY = 'select enemy'
 14ENEMY_ATTACK = 'enemy attack'
 15PLAYER_ATTACK = 'player attack'
 16SELECT_ITEM = 'select item'
 17SELECT_MAGIC = 'select magic'
 18RUN_AWAY = 'run away'
 19ATTACK_ANIMATION = 'attack animation'
 20
 21#EVENTS
 22
 23END_BATTLE = 'end battle'
 24
 25
 26class Battle(tools._State):
 27    def __init__(self):
 28        super(Battle, self).__init__()
 29
 30    def startup(self, current_time, game_data):
 31        """Initialize state attributes"""
 32        self.current_time = current_time
 33        self.allow_input = False
 34        self.game_data = game_data
 35        self.background = self.make_background()
 36        self.enemy_group, self.enemy_pos_list = self.make_enemies()
 37        self.player = self.make_player()
 38        self.attack_animations = pg.sprite.Group()
 39        self.info_box = InfoBox(game_data)
 40        self.arrow = SelectArrow(self.enemy_pos_list)
 41        self.attacked_enemy = None
 42        self.attacking_enemy = None
 43        self.select_box = SelectBox()
 44        self.state = SELECT_ACTION
 45        self.select_action_state_dict = self.make_selection_state_dict()
 46        self.name = 'battle'
 47        self.next = game_data['last state']
 48        self.observer = Observer(self)
 49        self.player.observer = self.observer
 50
 51    def make_background(self):
 52        """Make the blue/black background"""
 53        background = pg.sprite.Sprite()
 54        surface = pg.Surface(c.SCREEN_SIZE).convert()
 55        surface.fill(c.BLACK_BLUE)
 56        background.image = surface
 57        background.rect = background.image.get_rect()
 58        background_group = pg.sprite.Group(background)
 59
 60        return background_group
 61
 62    def make_enemies(self):
 63        """Make the enemies for the battle. Return sprite group"""
 64        pos_list  = []
 65
 66        for column in range(3):
 67            for row in range(3):
 68                x = (column * 100) + 100
 69                y = (row * 100) + 100
 70                pos_list.append([x,y])
 71
 72        enemy_group = pg.sprite.Group()
 73
 74        for enemy in range(random.randint(1, 6)):
 75            enemy_group.add(person.Person('devil', 0, 0,
 76                                          'down', 'battle resting'))
 77
 78        for i, enemy in enumerate(enemy_group):
 79            enemy.rect.topleft = pos_list[i]
 80            enemy.image = pg.transform.scale2x(enemy.image)
 81
 82        return enemy_group, pos_list[0:len(enemy_group)]
 83
 84    def make_player(self):
 85        """Make the sprite for the player's character"""
 86        player = person.Player('left', 630, 220, 'battle resting', 1)
 87        player.image = pg.transform.scale2x(player.image)
 88
 89        return player
 90
 91    def make_selection_state_dict(self):
 92        """
 93        Make a dictionary of states with arrow coordinates as keys.
 94        """
 95        pos_list = self.arrow.make_select_action_pos_list()
 96        state_list = [SELECT_ENEMY, SELECT_ITEM, SELECT_MAGIC, RUN_AWAY]
 97        return dict(zip(pos_list, state_list))
 98
 99    def update(self, surface, keys, current_time):
100        """Update the battle state"""
101        self.check_input(keys)
102        self.enemy_group.update(current_time)
103        self.player.update(keys, current_time)
104        self.attack_animations.update()
105        self.arrow.update(keys)
106
107        self.draw_battle(surface)
108
109    def check_input(self, keys):
110        """
111        Check user input to navigate GUI.
112        """
113        if self.allow_input:
114            if keys[pg.K_RETURN]:
115                self.notify(END_BATTLE)
116
117            elif keys[pg.K_SPACE]:
118                if self.state == SELECT_ACTION:
119                    self.state = self.select_action_state_dict[
120                        self.arrow.rect.topleft]
121                    self.notify(self.state)
122
123                elif self.state == SELECT_ENEMY:
124                    self.state = PLAYER_ATTACK
125                    self.notify(self.state)
126
127            self.allow_input = False
128
129        if keys[pg.K_RETURN] == False and keys[pg.K_SPACE] == False:
130            self.allow_input = True
131
132    def notify(self, event):
133        """
134        Notify observer of event.
135        """
136        self.observer.on_notify(event)
137
138    def end_battle(self):
139        """
140        End battle and flip back to previous state.
141        """
142        self.game_data['last state'] = self.name
143        self.done = True
144
145    def draw_battle(self, surface):
146        """Draw all elements of battle state"""
147        self.background.draw(surface)
148        self.enemy_group.draw(surface)
149        self.attack_animations.draw(surface)
150        surface.blit(self.player.image, self.player.rect)
151        surface.blit(self.info_box.image, self.info_box.rect)
152        surface.blit(self.select_box.image, self.select_box.rect)
153        surface.blit(self.arrow.image, self.arrow.rect)
154
155
156class Observer(object):
157    """
158    Observes events of battle and passes info to components.
159    """
160    def __init__(self, level):
161        self.level = level
162        self.info_box = level.info_box
163        self.select_box = level.info_box
164        self.arrow = level.arrow
165        self.player = level.player
166        self.enemies = level.enemy_group
167        self.event_dict = self.make_event_dict()
168
169    def make_event_dict(self):
170        """
171        Make a dictionary of events the Observer can
172        receive.
173        """
174        event_dict = {END_BATTLE: self.end_battle,
175                      SELECT_ACTION: self.select_action,
176                      SELECT_ITEM: self.select_item,
177                      SELECT_ENEMY: self.select_enemy,
178                      ENEMY_ATTACK: self.enemy_attack,
179                      PLAYER_ATTACK: self.player_attack,
180                      ATTACK_ANIMATION: self.attack_animation,
181                      RUN_AWAY: self.run_away}
182
183        return event_dict
184
185    def on_notify(self, event):
186        """
187        Notify Observer of event.
188        """
189        if event in self.event_dict:
190            self.event_dict[event]()
191
192    def end_battle(self):
193        """
194        End Battle and flip to previous state.
195        """
196        self.level.end_battle()
197
198    def select_action(self):
199        """
200        Set components to select action.
201        """
202        self.level.state = SELECT_ACTION
203        self.arrow.index = 0
204        self.arrow.state = SELECT_ACTION
205        self.arrow.image = setup.GFX['smallarrow']
206
207    def select_enemy(self):
208        self.level.state = SELECT_ENEMY
209        self.arrow.state = SELECT_ENEMY
210
211    def select_item(self):
212        self.level.state = SELECT_ITEM
213        self.info_box.image = self.info_box.make_image(SELECT_ITEM)
214
215    def enemy_attack(self):
216        pass
217
218    def player_attack(self):
219        enemy_posx = self.arrow.rect.x + 60
220        enemy_posy = self.arrow.rect.y - 20
221        enemy_pos = (enemy_posx, enemy_posy)
222        enemy_to_attack = None
223
224        for enemy in self.enemies:
225            if enemy.rect.topleft == enemy_pos:
226                enemy_to_attack = enemy
227
228        self.player.enter_attack_state(enemy_to_attack)
229        self.arrow.become_invisible_surface()
230
231    def attack_animation(self):
232        """
233        Make an attack animation over attacked enemy.
234        """
235        enemy = self.player.attacked_enemy
236        if enemy:
237            enemy.kill()
238            posx = enemy.rect.x - 32
239            posy = enemy.rect.y - 64
240            fire_sprite = attack.Fire(posx, posy)
241            self.level.attack_animations.add(fire_sprite)
242
243    def run_away(self):
244        self.level.end_battle()
245
246
247class InfoBox(object):
248    """
249    Info box that describes attack damage and other battle
250    related information.
251    """
252    def __init__(self, game_data):
253        self.game_data = game_data
254        self.state = SELECT_ACTION
255        self.title_font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 22)
256        self.title_font.set_underline(True)
257        self.font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 18)
258        self.message_dict = self.make_message_dict()
259        self.image = self.make_image(SELECT_ACTION)
260        self.rect = self.image.get_rect(bottom=608)
261
262    def make_message_dict(self):
263        """
264        Make dictionary of states Battle info can be in.
265        """
266        message_dict = {SELECT_ACTION: 'Select an action.',
267                        SELECT_MAGIC: 'Select a magic spell.',
268                        SELECT_ITEM: 'Select an item.',
269                        SELECT_ENEMY: 'Select an enemy.',
270                        ENEMY_ATTACK: 'The enemy attacks player!',
271                        PLAYER_ATTACK: 'Player attacks enemy.',
272                        RUN_AWAY: 'Run away'}
273
274        return message_dict
275
276    def make_item_text(self):
277        """
278        Make the text for when the player selects items.
279        """
280        inventory = self.game_data['player inventory']
281        allowed_item_list = ['Healing Potion']
282        title = 'SELECT ITEM'
283        item_text_list = [title]
284
285        for item in inventory:
286            if item in allowed_item_list:
287                text = item + ": " + str(inventory[item]['quantity'])
288                item_text_list.append(text)
289
290        item_text_list.append('BACK')
291
292        return item_text_list
293
294    def make_text_sprites(self, text_list):
295        """
296        Make sprites out of text.
297        """
298        sprite_group = pg.sprite.Group()
299
300        for i, text in enumerate(text_list):
301            sprite = pg.sprite.Sprite()
302
303            if i == 0:
304                x = 195
305                y = 10
306                surface = self.title_font.render(text, True, c.NEAR_BLACK)
307                rect = surface.get_rect(x=x, y=y)
308            else:
309                x = 100
310                y = (i * 30) + 20
311                surface = self.font.render(text, True, c.NEAR_BLACK)
312                rect = surface.get_rect(x=x, y=y)
313            sprite.image = surface
314            sprite.rect = rect
315            sprite_group.add(sprite)
316
317        return sprite_group
318
319    def make_image(self, state):
320        """
321        Make image out of box and message.
322        """
323        image = setup.GFX['shopbox']
324        rect = image.get_rect(bottom=608)
325        surface = pg.Surface(rect.size)
326        surface.set_colorkey(c.BLACK)
327        surface.blit(image, (0, 0))
328
329        if state == SELECT_ITEM:
330            text_sprites = self.make_text_sprites(self.make_item_text())
331            text_sprites.draw(surface)
332        else:
333            text_surface = self.font.render(self.message_dict[state], True, c.NEAR_BLACK)
334            text_rect = text_surface.get_rect(x=50, y=50)
335            surface.blit(text_surface, text_rect)
336
337        return surface
338
339
340class SelectBox(object):
341    """
342    Box to select whether to attack, use item, use magic or run away.
343    """
344    def __init__(self):
345        self.font = pg.font.Font(setup.FONTS[c.MAIN_FONT], 22)
346        self.slots = self.make_slots()
347        self.image = self.make_image()
348        self.rect = self.image.get_rect(bottom=608,
349                                        right=800)
350
351    def make_image(self):
352        """
353        Make the box image for
354        """
355        image = setup.GFX['goldbox']
356        rect = image.get_rect(bottom=608)
357        surface = pg.Surface(rect.size)
358        surface.set_colorkey(c.BLACK)
359        surface.blit(image, (0, 0))
360
361        for text in self.slots:
362            text_surface = self.font.render(text, True, c.NEAR_BLACK)
363            text_rect = text_surface.get_rect(x=self.slots[text]['x'],
364                                              y=self.slots[text]['y'])
365            surface.blit(text_surface, text_rect)
366
367        return surface
368
369    def make_slots(self):
370        """
371        Make the slots that hold the text selections, and locations.
372        """
373        slot_dict = {}
374        selections = ['Attack', 'Items', 'Magic', 'Run']
375
376        for i, text in enumerate(selections):
377            slot_dict[text] = {'x': 150,
378                               'y': (i*34)+10}
379
380        return slot_dict
381
382
383
384class SelectArrow(object):
385    """Small arrow for menu"""
386    def __init__(self, enemy_pos_list):
387        self.image = setup.GFX['smallarrow']
388        self.rect = self.image.get_rect()
389        self.state = 'select action'
390        self.state_dict = self.make_state_dict()
391        self.pos_list = self.make_select_action_pos_list()
392        self.index = 0
393        self.rect.topleft = self.pos_list[self.index]
394        self.allow_input = False
395        self.enemy_pos_list = enemy_pos_list
396
397    def make_state_dict(self):
398        """Make state dictionary"""
399        state_dict = {'select action': self.select_action,
400                      'select enemy': self.select_enemy}
401
402        return state_dict
403
404    def select_action(self, keys):
405        """
406        Select what action the player should take.
407        """
408        self.pos_list = self.make_select_action_pos_list()
409        self.rect.topleft = self.pos_list[self.index]
410
411        if self.allow_input:
412            if keys[pg.K_DOWN] and self.index < (len(self.pos_list) - 1):
413                self.index += 1
414                self.allow_input = False
415            elif keys[pg.K_UP] and self.index > 0:
416                self.index -= 1
417                self.allow_input = False
418
419        if keys[pg.K_DOWN] == False and keys[pg.K_UP] == False:
420            self.allow_input = True
421
422
423    def make_select_action_pos_list(self):
424        """
425        Make the list of positions the arrow can be in.
426        """
427        pos_list = []
428
429        for i in range(4):
430            x = 590
431            y = (i * 34) + 472
432            pos_list.append((x, y))
433
434        return pos_list
435
436    def select_enemy(self, keys):
437        """
438        Select what enemy you want to take action on.
439        """
440        self.pos_list = self.enemy_pos_list
441        pos = self.pos_list[self.index]
442        self.rect.x = pos[0] - 60
443        self.rect.y = pos[1] + 20
444
445        if self.allow_input:
446            if keys[pg.K_DOWN] and self.index < (len(self.pos_list) - 1):
447                self.index += 1
448                self.allow_input = False
449            elif keys[pg.K_UP] and self.index > 0:
450                self.index -= 1
451                self.allow_input = False
452
453
454        if keys[pg.K_DOWN] == False and keys[pg.K_UP] == False \
455                and keys[pg.K_RIGHT] == False and keys[pg.K_LEFT] == False:
456            self.allow_input = True
457
458    def become_invisible_surface(self):
459        """
460        Make image attribute an invisible surface.
461        """
462        self.image = pg.Surface((32, 32))
463        self.image.set_colorkey(c.BLACK)
464
465
466    def enter_select_action(self):
467        """
468        Assign values for the select action state.
469        """
470        pass
471
472    def enter_select_enemy(self):
473        """
474        Assign values for the select enemy state.
475        """
476        pass
477
478    def update(self, keys):
479        """
480        Update arrow position.
481        """
482        state_function = self.state_dict[self.state]
483        state_function(keys)
484
485    def draw(self, surface):
486        """
487        Draw to surface.
488        """
489        surface.blit(self.image, self.rect)
490