Return to repo list

heart-of-gold

Tactical RPG written in python, using pygame.
Return to HMagellan.com

turn.py (30948B)


      1 import pygame, os, json
      2 from . import manager, entity, status
      3 from .constants import *
      4 
      5 ###########
      6 # turn.py #
      7 ###########
      8 
      9 # TODO:
     10 #   1.  The project_turn_order is currently used in two distinct ways: 1) it is called during the refresh_turn_state method which is intended to
     11 #       represent a complete refresh of the status of the turns; 2) it is used by itself in kill_piece and possibly in other places. The 
     12 #       project_turn_order method is one of a few that should probably not be used by itself, but rather only in the context of refreshing the
     13 #       TurnManager's turn state. This hints at possible expansion of what 'refreshing the turn state' even means in the future.
     14 #   2.  The UI elements that TurnManager handles could possibly be handled instead by MenuManager and defined in a JSON as a menu object. This would
     15 #       require a major overhaul of the TurnManager object to decouple the UI functionality (which would also greatly affect the ManagerBus and
     16 #       Interface objects), as well as an expansion of the Menu object to be able to handle what the turn UI does with its generalized functions.
     17 #       This would be a long-term goal and probably part of a wider refactor targeting TurnManager and BoardManager particularly, but it should
     18 #       serve as a warning to implement further UIs through MenuManager. Ideally, Menus and Scenes would be general enough that they could, between
     19 #       themselves, represent the majority of the interactive (and non-interactive) gameplay, leading to the game mostly being just a series of
     20 #       complex JSON files.
     21 
     22 # This file contains the following:
     23 #   1.  The TurnManager class, which handles turn order and displaying turn info in the GUI
     24 #   2.  Several entity subclasses used by TurnManager to make up the battle UI
     25 #   3.  The ActionButton generic class and its associated subclasses that represent the actually-displayed action buttons
     26 
     27 #################################
     28 # Section 1 - TurnManager class #
     29 #################################
     30 
     31 class TurnManager(manager.Manager):
     32     """
     33     TurnManager handles organizing turn order, deciding
     34     who's turn it is, and displaying upcoming turns and
     35     info about the current turn in the UI.
     36     """
     37 
     38     def __init__(self, game, bus, camera, name):
     39         
     40         # Parent initialization
     41         super().__init__(game, bus, camera, name)
     42 
     43         # Important values
     44         self.current_turn = 1
     45         self.current_active_piece = None
     46         self.turn_projection = []
     47         self.turn_tick = 0
     48         self.in_play_pieces = []
     49         self.turn_depth = 10
     50         self.board_events = {}
     51         self.winning_team = None
     52         self.battle_resolution_timer = 0
     53 
     54         # Interval graphics and values
     55         self.intro_timer = 0
     56         self.intro_left_decal = None
     57         self.intro_left_decal_topleft = (0, 0)
     58         self.intro_right_decal = None
     59         self.intro_right_decal_topleft = (0, 0)
     60         self.intro_text = None
     61         self.intro_text_rect = None
     62 
     63         # Turn tray and other associated entities
     64         self.turn_tray = None
     65         self.turn_tray_more_button = None
     66         self.turn_icons = []
     67         self.action_buttons = []
     68         self.return_button = None
     69 
     70         # Stat screen values
     71         self.stat_screen = None
     72     
     73     def initialize_turns(self, pieces, boardname):
     74         """
     75         Load up the turns for the first time. This
     76         also sets up persistent parts of the turn UI
     77         such as the trays and action buttons. This
     78         also sets up the battle intro timer.
     79         """
     80         # TODO: Doing this here is not my favorite, at all...
     81         with open(os.path.join(BOARD_PATH, boardname, "events.json")) as b: self.board_events = json.load(b)
     82         #
     83         sheets = self.bus.fetch("sheet_manager", "sheets")
     84         anims = self.bus.fetch("sheet_manager", "animations")
     85         self.in_play_pieces = pieces
     86         self.turn_tray = TurnTray(sheets["turn_order_tray_1"], (0, 0), None, False, sheets["turn_order_tray_extended_1"])
     87         self.turn_tray_more_button = TurnTrayMoreButton(sheets["toggle_more_turn_button_1"], (0, 0), None, False, self)
     88         self.turn_tray.set_position((3 * (SCREEN_WIDTH // 4), (SCREEN_HEIGHT // 10) - 1))
     89         self.turn_tray_more_button.set_position((3 * (SCREEN_WIDTH // 4), self.turn_tray.rect.bottom + 2))
     90         self.turn_icons = []
     91         self.turn_projection = []
     92         self.former_candidates = []
     93         self.current_turn = 1
     94         self.battle_resolution_timer = 120
     95         self.return_button = ReturnButton(sheets["return_prompt_1"], (0, 0), None, False, self)
     96 
     97         # Create action buttons
     98         self.action_buttons.append(MoveActionButton(sheets["action_buttons_1"], (0, 0), None, False, self, 0))
     99         self.action_buttons.append(AttackActionButton(sheets["action_buttons_1"], (0, 1), None, False, self, 1))
    100         self.action_buttons.append(SkillActionButton(sheets["action_buttons_1"], (0, 2), None, False, self, 2))
    101         self.action_buttons.append(ItemActionButton(sheets["action_buttons_1"], (0, 3), None, False, self, 3))
    102         self.action_buttons.append(GuardActionButton(sheets["action_buttons_1"], (0, 4), None, False, self, 5))
    103         self.action_buttons.append(PushActionButton(sheets["action_buttons_1"], (0, 5), None, False, self, 4))
    104         self.action_buttons.append(EndTurnActionButton(sheets["action_buttons_1"], (0, 6), None, False, self, 6))
    105 
    106         self.shift_turns()
    107         # NOTE: This happens here to properly snap after the snap involved in a turn shift. This is, again, not pretty
    108         self.camera.snap_to_position(tuple(self.board_events["init_camera_pos"]))
    109         self.intro_timer = 70
    110         self.intro_left_decal = sheets["battle_intro_decal_1"].sprites[(0, 0)]
    111         self.intro_left_decal_topleft = (-1920, 460)
    112         self.intro_right_decal = sheets["battle_intro_decal_1"].sprites[(0, 1)]
    113         self.intro_right_decal_topleft = (1920, 490)
    114         if self.board_events["start_dialog"] != None:
    115             self.play_battle_dialog(self.board_events["start_dialog"])
    116 
    117     def shift_turns(self):
    118         """
    119         Shift to the next turn.
    120         """
    121         # First, clean up the active piece
    122         if self.current_active_piece != None:
    123             self.current_active_piece.taking_turn = False
    124 
    125         # Next, set up the values we need
    126         self.current_turn += 1
    127         candidates = []
    128 
    129         # If there are any pieces with readiness > 100, go ahead and populate those
    130         # pieces into the candidates (this will skip the following while loop)
    131         for pc in self.in_play_pieces:
    132             if pc.readiness >= 100:
    133                 candidates.append(pc)
    134 
    135         # Generate a list of next turn candidates. Readiness is increased only here
    136         while len(candidates) == 0:
    137             for p in self.in_play_pieces:
    138                 p.readiness += p.active_stats["INIT"] + max(1, (p.active_stats["SPD"] // 3))
    139                 if p.readiness >= 100:
    140                     candidates.append(p)
    141 
    142         # Sort the candidates by highest readiness, then (in the event of tied rediness) by
    143         # highest speed
    144         candidates.sort(reverse = True, key = lambda can: (can.readiness, can.active_stats["SPD"]))
    145 
    146         # Set the 0th candidate as the current active piece and let it take a turn
    147         self.current_active_piece = candidates.pop(0)
    148         self.current_active_piece.taking_turn = True
    149         self.current_active_piece.has_moved = False
    150         self.current_active_piece.has_acted = False
    151         if self.current_active_piece.has_guarded:
    152             self.current_active_piece.has_guarded = False
    153             self.current_active_piece.guarding = False
    154             self.current_active_piece.set_animation(self.current_active_piece.sheet.animations["stand_" + self.current_active_piece.facing.name], True)
    155 
    156         # Predict the next turn order
    157         #self.project_turn_order()
    158         self.refresh_turn_state()
    159         self.current_active_piece.readiness -= 100
    160 
    161         # Set up important UI vals
    162         for b in self.action_buttons:
    163             if not b.clickable:
    164                 b.toggle_activation()
    165 
    166         # Snap the camera to the moving piece
    167         self.camera.snap_to_position(self.current_active_piece.rect.center)
    168             
    169         # TODO: NOT THIS, OBVIOUSLY
    170         if self.current_active_piece.team != TEAMS.Player:
    171             self.shift_turns()
    172 
    173     def project_turn_order(self):
    174         """
    175         Make a projection of the upcoming turn order.
    176         Fills up a number of entries equal to the
    177         turn_depth value (accounting for that many 
    178         projected turns).
    179         """
    180         # Initial setup
    181         self.turn_projection = []
    182         self.turn_icons = []
    183         next_turn_candidates = []
    184         rds = { i : i.readiness for i in self.in_play_pieces }
    185 
    186         # If anybody has readiness >= 100 already, go ahead and
    187         # account for them
    188         for fc in rds:
    189             if rds[fc] >= 100:
    190                 self.turn_projection.append(fc)
    191                 rds[fc] -= 100
    192 
    193         # If we did the above, reverse-sort the turn projection
    194         if len(self.turn_projection) > 0:
    195             self.turn_projection.sort(reverse = True, key = lambda g: (g.readiness, g.active_stats["SPD"]))
    196         
    197         # Until we have the desired amount of projected turns, keep trying
    198         # to add new ones
    199         while len(self.turn_projection) < self.turn_depth:
    200             for p in rds:
    201                 rds[p] += p.active_stats["INIT"] + max(1, (p.active_stats["SPD"] // 3))
    202                 if rds[p] >= 100:
    203                     next_turn_candidates.append(p)
    204                     rds[p] -= 100
    205             if len(next_turn_candidates) > 0:
    206                 next_turn_candidates.sort(reverse = True, key = lambda can: (rds[can] + 100, can.active_stats["SPD"]))
    207             for ntc in next_turn_candidates:
    208                 if len(self.turn_projection) < self.turn_depth:
    209                     self.turn_projection.append(ntc)
    210             next_turn_candidates = []
    211 
    212         # Handle a case where the active piece might not be accounted for
    213         if self.current_active_piece != None and self.turn_projection[0] != self.current_active_piece:
    214             self.turn_projection.insert(0, self.current_active_piece)
    215             self.turn_projection.pop(len(self.turn_projection) - 1)
    216 
    217         # Fill up the turn icons
    218         j = 0
    219         for pj in self.turn_projection:
    220             self.construct_turn_icon(pj, j)
    221             j += 1
    222 
    223     def construct_turn_icon(self, piece, index):
    224         """
    225         Create a turn icon for the given piece and append
    226         it to the turn_icons list.
    227         """
    228         ni = TurnIcon(self.bus.fetch("sheet_manager", "sheets")["turn_icon_holder_1"], (0, 0), None, False, self, piece, index)
    229         self.turn_icons.append(ni)
    230 
    231     def create_stat_screen(self, piece):
    232         """
    233         Create a stat screen for the given piece and then
    234         display it. If 'piece' is none, turn off the stat
    235         display.
    236         """
    237         if piece != None:
    238             sh = self.bus.fetch("sheet_manager", "sheets")["stat_screen_1"]
    239             self.stat_screen = status.StatusDisplay(sh, (0, 0), None, False, self, piece.get_full_stat_def())
    240         else:
    241             self.stat_screen = None
    242 
    243     def play_battle_intro(self):
    244         """
    245         Play a brief battle intro transition. This happens
    246         immediately following any at-start battle dialogs,
    247         and immediately after loading the board if no dialogs
    248         are present. This method is meant to be called each
    249         frame during a transitional mode.
    250         """
    251         if self.intro_timer > 0:
    252             self.intro_timer -= 1
    253             if self.intro_timer > 36:
    254                 self.intro_left_decal_topleft = (self.intro_left_decal_topleft[0] + 40, self.intro_left_decal_topleft[1])
    255                 self.intro_right_decal_topleft = (self.intro_right_decal_topleft[0] - 40, self.intro_right_decal_topleft[1])
    256         else:
    257             self.camera.snap_to_position(self.current_active_piece.rect.center)
    258             self.game.control_mode = CTRL_MODES.Turn_Normal
    259 
    260     def trigger_button_at_pos(self, pos, double_clicking = False):
    261         """
    262         Trigger a button at the given pos, if any. The
    263         TurnManager buttons do not use the effect
    264         interface. Also handles double-clicking which
    265         has the same effect as double-clicking the piece.
    266         """
    267         if self.turn_tray_more_button != None:
    268             if self.turn_tray_more_button.rect.collidepoint(pos):
    269                 self.turn_tray_more_button.toggle_more()
    270                 self.turn_tray.toggle_sheet(self.turn_tray_more_button.more)
    271                 self.turn_tray_more_button.set_position((3 * (SCREEN_WIDTH // 4), self.turn_tray.rect.bottom + 2))
    272         for i in self.turn_icons:
    273             if i.rect.collidepoint(pos) and i.clickable:
    274                 if not double_clicking:
    275                     self.camera.snap_to_position(i.piece.rect.center)
    276                     break
    277                 else:
    278                     self.create_stat_screen(i.piece)
    279                     self.game.control_mode = CTRL_MODES.Turn_Display_Stats
    280                     break
    281 
    282         # ActionButton objects are easier
    283         for a in self.action_buttons:
    284             a.be_clicked(pos)
    285 
    286     def kill_piece(self, piece):
    287         """
    288         Remove a piece from everything.
    289         """
    290         if piece in self.in_play_pieces:
    291             self.in_play_pieces.pop(self.in_play_pieces.index(piece))
    292             self.project_turn_order()
    293 
    294     def play_battle_dialog(self, scenefile):
    295         """
    296         Play a dialog scene in mid-battle.
    297         """
    298         self.game.control_mode = CTRL_MODES.Battle_Dialog
    299         self.bus.perform_scene_manager_load_scene_from_file(scenefile)
    300 
    301     def check_return_button_click(self, pos):
    302         """
    303         See if the return button has been clicked.
    304         """
    305         if self.return_button.displayed and self.return_button.rect.collidepoint(pos):
    306             self.return_button.return_to_turn_normal()
    307 
    308     def refresh_turn_state(self):
    309         """
    310         Refresh the entire state of the turn
    311         manager.
    312         """
    313         # TODO: More to be added here. This might be a target for
    314         #       expansion in a future refactor.
    315         self.project_turn_order()
    316         self.check_battle_resolution()
    317 
    318     def check_battle_resolution(self):
    319         """
    320         Check if a battle is over. This is checked
    321         in the refresh_turn_state method.
    322         """
    323         # Begin by counting up the pieces on each team
    324         numplayer = 0
    325         numenemy = 0
    326         for p in self.in_play_pieces:
    327             if p.team == TEAMS.Player:
    328                 numplayer += 1
    329             elif p.team == TEAMS.Enemy:
    330                 numenemy += 1
    331 
    332         # Lose
    333         if numplayer == 0:
    334             self.game.control_mode = CTRL_MODES.Battle_Resolve
    335             self.winning_team = TEAMS.Player
    336         # Win
    337         elif numenemy == 0:
    338             self.game.control_mode = CTRL_MODES.Battle_Resolve
    339             self.winning_team = TEAMS.Enemy
    340 
    341     def expose(self):
    342         """
    343         Expose information about turns.
    344         """
    345         data = {
    346             "active_piece" : self.current_active_piece,
    347             "turn_number" : self.current_turn,
    348             "in_play_pieces" : self.in_play_pieces,
    349             "turn_projection" : self.turn_projection,
    350             "action_buttons" : self.action_buttons
    351         }
    352         self.bus.record(self.name, data)
    353 
    354     def update_managed(self, surface):
    355         """
    356         Update the entities this manager controls.
    357         """
    358         if surface != None:
    359             if self.game.control_mode == CTRL_MODES.Turn_Normal:
    360                 self.turn_tray.update(surface)
    361                 self.turn_tray_more_button.update(surface)
    362                 if self.turn_tray_more_button.more:
    363                     for i in self.turn_icons:
    364                         i.update(surface)
    365                         i.clickable = True
    366                 else:
    367                     for i in self.turn_icons:
    368                         i.clickable = False
    369                     for j in range(0, 4):
    370                         self.turn_icons[j].update(surface)
    371                         self.turn_icons[j].clickable = True
    372                 for b in self.action_buttons:
    373                     b.update(surface)
    374             elif self.game.control_mode in (CTRL_MODES.Turn_Watch_Move, CTRL_MODES.Turn_Watch_Guard):
    375                 if self.action_buttons[0].clickable:
    376                     self.action_buttons[0].toggle_activation()
    377             elif self.game.control_mode == CTRL_MODES.Turn_Watch_Attack:
    378                 for k in range(1, 6):
    379                     if self.action_buttons[k].clickable:
    380                         self.action_buttons[k].toggle_activation()
    381             elif self.game.control_mode == CTRL_MODES.Turn_Display_Stats:
    382                 self.stat_screen.update(surface)
    383             elif self.game.control_mode == CTRL_MODES.Battle_Intro:
    384                 self.play_battle_intro()
    385                 surface.blit(self.intro_left_decal, self.intro_left_decal_topleft)
    386                 surface.blit(self.intro_right_decal, self.intro_right_decal_topleft)
    387             elif self.game.control_mode == CTRL_MODES.Battle_Resolve:
    388                 if self.battle_resolution_timer > 0:
    389                     self.battle_resolution_timer -= 1
    390                 else:
    391                     self.game.control_mode = CTRL_MODES.Battle_Summary
    392             self.return_button.update(surface)
    393 
    394 #############################
    395 # Section 2 - Turn Entities #
    396 #############################
    397 
    398 class TurnTray(entity.Entity):
    399     """
    400     The TurnTray is the on-screen object where icons
    401     and names representing the next Pieces to take
    402     their turns are drawn to. It is a seperate class
    403     mostly because it must write specific text and
    404     it can grow to show more turns.
    405     """
    406 
    407     def __init__(self, sheet, sprite = (0, 0), animation = None, animated = False,
    408                  more_sheet = None):
    409 
    410         # Parent initialization
    411         super().__init__(sheet, sprite, animation, animated)
    412         
    413         # Other values
    414         self.less_sheet = sheet
    415         self.more_sheet = more_sheet
    416 
    417     def toggle_sheet(self, more):
    418         """
    419         Toggle the sheet from long to short.
    420         """
    421         if more:
    422             self.set_image(self.more_sheet)
    423         else:
    424             self.set_image(self.less_sheet)
    425 
    426 class TurnIcon(entity.Entity):
    427     """
    428     Class representing the buttons with icons and names
    429     that appear in order inside the TurnTray object.
    430     Clicking one will lock the cursor on that Piece.
    431     """
    432 
    433     def __init__(self, sheet, sprite = (0, 0), animation = None, animated = False,
    434                  manager = None, piece = None, index = -1):
    435 
    436         # Parent initialization
    437         super().__init__(sheet, sprite, animation, animated)
    438 
    439         # Initialize important settings based on piece
    440         self.manager = manager
    441         self.piece = piece
    442         self.piece_name = piece.name
    443         self.piece_picture = entity.Entity(self.manager.bus.fetch("sheet_manager", "sheets")[piece.pictures["icons_small"]])
    444         self.font = pygame.font.Font(os.path.join(FONT_PATH, UI_FONT), SCREEN_HEIGHT // 18)
    445         self.index = index
    446         self.set_position(((3 * (SCREEN_WIDTH // 4)) + 4, ((SCREEN_HEIGHT // 10) + 1) + ((SCREEN_HEIGHT // 24) * index)))
    447         self.piece_picture.set_position(self.rect.topleft)
    448         self.clickable = False
    449         self.health_bar = None
    450         self.health_bar_fill = None
    451         self.health_bar_rect = None
    452         self.health_bar_fill_rect = None
    453         self.create_health_bar()
    454         self.font = None
    455         self.name_surface = None
    456         self.name_surface_rect = None
    457         self.render_name()
    458         self.team_indicator = None
    459         self.indicate_team()
    460 
    461     def create_health_bar(self):
    462         """
    463         Create a health bar for the given piece.
    464         """
    465         self.health_bar = pygame.Surface((self.rect.width // 2, self.rect.height // 6)).convert()
    466         self.health_bar_rect = self.health_bar.get_rect()
    467         self.health_bar_fill = pygame.Surface((round((self.piece.active_stats["HP"] / self.piece.normal_stats["HP"]) * (self.health_bar_rect.width - 2)),
    468                                               self.health_bar_rect.height - 2)).convert()
    469         self.health_bar_fill_rect = self.health_bar_fill.get_rect()
    470         self.health_bar.fill((45, 45, 45))
    471         self.health_bar_fill.fill((0, 200, 0))
    472 
    473     def render_name(self):
    474         """
    475         Render the name font image for display.
    476         """
    477         self.font = pygame.font.Font(os.path.join(FONT_PATH, UI_FONT), self.rect.height // 2)
    478         self.name_surface = self.font.render(self.piece.name, False, (255, 255, 255)).convert()
    479         self.name_surface_rect = self.name_surface.get_rect()
    480 
    481     def indicate_team(self):
    482         """
    483         Create a small indicator to show what team
    484         this icon's piece is on.
    485         """
    486         if self.piece.team == TEAMS.Player:
    487             sp = (0, 0)
    488         elif self.piece.team == TEAMS.Enemy:
    489             sp = (0, 1)
    490         elif self.piece.team == TEAMS.Ally:
    491             sp = (1, 0)
    492         elif self.piece.team == TEAMS.Neutral:
    493             sp = (1, 1)
    494         else:
    495             sp = (0, 2)
    496         self.team_indicator = entity.Entity(self.manager.bus.fetch("sheet_manager", "sheets")["team_indicator_small_1"], sp)
    497         self.team_indicator.set_position((self.rect.width - self.team_indicator.rect.width, 0))
    498 
    499     def update(self, surface = None):
    500         """
    501         Overwrite of parent update() since this entity
    502         draws sub-sprites.
    503         """
    504         if self.team_indicator != None:
    505             self.team_indicator.update(self.image)
    506         super().update(surface)
    507         self.piece_picture.update(surface)
    508         if self.health_bar != None and self.health_bar_fill != None:
    509             self.health_bar_rect.center = (self.rect.center[0] + (TILE_WIDTH // 8), self.rect.center[1] + (TILE_HEIGHT // 8))
    510             self.health_bar_fill_rect.topleft = (self.health_bar_rect.topleft[0] + 1, self.health_bar_rect.topleft[1] + 1)
    511             surface.blit(self.health_bar, self.health_bar_rect)
    512             surface.blit(self.health_bar_fill, self.health_bar_fill_rect)
    513         if self.name_surface != None:
    514             self.name_surface_rect.center = (self.rect.center[0] + (TILE_WIDTH // 8), self.rect.center[1] - (TILE_HEIGHT // 16))
    515             surface.blit(self.name_surface, self.name_surface_rect)
    516 
    517 class TurnTrayMoreButton(entity.Entity):
    518     """
    519     Class representing the small button that is
    520     positioned at the bottom of the turn tray that
    521     can be clicked to toggle off and on the more
    522     turns display.
    523     """
    524 
    525     def __init__(self, sheet, sprite = (0, 0), animation = None, animated = False,
    526                  manager = None):
    527 
    528         # Parent initialization
    529         super().__init__(sheet, sprite, animation, animated)
    530 
    531         # Other values
    532         self.manager = manager
    533         self.more = False
    534 
    535     def toggle_more(self):
    536         """
    537         Toggle the more setting.
    538         """
    539         self.more = not self.more
    540         self.set_sprite((0, int(self.more)))
    541 
    542 class ReturnButton(entity.Entity):
    543     """
    544     Class representing the button that pops up
    545     near the bottom of the screen in exitable
    546     modes that will allow you to return to the
    547     previous mode.
    548     """
    549 
    550     def __init__(self, sheet, sprite = (0, 0), animation = None, animated = False,
    551                  manager = None):
    552 
    553         # Parent initialization
    554         super().__init__(sheet, sprite, animation, animated)
    555 
    556         # Other values
    557         self.manager = manager
    558         self.displayed = False
    559         self.set_position(((SCREEN_WIDTH // 12) * 11, (SCREEN_HEIGHT // 14) * 13))
    560 
    561     def return_to_turn_normal(self):
    562         """
    563         Return to the normal mode of play.
    564         """
    565         if self.displayed:
    566             self.manager.game.control_mode = CTRL_MODES.Turn_Normal
    567             self.manager.bus.perform_load_board_overlay()
    568 
    569     def update(self, surface = None):
    570         """
    571         Overwrite of update to avoid drawing unless
    572         needed.
    573         """
    574         if self.manager.game.control_mode in EXITABLE_TURN_MODES:
    575             self.displayed = True
    576         else:
    577             self.displayed = False
    578         if self.displayed:
    579             super().update(surface)
    580 
    581 #################################################
    582 # Section 3 - ActionButton class and subclasses #
    583 #################################################
    584 
    585 class ActionButton(entity.Entity):
    586     """
    587     Class representing the buttons that can be
    588     pressed to perform pieces' unit actions
    589     during their turn.
    590     """
    591 
    592     def __init__(self, sheet, sprite = (0, 0), animation = None, animated = None, manager = None, screen_pos_y_offset = 0):
    593 
    594         # Parent initialization
    595         super().__init__(sheet, sprite, animation, animated)
    596 
    597         # Setup
    598         self.set_position(((SCREEN_WIDTH // 8), (SCREEN_HEIGHT // 10) + ((SCREEN_HEIGHT // 14) - 4) * screen_pos_y_offset))
    599 
    600         # Other values
    601         self.manager = manager
    602         self.clickable = False
    603 
    604     def toggle_activation(self):
    605         """
    606         Toggle between on (clickable) and off (not)
    607         states.
    608         """
    609         self.clickable = not self.clickable
    610         self.set_sprite((int(not self.clickable), self.sprite[1]))
    611 
    612     def be_clicked(self, click_pos):
    613         """
    614         Check if this button has been clicked, and if so,
    615         activate.
    616         """
    617         if self.rect.collidepoint(click_pos) and self.clickable:
    618             self.activate()
    619             if self.result_ctrl_mode != None:
    620                 self.manager.game.control_mode = self.result_ctrl_mode
    621 
    622     def activate(self):
    623         """
    624         Generic activation function overwritten in children.
    625         """
    626         pass
    627 
    628 # Subclasses
    629 class MoveActionButton(ActionButton):
    630     """
    631     Class representing the button that lets the player move
    632     the current active piece.
    633     """
    634 
    635     def __init__(self, sheet, sprite = (0, 0), animation = None, animated = None, manager = None, screen_pos_y_offset = 0):
    636 
    637         # Parent initialization
    638         super().__init__(sheet, sprite, animation, animated, manager, screen_pos_y_offset)
    639 
    640         # Other
    641         self.result_ctrl_mode = CTRL_MODES.Turn_Select_Move
    642 
    643     def activate(self):
    644         """
    645         Move-button-specific activation effects.
    646         """
    647         self.manager.bus.perform_display_move_range_of_piece(self.manager.current_active_piece)
    648         self.manager.camera.snap_to_position(self.manager.current_active_piece.rect.center)
    649 
    650 class AttackActionButton(ActionButton):
    651     """
    652     Class representing the button that lets the player choose
    653     an attack target for the current active piece.
    654     """
    655     def __init__(self, sheet, sprite = (0, 0), animation = None, animated = None, manager = None, screen_pos_y_offset = 0):
    656 
    657         # Parent initialization
    658         super().__init__(sheet, sprite, animation, animated, manager, screen_pos_y_offset)
    659 
    660         # Other
    661         self.result_ctrl_mode = CTRL_MODES.Turn_Select_Attack
    662 
    663     def activate(self):
    664         """
    665         AttackButton-specific activation effects.
    666         """
    667         self.manager.bus.perform_display_attack_range_of_piece(self.manager.current_active_piece)
    668         self.manager.camera.snap_to_position(self.manager.current_active_piece.rect.center)
    669 
    670 class SkillActionButton(ActionButton):
    671     """
    672     Class representing the button that lets the player select
    673     a skill for the current active piece to use.
    674     """
    675 
    676     def __init__(self, sheet, sprite = (0, 0), animation = None, animated = None, manager = None, screen_pos_y_offset = 0):
    677 
    678         # Parent initialization
    679         super().__init__(sheet, sprite, animation, animated, manager, screen_pos_y_offset)
    680 
    681         # Other
    682         self.result_ctrl_mode = CTRL_MODES.Turn_Choose_Skill
    683 
    684     def activate(self):
    685         """
    686         SkillButton-specific activation effects.
    687         """
    688         # NOTE: In this button's case, the display will result as a consequence of the control
    689         #       mode change in the turn manager update method.
    690         self.manager.camera.snap_to_position(self.manager.current_active_piece.rect.center)
    691 
    692 class ItemActionButton(ActionButton):
    693     """
    694     Class representing the button that lets the player select
    695     an item for the current active piece to use.
    696     """
    697 
    698     def __init__(self, sheet, sprite = (0, 0), animation = None, animated = None, manager = None, screen_pos_y_offset = 0):
    699 
    700         # Parent initialization
    701         super().__init__(sheet, sprite, animation, animated, manager, screen_pos_y_offset)
    702 
    703         # Other
    704         self.result_ctrl_mode = CTRL_MODES.Turn_Choose_Item
    705 
    706     def activate(self):
    707         """
    708         ItemButton-specific activation effects.
    709         """
    710         self.manager.camera.snap_to_position(self.manager.current_active_piece.rect.center)
    711 
    712 class GuardActionButton(ActionButton):
    713     """
    714     Class representing the button that lets the player make
    715     the current active piece guard.
    716     """
    717 
    718     def __init__(self, sheet, sprite = (0, 0), animation = None, animated = None, manager = None, screen_pos_y_offset = 0):
    719 
    720         # Parent initialization
    721         super().__init__(sheet, sprite, animation, animated, manager, screen_pos_y_offset)
    722 
    723         # Other
    724         self.result_ctrl_mode = CTRL_MODES.Turn_Watch_Guard
    725 
    726     def activate(self):
    727         """
    728         GuardButton-specific activation effects.
    729         """
    730         self.manager.bus.perform_execute_guard(self.manager.current_active_piece)
    731 
    732 class PushActionButton(ActionButton):
    733     """
    734     Class representing the button that lets the player select
    735     a push target for the current active piece.
    736     """
    737 
    738     def __init__(self, sheet, sprite = (0, 0), animation = None, animated = None, manager = None, screen_pos_y_offset = 0):
    739 
    740         # Parent initialization
    741         super().__init__(sheet, sprite, animation, animated, manager, screen_pos_y_offset)
    742 
    743         # Other
    744         self.result_ctrl_mode = CTRL_MODES.Turn_Select_Push
    745 
    746     def activate(self):
    747         """
    748         PushButton-specific activation effects.
    749         """
    750         self.manager.camera.snap_to_position(self.manager.current_active_piece.rect.center)
    751         
    752 class EndTurnActionButton(ActionButton):
    753     """
    754     Class representing the button that lets the player end
    755     the current turn.
    756     """
    757 
    758     def __init__(self, sheet, sprite = (0, 0), animation = None, animated = None, manager = None, screen_pos_y_offset = 0):
    759 
    760         # Parent initialization
    761         super().__init__(sheet, sprite, animation, animated, manager, screen_pos_y_offset)
    762 
    763         # Other
    764         self.result_ctrl_mode = None
    765 
    766     def activate(self):
    767         """
    768         EndTurnButton-specific activation effects.
    769         """
    770         self.manager.shift_turns()