comparison gamescenecontroller.py @ 0:7a89ea5404b1

Initial commit of parpg-core.
author M. George Hansen <technopolitica@gmail.com>
date Sat, 14 May 2011 01:12:35 -0700
parents
children 06145a6ee387
comparison
equal deleted inserted replaced
-1:000000000000 0:7a89ea5404b1
1 # This file is part of PARPG.
2
3 # PARPG is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation, either version 3 of the License, or
6 # (at your option) any later version.
7
8 # PARPG is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12
13 # You should have received a copy of the GNU General Public License
14 # along with PARPG. If not, see <http://www.gnu.org/licenses/>.
15 """This file contains the GameSceneController that handles input when the game
16 is exploring a scene"""
17
18
19 from datetime import datetime
20 import random
21 import glob
22 import os
23
24 from fife import fife
25 from fife import extensions
26
27 from controllerbase import ControllerBase
28 from parpg.gui.hud import Hud
29 from parpg.gui import drag_drop_data as data_drag
30 from objects.action import ChangeMapAction, ExamineAction, OpenBoxAction, \
31 UnlockBoxAction, LockBoxAction, TalkAction, \
32 PickUpAction, DropItemAction
33
34 #For debugging/code analysis
35 if False:
36 from gamesceneview import GameSceneView
37 from gamemodel import GameModel
38 from parpg import PARPGApplication
39
40 import logging
41
42 logger = logging.getLogger('gamescenecontroller')
43
44 class GameSceneController(ControllerBase):
45 '''
46 This controller handles inputs when the game is in "scene" state.
47 "Scene" state is when the player can move around and interact
48 with objects. Like, talking to a npc or examining the contents of a box.
49 '''
50
51
52 def __init__(self, engine, view, model, application):
53 '''
54 Constructor
55 @param engine: Instance of the active fife engine
56 @type engine: fife.Engine
57 @param view: Instance of a GameSceneView
58 @param type: parpg.GameSceneView
59 @param model: The model that has the current gamestate
60 @type model: parpg.GameModel
61 @param application: The application that created this controller
62 @type application: parpg.PARPGApplication
63 @param settings: The current settings of the application
64 @type settings: fife.extensions.fife_settings.Setting
65 '''
66 ControllerBase.__init__(self,
67 engine,
68 view,
69 model,
70 application)
71 #this can be helpful for IDEs code analysis
72 if False:
73 assert(isinstance(self.engine, fife.Engine))
74 assert(isinstance(self.view, GameSceneView))
75 assert(isinstance(self.view, GameModel))
76 assert(isinstance(self.application, PARPGApplication))
77 assert(isinstance(self.event_manager, fife.EventManager))
78
79 # Last saved mouse coords
80 self.action_number = 1
81
82 self.has_mouse_focus = True
83 self.last_mousecoords = None
84 self.mouse_callback = None
85 self.original_cursor_id = self.engine.getCursor().getId()
86 self.scroll_direction = [0, 0]
87 self.scroll_timer = extensions.fife_timer.Timer(100,
88 lambda: self.view.moveCamera \
89 (self.scroll_direction))
90
91 #this is temporary until we can set the native cursor
92 self.resetMouseCursor()
93 self.paused = False
94
95 if model.settings.fife.EnableSound:
96 if not self.view.sounds.music_init:
97 music_file = random.choice(glob.glob(os.path.join(
98 "music",
99 "*.ogg")))
100 self.view.sounds.playMusic(music_file)
101 self.initHud()
102
103
104 def initHud(self):
105 """Initialize the hud member
106 @return: None"""
107 hud_callbacks = {
108 'saveGame': self.saveGame,
109 'loadGame': self.loadGame,
110 'quitGame': self.quitGame,
111 }
112 self.view.hud = Hud(self,
113 self.model.settings,
114 hud_callbacks)
115
116 def keyPressed(self, evt):
117 """Whenever a key is pressed, fife calls this routine.
118 @type evt: fife.event
119 @param evt: The event that fife caught
120 @return: None"""
121 key = evt.getKey()
122 key_val = key.getValue()
123
124 if(key_val == key.Q):
125 # we need to quit the game
126 self.view.hud.quitGame()
127 if(key_val == key.T):
128 self.model.active_map.toggleRenderer('GridRenderer')
129 if(key_val == key.F1):
130 # display the help screen and pause the game
131 self.view.hud.displayHelp()
132 if(key_val == key.F5):
133 self.model.active_map.toggleRenderer('CoordinateRenderer')
134 if(key_val == key.F7):
135 # F7 saves a screenshot to screenshots directory
136
137 settings = self.model.settings
138 screenshot_directory = os.path.join(settings.user_path,
139 settings.parpg.ScreenshotsPath)
140 # try to create the screenshots directory
141 try:
142 os.mkdir(screenshot_directory)
143 #TODO: distinguish between already existing permissions error
144 except OSError:
145 logger.warning("screenshot directory wasn't created.")
146
147 screenshot_file = os.path.join(screenshot_directory,
148 'screen-{0}.png'.format(
149 datetime.now().strftime(
150 '%Y-%m-%d-%H-%M-%S')))
151 self.engine.getRenderBackend().captureScreen(screenshot_file)
152 logger.info("PARPG: Saved: {0}".format(screenshot_file))
153 if(key_val == key.F10):
154 # F10 shows/hides the console
155 self.engine.getGuiManager().getConsole().toggleShowHide()
156 if(key_val == key.C):
157 # C opens and closes the character screen.
158 self.view.hud.toggleCharacterScreen()
159 if(key_val == key.I):
160 # I opens and closes the inventory
161 self.view.hud.toggleInventory()
162 if(key_val == key.A):
163 # A adds a test action to the action box
164 # The test actions will follow this format: Action 1,
165 # Action 2, etc.
166 self.view.hud.addAction("Action " + str(self.action_number))
167 self.action_number += 1
168 if(key_val == key.ESCAPE):
169 # Escape brings up the main menu
170 self.view.hud.displayMenu()
171 # Hide the quit menu
172 self.view.hud.quit_window.hide()
173 if(key_val == key.M):
174 self.view.sounds.toggleMusic()
175 if(key_val == key.PAUSE):
176 # Pause pause/unpause the game
177 self.model.togglePause()
178 self.pause(False)
179 if(key_val == key.SPACE):
180 self.model.active_map.centerCameraOnPlayer()
181
182 def mouseReleased(self, evt):
183 """If a mouse button is released, fife calls this routine.
184 We want to wait until the button is released, because otherwise
185 pychan captures the release if a menu is opened.
186 @type evt: fife.event
187 @param evt: The event that fife caught
188 @return: None"""
189 self.view.hud.hideContextMenu()
190 scr_point = fife.ScreenPoint(evt.getX(), evt.getY())
191 if(evt.getButton() == fife.MouseEvent.LEFT):
192 if(data_drag.dragging):
193 coord = self.model.getCoords(scr_point)\
194 .getExactLayerCoordinates()
195 commands = ({"Command": "ResetMouseCursor"},
196 {"Command": "StopDragging"})
197 self.model.game_state.player_character.approach([coord.x,
198 coord.y],
199 DropItemAction(self,
200 data_drag.dragged_item,
201 commands))
202 else:
203 self.model.movePlayer(self.model.getCoords(scr_point))
204 elif(evt.getButton() == fife.MouseEvent.RIGHT):
205 # is there an object here?
206 tmp_active_map = self.model.active_map
207 instances = tmp_active_map.cameras[tmp_active_map.my_cam_id].\
208 getMatchingInstances(scr_point,
209 tmp_active_map.agent_layer)
210 info = None
211 for inst in instances:
212 # check to see if this is an active item
213 if(self.model.objectActive(inst.getId())):
214 # yes, get the model
215 info = self.getItemActions(inst.getId())
216 break
217
218 # take the menu items returned by the engine or show a
219 # default menu if no items
220 data = info or \
221 [["Walk", "Walk here", self.view.onWalk,
222 self.model.getCoords(scr_point)]]
223 # show the menu
224 self.view.hud.showContextMenu(data, (scr_point.x, scr_point.y))
225
226
227 def updateMouse(self):
228 """Updates the mouse values"""
229 if self.paused:
230 return
231 cursor = self.engine.getCursor()
232 #this can be helpful for IDEs code analysis
233 if False:
234 assert(isinstance(cursor, fife.Cursor))
235 self.last_mousecoords = fife.ScreenPoint(cursor.getX(),
236 cursor.getY())
237 self.view.highlightFrontObject(self.last_mousecoords)
238
239 #set the trigger area in pixles
240 pixle_edge = 20
241
242 mouse_x = self.last_mousecoords.x
243 screen_width = self.model.engine.getSettings().getScreenWidth()
244 mouse_y = self.last_mousecoords.y
245 screen_height = self.model.engine.getSettings().getScreenHeight()
246
247 image = None
248 settings = self.model.settings
249
250
251 #edge logic
252 self.scroll_direction = [0, 0]
253 if self.has_mouse_focus:
254 direction = self.scroll_direction
255 #up
256 if mouse_y <= pixle_edge:
257 direction[0] += 1
258 direction[1] -= 1
259 image = os.path.join(settings.system_path,
260 settings.parpg.GuiPath,
261 settings.parpg.CursorPath,
262 settings.parpg.CursorUp)
263
264 #right
265 if mouse_x >= screen_width - pixle_edge:
266 direction[0] += 1
267 direction[1] += 1
268 image = os.path.join(settings.system_path,
269 settings.parpg.GuiPath,
270 settings.parpg.CursorPath,
271 settings.parpg.CursorRight)
272
273 #down
274 if mouse_y >= screen_height - pixle_edge:
275 direction[0] -= 1
276 direction[1] += 1
277 image = os.path.join(settings.system_path,
278 settings.parpg.GuiPath,
279 settings.parpg.CursorPath,
280 settings.parpg.CursorDown)
281
282 #left
283 if mouse_x <= pixle_edge:
284 direction[0] -= 1
285 direction[1] -= 1
286 image = os.path.join(settings.system_path,
287 settings.parpg.GuiPath,
288 settings.parpg.CursorPath,
289 settings.parpg.CursorLeft)
290
291 if image is not None and not data_drag.dragging:
292 self.setMouseCursor(image, image)
293
294
295 def handleCommands(self):
296 """Check if a command is to be executed
297 """
298 if self.model.map_change:
299 self.pause(True)
300 if self.model.active_map:
301 player_char = self.model.game_state.player_character
302 self.model.game_state.player_character = None
303 pc_agent = self.model.agents[self.model.ALL_AGENTS_KEY]\
304 ["PlayerCharacter"]
305 pc_agent.update(player_char.getStateForSaving())
306 pc_agent["Map"] = self.model.target_map_name
307 pc_agent["Position"] = self.model.target_position
308 pc_agent["Inventory"] = \
309 player_char.inventory.serializeInventory()
310 player_agent = self.model.active_map.\
311 agent_layer.getInstance("PlayerCharacter")
312 self.model.active_map.agent_layer.deleteInstance(player_agent)
313 self.model.loadMap(self.model.target_map_name)
314 self.model.setActiveMap(self.model.target_map_name)
315 self.model.readAgentsOfMap(self.model.target_map_name)
316 self.model.placeAgents()
317 self.model.placePC()
318 self.model.map_change = False
319 # The PlayerCharacter has an inventory, and also some
320 # filling of the ready slots in the HUD.
321 # At this point we sync the contents of the ready slots
322 # with the contents of the inventory.
323 self.view.hud.inventory = None
324 self.view.hud.initializeInventory()
325 self.pause(False)
326
327 def nullFunc(self, userdata):
328 """Sample callback for the context menus."""
329 logger.info(userdata)
330
331 def initTalk(self, npc_info):
332 """ Starts the PlayerCharacter talking to an NPC. """
333 # TODO: work more on this when we get NPCData and HeroData straightened
334 # out
335 npc = self.model.game_state.getObjectById(npc_info.ID,
336 self.model.game_state.\
337 current_map_name)
338 self.model.game_state.player_character.approach([npc.getLocation().\
339 getLayerCoordinates().x,
340 npc.getLocation().\
341 getLayerCoordinates().y],
342 TalkAction(self, npc))
343
344 def getItemActions(self, obj_id):
345 """Given the objects ID, return the text strings and callbacks.
346 @type obj_id: string
347 @param obj_id: ID of object
348 @rtype: list
349 @return: List of text and callbacks"""
350 actions = []
351 # note: ALWAYS check NPC's first!
352 obj = self.model.game_state.\
353 getObjectById(obj_id,
354 self.model.game_state.current_map_name)
355
356 if obj is not None:
357 if obj.trueAttr("NPC"):
358 # keep it simple for now, None to be replaced by callbacks
359 actions.append(["Talk", "Talk", self.initTalk, obj])
360 actions.append(["Attack", "Attack", self.nullFunc, obj])
361 else:
362 actions.append(["Examine", "Examine",
363 self.model.game_state.\
364 player_character.approach,
365 [obj.X, obj.Y],
366 ExamineAction(self,
367 obj_id, obj.name,
368 obj.text)])
369 # is it a Door?
370 if obj.trueAttr("door"):
371 actions.append(["Change Map", "Change Map",
372 self.model.game_state.player_character.approach,
373 [obj.X, obj.Y],
374 ChangeMapAction(self, obj.target_map_name,
375 obj.target_pos)])
376 # is it a container?
377 if obj.trueAttr("container"):
378 actions.append(["Open", "Open",
379 self.model.game_state.\
380 player_character.approach,
381 [obj.X, obj.Y],
382 OpenBoxAction(self, obj)])
383 actions.append(["Unlock", "Unlock",
384 self.model.game_state.\
385 player_character.approach,
386 [obj.X, obj.Y],
387 UnlockBoxAction(self, obj)])
388 actions.append(["Lock", "Lock",
389 self.model.game_state.\
390 player_character.approach,
391 [obj.X, obj.Y],
392 LockBoxAction(self, obj)])
393 # can you pick it up?
394 if obj.trueAttr("carryable"):
395 actions.append(["Pick Up", "Pick Up",
396 self.model.game_state.\
397 player_character.approach,
398 [obj.X, obj.Y],
399 PickUpAction(self, obj)])
400
401 return actions
402
403 def saveGame(self, *args, **kwargs):
404 """Saves the game state, delegates call to engine.Engine
405 @return: None"""
406 self.model.pause(False)
407 self.pause(False)
408 self.view.hud.enabled = True
409 self.model.save(*args, **kwargs)
410
411 def loadGame(self, *args, **kwargs):
412 """Loads the game state, delegates call to engine.Engine
413 @return: None"""
414 # Remove all currently loaded maps so we can start fresh
415 self.model.pause(False)
416 self.pause(False)
417 self.view.hud.enabled = True
418 self.model.deleteMaps()
419 self.view.hud.inventory = None
420
421 self.model.load(*args, **kwargs)
422 self.view.hud.initializeInventory()
423
424 def quitGame(self):
425 """Quits the game
426 @return: None"""
427 self.application.listener.quitGame()
428
429 def pause(self, paused):
430 """Pauses the controller"""
431 super(GameSceneController, self).pause(paused)
432 self.paused = paused
433 if paused:
434 self.scroll_timer.stop()
435 self.resetMouseCursor()
436
437 def onCommand(self, command):
438 if(command.getCommandType() == fife.CMD_MOUSE_FOCUS_GAINED):
439 self.has_mouse_focus = True
440 elif(command.getCommandType() == fife.CMD_MOUSE_FOCUS_LOST):
441 self.has_mouse_focus = False
442
443 def pump(self):
444 """Routine called during each frame. Our main loop is in ./run.py"""
445 # uncomment to instrument
446 # t0 = time.time()
447 if self.paused:
448 return
449 self.updateMouse()
450 if self.model.active_map:
451 self.view.highlightFrontObject(self.last_mousecoords)
452 self.view.refreshTopLayerTransparencies()
453 if self.scroll_direction != [0, 0]:
454 self.scroll_timer.start()
455 else:
456 self.scroll_timer.stop()
457 if not data_drag.dragging:
458 self.resetMouseCursor()
459
460 self.handleCommands()
461 # print "%05f" % (time.time()-t0,)