Return to repo list

heart-of-gold

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

interface.py (16725B)


      1 import pygame
      2 from . import subsystem
      3 from .constants import *
      4 
      5 ################
      6 # interface.py #
      7 ################
      8 
      9 # This file contains:
     10 #   1.  The GameInterface class, which handles all kinds of interaction from the player
     11 
     12 #######################################
     13 # Section 1 - The GameInterface class #
     14 #######################################
     15 
     16 class GameInterface(subsystem.GameSubsystem):
     17     """
     18     GameInterface handles all PyGame events, meaning 
     19     it is responsible for all input and the responses 
     20     to that input. GameInterface takes extra arguments
     21     compared to other subsystems, representing its need
     22     to directly access manager calls via bus and the
     23     game camera.
     24     """
     25 
     26     def __init__(self, game, bus, camera):
     27 
     28         # Parent init
     29         super().__init__(game)
     30         
     31         # Others
     32         self.bus = bus
     33         self.camera = camera
     34         self.drag_piece = False
     35         self.drag_camera = False
     36         self.drag_piece_from_pos = (0, 0)
     37         self.drag_camera_from_pos = (0, 0)
     38         self.left_double_clicking = False
     39         self.left_double_click_timer = 0
     40         self.left_double_click_mousepos = None
     41         self.right_double_clicking = False
     42         self.right_double_click_timer = 0
     43         self.right_double_click_mousepos = None
     44         self.old_mousepos = None
     45         self.camera_motions = [0, 0]
     46         self.key_bools = [ False for k in range(0, 350) ]
     47         
     48     def handle_events(self, events):
     49         """
     50         Handle any kind of PyGame event and react appropriately.
     51         """
     52         for event in events:
     53             if event.type == pygame.KEYDOWN:
     54                 self.handle_key_press(event)
     55             elif event.type == pygame.KEYUP:
     56                 self.handle_key_release(event)
     57             elif event.type == pygame.MOUSEBUTTONDOWN:
     58                 self.handle_mouse_click(event)
     59             elif event.type == pygame.MOUSEBUTTONUP:
     60                 self.handle_mouse_release(event)
     61             elif event.type == pygame.QUIT:
     62                 self.game.quit_game()
     63 
     64     def handle_key_press(self, event):
     65         """
     66         React to a key being pressed.
     67         """
     68         #print(event.key)
     69         if event.key < len(self.key_bools):
     70             self.key_bools[event.key] = True
     71 
     72     def handle_key_release(self, event):
     73         """
     74         React to a key being released.
     75         """
     76         if event.key < len(self.key_bools):
     77             self.key_bools[event.key] = False
     78 
     79     def react_to_keys(self):
     80         """
     81         React to certain pressed/not-pressed statuses
     82         of keys on a mode-by-mode basis. Called during 
     83         update.
     84         """
     85         # HQ REACTIONS
     86         if self.game.state_mode == STATE_MODES.Base_Mode:
     87 
     88             # Handle pausing the game
     89             if self.game.control_mode == CTRL_MODES.Base_Normal:
     90                 if self.key_bools[32]:
     91                     self.game.control_mode = CTRL_MODES.Base_Pause
     92                     self.key_bools[32] = False
     93 
     94             # Handle unpausing the game
     95             elif self.game.control_mode == CTRL_MODES.Base_Pause:
     96                 if self.key_bools[32]:
     97                     self.game.control_mode = CTRL_MODES.Base_Normal
     98                     self.key_bools[32] = False
     99                     
    100         # BATTLE REACTIONS
    101         elif self.game.state_mode == STATE_MODES.Battle_Mode:
    102 
    103             if self.game.control_mode == CTRL_MODES.Turn_Normal:
    104                 # Handle opening the stat display dialog with the keys
    105                 if self.key_bools[105]:
    106                     self.bus.perform_turn_manager_display_stats(self.bus.fetch("turn_manager", "active_piece"))
    107                     self.game.control_mode = CTRL_MODES.Turn_Display_Stats
    108                     self.key_bools[105] = False
    109                     self.key_bools[113] = False
    110                 # Handle pausing the game
    111                 elif self.key_bools[32]:
    112                     self.game.control_mode = CTRL_MODES.Turn_Pause
    113                     self.key_bools[32] = False
    114 
    115             # Handle unpausing the game
    116             elif self.game.control_mode == CTRL_MODES.Turn_Pause:
    117                 if self.key_bools[32]:
    118                     self.game.control_mode = CTRL_MODES.Turn_Normal
    119                     self.key_bools[32] = False
    120 
    121             # Handle exiting the stat display
    122             elif self.game.control_mode == CTRL_MODES.Turn_Display_Stats:
    123                 if self.key_bools[105] or self.key_bools[113]:
    124                     self.bus.perform_turn_manager_display_stats(None)
    125                     self.game.control_mode = CTRL_MODES.Turn_Normal
    126                     self.key_bools[105] = False
    127                     self.key_bools[113] = False
    128 
    129             # Handle exiting in exitable modes with the keys
    130             if self.game.control_mode in EXITABLE_TURN_MODES:
    131                 if self.key_bools[113]:
    132                     self.game.control_mode = CTRL_MODES.Turn_Normal
    133                     self.bus.perform_load_board_overlay()
    134 
    135             # Handle scrolling the camera with the keys
    136             if self.game.control_mode in SCROLLABLE_TURN_MODES:
    137                 mx = max(int(self.key_bools[97]), int(self.key_bools[276])) - max(int(self.key_bools[100]), int(self.key_bools[275]))
    138                 my = max(int(self.key_bools[119]), int(self.key_bools[273])) - max(int(self.key_bools[115]), int(self.key_bools[274]))
    139                 self.camera_motions = [mx, my]
    140 
    141     def handle_mouse_click(self, event):
    142         """
    143         React to a mousebutton being clicked.
    144         """
    145         # First, get important mouse positional info, namely unoffset mouse position and camera-offset mouse position
    146         mouseraw = pygame.mouse.get_pos()
    147         mousepos = (mouseraw[0] - self.camera.camera_surface_offset[0], mouseraw[1] - self.camera.camera_surface_offset[1])
    148 
    149         # Handle left-click 
    150         if event.button == 1:
    151 
    152             # Next, set up for double click
    153             if self.left_double_click_timer == 0 and not self.left_double_clicking:
    154                 self.left_double_click_timer = 15
    155                 self.left_double_click_mousepos = mousepos
    156             elif not self.left_double_clicking and mousepos == self.left_double_click_mousepos:
    157                 self.left_double_clicking = True
    158                 self.left_double_click_timer = 0
    159 
    160             # State checker
    161             # Main menu mode behavior
    162             if self.game.state_mode == STATE_MODES.Main_Menu_Mode:
    163 
    164                 # Normal main menu control
    165                 if self.game.control_mode == CTRL_MODES.Main_Menu_Normal:
    166                     self.bus.perform_trigger_menu_button_at_pos(mousepos)
    167 
    168             # Base mode behavior
    169             if self.game.state_mode == STATE_MODES.Base_Mode:
    170 
    171                 # Check for clicking on base UI elements in all CTRL_MODES
    172                 self.bus.perform_click_base_ui_at_pos(mousepos)
    173 
    174                 # Check for clicking on base entities
    175                 if self.game.control_mode == CTRL_MODES.Base_Normal:
    176                     self.bus.perform_click_base_entity_at_pos(mousepos)
    177 
    178                 # Handle cycling script segments during scenes in base mode
    179                 elif self.game.control_mode == CTRL_MODES.Base_Dialog:
    180                     self.bus.perform_click_current_scene()
    181 
    182             # Battle mode behavior
    183             elif self.game.state_mode == STATE_MODES.Battle_Mode:
    184 
    185                 # Normal in-battle turn control
    186                 # See if we clicked a UI button
    187                 if self.game.control_mode == CTRL_MODES.Turn_Normal:
    188                     self.bus.perform_trigger_turn_manager_buttons_at_pos(mouseraw, self.left_double_clicking)
    189                     self.bus.perform_select_piece_with_tile_cursor()
    190                     sp = self.bus.fetch("piece_manager", "selected_piece") 
    191                     if sp != None:
    192                         # Drag a piece for facing
    193                         if not self.left_double_clicking and self.bus.fetch("turn_manager", "active_piece") == sp:
    194                             self.drag_piece = True
    195                             self.drag_piece_from_pos = mouseraw
    196                         # Double-click a piece for accessing status
    197                         elif self.left_double_clicking:
    198                             self.left_double_clicking = False
    199                             self.bus.perform_turn_manager_display_stats(sp)
    200                             self.game.control_mode = CTRL_MODES.Turn_Display_Stats
    201 
    202                 # Selecting a move for the active piece control
    203                 elif self.game.control_mode == CTRL_MODES.Turn_Select_Move:
    204                     if self.bus.check_for_overlay_move_entity_by_screen_pos(mousepos) != None:
    205                         ap = self.bus.fetch("turn_manager", "active_piece")
    206                         targ = self.bus.check_for_tile_pos_by_screen_pos(mousepos)
    207                         to_path = self.bus.check_for_piece_path_by_previous_moves(ap.tile_pos, targ)
    208                         if to_path != None:
    209                             self.bus.perform_set_piece_move_along_tile_path(ap, to_path)
    210                             self.game.control_mode = CTRL_MODES.Turn_Watch_Move
    211                             self.bus.perform_load_board_overlay()
    212 
    213                 # Selecting a target to attack
    214                 elif self.game.control_mode == CTRL_MODES.Turn_Select_Attack:
    215                     if self.bus.check_for_overlay_attack_entity_by_screen_pos(mousepos):
    216                         ap = self.bus.fetch("turn_manager", "active_piece")
    217                         targ = self.bus.check_for_tile_pos_by_screen_pos(mousepos)
    218                         ps = self.bus.fetch("piece_manager", "pieces_by_tile_pos")
    219                         if targ in ps and ps[targ].team != ap.team:
    220                             self.bus.perform_execute_attack(ap, ps[targ])
    221                             self.game.control_mode = CTRL_MODES.Turn_Watch_Attack
    222                             self.bus.perform_load_board_overlay()
    223 
    224                 # StillScene in-battle dialog options
    225                 elif self.game.control_mode == CTRL_MODES.Battle_Dialog:
    226                     self.bus.perform_click_current_scene()
    227 
    228                 # Battle summary situation
    229                 elif self.game.control_mode == CTRL_MODES.Battle_Summary:
    230                     # TODO: This should eventually involve clicking a continue button rather
    231                     #       than just clicking at all.
    232                     self.game.switch_mode(STATE_MODES.Base_Mode, "testbase1")
    233                     self.bus.perform_piece_manager_reset_state()
    234 
    235                 # Exitable mode checks (not an else!)
    236                 if self.game.control_mode in EXITABLE_TURN_MODES:
    237                     self.bus.perform_turn_manager_check_return_button_click(mouseraw)
    238 
    239             # Still-scene mode behavior
    240             elif self.game.state_mode == STATE_MODES.Still_Scene_Mode:
    241 
    242                 # Normal still-scene control
    243                 if self.game.control_mode == CTRL_MODES.Still_Scene_Normal:
    244                     self.bus.perform_click_current_scene()
    245 
    246         # Handle right-click
    247         elif event.button == 3:
    248             
    249             # Battle mode behavior
    250             if self.game.state_mode == STATE_MODES.Battle_Mode:
    251 
    252                 # Handle dragging camera
    253                 if not self.drag_camera:
    254                     self.drag_camera = True
    255                     self.drag_camera_from_pos = mouseraw
    256             
    257         # Keepover
    258         self.old_mousepos = mousepos
    259 
    260     def handle_mouse_release(self, event):
    261         """
    262         React to a mousebutton being released.
    263         """
    264         # First, get important mouse positional info, namely unoffset mouse position and camera-offset mouse position
    265         mouseraw = pygame.mouse.get_pos()
    266         mousepos = (mouseraw[0] - self.camera.camera_surface_offset[0], mouseraw[1] - self.camera.camera_surface_offset[1])
    267 
    268         # Handle left-click 
    269         if event.button == 1:
    270 
    271             # Toggle off dragging piece
    272             if self.game.state_mode == STATE_MODES.Battle_Mode:
    273                 if self.drag_piece:
    274                     self.drag_piece = False
    275 
    276         # Handle rigtht-click
    277         elif event.button == 3:
    278             
    279             # Toggle off dragging camera
    280             if self.game.state_mode == STATE_MODES.Battle_Mode:
    281                 if self.drag_camera:
    282                     self.drag_camera = False
    283 
    284     def update_interface(self):
    285         """
    286         Update interface elements (such as the cursor) once
    287         per frame. This is not the same as a drawing update for
    288         e.g. an Entity, and is logic-only.
    289         """
    290         mouseraw = pygame.mouse.get_pos()
    291         mousepos = (mouseraw[0] - self.camera.camera_surface_offset[0], mouseraw[1] - self.camera.camera_surface_offset[1])
    292 
    293         # UNIVERSAL UPDATES
    294         # Doubleclick countdown
    295         if self.left_double_click_timer > 0:
    296             self.left_double_click_timer -= 1
    297         else:
    298             self.left_double_clicking = False
    299         # React to keys
    300         self.react_to_keys()
    301 
    302         # BASE MODE ONLY UPDATES
    303         # Update tile cursor
    304         if self.game.state_mode == STATE_MODES.Base_Mode:
    305             tilepos = self.bus.check_for_tile_by_screen_pos(mousepos)
    306             if tilepos != None:
    307                 self.bus.perform_base_manager_position_tile_cursor((tilepos[0], tilepos[1]))
    308 
    309         # BATTLE MODE ONLY UPDATES
    310         # Update mouse position
    311         elif self.game.state_mode == STATE_MODES.Battle_Mode:
    312             tilepos = self.bus.check_for_tile_by_screen_pos(mousepos)
    313 
    314             # Drag facing
    315             if self.drag_piece:
    316                 ap = self.bus.fetch("turn_manager", "active_piece")
    317                 diff = (self.drag_piece_from_pos[0] - mouseraw[0], self.drag_piece_from_pos[1] - mouseraw[1])
    318                 if diff != (0, 0):
    319                     if abs(diff[0]) > abs(diff[1]):
    320                         if diff[0] >= 0:
    321                             ap.facing = FACE_DIR.L
    322                         else:
    323                             ap.facing = FACE_DIR.R
    324                     else:
    325                         if diff[1] >= 0:
    326                             ap.facing = FACE_DIR.U
    327                         else:
    328                             ap.facing = FACE_DIR.D
    329                     ap.set_animation(ap.sheet.animations["stand_" + ap.facing.name], True)
    330 
    331             # Position cursor in scrollable modes and/or scroll
    332             if self.game.control_mode in SCROLLABLE_TURN_MODES:
    333                 # Move camera with keys
    334                 if tuple(self.camera_motions) != (0, 0):
    335                     self.camera.move_offset(self.camera_motions, SCROLL_SPEED)
    336                 # Drag camera
    337                 elif self.drag_camera:
    338                     camdiff = (self.drag_camera_from_pos[0] - mouseraw[0], self.drag_camera_from_pos[1] - mouseraw[1])
    339                     if abs(camdiff[0]) > 3 or abs(camdiff[1]) > 3:
    340                         sg = lambda x: int((x > 0) - (x < 0))
    341                         dragdir = [(SCREEN_WIDTH // 2) - mouseraw[0], (SCREEN_HEIGHT // 2) - mouseraw[1]]
    342                         for k in (0, 1):
    343                             if abs(dragdir[k]) < 80:
    344                                 dragdir[k] = 0
    345                             else:
    346                                 dragdir[k] = sg(dragdir[k])
    347                         if dragdir != (0, 0):
    348                             self.camera.move_offset(tuple(dragdir), SCROLL_SPEED)
    349                 # Position cursor
    350                 if tilepos != None:
    351                     self.bus.perform_piece_manager_position_tile_cursor((tilepos[0], tilepos[1]))
    352                     
    353             # Watching a move complete
    354             elif self.game.control_mode == CTRL_MODES.Turn_Watch_Move:
    355                 ap = self.bus.fetch("turn_manager", "active_piece")
    356                 if not ap.path_moving:
    357                     self.game.control_mode = CTRL_MODES.Turn_Normal
    358 
    359             # Watching the brief guard anim
    360             elif self.game.control_mode == CTRL_MODES.Turn_Watch_Guard:
    361                 ap = self.bus.fetch("turn_manager", "active_piece")
    362                 if ap.has_guarded:
    363                     self.game.control_mode = CTRL_MODES.Turn_Normal
    364                     self.bus.perform_shift_turns()
    365 
    366             # Watching an attack complete
    367             elif self.game.control_mode == CTRL_MODES.Turn_Watch_Attack:
    368                 ap = self.bus.fetch("turn_manager", "active_piece")
    369                 if not ap.attacking and not ap.attack_target.being_damaged:
    370                     # NOTE: attack target dropped here for timing reasons, a bit hacky...
    371                     ap.attack_target = None
    372                     self.game.control_mode = CTRL_MODES.Turn_Normal