Mercurial > parpg-core
view src/parpg/gamescenecontroller.py @ 133:ecac92680bef
Added animation queue and method the base behaviour class.
author | KarstenBock@gmx.net |
---|---|
date | Fri, 30 Sep 2011 14:04:29 +0200 |
parents | 0ffebdca7ba3 |
children | c938a828a38a |
line wrap: on
line source
# This file is part of PARPG. # PARPG is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # PARPG is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with PARPG. If not, see <http://www.gnu.org/licenses/>. """This file contains the GameSceneController that handles input when the game is exploring a scene""" from datetime import datetime import random import glob import os import logging from fife import fife from fife import extensions from controllerbase import ControllerBase from parpg.gui.hud import Hud from parpg.gui import drag_drop_data as data_drag from objects.action import ChangeMapAction, ExamineAction, OpenBoxAction, \ UnlockBoxAction, LockBoxAction, TalkAction, \ PickUpAction, DropItemAction from parpg.world import World #For debugging/code analysis if False: from gamesceneview import GameSceneView from gamemodel import GameModel from parpg import PARPGApplication logger = logging.getLogger('gamescenecontroller') class GameSceneController(World, ControllerBase): ''' This controller handles inputs when the game is in "scene" state. "Scene" state is when the player can move around and interact with objects. Like, talking to a npc or examining the contents of a box. ''' def __init__(self, engine, view, model, application): ''' Constructor @param engine: Instance of the active fife engine @type engine: fife.Engine @param view: Instance of a GameSceneView @param type: parpg.GameSceneView @param model: The model that has the current gamestate @type model: parpg.GameModel @param application: The application that created this controller @type application: parpg.PARPGApplication @param settings: The current settings of the application @type settings: fife.extensions.fife_settings.Setting ''' ControllerBase.__init__(self, engine, view, model, application) World.__init__(self) #this can be helpful for IDEs code analysis if False: assert(isinstance(self.engine, fife.Engine)) assert(isinstance(self.view, GameSceneView)) assert(isinstance(self.view, GameModel)) assert(isinstance(self.application, PARPGApplication)) assert(isinstance(self.event_manager, fife.EventManager)) # Last saved mouse coords self.action_number = 1 self.has_mouse_focus = True self.last_mousecoords = None self.mouse_callback = None self.original_cursor_id = self.engine.getCursor().getId() self.scroll_data = {"mouse":[], "kb":[], "offset":[0,0]} self.scroll_timer = extensions.fife_timer.Timer( 100, lambda: self.view.moveCamera(self.scroll_data["offset"]), ) #this is temporary until we can set the native cursor self.resetMouseCursor() self.paused = False if model.settings.fife.EnableSound: if not self.view.sounds.music_init: music_path = 'music' music_file = random.choice( glob.glob('/'.join([music_path, '*.ogg'])) ) self.view.sounds.playMusic(music_file) self.initHud() def initHud(self): """Initialize the hud member @return: None""" hud_callbacks = { 'saveGame': self.saveGame, 'loadGame': self.loadGame, 'quitGame': self.quitGame, } self.view.hud = Hud(self, self.model.settings, hud_callbacks) def keyPressed(self, evt): """Whenever a key is pressed, fife calls this routine. @type evt: fife.event @param evt: The event that fife caught @return: None""" key = evt.getKey() key_val = key.getValue() if(key_val == key.Q): # we need to quit the game self.view.hud.quitGame() if(key_val == key.T): self.model.active_map.toggleRenderer('GridRenderer') if(key_val == key.F1): # display the help screen and pause the game self.view.hud.displayHelp() if(key_val == key.F5): self.model.active_map.toggleRenderer('CoordinateRenderer') if(key_val == key.F7): # F7 saves a screenshot to screenshots directory settings = self.model.settings # FIXME M. George Hansen 2011-06-06: Not sure that user_path is set # correctly atm. screenshot_directory = os.path.join(settings.user_path, 'screenshots') # try to create the screenshots directory try: os.mkdir(screenshot_directory) #TODO: distinguish between already existing permissions error except OSError: logger.warning("screenshot directory wasn't created.") screenshot_file = os.path.join(screenshot_directory, 'screen-{0}.png'.format( datetime.now().strftime( '%Y-%m-%d-%H-%M-%S'))) self.engine.getRenderBackend().captureScreen(screenshot_file) logger.info("PARPG: Saved: {0}".format(screenshot_file)) if(key_val == key.F10): # F10 shows/hides the console self.engine.getGuiManager().getConsole().toggleShowHide() if(key_val == key.C): # C opens and closes the character screen. self.view.hud.toggleCharacterScreen() if(key_val == key.I): # I opens and closes the inventory self.view.hud.toggleInventory() if(key_val == key.A): # A adds a test action to the action box # The test actions will follow this format: Action 1, # Action 2, etc. self.view.hud.addAction("Action " + str(self.action_number)) self.action_number += 1 if(key_val == key.ESCAPE): # Escape brings up the main menu self.view.hud.displayMenu() # Hide the quit menu self.view.hud.quit_window.hide() if(key_val == key.M): self.view.sounds.toggleMusic() if(key_val == key.PAUSE): # Pause pause/unpause the game self.model.togglePause() self.pause(False) if(key_val == key.SPACE): self.model.active_map.centerCameraOnPlayer() #alter scroll data if a directional key is hit if(key_val == key.UP): if not "up" in self.scroll_data["kb"]: self.scroll_data["kb"].append("up") if(key_val == key.RIGHT): if not "right" in self.scroll_data["kb"]: self.scroll_data["kb"].append("right") if(key_val == key.DOWN): if not "down" in self.scroll_data["kb"]: self.scroll_data["kb"].append("down") if(key_val == key.LEFT): if not "left" in self.scroll_data["kb"]: self.scroll_data["kb"].append("left") def keyReleased(self, evt): """Whenever a key is pressed, fife calls this routine. @type evt: fife.event @param evt: The event that fife caught @return: None""" key = evt.getKey() key_val = key.getValue() #alter scroll data if a directional key is released if(key_val == key.UP): if "up" in self.scroll_data["kb"]: self.scroll_data["kb"].remove("up") if(key_val == key.RIGHT): if "right" in self.scroll_data["kb"]: self.scroll_data["kb"].remove("right") if(key_val == key.DOWN): if "down" in self.scroll_data["kb"]: self.scroll_data["kb"].remove("down") if(key_val == key.LEFT): if "left" in self.scroll_data["kb"]: self.scroll_data["kb"].remove("left") def mouseReleased(self, evt): """If a mouse button is released, fife calls this routine. We want to wait until the button is released, because otherwise pychan captures the release if a menu is opened. @type evt: fife.event @param evt: The event that fife caught @return: None""" self.view.hud.hideContextMenu() scr_point = fife.ScreenPoint(evt.getX(), evt.getY()) if(evt.getButton() == fife.MouseEvent.LEFT): if(data_drag.dragging): coord = self.model.getCoords(scr_point)\ .getExactLayerCoordinates() commands = ({"Command": "ResetMouseCursor"}, {"Command": "StopDragging"}) self.model.game_state.getObjectById("PlayerCharacter").fifeagent.approach([coord.x, coord.y], DropItemAction(self, data_drag.dragged_item, commands)) else: self.model.movePlayer(self.model.getCoords(scr_point)) elif(evt.getButton() == fife.MouseEvent.RIGHT): # is there an object here? tmp_active_map = self.model.active_map instances = tmp_active_map.cameras[tmp_active_map.my_cam_id].\ getMatchingInstances(scr_point, tmp_active_map.agent_layer) info = None for inst in instances: # check to see if this is an active item if(self.model.objectActive(inst.getId())): # yes, get the model info = self.getItemActions(inst.getId()) break # take the menu items returned by the engine or show a # default menu if no items data = info or \ [["Walk", "Walk here", self.view.onWalk, self.model.getCoords(scr_point)]] # show the menu self.view.hud.showContextMenu(data, (scr_point.x, scr_point.y)) def updateMouse(self): """Updates the mouse values""" if self.paused: return cursor = self.engine.getCursor() #this can be helpful for IDEs code analysis if False: assert(isinstance(cursor, fife.Cursor)) self.last_mousecoords = fife.ScreenPoint(cursor.getX(), cursor.getY()) self.view.highlightFrontObject(self.last_mousecoords) #set the trigger area in pixles pixle_edge = 20 mouse_x = self.last_mousecoords.x screen_width = self.model.engine.getSettings().getScreenWidth() mouse_y = self.last_mousecoords.y screen_height = self.model.engine.getSettings().getScreenHeight() image = None settings = self.model.settings #edge logic if self.has_mouse_focus: direction = self.scroll_data["mouse"] = [] #up if mouse_y <= pixle_edge: direction.append("up") image = '/'.join(['gui/cursors', settings.parpg.CursorUp]) #right if mouse_x >= screen_width - pixle_edge: direction.append("right") image = '/'.join(['gui/cursors', settings.parpg.CursorRight]) #down if mouse_y >= screen_height - pixle_edge: direction.append("down") image = '/'.join(['gui/cursors', settings.parpg.CursorDown]) #left if mouse_x <= pixle_edge: direction.append("left") image = '/'.join(['gui/cursors', settings.parpg.CursorLeft]) if image is not None and not data_drag.dragging: self.setMouseCursor(image, image) def handleCommands(self): """Check if a command is to be executed """ if self.model.map_change: self.pause(True) if self.model.active_map: self.model.updateObjectDB(self) player_char = self.model.game_state.\ getObjectById("PlayerCharacter").fifeagent pc_agent = self.model.agents\ [self.model.ALL_AGENTS_KEY]["PlayerCharacter"] pc_agent["Map"] = self.model.target_map_name pc_agent["Position"] = (self.model.target_position or pc_agent["Position"]) player_agent = self.model.active_map.\ agent_layer.getInstance("PlayerCharacter") self.model.game_state.deleteObject("PlayerCharacter").delete() deleted = self.model.game_state.deleteObjectsFromMap( self.model.game_state.current_map_name ) deleted.extend( self.model.game_state.deleteObjectsFromMap(None) ) for obj in deleted: obj.delete() self.model.loadMap(self.model.target_map_name) self.model.setActiveMap(self.model.target_map_name) self.model.readAgentsOfMap(self.model.target_map_name) self.model.placeAgents(self) self.model.placePC(self) self.model.map_change = False # The PlayerCharacter has an inventory, and also some # filling of the ready slots in the HUD. # At this point we sync the contents of the ready slots # with the contents of the inventory. self.view.hud.inventory = None self.view.hud.initializeInventory() self.pause(False) def handleScrolling(self): """ Merge kb and mouse related scroll data, limit the speed and move the camera. """ #this is how many pxls the camera is moved in one time frame scroll_offset = self.scroll_data["offset"] = [0,0] mouse = self.scroll_data["mouse"] keyboard = self.scroll_data["kb"] speed = self.model.settings.parpg.ScrollSpeed #adds a value to the offset depending on the contents of each # of the controllers: set() removes doubles scroll_direction = set(mouse+keyboard) for direction in scroll_direction: if direction == "up": scroll_offset[0] +=1 scroll_offset[1] -=1 elif direction == "right": scroll_offset[0] +=1 scroll_offset[1] +=1 elif direction == "down": scroll_offset[0] -=1 scroll_offset[1] +=1 elif direction == "left": scroll_offset[0] -=1 scroll_offset[1] -=1 #keep the speed within bounds if scroll_offset[0] > 0: scroll_offset[0] = speed if scroll_offset[0] < 0: scroll_offset[0] = -speed if scroll_offset[1] > 0: scroll_offset[1] = speed if scroll_offset[1] < 0: scroll_offset[1] = -speed #de/activate scrolling if scroll_offset != [0, 0]: self.scroll_timer.start() else: self.scroll_timer.stop() if not data_drag.dragging: self.resetMouseCursor() def nullFunc(self, userdata): """Sample callback for the context menus.""" logger.info(userdata) def initTalk(self, npc_info): """ Starts the PlayerCharacter talking to an NPC. """ # TODO: work more on this when we get NPCData and HeroData straightened # out npc = self.model.game_state.getObjectById( npc_info.general.identifier, self.model.game_state.current_map_name ) npc_pos = npc.fifeagent.behaviour.getLocation().getLayerCoordinates() self.model.game_state.getObjectById("PlayerCharacter").fifeagent.\ behaviour.approach([npc_pos.x, npc_pos.y], TalkAction(self, npc)) def getItemActions(self, obj_id): """Given the objects ID, return the text strings and callbacks. @type obj_id: string @param obj_id: ID of object @rtype: list @return: List of text and callbacks""" actions = [] obj = self.model.game_state.\ getObjectById(obj_id, self.model.game_state.current_map_name) obj_pos = obj.fifeagent.behaviour.getLocation().getLayerCoordinates() player = self.model.game_state.getObjectById("PlayerCharacter") is_player = obj.general.identifier == player.general.identifier #TODO: Check all actions to be compatible with the grease components if obj is not None: if obj.dialogue and not is_player: actions.append(["Talk", "Talk", self.initTalk, obj]) if obj.characterstats and not is_player: actions.append(["Attack", "Attack", self.nullFunc, obj]) if obj.description and obj.description.desc: actions.append(["Examine", "Examine", player.fifeagent.behaviour.approach, [obj_pos.x, obj_pos.y], ExamineAction(self, obj_id, obj.description.view_name, obj.description.desc)]) if obj.change_map: actions.append(["Change Map", "Change Map", player.fifeagent.behaviour.approach, [obj_pos.x, obj_pos.y], ChangeMapAction(self, obj.change_map.target_map, obj.change_map.target_position)]) if obj.lockable: actions.append(["Open", "Open", player.fifeagent.behaviour.approach, [obj_pos.x, obj_pos.y], OpenBoxAction(self, obj)]) actions.append(["Unlock", "Unlock", player.fifeagent.behaviour.approach, [obj_pos.x, obj_pos.y], UnlockBoxAction(self, obj)]) actions.append(["Lock", "Lock", player.fifeagent.behaviour.approach, [obj_pos.x, obj_pos.y], LockBoxAction(self, obj)]) if obj.containable: actions.append(["Pick Up", "Pick Up", player.fifeagent.behaviour.approach, [obj_pos.x, obj_pos.y], PickUpAction(self, obj)]) return actions def saveGame(self, *args, **kwargs): """Saves the game state, delegates call to gamemodel.GameModel @return: None""" self.model.pause(False) self.pause(False) self.view.hud.enabled = True self.model.updateObjectDB(self) self.model.save(*args, **kwargs) def loadGame(self, *args, **kwargs): """Loads the game state, delegates call to gamemodel.GameModel @return: None""" # Remove all currently loaded maps so we can start fresh self.model.pause(False) self.pause(False) self.view.hud.enabled = True self.model.deleteMaps() for entity in self.entities.copy(): entity.delete() self.view.hud.inventory = None self.model.load(*args, **kwargs) # Load the current map if self.model.game_state.current_map_name: self.model.loadMap(self.model.game_state.current_map_name) self.model.placeAgents(self) self.model.placePC(self) self.view.hud.initializeInventory() def quitGame(self): """Quits the game @return: None""" self.application.listener.quitGame() def pause(self, paused): """Pauses the controller""" super(GameSceneController, self).pause(paused) self.paused = paused if paused: self.scroll_timer.stop() self.resetMouseCursor() def onCommand(self, command): if(command.getCommandType() == fife.CMD_MOUSE_FOCUS_GAINED): self.has_mouse_focus = True elif(command.getCommandType() == fife.CMD_MOUSE_FOCUS_LOST): self.has_mouse_focus = False def pump(self, dt): """Routine called during each frame. Our main loop is in ./run.py""" # uncomment to instrument # t0 = time.time() if self.paused: return ControllerBase.pump(self, dt) self.updateMouse() if self.model.active_map: self.view.highlightFrontObject(self.last_mousecoords) self.view.refreshTopLayerTransparencies() self.handleScrolling() self.handleCommands() # print "%05f" % (time.time()-t0,)