Mercurial > parpg-core
changeset 203:92bb014104ee
Added parpg-source subrepo in parpg directory,
which replaces the src directory.
author | KarstenBock@gmx.net |
---|---|
date | Thu, 12 Jan 2012 19:13:29 +0100 |
parents | 7747b0f73694 |
children | 132da1d0b5b4 |
files | .hgsub .hgsubstate src/parpg.py src/parpg/__init__.py src/parpg/application.py src/parpg/bGrease/__init__.py src/parpg/bGrease/collision.py src/parpg/bGrease/color.py src/parpg/bGrease/component/__init__.py src/parpg/bGrease/component/base.py src/parpg/bGrease/component/field.py src/parpg/bGrease/component/general.py src/parpg/bGrease/component/schema.py src/parpg/bGrease/controller/__init__.py src/parpg/bGrease/controller/integrator.py src/parpg/bGrease/entity.py src/parpg/bGrease/geometry.py src/parpg/bGrease/impl/__init__.py src/parpg/bGrease/impl/controls.py src/parpg/bGrease/impl/mode.py src/parpg/bGrease/impl/world.py src/parpg/bGrease/mode.py src/parpg/bGrease/renderer/__init__.py src/parpg/bGrease/renderer/camera.py src/parpg/bGrease/renderer/vector.py src/parpg/bGrease/world.py src/parpg/behaviours/__init__.py src/parpg/behaviours/base.py src/parpg/behaviours/moving.py src/parpg/behaviours/npc.py src/parpg/behaviours/player.py src/parpg/charactercreationcontroller.py src/parpg/charactercreationview.py src/parpg/characterstatistics.py src/parpg/common/__init__.py src/parpg/common/listeners/__init__.py src/parpg/common/listeners/command_listener.py src/parpg/common/listeners/console_executor.py src/parpg/common/listeners/event_listener.py src/parpg/common/listeners/key_listener.py src/parpg/common/listeners/mouse_listener.py src/parpg/common/listeners/widget_listener.py src/parpg/common/ordereddict.py src/parpg/common/utils.py src/parpg/components/__init__.py src/parpg/components/base.py src/parpg/components/behaviour.py src/parpg/components/change_map.py src/parpg/components/character_statistics.py src/parpg/components/containable.py src/parpg/components/container.py src/parpg/components/description.py src/parpg/components/dialogue.py src/parpg/components/equip.py src/parpg/components/equipable.py src/parpg/components/fifeagent.py src/parpg/components/general.py src/parpg/components/graphics.py src/parpg/components/lockable.py src/parpg/components/usable.py src/parpg/console.py src/parpg/controllerbase.py src/parpg/dialogue.py src/parpg/dialogueactions.py src/parpg/dialoguecontroller.py src/parpg/dialogueparsers.py src/parpg/dialogueprocessor.py src/parpg/entities/__init__.py src/parpg/entities/action.py src/parpg/entities/general.py src/parpg/font.py src/parpg/gamemap.py src/parpg/gamemodel.py src/parpg/gamescenecontroller.py src/parpg/gamesceneview.py src/parpg/gamestate.py src/parpg/gui/__init__.py src/parpg/gui/actionsbox.py src/parpg/gui/charactercreationview.py src/parpg/gui/containergui.py src/parpg/gui/containergui_base.py src/parpg/gui/dialogs.py src/parpg/gui/dialoguegui.py src/parpg/gui/drag_drop_data.py src/parpg/gui/filebrowser.py src/parpg/gui/hud.py src/parpg/gui/inventorygui.py src/parpg/gui/menus.py src/parpg/gui/popups.py src/parpg/gui/spinners.py src/parpg/gui/tabwidget.py src/parpg/inventory.py src/parpg/main.py src/parpg/mainmenucontroller.py src/parpg/mainmenuview.py src/parpg/mode.py src/parpg/quest_engine.py src/parpg/serializers.py src/parpg/settings.py src/parpg/sounds.py src/parpg/systems/__init__.py src/parpg/systems/gamerulessystem.py src/parpg/systems/scriptingsystem.py src/parpg/vfs.py src/parpg/viewbase.py src/parpg/world.py |
diffstat | 104 files changed, 2 insertions(+), 14891 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgsub Thu Jan 12 18:01:28 2012 +0100 +++ b/.hgsub Thu Jan 12 19:13:29 2012 +0100 @@ -1,2 +1,3 @@ +parpg = ../parpg-source data = [svn]http://subversion.assembla.com/svn/parpg-assets/trunk/ tools = https://hg.assembla.com/parpg-tools
--- a/.hgsubstate Thu Jan 12 18:01:28 2012 +0100 +++ b/.hgsubstate Thu Jan 12 19:13:29 2012 +0100 @@ -1,2 +1,3 @@ 23 data +0000000000000000000000000000000000000000 parpg 833aac39c9bd43873554f19ab3d6c27ddba317ff tools
--- a/src/parpg.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -#!/usr/bin/env python2 -# This program 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. - -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -#TODO: Modularize this script -import sys -from optparse import OptionParser -from os import path - -from parpg.main import main - -usage = ('usage: %prog [options] settings_path [system_path user_path]\n\n' - 'The settings_path argument is mandatory and is the directory in \n' - 'which your parpg.cfg file is located. Optionally, you may \n' - 'specify where data files are located (system_path), and where \n' - 'the user settings and data files should be saved to (user_path)\n\n' - 'Example: python %prog .') - -parser = OptionParser(description='PARPG Launcher Script', usage=usage) -parser.add_option('-f', '--logfile', - help='Name of log file to save to') -parser.add_option('-l', '--loglevel', default='critical', - help='desired output level for log file') -opts, args = parser.parse_args() - -if not args: - if path.isfile("./parpg.cfg"): - args =(".") - else: - parser.print_help() - sys.exit(1) - -main(args, opts) \ No newline at end of file
--- a/src/parpg/__init__.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -# 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/>. - -COPYRIGHT_HEADER = """\ -# 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/>. -"""
--- a/src/parpg/application.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,220 +0,0 @@ -# This program 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. - -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. -"""This module contains the main Application class -and the basic Listener for PARPG """ - -import os -import sys - -from fife import fife -from fife.extensions import pychan -from fife.extensions.basicapplication import ApplicationBase - -from parpg import console, vfs -from parpg.font import PARPGFont -from parpg.gamemodel import GameModel -from parpg.mainmenuview import MainMenuView -from parpg.mainmenucontroller import MainMenuController -from parpg.common.listeners.event_listener import EventListener -from parpg.common.listeners.key_listener import KeyListener -from parpg.common.listeners.mouse_listener import MouseListener -from parpg.common.listeners.command_listener import CommandListener -from parpg.common.listeners.console_executor import ConsoleExecuter -from parpg.common.listeners.widget_listener import WidgetListener -from parpg.mode import FifeManager - -class KeyFilter(fife.IKeyFilter): - """ - This is the implementation of the fife.IKeyFilter class. - - Prevents any filtered keys from being consumed by guichan. - """ - def __init__(self, keys): - fife.IKeyFilter.__init__(self) - self._keys = keys - - def isFiltered(self, event): - """Checks if an key is filtered""" - return event.getKey().getValue() in self._keys - -class ApplicationListener(KeyListener, - MouseListener, - ConsoleExecuter, - CommandListener, - WidgetListener): - """Basic listener for PARPG""" - - def __init__(self, event_listener, engine, view, model): - """Initialize the instance. - @type engine: fife.engine - @param engine: ??? - @type view: viewbase.ViewBase - @param view: View that draws the current state - @type model: GameModel - @param model: The game model""" - - KeyListener.__init__(self, event_listener) - MouseListener.__init__(self, event_listener) - ConsoleExecuter.__init__(self, event_listener) - CommandListener.__init__(self, event_listener) - WidgetListener.__init__(self, event_listener) - self.engine = engine - self.view = view - self.model = model - keyfilter = KeyFilter([fife.Key.ESCAPE]) - keyfilter.__disown__() - - engine.getEventManager().setKeyFilter(keyfilter) - self.quit = False - self.about_window = None - self.console = console.Console(self) - - def quitGame(self): - """Forces a quit game on next cycle. - @return: None""" - self.quit = True - - def onConsoleCommand(self, command): - """ - Called on every console comand, delegates calls to the a console - object, implementing the callbacks - @type command: string - @param command: the command to run - @return: result - """ - return self.console.handleConsoleCommand(command) - - def onCommand(self, command): - """Enables the game to be closed via the 'X' button on the window frame - @type command: fife.Command - @param command: The command to read. - @return: None""" - if(command.getCommandType() == fife.CMD_QUIT_GAME): - self.quit = True - command.consume() - -class PARPGApplication(ApplicationBase): - """Main Application class - We use an MVC model model - self.gamesceneview is our view,self.model is our model - self.controller is the controller""" - - def __init__(self, setting): - """Initialise the instance. - @return: None""" - self._setting = setting - self.manager = FifeManager() - self.engine = fife.Engine() - self.loadSettings() - self.engine.init() - # KLUDGE M. George Hansen 2011-06-04: See parpg/vfs.py. - vfs.VFS = self.engine.getVFS() - vfs.VFS.addNewSource(setting.parpg.DataPath) - - pychan.init(self.engine, debug = True) - pychan.setupModalExecution(self.mainLoop,self.breakFromMainLoop) - - self.quitRequested = False - self.breakRequested = False - self.returnValues = [] - #self.engine.getModel(self) - self.model = GameModel(self.engine, setting) - self.model.readMapFiles() - self.model.readObjectDB() - self.model.getAgentImportFiles() - self.model.readAllAgents() - self.model.getDialogues() - # KLUDGE M. George Hansen 2011-06-04: Hack to allow loaded PyChan XML - # scripts to locate their resources. - os.chdir(setting.parpg.DataPath) - self.view = MainMenuView(self.engine, self.model) - self.loadFonts() - self.event_listener = EventListener(self.engine) - controller = MainMenuController(self.engine, self.view, self.model, - self) - #controller.initHud() - self.manager.push_mode(controller) - self.listener = ApplicationListener(self.event_listener, - self.engine, - self.view, - self.model) - #start_map = self._setting.fife.get("PARPG", "Map") - #self.model.changeMap(start_map) - - def loadFonts(self): - # add the fonts path to the system path to import font definitons - sys.path.insert(0, os.path.join(self._setting.parpg.DataPath, 'fonts')) - from oldtypewriter import fontdefs - - for fontdef in fontdefs: - pychan.internal.get_manager().addFont(PARPGFont(fontdef, - self._setting)) - - - def loadSettings(self): - """ - Load the settings from a python file and load them into the engine. - Called in the ApplicationBase constructor. - """ - - engineSetting = self.engine.getSettings() - assert(isinstance(engineSetting, fife.EngineSettings)) - engineSetting.setDefaultFontGlyphs(self._setting.fife.FontGlyphs) - engineSetting.setDefaultFontPath( - '{0}/fonts/{1}'.format(self._setting.parpg.DataPath, - self._setting.fife.Font) - ) - engineSetting.setDefaultFontSize(self._setting.fife.DefaultFontSize) - engineSetting.setBitsPerPixel(self._setting.fife.BitsPerPixel) - engineSetting.setInitialVolume(self._setting.fife.InitialVolume) - engineSetting.setSDLRemoveFakeAlpha( - self._setting.fife.SDLRemoveFakeAlpha - ) - engineSetting.setGLUseFramebuffer(self._setting.fife.GLUseFramebuffer) - engineSetting.setGLUseNPOT(self._setting.fife.GLUseNPOT) - engineSetting.setScreenWidth(self._setting.fife.ScreenWidth) - engineSetting.setScreenHeight(self._setting.fife.ScreenHeight) - engineSetting.setRenderBackend(self._setting.fife.RenderBackend) - engineSetting.setFullScreen(self._setting.fife.FullScreen) - engineSetting.setVideoDriver(self._setting.fife.VideoDriver) - engineSetting.setLightingModel(self._setting.fife.Lighting) - engineSetting.setColorKeyEnabled(self._setting.fife.ColorKeyEnabled) - engineSetting.setMouseSensitivity(self._setting.fife.MouseSensitivity) - engineSetting.setMouseAcceleration( - self._setting.fife.MouseAcceleration - ) - - engineSetting.setColorKey(*[int(digit) - for digit in self._setting.fife.ColorKey]) - - engineSetting.setWindowTitle(self._setting.fife.WindowTitle) - engineSetting.setWindowIcon( - '/'.join(['gui/icons', self._setting.fife.WindowIcon]) - ) - - def createListener(self): - """ __init__ takes care of creating an event listener, so - basicapplication's createListener is harmful. Without - overriding it, the program quit's on esc press, rather than - invoking the main menu - """ - pass - - def _pump(self): - """Main game loop. - There are 2 main loops, this one and the one in GameSceneView. - @return: None""" - if self.listener.quit: - self.breakRequested = True #pylint: disable-msg=C0103 - else: - self.manager._pump() \ No newline at end of file
--- a/src/parpg/bGrease/__init__.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# -############################################################################# - -__versioninfo__ = (0, 3, 0) -__version__ = '.'.join(str(n) for n in __versioninfo__) - -__all__ = ('BaseWorld', 'Entity', 'System', 'Renderer') - -import component -import geometry -import collision -from entity import Entity -from world import BaseWorld - -import abc - -class System(object): - """Grease system abstract base class. Systems define behaviorial aspects - of a |World|. All systems must define a :meth:`step` - method that is invoked by the world each timestep. User-defined systems - are not required to subclass this class. - - See :ref:`an example system from the tutorial <tut-system-example>`. - """ - __metaclass__ = abc.ABCMeta - - world = None - """The |World| this system belongs to""" - - def set_world(self, world): - """Bind the system to a world""" - self.world = world - - @abc.abstractmethod - def step(self, dt): - """Execute a time step for the system. Must be defined - by all system classes. - - :param dt: Time since last step invocation - :type dt: float - """ - -class Renderer(object): - """Grease renderer abstract base class. Renderers define the presentation - of a |World|. All renderers must define a :meth:`draw` - method that is invoked by the world when the display needs to be redrawn. - User-defined renderers are not required to subclass this class. - - See :ref:`an example renderer from the tutorial <tut-renderer-example>`. - """ - __metaclass__ = abc.ABCMeta - - world = None - """The |World| this renderer belongs to""" - - def set_world(self, world): - """Bind the system to a world""" - self.world = world - - @abc.abstractmethod - def draw(self): - """Issue drawing commands for this renderer. Must be defined - for all renderer classes. - """ -
--- a/src/parpg/bGrease/collision.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,527 +0,0 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# -############################################################################# -""" -**Grease collision detection systems** - -Grease uses two-phase broad and narrow collision detection. *Broad-phase* -collision systems are used to efficiently identify pairs that may be colliding -without resorting to a brute-force check of all possible pairs. *Narrow-phase* -collision systems use the pairs generated by the broad-phase and perform more -precise collision tests to determine if a collision has actually occurred. The -narrow-phase system also calculates more details about each collision, -including collision point and normal vector for use in collision response. - -A typical collision detection system consists of a narrow-phase system that -contains a broad-phased system. The narrow-phase system is usually the only - -one that the application directly interacts with, though the application is -free to use the broad-phased system directly if desired. This could be -useful in cases where speed, rather than precision is paramount. - -The narrow-phase system can be assigned handler objects to run after -collision detection. These can perform tasks like handling collision response -or dispatching collision events to application handlers. - -Note that broad-phase systems can return false positives, though they should -never return false negatives. Do not assume that all pairs returned by a -broad-phase system are actually in collision. -""" - -__version__ = '$Id$' - -from parpg.bGrease.geometry import Vec2d -from bisect import bisect_right - - -class Pair(tuple): - """Pair of entities in collision. This is an ordered sequence of two - entities, that compares and hashes unordered. - - Also stores additional collision point and normal vectors - for each entity. - - Sets of ``Pair`` objects are exposed in the ``collision_pairs`` - attribute of collision systems to indicate the entity pairs in - collision. - """ - info = None - """A sequence of (entity, collision point, collision normal) - for each entity in the pair - """ - - def __new__(cls, entity1, entity2, point=None, normal=None): - pair = tuple.__new__(cls, (entity1, entity2)) - return pair - - def __hash__(self): - return hash(self[0]) ^ hash(self[1]) - - def __eq__(self, other): - other = tuple(other) - return tuple(self) == other or (self[1], self[0]) == other - - def __repr__(self): - return '%s%r' % (self.__class__.__name__, tuple(self)) - - def set_point_normal(self, point0, normal0, point1, normal1): - """Set the collision point and normal for both entities""" - self.info = ( - (self[0], point0, normal0), - (self[1], point1, normal1), - ) - - -class BroadSweepAndPrune(object): - """2D Broad-phase sweep and prune bounding box collision detector - - This algorithm is efficient for collision detection between many - moving bodies. It has linear algorithmic complexity and takes - advantage of temporal coherence between frames. It also does - not suffer from bad worst-case performance (like RDC can). - Unlike spacial hashing, it does not need to be optimized for - specific space and body sizes. - - Other algorithms may be more efficient for collision detection with - stationary bodies, bodies that are always evenly distributed, or ad-hoc - queries. - - :param collision_component: Name of the collision component used by this - system, defaults to 'collision'. This component supplies each - entities' aabb and collision masks. - :type collision_component: str - """ - world = None - """|World| object this system belongs to""" - - collision_component = None - """Name of world's collision component used by this system""" - - LEFT_ATTR = "left" - RIGHT_ATTR = "right" - TOP_ATTR = "top" - BOTTOM_ATTR = "bottom" - - def __init__(self, collision_component='collision'): - self.collision_component = collision_component - self._by_x = None - self._by_y = None - self._collision_pairs = None - - def set_world(self, world): - """Bind the system to a world""" - self.world = world - - def step(self, dt): - """Update the system for this time step, updates and sorts the - axis arrays. - """ - component = getattr(self.world.components, self.collision_component) - LEFT = self.LEFT_ATTR - RIGHT = self.RIGHT_ATTR - TOP = self.TOP_ATTR - BOTTOM = self.BOTTOM_ATTR - if self._by_x is None: - # Build axis lists from scratch - # Note we cache the box positions here - # so that we can perform hit tests efficiently - # it also isolates us from changes made to the - # box positions after we run - by_x = self._by_x = [] - append_x = by_x.append - by_y = self._by_y = [] - append_y = by_y.append - for data in component.itervalues(): - append_x([data.aabb.left, LEFT, data]) - append_x([data.aabb.right, RIGHT, data]) - append_y([data.aabb.bottom, BOTTOM, data]) - append_y([data.aabb.top, TOP, data]) - else: - by_x = self._by_x - by_y = self._by_y - removed = [] - for entry in by_x: - entry[0] = getattr(entry[2].aabb, entry[1]) - for entry in by_y: - entry[0] = getattr(entry[2].aabb, entry[1]) - # Removing entities is inefficient, but expected to be rare - if component.deleted_entities: - deleted_entities = component.deleted_entities - deleted_x = [] - deleted_y = [] - for i, (_, _, data) in enumerate(by_x): - if data.entity in deleted_entities: - deleted_x.append(i) - deleted_x.reverse() - for i in deleted_x: - del by_x[i] - for i, (_, _, data) in enumerate(by_y): - if data.entity in deleted_entities: - deleted_y.append(i) - deleted_y.reverse() - for i in deleted_y: - del by_y[i] - # Tack on new entities - for entity in component.new_entities: - data = component[entity] - by_x.append([data.aabb.left, LEFT, data]) - by_x.append([data.aabb.right, RIGHT, data]) - by_y.append([data.aabb.bottom, BOTTOM, data]) - by_y.append([data.aabb.top, TOP, data]) - - # Tim-sort is highly efficient with mostly sorted lists. - # Because positions tend to change little each frame - # we take advantage of this here. Obviously things are - # less efficient with very fast moving, or teleporting entities - by_x.sort() - by_y.sort() - self._collision_pairs = None - - @property - def collision_pairs(self): - """Set of candidate collision pairs for this timestep""" - if self._collision_pairs is None: - if self._by_x is None: - # Axis arrays not ready - return set() - - LEFT = self.LEFT_ATTR - RIGHT = self.RIGHT_ATTR - TOP = self.TOP_ATTR - BOTTOM = self.BOTTOM_ATTR - # Build candidates overlapping along the x-axis - component = getattr(self.world.components, self.collision_component) - xoverlaps = set() - add_xoverlap = xoverlaps.add - discard_xoverlap = xoverlaps.discard - open = {} - for _, side, data in self._by_x: - if side is LEFT: - for open_entity, (from_mask, into_mask) in open.iteritems(): - if data.from_mask & into_mask or from_mask & data.into_mask: - add_xoverlap(Pair(data.entity, open_entity)) - open[data.entity] = (data.from_mask, data.into_mask) - elif side is RIGHT: - del open[data.entity] - - if len(xoverlaps) <= 10 and len(xoverlaps)*4 < len(self._by_y): - # few candidates were found, so just scan the x overlap candidates - # along y. This requires an additional sort, but it should - # be cheaper than scanning everyone and its simpler - # than a separate brute-force check - entities = set([entity for entity, _ in xoverlaps] - + [entity for _, entity in xoverlaps]) - by_y = [] - for entity in entities: - data = component[entity] - # We can use tuples here, which are cheaper to create - by_y.append((data.aabb.bottom, BOTTOM, data)) - by_y.append((data.aabb.top, TOP, data)) - by_y.sort() - else: - by_y = self._by_y - - # Now check the candidates along the y-axis - open = set() - add_open = open.add - discard_open = open.discard - self._collision_pairs = set() - add_pair = self._collision_pairs.add - for _, side, data in by_y: - if side is BOTTOM: - for open_entity in open: - pair = Pair(data.entity, open_entity) - if pair in xoverlaps: - discard_xoverlap(pair) - add_pair(pair) - if not xoverlaps: - # No more candidates, bail - return self._collision_pairs - add_open(data.entity) - elif side is TOP: - discard_open(data.entity) - return self._collision_pairs - - def query_point(self, x_or_point, y=None, from_mask=0xffffffff): - """Hit test at the point specified. - - :param x_or_point: x coordinate (float) or sequence of (x, y) floats. - - :param y: y coordinate (float) if x is not a sequence - - :param from_mask: Bit mask used to filter query results. This value - is bit ANDed with candidate entities' ``collision.into_mask``. - If the result is non-zero, then it is considered a hit. By - default all entities colliding with the input point are - returned. - - :return: A set of entities where the point is inside their bounding - boxes as of the last time step. - """ - if self._by_x is None: - # Axis arrays not ready - return set() - if y is None: - x, y = x_or_point - else: - x = x_or_point - LEFT = self.LEFT_ATTR - RIGHT = self.RIGHT_ATTR - TOP = self.TOP_ATTR - BOTTOM = self.BOTTOM_ATTR - x_index = bisect_right(self._by_x, [x]) - x_hits = set() - add_x_hit = x_hits.add - discard_x_hit = x_hits.discard - if x_index <= len(self._by_x) // 2: - # closer to the left, scan from left to right - while (x == self._by_x[x_index][0] - and self._by_x[x_index][1] is LEFT - and x_index < len(self._by_x)): - # Ensure we hit on exact left edge matches - x_index += 1 - for _, side, data in self._by_x[:x_index]: - if side is LEFT and from_mask & data.into_mask: - add_x_hit(data.entity) - else: - discard_x_hit(data.entity) - else: - # closer to the right - for _, side, data in reversed(self._by_x[x_index:]): - if side is RIGHT and from_mask & data.into_mask: - add_x_hit(data.entity) - else: - discard_x_hit(data.entity) - if not x_hits: - return x_hits - - y_index = bisect_right(self._by_y, [y]) - y_hits = set() - add_y_hit = y_hits.add - discard_y_hit = y_hits.discard - if y_index <= len(self._by_y) // 2: - # closer to the bottom - while (y == self._by_y[y_index][0] - and self._by_y[y_index][1] is BOTTOM - and y_index < len(self._by_y)): - # Ensure we hit on exact bottom edge matches - y_index += 1 - for _, side, data in self._by_y[:y_index]: - if side is BOTTOM: - add_y_hit(data.entity) - else: - discard_y_hit(data.entity) - else: - # closer to the top - for _, side, data in reversed(self._by_y[y_index:]): - if side is TOP: - add_y_hit(data.entity) - else: - discard_y_hit(data.entity) - if y_hits: - return x_hits & y_hits - else: - return y_hits - - -class Circular(object): - """Basic narrow-phase collision detector which treats all entities as - circles with their radius defined in the collision component. - - :param handlers: A sequence of collision handler functions that are invoked - after collision detection. - :type handlers: sequence of functions - - :param collision_component: Name of collision component for this system, - defaults to 'collision'. This supplies each entity's collision - radius and masks. - :type collision_component: str - - :param position_component: Name of position component for this system, - defaults to 'position'. This supplies each entity's position. - :type position_component: str - - :param update_aabbs: If True (the default), then the entities' - `collision.aabb` fields will be updated using their position - and collision radius before invoking the broad phase system. - Set this False if another system updates the aabbs. - :type update_aabbs: bool - - :param broad_phase: A broad-phase collision system to use as a source - for collision pairs. If not specified, a :class:`BroadSweepAndPrune` - system will be created automatically. - """ - world = None - """|World| object this system belongs to""" - - position_component = None - """Name of world's position component used by this system""" - - collision_component = None - """Name of world's collision component used by this system""" - - update_aabbs = True - """Flag to indicate whether the system updates the entities' `collision.aabb` - field before invoking the broad phase collision system - """ - - handlers = None - """A sequence of collision handler functions invoke after collision - detection - """ - - broad_phase = None - """Broad phase collision system used as a source for collision pairs""" - - def __init__(self, handlers=(), position_component='position', - collision_component='collision', update_aabbs=True, broad_phase=None): - self.handlers = tuple(handlers) - if broad_phase is None: - broad_phase = BroadSweepAndPrune(collision_component) - self.collision_component = collision_component - self.position_component = position_component - self.update_aabbs = bool(update_aabbs) - self.broad_phase = broad_phase - self._collision_pairs = None - - def set_world(self, world): - """Bind the system to a world""" - self.world = world - self.broad_phase.set_world(world) - for handler in self.handlers: - if hasattr(handler, 'set_world'): - handler.set_world(world) - - def step(self, dt): - """Update the collision system for this time step and invoke - the handlers - """ - if self.update_aabbs: - for position, collision in self.world.components.join( - self.position_component, self.collision_component): - aabb = collision.aabb - x, y = position.position - radius = collision.radius - aabb.left = x - radius - aabb.right = x + radius - aabb.bottom = y - radius - aabb.top = y + radius - self.broad_phase.step(dt) - self._collision_pairs = None - for handler in self.handlers: - handler(self) - - @property - def collision_pairs(self): - """The set of entity pairs in collision in this timestep""" - if self._collision_pairs is None: - position = getattr(self.world.components, self.position_component) - collision = getattr(self.world.components, self.collision_component) - pairs = self._collision_pairs = set() - for pair in self.broad_phase.collision_pairs: - entity1, entity2 = pair - position1 = position[entity1].position - position2 = position[entity2].position - radius1 = collision[entity1].radius - radius2 = collision[entity2].radius - separation = position2 - position1 - if separation.get_length_sqrd() <= (radius1 + radius2)**2: - normal = separation.normalized() - pair.set_point_normal( - normal * radius1 + position1, normal, - normal * -radius2 + position2, -normal) - pairs.add(pair) - return self._collision_pairs - - def query_point(self, x_or_point, y=None, from_mask=0xffffffff): - """Hit test at the point specified. - - :param x_or_point: x coordinate (float) or sequence of (x, y) floats. - - :param y: y coordinate (float) if x is not a sequence - - :param from_mask: Bit mask used to filter query results. This value - is bit ANDed with candidate entities' ``collision.into_mask``. - If the result is non-zero, then it is considered a hit. By - default all entities colliding with the input point are - returned. - - :return: A set of entities where the point is inside their collision - radii as of the last time step. - - """ - if y is None: - point = Vec2d(x_or_point) - else: - point = Vec2d(x_or_point, y) - hits = set() - position = getattr(self.world.components, self.position_component) - collision = getattr(self.world.components, self.collision_component) - for entity in self.broad_phase.query_point(x_or_point, y, from_mask): - separation = point - position[entity].position - if separation.get_length_sqrd() <= collision[entity].radius**2: - hits.add(entity) - return hits - - -def dispatch_events(collision_system): - """Collision handler that dispatches `on_collide()` events to entities - marked for collision by the specified collision system. The `on_collide()` - event handler methods are defined by the application on the desired entity - classes. These methods should have the following signature:: - - def on_collide(self, other_entity, collision_point, collision_normal): - '''Handle A collision between this entity and `other_entity` - - - other_entity (Entity): The other entity in collision with - `self` - - - collision_point (Vec2d): The point on this entity (`self`) - where the collision occurred. Note this may be `None` for - some collision systems that do not report it. - - - collision_normal (Vec2d): The normal vector at the point of - collision. As with `collision_point`, this may be None for - some collision systems. - ''' - - Note the arguments to `on_collide()` are always passed positionally, so you - can use different argument names than above if desired. - - If a pair of entities are in collision, then the event will be dispatched - to both objects in arbitrary order if all of their collision masks align. - """ - collision = getattr(collision_system.world.components, - collision_system.collision_component) - for pair in collision_system.collision_pairs: - entity1, entity2 = pair - if pair.info is not None: - args1, args2 = pair.info - else: - args1 = entity1, None, None - args2 = entity2, None, None - try: - on_collide = entity1.on_collide - masks_align = collision[entity2].from_mask & collision[entity1].into_mask - except (AttributeError, KeyError): - pass - else: - if masks_align: - on_collide(*args2) - try: - on_collide = entity2.on_collide - masks_align = collision[entity1].from_mask & collision[entity2].into_mask - except (AttributeError, KeyError): - pass - else: - if masks_align: - on_collide(*args1)
--- a/src/parpg/bGrease/color.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ - -class RGBA(object): - """Four channel color representation. - - RGBA colors are floating point color representations with color channel - values between (0..1). Colors may be initialized from 3 or 4 floating - point numbers or a hex string:: - - RGBA(1.0, 1.0, 1.0) # Alpha defaults to 1.0 - RGBA(1.0, 1.0, 0, 0.5) - RGBA("#333") - RGBA("#7F7F7F") - - Individual color channels can be accessed by attribute name, or the - color object can be treated as a sequence of 4 floats. - """ - - def __init__(self, r_or_colorstr, g=None, b=None, a=None): - if isinstance(r_or_colorstr, str): - assert g is b is a is None, "Ambiguous color arguments" - self.r, self.g, self.b, self.a = self._parse_colorstr(r_or_colorstr) - elif g is b is a is None: - try: - self.r, self.g, self.b, self.a = r_or_colorstr - except ValueError: - self.r, self.g, self.b = r_or_colorstr - self.a = 1.0 - else: - self.r = r_or_colorstr - self.g = g - self.b = b - self.a = a - if self.a is None: - self.a = 1.0 - - def _parse_colorstr(self, colorstr): - length = len(colorstr) - if not colorstr.startswith("#") or length not in (4, 5, 7, 9): - raise ValueError("Invalid color string: " + colorstr) - if length <= 5: - parsed = [int(c*2, 16) / 255.0 for c in colorstr[1:]] - else: - parsed = [int(colorstr[i:i+2], 16) / 255.0 for i in range(1, length, 2)] - if len(parsed) == 3: - parsed.append(1.0) - return parsed - - def __len__(self): - return 4 - - def __getitem__(self, item): - return (self.r, self.g, self.b, self.a)[item] - - def __iter__(self): - return iter((self.r, self.g, self.b, self.a)) - - def __eq__(self, other): - return tuple(self) == tuple(other) - - def __repr__(self): - return "%s(%.2f, %.2f, %.2f, %.2f)" % (self.__class__.__name__, - self.r, self.g, self.b, self.a) - -
--- a/src/parpg/bGrease/component/__init__.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,148 +0,0 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# -############################################################################# -"""Components store all entity data in a given |World|. You can -think of components as tables with entities as their primary keys. Like -database tables, components are defined with a "schema" that specifies -the data fields. Each field in a component has a name and a type. - -Component objects themselves have a dict-like interface with entities -as keys and data records as values. An application will typically -interact with components via entity attributes, entity extents or -by joining them. For more information see: - -- :class:`~grease.entity.Entity` class. -- :class:`~grease.world.EntityExtent` class. -- :meth:`~grease.world.ComponentParts.join` method of ComponentParts. - -See also :ref:`defining custom components in the tutorial <custom-component-example>`. -""" - -__version__ = '$Id$' - -__all__ = ('Component', 'ComponentError', 'Position', 'Transform', 'Movement', - 'Shape', 'Renderable', 'Collision') - -from parpg.bGrease.component.general import Component -from parpg.bGrease.geometry import Vec2d, Vec2dArray, Rect -from parpg.bGrease import color - - -class ComponentError(Exception): - """General component error""" - - -class Position(Component): - """Predefined component that stores position and orientation info for - entities. - - Fields: - - - **position** (Vec2d) -- Position vector - - **angle** (float) -- Angle, in degrees - """ - - def __init__(self): - Component.__init__(self, position=Vec2d, angle=float) - - -class Transform(Component): - """Predefined component that stores offset, shear, - rotation and scale info for entity shapes. - - Fields: - - - **offset** (Vec2d) - - **shear** (Vec2d) - - **rotation** (float) - - **scale** (float, default 1.0) - """ - - def __init__(self): - Component.__init__(self, offset=Vec2d, shear=Vec2d, rotation=float, scale=float) - self.fields['scale'].default = lambda: 1.0 - - -class Movement(Component): - """Predefined component that stores velocity, - acceleration and rotation info for entities. - - Fields: - - - **velocity** (Vec2d) -- Rate of change of entity position - - **accel** (Vec2d) -- Rate of change of entity velocity - - **rotation** (Vec2d) -- Rate of change of entity angle, in degrees/time - """ - - def __init__(self): - Component.__init__(self, velocity=Vec2d, accel=Vec2d, rotation=float) - - -class Shape(Component): - """Predefined component that stores shape vertices for entities - - - **closed** (bool) -- If the shapes is closed implying an edge between - last and first vertices. - - **verts** (Vec2dArray) -- Array of vertex points - """ - - def __init__(self): - Component.__init__(self, closed=int, verts=Vec2dArray) - self.fields['closed'].default = lambda: 1 - - -class Renderable(Component): - """Predefined component that identifies entities to be - rendered and provides their depth and color. - - - **depth** (float) -- Drawing depth, can be used to determine z-order - while rendering. - - **color** (color.RGBA) -- Color used for entity. The effect of this - field depends on the renderer. - """ - - def __init__(self): - Component.__init__(self, depth=float, color=color.RGBA) - self.fields['color'].default = lambda: color.RGBA(1,1,1,1) - - -class Collision(Component): - """Predefined component that stores collision masks to determine - which entities can collide. - - Fields: - - - **aabb** (Rect) -- The axis-aligned bounding box for the entity. - This is used for broad-phase collision detection. - - - **radius** (float) -- The collision radius of the entity, used for narrow-phase - collision detection. The exact meaning of this value depends on the collision - system in use. - - - **from_mask** (int) -- A bitmask that determines what entities this object - can collide with. - - - **into_mask** (int) -- A bitmask that determines what entities can collide - with this object. - - When considering an entity A for collision with entity B, A's ``from_mask`` is - bit ANDed with B's ``into_mask``. If the result is nonzero (meaning 1 or more - bits is set the same for each) then the collision test is made. Otherwise, - the pair cannot collide. - - The default value for both of these masks is ``0xffffffff``, which means that - all entities will collide with each other by default. - """ - def __init__(self): - Component.__init__(self, aabb=Rect, radius=float, from_mask=int, into_mask=int) - self.fields['into_mask'].default = lambda: 0xffffffff - self.fields['from_mask'].default = lambda: 0xffffffff
--- a/src/parpg/bGrease/component/base.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,74 +0,0 @@ -class ComponentBase(object): - """Component abstract base class - - Strictly speaking you do not need to derive from this class to create your - own components, but it does serve to document the full interface that a - component implements and it provides some basic implementations for - certain methods - """ - - ## Optional attributes and methods ## - - def set_manager(self, manager): - """Set the manager of this component. If this method exists it will be - automatically called when the component is added to a manager. - - This method stores the manager and allows the component to be added - only once to a single manager. - """ - assert getattr(self, 'manager', None) is None, 'Component cannot be added to multiple managers' - self.manager = manager - - def __del__(self): - """Break circrefs to allow faster collection""" - if hasattr(self, 'manager'): - del self.manager - - ## Mandatory methods ## - - def add(self, entity_id, data=None, **data_kw): - """Add a data entry in the component for the given entity. Additional - data (if any) for the entry can be provided in the data argument or as - keyword arguments. Additional data is optional and if omitted then - suitable defaults will be used. Return an entity data object - for the new entity entry. - - The semantics of the data arguments is up to the component. - - An entity_id is a unique key, thus multiple separate data entries for - a given entity are not allowed. Components can indivdually decide - what to do if an entity_id is added multiple times to the component. - Potential options include, raising an exception, replacing the - existing data or coalescing it somehow. - """ - - def remove(self, entity_id): - """Remove the entity data entry from the component. If the - entity is not in the component, raise KeyError - """ - - def __delitem_(self, entity_id): - """Same as remove()""" - - def __len__(self): - """Return the number of entities in the component""" - raise NotImplementedError() - - def __iter__(self): - """Return an iterator of entity data objects in this component - - No order is defined for these data objects - """ - raise NotImplementedError() - - def __contains__(self, entity_id): - """Return True if the entity is contained in the component""" - raise NotImplementedError() - - def __getitem__(self, entity_id): - """Return the entity data object for the given entity. - The entity data object returned may be mutable, immutable or a - mutable copy of the data at the discretion of the component - """ - raise NotImplementedError() -
--- a/src/parpg/bGrease/component/field.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,303 +0,0 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# -############################################################################# - -__version__ = '$Id$' - -import operator -from parpg.bGrease.geometry import Vec2d, Vec2dArray, Rect -from parpg.bGrease import color - -# Allowed field types -> default values -types = {int:lambda: 0, - float:lambda: 0.0, - bool:lambda: False, - str:lambda:"", - object:lambda:None, - Vec2d:lambda: Vec2d(0,0), - Vec2dArray:lambda: Vec2dArray(), - color.RGBA: lambda: color.RGBA(0.0, 0.0, 0.0, 0.0), - Rect: lambda: Rect(0.0, 0.0, 0.0, 0.0), - list: lambda: list(), - dict: lambda: dict(), - } - -class Schema(dict): - """Field schema definition for custom components""" - - def __init__(self, **fields): - for ftype in fields.values(): - assert ftype in types, fname + " has an illegal field type" - self.update(fields) - - -class FieldAccessor(object): - """Facade for manipulating a field for a set of entities""" - - __field = None - __entities = None - __attrs = None - __getter = None - __parent_getters = () - - def __init__(self, field, entities, attrs=()): - self.__field = field - self.__entities = entities - field_getter = operator.attrgetter(field.name) - self.__attrs = attrs - if attrs: - getters = [field_getter] + [operator.attrgetter(attr) for attr in attrs] - def get(entity): - value = entity - for getter in getters: - value = getter(value) - return value - self.__getter = get - self.__parent_getters = getters[:-1] - else: - self.__getter = field_getter - - def __getattr__(self, name): - """Return a FieldAccessor for the child attribute""" - return self.__class__(self.__field, self.__entities, self.__attrs + (name,)) - - def __setattr__(self, name, value): - if value is self: - return # returned by mutators - if hasattr(self.__class__, name): - # Set local attr - self.__dict__[name] = value - elif not name.startswith('_'): - getattr(self, name).__set__(value) - else: - raise AttributeError("Cannot set field attribute: %s" % name) - - @property - def __setter(self): - """Return the proper setter function for setting the field value""" - if not self.__attrs: - return setattr - else: - parent_getters = self.__parent_getters - def setter(data, name, value): - for getter in parent_getters: - data = getter(data) - setattr(data, name, value) - self.__setter = setter - return setter - - def __set__(self, value): - """Set field values en masse""" - # Mass set field attr - setter = self.__setter - component = self.__field.component - if self.__attrs: - name = self.__attrs[-1] - else: - name = self.__field.name - if isinstance(value, FieldAccessor): - # Join set between two entity sets - if not self.__attrs: - cast = self.__field.cast - else: - cast = lambda x: x - for entity in self.__entities: - try: - setter(component[entity], name, cast(value[entity])) - except KeyError: - pass - else: - if not self.__attrs: - value = self.__field.cast(value) - for entity in self.__entities: - try: - setter(component[entity], name, value) - except KeyError: - pass - - def __getitem__(self, entity): - """Return the field value for a single entity (used for joins)""" - if entity in self.__entities: - return self.__getter(self.__field.component[entity]) - raise KeyError(entity) - - def __contains__(self, entity): - return entity in self.__entities - - def __repr__(self): - return '<%s %s @ %x>' % ( - self.__class__.__name__, - '.'.join((self.__field.name,) + self.__attrs), id(self)) - - def __nonzero__(self): - return bool(self.__entities) - - def __iter__(self): - """Return an iterator of all field values in the set""" - component = self.__field.component - getter = self.__getter - for entity in self.__entities: - try: - data = component[entity] - except KeyError: - continue - yield getter(data) - - ## batch comparison operators ## - - def __match(self, value, op): - component = self.__field.component - getter = self.__getter - matches = set() - add = matches.add - if isinstance(value, FieldAccessor): - # Join match between entity sets - for entity in self.__entities: - try: - data = component[entity] - other = value[entity] - except KeyError: - continue - if op(getter(data), other): - add(entity) - else: - for entity in self.__entities: - try: - data = component[entity] - except KeyError: - continue - if op(getter(data), value): - add(entity) - return matches - - def __eq__(self, value): - """Return an entity set of all entities with a matching field value""" - return self.__match(value, operator.eq) - - def __ne__(self, value): - """Return an entity set of all entities not matching field value""" - return self.__match(value, operator.ne) - - def __gt__(self, value): - """Return an entity set of all entities with a greater field value""" - return self.__match(value, operator.gt) - - def __ge__(self, value): - """Return an entity set of all entities with a greater or equal field value""" - return self.__match(value, operator.ge) - - def __lt__(self, value): - """Return an entity set of all entities with a lesser field value""" - return self.__match(value, operator.lt) - - def __le__(self, value): - """Return an entity set of all entities with a lesser or equal field value""" - return self.__match(value, operator.le) - - def _contains(self, values): - """Return an entity set of all entities with a field value contained in values""" - return self.__match(values, operator.contains) - - ## Batch in-place mutator methods - - def __mutate(self, value, op): - component = self.__field.component - if self.__attrs: - name = self.__attrs[-1] - else: - name = self.__field.name - getter = self.__getter - setter = self.__setter - if isinstance(value, FieldAccessor): - # Join between entity sets - for entity in self.__entities: - try: - data = component[entity] - other = value[entity] - except KeyError: - continue - setter(data, name, op(getter(data), other)) - else: - for entity in self.__entities: - try: - data = component[entity] - except KeyError: - continue - setter(data, name, op(getter(data), value)) - return self - - def __iadd__(self, value): - return self.__mutate(value, operator.iadd) - - def __isub__(self, value): - return self.__mutate(value, operator.isub) - - def __imul__(self, value): - return self.__mutate(value, operator.imul) - - def __idiv__(self, value): - return self.__mutate(value, operator.idiv) - - def __itruediv__(self, value): - return self.__mutate(value, operator.itruediv) - - def __ifloordiv__(self, value): - return self.__mutate(value, operator.ifloordiv) - - def __imod__(self, value): - return self.__mutate(value, operator.imod) - - def __ipow__(self, value): - return self.__mutate(value, operator.ipow) - - def __ilshift__(self, value): - return self.__mutate(value, operator.ilshift) - - def __irshift__(self, value): - return self.__mutate(value, operator.irshift) - - def __iand__(self, value): - return self.__mutate(value, operator.iand) - - def __ior__(self, value): - return self.__mutate(value, operator.ior) - - def __ixor__(self, value): - return self.__mutate(value, operator.ixor) - - -class Field(object): - """Component field metadata and accessor interface""" - - def __init__(self, component, name, type, accessor_factory=FieldAccessor): - self.component = component - self.name = name - self.type = type - self.default = types.get(type) - self.accessor_factory = accessor_factory - - def cast(self, value): - """Cast value to the appropriate type for thi field""" - if self.type is not object: - return self.type(value) - else: - return value - - def accessor(self, entities=None): - """Return the field accessor for the entities in the component, - or all entities in the set specified that are also in the component - """ - if entities is None or entities is self.component.entities: - entities = self.component.entities - else: - entities = entities & self.component.entities - return self.accessor_factory(self, entities)
--- a/src/parpg/bGrease/component/general.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,142 +0,0 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# -############################################################################# - -__version__ = '$Id$' - -from parpg.bGrease.component import base -from parpg.bGrease.component import field -from parpg.bGrease.entity import ComponentEntitySet - - -class Component(dict): - """General component with a configurable schema - - The field schema is defined via keyword args where the - arg name is the field name and the value is the type object. - - The following types are supported for fields: - - - :class:`int` - - :class:`float` - - :class:`bool` - - :class:`str` - - :class:`object` - - |Vec2d| - - |Vec2dArray| - - |RGBA| - - |Rect| - """ - - deleted_entities = () - """List of entities deleted from the component since the last time step""" - - new_entities = () - """List of entities added to the component since the last time step""" - - def __init__(self, **fields): - self.fields = {} - for fname, ftype in fields.items(): - assert ftype in field.types, fname + " has an illegal field type" - self.fields[fname] = field.Field(self, fname, ftype) - self.entities = ComponentEntitySet(self) - self._added = [] - self._deleted = [] - - def set_world(self, world): - self.world = world - - def step(self, dt): - """Update the component for the next timestep""" - delitem = super(Component, self).__delitem__ - for entity in self._deleted: - delitem(entity) - self.new_entities = self._added - self.deleted_entities = self._deleted - self._added = [] - self._deleted = [] - - def set(self, entity, data=None, **data_kw): - """Set the component data for an entity, adding it to the - component if it is not already a member. - - If data is specified, its data for the new entity's fields are - copied from its attributes, making it easy to copy another - entity's data. Keyword arguments are also matched to fields. - If both a data attribute and keyword argument are supplied for - a single field, the keyword arg is used. - """ - if data is not None: - for fname, field in self.fields.items(): - if fname not in data_kw and hasattr(data, fname): - data_kw[fname] = getattr(data, fname) - data = self[entity] = Data(self.fields, entity, **data_kw) - return data - - def __setitem__(self, entity, data): - assert entity.world is self.world, "Entity not in component's world" - if entity not in self.entities: - self._added.append(entity) - self.entities.add(entity) - super(Component, self).__setitem__(entity, data) - - def remove(self, entity): - if entity in self.entities: - self._deleted.append(entity) - self.entities.remove(entity) - return True - return False - - __delitem__ = remove - - def __repr__(self): - return '<%s %x of %r>' % ( - self.__class__.__name__, id(self), getattr(self, 'world', None)) - - -class Singleton(Component): - """Component that may contain only a single entity""" - - def add(self, entity_id, data=None, **data_kw): - if entity_id not in self._data: - self.entity_id_set.clear() - self._data.clear() - Component.add(self, entity_id, data, **data_kw) - - @property - def entity(self): - """Return the entity in the component, or None if empty""" - if self._data: - return self.manager[self._data.keys()[0]] - - -class Data(object): - - def __init__(self, fields, entity, **data): - self.__dict__['_Data__fields'] = fields - self.__dict__['entity'] = entity - for field in fields.values(): - if field.name in data: - setattr(self, field.name, data[field.name]) - else: - setattr(self, field.name, field.default()) - - def __setattr__(self, name, value): - if name in self.__fields: - self.__dict__[name] = self.__fields[name].cast(value) - else: - raise AttributeError("Invalid data field: " + name) - - def __repr__(self): - return '<%s(%r)>' % (self.__class__.__name__, self.__dict__) - -
--- a/src/parpg/bGrease/component/schema.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -
--- a/src/parpg/bGrease/controller/__init__.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,4 +0,0 @@ - -__all__ = ('EulerMovement',) - -from parpg.bGrease.controller.integrator import EulerMovement
--- a/src/parpg/bGrease/controller/integrator.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# -############################################################################# - -__version__ = '$Id$' - - -class EulerMovement(object): - """System that applies entity movement to position using Euler's method - - :param position_component: Name of :class:`grease.component.Position` - component to update. - :param movement_component: Name of :class:`grease.component.Movement` - component used to update position. - """ - - def __init__(self, position_component='position', movement_component='movement'): - self.position_component = position_component - self.movement_component = movement_component - - def set_world(self, world): - """Bind the system to a world""" - self.world = world - - def step(self, dt): - """Apply movement to position""" - assert self.world is not None, "Cannot run with no world set" - for position, movement in self.world.components.join( - self.position_component, self.movement_component): - movement.velocity += movement.accel * dt - position.position += movement.velocity * dt - position.angle += movement.rotation * dt -
--- a/src/parpg/bGrease/entity.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,212 +0,0 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# -############################################################################# -"""Grease entities are useful as actionable, interactive -game elements that are often visible to the player. - -You might use entities to represent: - -- Characters -- Bullets -- Particles -- Pick-ups -- Space Ships -- Weapons -- Trees -- Planets -- Explosions - -See :ref:`an example entity class in the tutorial <tut-entity-example>`. -""" - -__version__ = '$Id$' - -__all__ = ('Entity', 'EntityComponentAccessor', 'ComponentEntitySet') - - -class EntityMeta(type): - """The entity metaclass enforces fixed slots of `entity_id` and `world` - for all subclasses. This prevents accidental use of other entity instance - attributes, which may not be saved. - - Class attributes are not affected by this restriction, but subclasses - should be careful not to cause name collisions with world components, - which are exposed as entity attributes. Using a naming convention for - class attributes, such as UPPER_CASE_WITH_UNDERSCORES is recommended to - avoid name clashes. - - Note as a result of this, entity subclasses are not allowed to define - `__slots__`, and doing so will cause a `TypeError` to be raised. - """ - - def __new__(cls, name, bases, clsdict): - if '__slots__' in clsdict: - raise TypeError('__slots__ may not be defined in Entity subclasses') - clsdict['__slots__'] = ('world', 'entity_id') - return type.__new__(cls, name, bases, clsdict) - - -class Entity(object): - """Base class for grease entities. - - Entity objects themselves are merely identifiers within a :class:`grease.world.World`. - They also provide a facade for convenient entity-wise access of component - data. However, they do not contain any data themselves other than an - entity id. - - Entities must be instantiated in the context of a world. To instantiate an - entity, you must pass the world as the first argument to the constructor. - Subclasses that implement the :meth:`__init__()` method, must accept the world - as their first argument (after ``self``). Other constructor arguments can be - specified arbitarily by the subclass. - """ - __metaclass__ = EntityMeta - - def __new__(cls, world, *args, **kw): - """Create a new entity and add it to the world""" - entity = object.__new__(cls) - entity.world = world - entity.entity_id = world.new_entity_id() - world.entities.add(entity) - return entity - - def __getattr__(self, name): - """Return an :class:`EntityComponentAccessor` for this entity - for the component named. - - Example:: - - my_entity.movement - """ - component = getattr(self.world.components, name) - return EntityComponentAccessor(component, self) - - def __setattr__(self, name, value): - """Set the entity data in the named component for this entity. - This sets the values of the component fields to the values of - the matching attributes of the value provided. This value must - have attributes for each of the component fields. - - This allows you to easily copy component data from one entity - to another. - - Example:: - - my_entity.position = other_entity.position - """ - if name in self.__class__.__slots__: - super(Entity, self).__setattr__(name, value) - else: - component = getattr(self.world.components, name) - component.set(self, value) - - def __delattr__(self, name): - """Remove this entity and its data from the component. - - Example:: - - del my_entity.renderable - """ - component = getattr(self.world.components, name) - del component[self] - - def __hash__(self): - return self.entity_id - - def __eq__(self, other): - return self.world is other.world and self.entity_id == other.entity_id - - def __repr__(self): - return "<%s id: %s of %s %x>" % ( - self.__class__.__name__, self.entity_id, - self.world.__class__.__name__, id(self.world)) - - def delete(self): - """Delete the entity from its world. This removes all of its - component data. If then entity has already been deleted, - this call does nothing. - """ - self.world.entities.discard(self) - - @property - def exists(self): - """True if the entity still exists in the world""" - return self in self.world.entities - - -class EntityComponentAccessor(object): - """A facade for accessing specific component data for a single entity. - The implementation is lazy and does not actually access the component - data until needed. If an attribute is set for a component that the - entity is not yet a member of, it is automatically added to the - component first. - - :param component: The :class:`grease.Component` being accessed - :param entity: The :class:`Entity` being accessed - """ - - # beware, name mangling ahead. We want to avoid clashing with any - # user-configured component field names - __data = None - - def __init__(self, component, entity): - clsname = self.__class__.__name__ - self.__dict__['_%s__component' % clsname] = component - self.__dict__['_%s__entity' % clsname] = entity - - def __nonzero__(self): - """The accessor is True if the entity is in the component, - False if not, for convenient membership tests - """ - return self.__entity in self.__component - - def __getattr__(self, name): - """Return the data for the specified field of the entity's component""" - if self.__data is None: - try: - data = self.__component[self.__entity] - except KeyError: - raise AttributeError(name) - clsname = self.__class__.__name__ - self.__dict__['_%s__data' % clsname] = data - return getattr(self.__data, name) - - def __setattr__(self, name, value): - """Set the data for the specified field of the entity's component""" - if self.__data is None: - clsname = self.__class__.__name__ - if self.__entity in self.__component: - self.__dict__['_%s__data' % clsname] = self.__component[self.__entity] - else: - self.__dict__['_%s__data' % clsname] = self.__component.set(self.__entity) - setattr(self.__data, name, value) - - -class ComponentEntitySet(set): - """Set of entities in a component, can be queried by component fields""" - - _component = None - - def __init__(self, component, entities=()): - self.__dict__['_component'] = component - super(ComponentEntitySet, self).__init__(entities) - - def __getattr__(self, name): - if self._component is not None and name in self._component.fields: - return self._component.fields[name].accessor(self) - raise AttributeError(name) - - def __setattr__(self, name, value): - if self._component is not None and name in self._component.fields: - self._component.fields[name].accessor(self).__set__(value) - raise AttributeError(name) -
--- a/src/parpg/bGrease/geometry.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,600 +0,0 @@ -__version__ = "$Id$" -__docformat__ = "reStructuredText" - -import operator -import math -import ctypes - -class Vec2d(ctypes.Structure): - """2d vector class, supports vector and scalar operators, - and also provides a bunch of high level functions - """ - __slots__ = ['x', 'y'] - - @classmethod - def from_param(cls, arg): - return cls(arg) - - def __init__(self, x_or_pair, y = None): - - if y == None: - self.x = x_or_pair[0] - self.y = x_or_pair[1] - else: - self.x = x_or_pair - self.y = y - - def __len__(self): - return 2 - - def __getitem__(self, key): - if key == 0: - return self.x - elif key == 1: - return self.y - else: - raise IndexError("Invalid subscript "+str(key)+" to Vec2d") - - def __setitem__(self, key, value): - if key == 0: - self.x = value - elif key == 1: - self.y = value - else: - raise IndexError("Invalid subscript "+str(key)+" to Vec2d") - - # String representaion (for debugging) - def __repr__(self): - return 'Vec2d(%s, %s)' % (self.x, self.y) - - # Comparison - def __eq__(self, other): - if hasattr(other, "__getitem__") and len(other) == 2: - return self.x == other[0] and self.y == other[1] - else: - return False - - def __ne__(self, other): - if hasattr(other, "__getitem__") and len(other) == 2: - return self.x != other[0] or self.y != other[1] - else: - return True - - def __nonzero__(self): - return self.x or self.y - - # Generic operator handlers - def _o2(self, other, f): - "Any two-operator operation where the left operand is a Vec2d" - if isinstance(other, Vec2d): - return Vec2d(f(self.x, other.x), - f(self.y, other.y)) - elif (hasattr(other, "__getitem__")): - return Vec2d(f(self.x, other[0]), - f(self.y, other[1])) - else: - return Vec2d(f(self.x, other), - f(self.y, other)) - - def _r_o2(self, other, f): - "Any two-operator operation where the right operand is a Vec2d" - if (hasattr(other, "__getitem__")): - return Vec2d(f(other[0], self.x), - f(other[1], self.y)) - else: - return Vec2d(f(other, self.x), - f(other, self.y)) - - def _io(self, other, f): - "inplace operator" - if (hasattr(other, "__getitem__")): - self.x = f(self.x, other[0]) - self.y = f(self.y, other[1]) - else: - self.x = f(self.x, other) - self.y = f(self.y, other) - return self - - # Addition - def __add__(self, other): - if isinstance(other, Vec2d): - return Vec2d(self.x + other.x, self.y + other.y) - elif hasattr(other, "__getitem__"): - return Vec2d(self.x + other[0], self.y + other[1]) - else: - return Vec2d(self.x + other, self.y + other) - __radd__ = __add__ - - def __iadd__(self, other): - if isinstance(other, Vec2d): - self.x += other.x - self.y += other.y - elif hasattr(other, "__getitem__"): - self.x += other[0] - self.y += other[1] - else: - self.x += other - self.y += other - return self - - # Subtraction - def __sub__(self, other): - if isinstance(other, Vec2d): - return Vec2d(self.x - other.x, self.y - other.y) - elif (hasattr(other, "__getitem__")): - return Vec2d(self.x - other[0], self.y - other[1]) - else: - return Vec2d(self.x - other, self.y - other) - def __rsub__(self, other): - if isinstance(other, Vec2d): - return Vec2d(other.x - self.x, other.y - self.y) - if (hasattr(other, "__getitem__")): - return Vec2d(other[0] - self.x, other[1] - self.y) - else: - return Vec2d(other - self.x, other - self.y) - def __isub__(self, other): - if isinstance(other, Vec2d): - self.x -= other.x - self.y -= other.y - elif (hasattr(other, "__getitem__")): - self.x -= other[0] - self.y -= other[1] - else: - self.x -= other - self.y -= other - return self - - # Multiplication - def __mul__(self, other): - if isinstance(other, Vec2d): - return Vec2d(self.x*other.y, self.y*other.y) - if (hasattr(other, "__getitem__")): - return Vec2d(self.x*other[0], self.y*other[1]) - else: - return Vec2d(self.x*other, self.y*other) - __rmul__ = __mul__ - - def __imul__(self, other): - if isinstance(other, Vec2d): - self.x *= other.x - self.y *= other.y - elif (hasattr(other, "__getitem__")): - self.x *= other[0] - self.y *= other[1] - else: - self.x *= other - self.y *= other - return self - - # Division - def __div__(self, other): - return self._o2(other, operator.div) - def __rdiv__(self, other): - return self._r_o2(other, operator.div) - def __idiv__(self, other): - return self._io(other, operator.div) - - def __floordiv__(self, other): - return self._o2(other, operator.floordiv) - def __rfloordiv__(self, other): - return self._r_o2(other, operator.floordiv) - def __ifloordiv__(self, other): - return self._io(other, operator.floordiv) - - def __truediv__(self, other): - return self._o2(other, operator.truediv) - def __rtruediv__(self, other): - return self._r_o2(other, operator.truediv) - def __itruediv__(self, other): - return self._io(other, operator.floordiv) - - # Modulo - def __mod__(self, other): - return self._o2(other, operator.mod) - def __rmod__(self, other): - return self._r_o2(other, operator.mod) - - def __divmod__(self, other): - return self._o2(other, divmod) - def __rdivmod__(self, other): - return self._r_o2(other, divmod) - - # Exponentation - def __pow__(self, other): - return self._o2(other, operator.pow) - def __rpow__(self, other): - return self._r_o2(other, operator.pow) - - # Bitwise operators - def __lshift__(self, other): - return self._o2(other, operator.lshift) - def __rlshift__(self, other): - return self._r_o2(other, operator.lshift) - - def __rshift__(self, other): - return self._o2(other, operator.rshift) - def __rrshift__(self, other): - return self._r_o2(other, operator.rshift) - - def __and__(self, other): - return self._o2(other, operator.and_) - __rand__ = __and__ - - def __or__(self, other): - return self._o2(other, operator.or_) - __ror__ = __or__ - - def __xor__(self, other): - return self._o2(other, operator.xor) - __rxor__ = __xor__ - - # Unary operations - def __neg__(self): - return Vec2d(operator.neg(self.x), operator.neg(self.y)) - - def __pos__(self): - return Vec2d(operator.pos(self.x), operator.pos(self.y)) - - def __abs__(self): - return Vec2d(abs(self.x), abs(self.y)) - - def __invert__(self): - return Vec2d(-self.x, -self.y) - - # vectory functions - def get_length_sqrd(self): - """Get the squared length of the vector. - It is more efficent to use this method instead of first call - get_length() or access .length and then do a sqrt(). - - :return: The squared length - """ - return self.x**2 + self.y**2 - - def get_length(self): - """Get the length of the vector. - - :return: The length - """ - return math.sqrt(self.x**2 + self.y**2) - def __setlength(self, value): - length = self.get_length() - self.x *= value/length - self.y *= value/length - length = property(get_length, __setlength, doc = """Gets or sets the magnitude of the vector""") - - def rotate(self, angle_degrees): - """Rotate the vector by angle_degrees degrees clockwise.""" - radians = -math.radians(angle_degrees) - cos = math.cos(radians) - sin = math.sin(radians) - x = self.x*cos - self.y*sin - y = self.x*sin + self.y*cos - self.x = x - self.y = y - - def rotated(self, angle_degrees): - """Create and return a new vector by rotating this vector by - angle_degrees degrees clockwise. - - :return: Rotated vector - """ - radians = -math.radians(angle_degrees) - cos = math.cos(radians) - sin = math.sin(radians) - x = self.x*cos - self.y*sin - y = self.x*sin + self.y*cos - return Vec2d(x, y) - - def get_angle(self): - if (self.get_length_sqrd() == 0): - return 0 - return math.degrees(math.atan2(self.y, self.x)) - def __setangle(self, angle_degrees): - self.x = self.length - self.y = 0 - self.rotate(angle_degrees) - angle = property(get_angle, __setangle, doc="""Gets or sets the angle of a vector""") - - def get_angle_between(self, other): - """Get the angle between the vector and the other in degrees - - :return: The angle - """ - cross = self.x*other[1] - self.y*other[0] - dot = self.x*other[0] + self.y*other[1] - return math.degrees(math.atan2(cross, dot)) - - def normalized(self): - """Get a normalized copy of the vector - - :return: A normalized vector - """ - length = self.length - if length != 0: - return self/length - return Vec2d(self) - - def normalize_return_length(self): - """Normalize the vector and return its length before the normalization - - :return: The length before the normalization - """ - length = self.length - if length != 0: - self.x /= length - self.y /= length - return length - - def perpendicular(self): - return Vec2d(-self.y, self.x) - - def perpendicular_normal(self): - length = self.length - if length != 0: - return Vec2d(-self.y/length, self.x/length) - return Vec2d(self) - - def dot(self, other): - """The dot product between the vector and other vector - v1.dot(v2) -> v1.x*v2.x + v1.y*v2.y - - :return: The dot product - """ - return float(self.x*other[0] + self.y*other[1]) - - def get_distance(self, other): - """The distance between the vector and other vector - - :return: The distance - """ - return math.sqrt((self.x - other[0])**2 + (self.y - other[1])**2) - - def get_dist_sqrd(self, other): - """The squared distance between the vector and other vector - It is more efficent to use this method than to call get_distance() - first and then do a sqrt() on the result. - - :return: The squared distance - """ - return (self.x - other[0])**2 + (self.y - other[1])**2 - - def projection(self, other): - other_length_sqrd = other[0]*other[0] + other[1]*other[1] - projected_length_times_other_length = self.dot(other) - return other*(projected_length_times_other_length/other_length_sqrd) - - def cross(self, other): - """The cross product between the vector and other vector - v1.cross(v2) -> v1.x*v2.y - v2.y-v1.x - - :return: The cross product - """ - return self.x*other[1] - self.y*other[0] - - def interpolate_to(self, other, range): - return Vec2d(self.x + (other[0] - self.x)*range, self.y + (other[1] - self.y)*range) - - def convert_to_basis(self, x_vector, y_vector): - return Vec2d(self.dot(x_vector)/x_vector.get_length_sqrd(), self.dot(y_vector)/y_vector.get_length_sqrd()) - - # Extra functions, mainly for chipmunk - def cpvrotate(self, other): - return Vec2d(self.x*other.x - self.y*other.y, self.x*other.y + self.y*other.x) - def cpvunrotate(self, other): - return Vec2d(self.x*other.x + self.y*other.y, self.y*other.x - self.x*other.y) - - # Pickle, does not work atm. - def __getstate__(self): - return [self.x, self.y] - - def __setstate__(self, dict): - self.x, self.y = dict - def __newobj__(cls, *args): - return cls.__new__(cls, *args) -Vec2d._fields_ = [ - ('x', ctypes.c_double), - ('y', ctypes.c_double), - ] - - -class Vec2dArray(list): - - def __init__(self, iterable=()): - list.__init__(self, (Vec2d(i) for i in iterable)) - - def __setitem__(self, index, value): - list.__setitem__(self, index, Vec2d(value)) - - def append(self, value): - """Append a vector to the array""" - list.append(self, Vec2d(value)) - - def insert(self, index, value): - """Insert a vector into the array""" - list.insert(self, index, Vec2d(value)) - - def transform(self, offset=Vec2d(0,0), angle=0, scale=1.0): - """Return a new transformed Vec2dArray""" - offset = Vec2d(offset) - angle = math.radians(-angle) - rot_vec = Vec2d(math.cos(angle), math.sin(angle)) - xformed = Vec2dArray() - for vec in self: - xformed.append(vec.cpvrotate(rot_vec) * scale + offset) - return xformed - - def segments(self, closed=True): - """Generate arrays of line segments connecting adjacent vetices - in this array, exploding the shape into it's constituent segments - """ - if len(self) >= 2: - last = self[0] - for vert in self[1:]: - yield Vec2dArray((last, vert)) - last = vert - if closed: - yield Vec2dArray((last, self[0])) - elif self and closed: - yield Vec2dArray((self[0], self[0])) - - - -class Rect(ctypes.Structure): - """Simple rectangle. Will gain more functionality as needed""" - _fields_ = [ - ('left', ctypes.c_double), - ('top', ctypes.c_double), - ('right', ctypes.c_double), - ('bottom', ctypes.c_double), - ] - - def __init__(self, rect_or_left, bottom=None, right=None, top=None): - if bottom is not None: - assert right is not None and top is not None, "No enough arguments to Rect" - self.left = rect_or_left - self.bottom = bottom - self.right = right - self.top = top - else: - self.left = rect_or_left.left - self.bottom = rect_or_left.bottom - self.right = rect_or_left.right - self.top = rect_or_left.top - - @property - def width(self): - """Rectangle width""" - return self.right - self.left - - @property - def height(self): - """Rectangle height""" - return self.top - self.bottom - - -######################################################################## -## Unit Testing ## -######################################################################## -if __name__ == "__main__": - - import unittest - import pickle - - #################################################################### - class UnitTestVec2d(unittest.TestCase): - - def setUp(self): - pass - - def testCreationAndAccess(self): - v = Vec2d(111, 222) - self.assert_(v.x == 111 and v.y == 222) - v.x = 333 - v[1] = 444 - self.assert_(v[0] == 333 and v[1] == 444) - - def testMath(self): - v = Vec2d(111,222) - self.assertEqual(v + 1, Vec2d(112, 223)) - self.assert_(v - 2 == [109, 220]) - self.assert_(v * 3 == (333, 666)) - self.assert_(v / 2.0 == Vec2d(55.5, 111)) - #self.assert_(v / 2 == (55, 111)) # Not supported since this is a c_float structure in the bottom - self.assert_(v ** Vec2d(2, 3) == [12321, 10941048]) - self.assert_(v + [-11, 78] == Vec2d(100, 300)) - #self.assert_(v / [11,2] == [10,111]) # Not supported since this is a c_float structure in the bottom - - def testReverseMath(self): - v = Vec2d(111, 222) - self.assert_(1 + v == Vec2d(112, 223)) - self.assert_(2 - v == [-109, -220]) - self.assert_(3 * v == (333, 666)) - #self.assert_([222,999] / v == [2,4]) # Not supported since this is a c_float structure in the bottom - self.assert_([111, 222] ** Vec2d(2, 3) == [12321, 10941048]) - self.assert_([-11, 78] + v == Vec2d(100, 300)) - - def testUnary(self): - v = Vec2d(111, 222) - v = -v - self.assert_(v == [-111, -222]) - v = abs(v) - self.assert_(v == [111, 222]) - - def testLength(self): - v = Vec2d(3,4) - self.assert_(v.length == 5) - self.assert_(v.get_length_sqrd() == 25) - self.assert_(v.normalize_return_length() == 5) - self.assertAlmostEquals(v.length, 1) - v.length = 5 - self.assert_(v == Vec2d(3, 4)) - v2 = Vec2d(10, -2) - self.assert_(v.get_distance(v2) == (v - v2).get_length()) - - def testAngles(self): - v = Vec2d(0, 3) - self.assertEquals(v.angle, 90) - v2 = Vec2d(v) - v.rotate(-90) - self.assertEqual(v.get_angle_between(v2), 90) - v2.angle -= 90 - self.assertEqual(v.length, v2.length) - self.assertEquals(v2.angle, 0) - self.assertEqual(v2, [3, 0]) - self.assert_((v - v2).length < .00001) - self.assertEqual(v.length, v2.length) - v2.rotate(300) - self.assertAlmostEquals(v.get_angle_between(v2), -60, 5) # Allow a little more error than usual (floats..) - v2.rotate(v2.get_angle_between(v)) - angle = v.get_angle_between(v2) - self.assertAlmostEquals(v.get_angle_between(v2), 0) - - def testHighLevel(self): - basis0 = Vec2d(5.0, 0) - basis1 = Vec2d(0, .5) - v = Vec2d(10, 1) - self.assert_(v.convert_to_basis(basis0, basis1) == [2, 2]) - self.assert_(v.projection(basis0) == (10, 0)) - self.assert_(basis0.dot(basis1) == 0) - - def testCross(self): - lhs = Vec2d(1, .5) - rhs = Vec2d(4, 6) - self.assert_(lhs.cross(rhs) == 4) - - def testComparison(self): - int_vec = Vec2d(3, -2) - flt_vec = Vec2d(3.0, -2.0) - zero_vec = Vec2d(0, 0) - self.assert_(int_vec == flt_vec) - self.assert_(int_vec != zero_vec) - self.assert_((flt_vec == zero_vec) == False) - self.assert_((flt_vec != int_vec) == False) - self.assert_(int_vec == (3, -2)) - self.assert_(int_vec != [0, 0]) - self.assert_(int_vec != 5) - self.assert_(int_vec != [3, -2, -5]) - - def testInplace(self): - inplace_vec = Vec2d(5, 13) - inplace_ref = inplace_vec - inplace_src = Vec2d(inplace_vec) - inplace_vec *= .5 - inplace_vec += .5 - inplace_vec /= (3, 6) - inplace_vec += Vec2d(-1, -1) - alternate = (inplace_src*.5 + .5)/Vec2d(3, 6) + [-1, -1] - self.assertEquals(inplace_vec, inplace_ref) - self.assertEquals(inplace_vec, alternate) - - def testPickle(self): - return # pickling does not work atm - testvec = Vec2d(5, .3) - testvec_str = pickle.dumps(testvec) - loaded_vec = pickle.loads(testvec_str) - self.assertEquals(testvec, loaded_vec) - - #################################################################### - unittest.main() -
--- a/src/parpg/bGrease/impl/__init__.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# -############################################################################# - -__versioninfo__ = (0, 3, 0) -__version__ = '.'.join(str(n) for n in __versioninfo__) - -__all__ = ('Mode', 'World') - -from mode import Mode -from world import World - -
--- a/src/parpg/bGrease/impl/controls.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,216 +0,0 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# -############################################################################# -"""Control systems for binding controls to game logic""" - -import bGrease -from pyglet.window import key - -class KeyControls(grease.System): - """System that maps subclass-defined action methods to keys. - - Keys may be mapped in the subclass definition using decorators - defined here as class methods or at runtime using the ``bind_key_*`` - instance methods. - - See :ref:`an example implementation in the tutorial <tut-controls-example>`. - """ - MODIFIER_MASK = ~(key.MOD_NUMLOCK | key.MOD_SCROLLLOCK | key.MOD_CAPSLOCK) - """The MODIFIER_MASK allows you to filter out modifier keys that should be - ignored by the application. By default, capslock, numlock, and scrolllock - are ignored. - """ - - world = None - """:class:`grease.World` object this system is bound to""" - - def __init__(self): - self._key_press_map = {} - self._key_release_map = {} - self._key_hold_map = {} - for name in self.__class__.__dict__: - member = getattr(self, name) - if hasattr(member, '_grease_hold_key_binding'): - for binding in member._grease_hold_key_binding: - self.bind_key_hold(member, *binding) - if hasattr(member, '_grease_press_key_binding'): - for binding in member._grease_press_key_binding: - self.bind_key_press(member, *binding) - if hasattr(member, '_grease_release_key_binding'): - for binding in member._grease_release_key_binding: - self.bind_key_release(member, *binding) - self.held_keys = set() - - ## decorator methods for binding methods to key input events ## - - @classmethod - def key_hold(cls, symbol, modifiers=0): - """Decorator to bind a method to be executed where a key is held down""" - def bind(f): - if not hasattr(f, '_grease_hold_key_binding'): - f._grease_hold_key_binding = [] - f._grease_hold_key_binding.append((symbol, modifiers & cls.MODIFIER_MASK)) - return f - return bind - - @classmethod - def key_press(cls, symbol, modifiers=0): - """Decorator to bind a method to be executed where a key is initially depressed""" - def bind(f): - if not hasattr(f, '_grease_press_key_binding'): - f._grease_press_key_binding = [] - f._grease_press_key_binding.append((symbol, modifiers & cls.MODIFIER_MASK)) - return f - return bind - - @classmethod - def key_release(cls, symbol, modifiers=0): - """Decorator to bind a method to be executed where a key is released""" - def bind(f): - if not hasattr(f, '_grease_release_key_binding'): - f._grease_release_key_binding = [] - f._grease_release_key_binding.append((symbol, modifiers & cls.MODIFIER_MASK)) - return f - return bind - - ## runtime binding methods ## - - def bind_key_hold(self, method, key, modifiers=0): - """Bind a method to a key at runtime to be invoked when the key is - held down, this replaces any existing key hold binding for this key. - To unbind the key entirely, pass ``None`` for method. - """ - if method is not None: - self._key_hold_map[key, modifiers & self.MODIFIER_MASK] = method - else: - try: - del self._key_hold_map[key, modifiers & self.MODIFIER_MASK] - except KeyError: - pass - - def bind_key_press(self, method, key, modifiers=0): - """Bind a method to a key at runtime to be invoked when the key is initially - pressed, this replaces any existing key hold binding for this key. To unbind - the key entirely, pass ``None`` for method. - """ - if method is not None: - self._key_press_map[key, modifiers & self.MODIFIER_MASK] = method - else: - try: - del self._key_press_map[key, modifiers & self.MODIFIER_MASK] - except KeyError: - pass - - def bind_key_release(self, method, key, modifiers=0): - """Bind a method to a key at runtime to be invoked when the key is releaseed, - this replaces any existing key hold binding for this key. To unbind - the key entirely, pass ``None`` for method. - """ - if method is not None: - self._key_release_map[key, modifiers & self.MODIFIER_MASK] = method - else: - try: - del self._key_release_map[key, modifiers & self.MODIFIER_MASK] - except KeyError: - pass - - def step(self, dt): - """invoke held key functions""" - already_run = set() - for key in self.held_keys: - func = self._key_hold_map.get(key) - if func is not None and func not in already_run: - already_run.add(func) - func(dt) - - def on_key_press(self, key, modifiers): - """Handle pyglet key press. Invoke key press methods and - activate key hold functions - """ - key_mod = (key, modifiers & self.MODIFIER_MASK) - if key_mod in self._key_press_map: - self._key_press_map[key_mod]() - self.held_keys.add(key_mod) - - def on_key_release(self, key, modifiers): - """Handle pyglet key release. Invoke key release methods and - deactivate key hold functions - """ - key_mod = (key, modifiers & self.MODIFIER_MASK) - if key_mod in self._key_release_map: - self._key_release_map[key_mod]() - self.held_keys.discard(key_mod) - - -if __name__ == '__main__': - import pyglet - - class TestKeyControls(KeyControls): - - MODIFIER_MASK = ~(key.MOD_NUMLOCK | key.MOD_SCROLLLOCK | key.MOD_CTRL) - - remapped = False - - @KeyControls.key_hold(key.UP) - @KeyControls.key_hold(key.W) - def up(self, dt): - print 'UP!' - - @KeyControls.key_hold(key.LEFT) - @KeyControls.key_hold(key.A) - def left(self, dt): - print 'LEFT!' - - @KeyControls.key_hold(key.RIGHT) - @KeyControls.key_hold(key.D) - def right(self, dt): - print 'RIGHT!' - - @KeyControls.key_hold(key.DOWN) - @KeyControls.key_hold(key.S) - def down(self, dt): - print 'DOWN!' - - @KeyControls.key_press(key.SPACE) - def fire(self): - print 'FIRE!' - - @KeyControls.key_press(key.R) - def remap_keys(self): - if not self.remapped: - self.bind_key_hold(None, key.W) - self.bind_key_hold(None, key.A) - self.bind_key_hold(None, key.S) - self.bind_key_hold(None, key.D) - self.bind_key_hold(self.up, key.I) - self.bind_key_hold(self.left, key.J) - self.bind_key_hold(self.right, key.L) - self.bind_key_hold(self.down, key.K) - else: - self.bind_key_hold(None, key.I) - self.bind_key_hold(None, key.J) - self.bind_key_hold(None, key.K) - self.bind_key_hold(None, key.L) - self.bind_key_hold(self.up, key.W) - self.bind_key_hold(self.left, key.A) - self.bind_key_hold(self.right, key.D) - self.bind_key_hold(self.down, key.S) - self.remapped = not self.remapped - - - window = pyglet.window.Window() - window.clear() - controls = TestKeyControls() - window.push_handlers(controls) - pyglet.clock.schedule_interval(controls.step, 0.5) - pyglet.app.run() -
--- a/src/parpg/bGrease/impl/mode.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,202 +0,0 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# -############################################################################# -""" -Modes manage the state and transition between different application modes. -Typically such modes are presented as different screens that the user can -navigate between, similar to the way a browser navigates web pages. Individual -modes may be things like: - -- Title screen -- Options dialog -- About screen -- In-progress game -- Inventory interface - -The modal framework provides a simple mechanism to ensure that modes are -activated and deactivated properly. An activated mode is running and receives -events. A deactivated mode is paused and does not receive events. - -Modes may be managed as a *last-in-first-out* stack, or as a list, or ring -of modes in sequence, or some combination of all. - -For example usage see: :ref:`the mode section of the tutorial <tut-mode-section>`. -""" - -__version__ = '$Id$' - -import abc -import pyglet -from parpg.bGrease.mode import * - -class PygletManager(BaseManager): - """Mode manager abstract base class using pyglet. - - The mode manager keeps a stack of modes where a single mode - is active at one time. As modes are pushed on and popped from - the stack, the mode at the top is always active. The current - active mode receives events from the manager's event dispatcher. - """ - - event_dispatcher = None - """:class:`pyglet.event.EventDispatcher` object that the - active mode receive events from. - """ - - def activate_mode(self, mode): - """Perform actions to activate a node - - :param mode: The :class: 'Mode' object to activate - """ - BaseManager.activate_mode(self, mode) - self.event_dispatcher.push_handlers(mode) - - def deactivate_mode(self, mode): - """Perform actions to deactivate a node - - :param mode: The :class: 'Mode' object to deactivate - """ - BaseManager.deactivate_mode(self, mode) - self.event_dispatcher.remove_handlers(mode) - -class Manager(PygletManager): - """A basic mode manager that wraps a single - :class:`pyglet.event.EventDispatcher` object for use by its modes. - """ - - def __init__(self, event_dispatcher): - self.modes = [] - self.event_dispatcher = event_dispatcher - - -class ManagerWindow(PygletManager, pyglet.window.Window): - """An integrated mode manager and pyglet window for convenience. - The window is the event dispatcher used by modes pushed to - this manager. - - Constructor arguments are identical to :class:`pyglet.window.Window` - """ - - def __init__(self, *args, **kw): - super(ManagerWindow, self).__init__(*args, **kw) - self.modes = [] - self.event_dispatcher = self - - def on_key_press(self, symbol, modifiers): - """Default :meth:`on_key_press handler`, pops the current mode on ``ESC``""" - if symbol == pyglet.window.key.ESCAPE: - self.pop_mode() - - def on_last_mode_pop(self, mode): - """Hook executed when the last mode is popped from the manager. - When the last mode is popped from a window, an :meth:`on_close` event - is dispatched. - - :param mode: The :class:`Mode` object just popped from the manager - """ - self.dispatch_event('on_close') - - -class Mode(BaseMode): - """Application mode abstract base class using pyglet - - Subclasses must implement the :meth:`step` method - - :param step_rate: The rate of :meth:`step()` calls per second. - - :param master_clock: The :class:`pyglet.clock.Clock` interface used - as the master clock that ticks the world's clock. This - defaults to the main pyglet clock. - """ - clock = None - """The :class:`pyglet.clock.Clock` instance used as this mode's clock. - You should use this clock to schedule tasks for this mode, so they - properly respect when the mode is active or inactive - - Example:: - - my_mode.clock.schedule_once(my_cool_function, 4) - """ - - def __init__(self, step_rate=60, master_clock=pyglet.clock, - clock_factory=pyglet.clock.Clock): - BaseMode.__init__(self) - self.step_rate = step_rate - self.time = 0.0 - self.master_clock = master_clock - self.clock = clock_factory(time_function=lambda: self.time) - self.clock.schedule_interval(self.step, 1.0 / step_rate) - - def on_activate(self): - """Being called when the Mode is activated""" - self.master_clock.schedule(self.tick) - - def on_deactivate(self): - """Being called when the Mode is deactivated""" - self.master_clock.unschedule(self.tick) - - def tick(self, dt): - """Tick the mode's clock. - - :param dt: The time delta since the last tick - :type dt: float - """ - self.time += dt - self.clock.tick(poll=False) - - @abc.abstractmethod - def step(self, dt): - """Execute a timestep for this mode. Must be defined by subclasses. - - :param dt: The time delta since the last time step - :type dt: float - """ - -class Multi(BaseMulti, Mode): - """A mode with multiple submodes. One submode is active at one time. - Submodes can be switched to directly or switched in sequence. If - the Multi is active, then one submode is always active. - - Multis are useful when modes can switch in an order other than - a LIFO stack, such as in "hotseat" multiplayer games, a - "wizard" style ui, or a sequence of slides. - - Note unlike a normal :class:`Mode`, a :class:`Multi` doesn't have it's own - :attr:`clock` and :attr:`step_rate`. The active submode's are used - instead. - """ - - def __init__(self, submodes): - BaseMulti.__init__(self, submodes) - self.time = 0.0 - - - def _set_active_submode(self, submode): - BaseMulti._set_active_submode(self, submode) - self.master_clock = submode.master_clock - self.clock = submode.clock - - def clear_subnode(self): - """Clear any subnmode data""" - BaseMulti.clear_subnode(self) - self.master_clock = None - self.clock = None - - def tick(self, dt): - """Tick the active submode's clock. - - :param dt: The time delta since the last tick - :type dt: float - """ - self.time += dt - if self.active_submode is not None: - self.active_submode.clock.tick(poll=False)
--- a/src/parpg/bGrease/impl/world.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# -############################################################################# -"""Worlds are environments described by a configuration of components, systems and -renderers. These parts describe the data, behavioral and presentation aspects -of the world respectively. - -The world environment is the context within which entities exist. A typical -application consists of one or more worlds containing entities that evolve -over time and react to internal and external interaction. - -See :ref:`an example of world configuration in the tutorial <tut-world-example>`. -""" - -__version__ = '$Id$' - -import itertools -import pyglet -from pyglet import gl -from parpg.bGrease.world import * -from parpg.bGrease.impl import Mode - -class World(Mode, BaseWorld): - """A coordinated collection of components, systems and entities - - A world is also a mode that may be pushed onto a - :class:`grease.mode.Manager` - - :param step_rate: The rate of :meth:`step()` calls per second. - - :param master_clock: The :class:`pyglet.clock.Clock` interface used - as the master clock that ticks the world's clock. This - defaults to the main pyglet clock. - """ - - clock = None - """:class:`pyglet.clock` interface for use by constituents - of the world for scheduling - """ - - time = None - """Current clock time of the world, starts at 0 when the world - is instantiated - """ - - running = True - """Flag to indicate that the world clock is running, advancing time - and stepping the world. Set running to False to pause the world. - """ - - def __init__(self, step_rate=60, master_clock=pyglet.clock, - clock_factory=pyglet.clock.Clock): - Mode.__init__(self, step_rate, master_clock, clock_factory) - BaseWorld.__init__(self) - - def activate(self, manager): - """Activate the world/mode for the given manager, if the world is already active, - do nothing. This method is typically not used directly, it is called - automatically by the mode manager when the world becomes active. - - The systems of the world are pushed onto `manager.event_dispatcher` - so they can receive system events. - - :param manager: :class:`mode.BaseManager` instance - """ - if not self.active: - for system in self.systems: - manager.event_dispatcher.push_handlers(system) - super(World, self).activate(manager) - - def deactivate(self, manager): - """Deactivate the world/mode, if the world is not active, do nothing. - This method is typically not used directly, it is called - automatically by the mode manager when the world becomes active. - - Removes the system handlers from the `manager.event_dispatcher` - - :param manager: :class:`mode.BaseManager` instance - """ - for system in self.systems: - manager.event_dispatcher.remove_handlers(system) - super(World, self).deactivate(manager) - - def tick(self, dt): - """Tick the mode's clock, but only if the world is currently running - - :param dt: The time delta since the last tick - :type dt: float - """ - if self.running: - super(World, self).tick(dt) - - def step(self, dt): - """Execute a time step for the world. Updates the world `time` - and invokes the world's systems. - - Note that the specified time delta will be pinned to 10x the - configured step rate. For example if the step rate is 60, - then dt will be pinned at a maximum of 0.1666. This avoids - pathological behavior when the time between steps goes - much longer than expected. - - :param dt: The time delta since the last time step - :type dt: float - """ - dt = min(dt, 10.0 / self.step_rate) - for component in self.components: - if hasattr(component, "step"): - component.step(dt) - for system in self.systems: - if hasattr(system, "step"): - system.step(dt) - - def on_draw(self, gl=pyglet.gl): - """Clear the current OpenGL context, reset the model/view matrix and - invoke the `draw()` methods of the renderers in order - """ - gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT) - gl.glLoadIdentity() - BaseWorld.draw_renderers(self) \ No newline at end of file
--- a/src/parpg/bGrease/mode.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,391 +0,0 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# -############################################################################# -""" -Modes manage the state and transition between different application modes. -Typically such modes are presented as different screens that the user can -navigate between, similar to the way a browser navigates web pages. Individual -modes may be things like: - -- Title screen -- Options dialog -- About screen -- In-progress game -- Inventory interface - -The modal framework provides a simple mechanism to ensure that modes are -activated and deactivated properly. An activated mode is running and receives -events. A deactivated mode is paused and does not receive events. - -Modes may be managed as a *last-in-first-out* stack, or as a list, or ring -of modes in sequence, or some combination of all. - -For example usage see: :ref:`the mode section of the tutorial <tut-mode-section>`. -""" - -__version__ = '$Id$' - -import abc - - -class BaseManager(object): - """Mode manager abstract base class. - - The mode manager keeps a stack of modes where a single mode - is active at one time. As modes are pushed on and popped from - the stack, the mode at the top is always active. The current - active mode receives events from the manager's event dispatcher. - """ - - modes = () - """The mode stack sequence. The last mode in the stack is - the current active mode. Read-only. - """ - - @property - def current_mode(self): - """The current active mode or ``None``. Read-only""" - try: - return self.modes[-1] - except IndexError: - return None - - def on_last_mode_pop(self, mode): - """Hook executed when the last mode is popped from the manager. - Implementing this method is optional for subclasses. - - :param mode: The :class:`Mode` object just popped from the manager - """ - - def activate_mode(self, mode): - """Perform actions to activate a node - - :param mode: The :class: 'Mode' object to activate - """ - mode.activate(self) - - def deactivate_mode(self, mode): - """Perform actions to deactivate a node - - :param mode: The :class: 'Mode' object to deactivate - """ - mode.deactivate(self) - - - def push_mode(self, mode): - """Push a mode to the top of the mode stack and make it active - - :param mode: The :class:`Mode` object to make active - """ - current = self.current_mode - if current is not None: - self.deactivate_mode(current) - self.modes.append(mode) - self.activate_mode(mode) - - def pop_mode(self): - """Pop the current mode off the top of the stack and deactivate it. - The mode now at the top of the stack, if any is then activated. - - :param mode: The :class:`Mode` object popped from the stack - """ - mode = self.modes.pop() - mode.deactivate(self) - current = self.current_mode - if current is not None: - self.activate_mode(current) - else: - self.on_last_mode_pop(mode) - return mode - - def swap_modes(self, mode): - """Exchange the specified mode with the mode at the top of the stack. - This is similar to popping the current mode and pushing the specified - one, but without activating the previous mode on the stack or - executing :meth:`on_last_mode_pop()` if there is no previous mode. - - :param mode: The :class:`Mode` object that was deactivated and replaced. - """ - old_mode = self.modes.pop() - self.deactivate_mode(old_mode) - self.modes.append(mode) - self.activate_mode(mode) - return old_mode - - def remove_mode(self, mode): - """Remove the specified mode. If the mode is at the top of the stack, - this is equivilent to :meth:`pop_mode()`. If not, no other modes - are affected. If the mode is not in the manager, do nothing. - - :param mode: The :class:`Mode` object to remove from the manager. - """ - if self.current_mode is mode: - self.pop_mode() - else: - try: - self.modes.remove(mode) - except ValueError: - pass - -class BaseMode(object): - """Application mode very abstract base class - """ - __metaclass__ = abc.ABCMeta - - manager = None - """The :class:`BaseManager` that manages this mode""" - - def __init__(self): - self.active = False - - def on_activate(self): - """Being called when the Mode is activated""" - pass - - def activate(self, mode_manager): - """Activate the mode for the given mode manager, if the mode is already active, - do nothing - - The default implementation schedules time steps at :attr:`step_rate` per - second, sets the :attr:`manager` and sets the :attr:`active` flag to True. - """ - if not self.active: - self.on_activate() - self.manager = mode_manager - self.active = True - - def on_deactivate(self): - """Being called when the Mode is deactivated""" - pass - - def deactivate(self, mode_manager): - """Deactivate the mode, if the mode is not active, do nothing - - The default implementation unschedules time steps for the mode and - sets the :attr:`active` flag to False. - """ - self.on_deactivate() - self.active = False - - -class BaseMulti(BaseMode): - """A mode with multiple submodes. One submode is active at one time. - Submodes can be switched to directly or switched in sequence. If - the Multi is active, then one submode is always active. - - Multis are useful when modes can switch in an order other than - a LIFO stack, such as in "hotseat" multiplayer games, a - "wizard" style ui, or a sequence of slides. - - Note unlike a normal :class:`Mode`, a :class:`Multi` doesn't have it's own - :attr:`clock` and :attr:`step_rate`. The active submode's are used - instead. - """ - active_submode = None - """The currently active submode""" - - def __init__(self, *submodes): - # We do not invoke the superclass __init__ intentionally - self.active = False - self.submodes = list(submodes) - - def add_submode(self, mode, before=None, index=None): - """Add the submode, but do not make it active. - - :param mode: The :class:`Mode` object to add. - - :param before: The existing mode to insert the mode before. - If the mode specified is not a submode, raise - ValueError. - - :param index: The place to insert the mode in the mode list. - Only one of ``before`` or ``index`` may be specified. - - If neither ``before`` or ``index`` are specified, the - mode is appended to the end of the list. - """ - assert before is None or index is None, ( - "Cannot specify both 'before' and 'index' arguments") - if before is not None: - index = self.submodes.index(mode) - if index is not None: - self.submodes.insert(index, mode) - else: - self.submodes.append(mode) - - def remove_submode(self, mode=None): - """Remove the submode. - - :param mode: The submode to remove, if omitted the active submode - is removed. If the mode is not present, do nothing. If the - mode is active, it is deactivated, and the next mode, if any - is activated. If the last mode is removed, the :class:`Multi` - is removed from its manager. - """ - # TODO handle multiple instances of the same subnode - if mode is None: - mode = self.active_submode - elif mode not in self.submodes: - return - next_mode = self.activate_next() - self.submodes.remove(mode) - if next_mode is mode: - if self.manager is not None: - self.manager.remove_mode(self) - self._deactivate_submode() - - def activate_subnode(self, mode, before=None, index=None): - """Activate the specified mode, adding it as a subnode - if it is not already. If the mode is already the active - submode, do nothing. - - :param mode: The mode to activate, and add as necesary. - - :param before: The existing mode to insert the mode before - if it is not already a submode. If the mode specified is not - a submode, raise ValueError. - - :param index: The place to insert the mode in the mode list - if it is not already a submode. Only one of ``before`` or - ``index`` may be specified. - - If the mode is already a submode, the ``before`` and ``index`` - arguments are ignored. - """ - if mode not in self.submodes: - self.add_submode(mode, before, index) - if self.active_submode is not mode: - self._activate_submode(mode) - - def activate_next(self, loop=True): - """Activate the submode after the current submode in order. If there - is no current submode, the first submode is activated. - - Note if there is only one submode, it's active, and `loop` is True - (the default), then this method does nothing and the subnode remains - active. - - :param loop: When :meth:`activate_next` is called - when the last submode is active, a True value for ``loop`` will - cause the first submode to be activated. Otherwise the - :class:`Multi` is removed from its manager. - :type loop: bool - - :return: - The submode that was activated or None if there is no - other submode to activate. - """ - assert self.submodes, "No submode to activate" - next_mode = None - if self.active_submode is None: - next_mode = self.submodes[0] - else: - last_mode = self.active_submode - index = self.submodes.index(last_mode) + 1 - if index < len(self.submodes): - next_mode = self.submodes[index] - elif loop: - next_mode = self.submodes[0] - self._activate_submode(next_mode) - return next_mode - - def activate_previous(self, loop=True): - """Activate the submode before the current submode in order. If there - is no current submode, the last submode is activated. - - Note if there is only one submode, it's active, and `loop` is True - (the default), then this method does nothing and the subnode remains - active. - - :param loop: When :meth:`activate_previous` is called - when the first submode is active, a True value for ``loop`` will - cause the last submode to be activated. Otherwise the - :class:`Multi` is removed from its manager. - :type loop: bool - - :return: - The submode that was activated or None if there is no - other submode to activate. - """ - assert self.submodes, "No submode to activate" - prev_mode = None - if self.active_submode is None: - prev_mode = self.submodes[-1] - else: - last_mode = self.active_submode - index = self.submodes.index(last_mode) - 1 - if loop or index >= 0: - prev_mode = self.submodes[index] - self._activate_submode(prev_mode) - return prev_mode - - def _set_active_submode(self, submode): - self.active_submode = submode - self.step_rate = submode.step_rate - - def _activate_submode(self, submode): - """Activate a submode deactivating any current submode. If the Multi - itself is active, this happens immediately, otherwise the actual - activation is deferred until the Multi is activated. If the submode - is None, the Mulitmode is removed from its manager. - - If submode is already the active submode, do nothing. - """ - if self.active_submode is submode: - return - assert submode in self.submodes, "Unknown submode" - self._deactivate_submode() - self._set_active_submode(submode) - if submode is not None: - if self.active: - self.manager.activate_mode(submode) - else: - if self.manager is not None: - self.manager.remove_mode(self) - - def clear_subnode(self): - """Clear any subnmode data""" - self.active_submode = None - self.step_rate = None - - def _deactivate_submode(self, clear_subnode=True): - """Deactivate the current submode, if any. if `clear_subnode` is - True, `active_submode` is always None when this method returns - """ - if self.active_submode is not None: - if self.active: - self.manager.deactivate_mode(self.active_submode) - if clear_subnode: - self.clear_subnode() - - def activate(self, mode_manager): - """Activate the :class:`Multi` for the specified manager. The - previously active submode of the :class:`Multi` is activated. If there - is no previously active submode, then the first submode is made active. - A :class:`Multi` with no submodes cannot be activated - """ - assert self.submodes, "No submode to activate" - self.manager = mode_manager - if self.active_submode is None: - self._set_active_submode(self.submodes[0]) - else: - self._set_active_submode(self.active_submode) - self.manager.activate_mode(self.active_submode) - super(BaseMulti, self).activate(mode_manager) - - def deactivate(self, mode_manager): - """Deactivate the :class:`Multi` for the specified manager. - The `active_submode`, if any, is deactivated. - """ - self._deactivate_submode(clear_subnode=False) - super(BaseMulti, self).deactivate(mode_manager) -
--- a/src/parpg/bGrease/renderer/__init__.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# -############################################################################# -"""Renderers define world presentation. This module contains the -built-in renderer classes. - -See also: - -- :class:`~grease.Renderer` abstract base class. -- :ref:`Example renderer class in the tutorial <tut-renderer-example>` -""" - -__all__ = ('Vector', 'Camera') - -from parpg.bGrease.renderer.vector import Vector -from parpg.bGrease.renderer.camera import Camera
--- a/src/parpg/bGrease/renderer/camera.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -import pyglet - -class Camera(object): - """Sets the point of view for further renderers by altering the - model/view matrix when it is drawn. It does not actually perform - any drawing itself. - - :param position: The position vector for the camera. Sets the center of the view. - :type position: Vec2d - :param angle: Camera rotation in degrees about the z-axis. - :type angle: float - :param zoom: Scaling vector for the coordinate axis. - :type zoom: Vec2d - :param relative: Flag to indicate if the camera settings are relative - to the previous view state. If ``False`` the view state is reset before - setting the camera view by loading the identity model/view matrix. - - At runtime the camera may be manipulated via attributes with the - same names and functions as the parameters above. - """ - - def __init__(self, position=None, angle=None, zoom=None, relative=False): - self.position = position - self.angle = angle - self.zoom = zoom - self.relative = relative - - def draw(self, gl=pyglet.gl): - if not self.relative: - gl.glLoadIdentity() - if self.position is not None: - px, py = self.position - gl.glTranslatef(px, py, 0) - if self.angle is not None: - gl.glRotatef(self.angle, 0, 0, 1) - if self.zoom is not None: - sx, sy = self.zoom - gl.glScalef(sx, sy ,0) -
--- a/src/parpg/bGrease/renderer/vector.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,165 +0,0 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# -############################################################################# - -__version__ = '$Id$' - -__all__ = ('Vector',) - -from parpg.bGrease.geometry import Vec2d -import ctypes -from math import sin, cos, radians -import pyglet - - -class Vector(object): - """Renders shapes in a classic vector graphics style - - :param scale: Scaling factor applied to shape vertices when rendered. - - :param line_width: The line width provided to ``glLineWidth`` before rendering. - If not specified or None, ``glLineWidth`` is not called, and the line - width used is determined by the OpenGL state at the time of rendering. - - :param anti_alias: If ``True``, OpenGL blending and line smoothing is enabled. - This allows for fractional line widths as well. If ``False``, the blending - and line smoothing modes are unchanged. - - :param corner_fill: If true (the default), the shape corners will be filled - with round points when the ``line_width`` exceeds 2.0. This improves - the visual quality of the rendering at larger line widths at some - cost to performance. Has no effect if ``line_width`` is not specified. - - :param position_component: Name of :class:`grease.component.Position` - component to use. Shapes rendered are offset by the entity positions. - - :param renderable_component: Name of :class:`grease.component.Renderable` - component to use. This component specifies the entities to be - rendered and their base color. - - :param shape_component: Name of :class:`grease.component.Shape` - component to use. Source of the shape vertices for each entity. - - The entities rendered are taken from the intersection of he position, - renderable and shape components each time :meth:`draw` is called. - """ - - CORNER_FILL_SCALE = 0.6 - CORNER_FILL_THRESHOLD = 2.0 - - def __init__(self, scale=1.0, line_width=None, anti_alias=True, corner_fill=True, - position_component='position', - renderable_component='renderable', - shape_component='shape'): - self.scale = float(scale) - self.corner_fill = corner_fill - self.line_width = line_width - self.anti_alias = anti_alias - self._max_line_width = None - self.position_component = position_component - self.renderable_component = renderable_component - self.shape_component = shape_component - - def set_world(self, world): - self.world = world - - def _generate_verts(self): - """Generate vertex and index arrays for rendering""" - vert_count = sum(len(shape.verts) + 1 - for shape, ignored, ignored in self.world.components.join( - self.shape_component, self.position_component, self.renderable_component)) - v_array = (CVertColor * vert_count)() - if vert_count > 65536: - i_array = (ctypes.c_uint * 2 * vert_count)() - i_size = pyglet.gl.GL_UNSIGNED_INT - else: - i_array = (ctypes.c_ushort * (2 * vert_count))() - i_size = pyglet.gl.GL_UNSIGNED_SHORT - v_index = 0 - i_index = 0 - scale = self.scale - rot_vec = Vec2d(0, 0) - for shape, position, renderable in self.world.components.join( - self.shape_component, self.position_component, self.renderable_component): - shape_start = v_index - angle = radians(-position.angle) - rot_vec.x = cos(angle) - rot_vec.y = sin(angle) - r = int(renderable.color.r * 255) - g = int(renderable.color.g * 255) - b = int(renderable.color.b * 255) - a = int(renderable.color.a * 255) - for vert in shape.verts: - vert = vert.cpvrotate(rot_vec) * scale + position.position - v_array[v_index].vert.x = vert.x - v_array[v_index].vert.y = vert.y - v_array[v_index].color.r = r - v_array[v_index].color.g = g - v_array[v_index].color.b = b - v_array[v_index].color.a = a - if v_index > shape_start: - i_array[i_index] = v_index - 1 - i_index += 1 - i_array[i_index] = v_index - i_index += 1 - v_index += 1 - if shape.closed and v_index - shape_start > 2: - i_array[i_index] = v_index - 1 - i_index += 1 - i_array[i_index] = shape_start - i_index += 1 - return v_array, i_size, i_array, i_index - - def draw(self, gl=pyglet.gl): - vertices, index_size, indices, index_count = self._generate_verts() - if index_count: - if self.anti_alias: - gl.glEnable(gl.GL_LINE_SMOOTH) - gl.glHint(gl.GL_LINE_SMOOTH_HINT, gl.GL_NICEST) - gl.glEnable(gl.GL_BLEND) - gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA) - gl.glPushClientAttrib(gl.GL_CLIENT_VERTEX_ARRAY_BIT) - gl.glEnableClientState(gl.GL_VERTEX_ARRAY) - gl.glEnableClientState(gl.GL_COLOR_ARRAY) - gl.glVertexPointer( - 2, gl.GL_FLOAT, ctypes.sizeof(CVertColor), ctypes.pointer(vertices)) - gl.glColorPointer( - 4, gl.GL_UNSIGNED_BYTE, ctypes.sizeof(CVertColor), - ctypes.pointer(vertices[0].color)) - if self.line_width is not None: - gl.glLineWidth(self.line_width) - if self._max_line_width is None: - range_out = (ctypes.c_float * 2)() - gl.glGetFloatv(gl.GL_ALIASED_LINE_WIDTH_RANGE, range_out) - self._max_line_width = float(range_out[1]) * self.CORNER_FILL_SCALE - if self.corner_fill and self.line_width > self.CORNER_FILL_THRESHOLD: - gl.glEnable(gl.GL_POINT_SMOOTH) - gl.glPointSize( - min(self.line_width * self.CORNER_FILL_SCALE, self._max_line_width)) - gl.glDrawArrays(gl.GL_POINTS, 0, index_count) - gl.glDrawElements(gl.GL_LINES, index_count, index_size, ctypes.pointer(indices)) - gl.glPopClientAttrib() - - -class CVert(ctypes.Structure): - _fields_ = [("x", ctypes.c_float), ("y", ctypes.c_float)] - -class CColor(ctypes.Structure): - _fields_ = [ - ("r", ctypes.c_ubyte), - ("g", ctypes.c_ubyte), - ("b", ctypes.c_ubyte), - ("a", ctypes.c_ubyte), - ] - -class CVertColor(ctypes.Structure): - _fields_ = [("vert", CVert), ("color", CColor)]
--- a/src/parpg/bGrease/world.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,301 +0,0 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# -############################################################################# -"""Worlds are environments described by a configuration of components, systems and -renderers. These parts describe the data, behavioral and presentation aspects -of the world respectively. - -The world environment is the context within which entities exist. A typical -application consists of one or more worlds containing entities that evolve -over time and react to internal and external interaction. - -See :ref:`an example of world configuration in the tutorial <tut-world-example>`. -""" - -__version__ = '$Id$' - -import itertools -from parpg.bGrease import mode -from parpg.bGrease.component import ComponentError -from parpg.bGrease.entity import Entity, ComponentEntitySet - - -class BaseWorld(object): - """A coordinated collection of components, systems and entities - - A world is also a mode that may be pushed onto a - :class:`grease.mode.Manager` - """ - - components = None - """:class:`ComponentParts` object containing all world components. - :class:`grease.component.Component` objects define and contain all entity data - """ - - systems = None - """:class:`Parts` object containing all world systems. - :class:`grease.System` objects define world and entity behavior - """ - - renderers = None - """:class:`Parts` object containing all world renderers. - :class:`grease.Renderer` objects define world presentation - """ - - entities = None - """Set of all entities that exist in the world""" - - def __init__(self): - self.components = ComponentParts(self) - self.systems = Parts(self) - self.renderers = Parts(self) - self.new_entity_id = itertools.count().next - self.new_entity_id() # skip id 0 - self.entities = WorldEntitySet(self) - self._full_extent = EntityExtent(self, self.entities) - self._extents = {} - self.configure() - - def configure(self): - """Hook to configure the world after construction. This method - is called immediately after the world is initialized. Override - in a subclass to configure the world's components, systems, - and renderers. - - The default implementation does nothing. - """ - - def __getitem__(self, entity_class): - """Return an :class:`EntityExtent` for the given entity class. This extent - can be used to access the set of entities of that class in the world - or to query these entities via their components. - - Examples:: - - world[MyEntity] - world[...] - - :param entity_class: The entity class for the extent. - - May also be a tuple of entity classes, in which case - the extent returned contains union of all entities of the classes - in the world. - - May also be the special value ellipsis (``...``), which - returns an extent containing all entities in the world. This allows - you to conveniently query all entities using ``world[...]``. - """ - if isinstance(entity_class, tuple): - entities = set() - for cls in entity_class: - if cls in self._extents: - entities |= self._extents[cls].entities - return EntityExtent(self, entities) - elif entity_class is Ellipsis: - return self._full_extent - try: - return self._extents[entity_class] - except KeyError: - extent = self._extents[entity_class] = EntityExtent(self, set()) - return extent - - def draw_renderers(self): - """Draw all renderers""" - for renderer in self.renderers: - renderer.draw() - -class WorldEntitySet(set): - """Entity set for a :class:`World`""" - - def __init__(self, world): - self.world = world - - def add(self, entity): - """Add the entity to the set and all necessary class sets - Return the unique entity id for the entity, creating one - as needed. - """ - super(WorldEntitySet, self).add(entity) - for cls in entity.__class__.__mro__: - if issubclass(cls, Entity): - self.world[cls].entities.add(entity) - - def remove(self, entity): - """Remove the entity from the set and, world components, - and all necessary class sets - """ - super(WorldEntitySet, self).remove(entity) - for component in self.world.components: - try: - del component[entity] - except KeyError: - pass - for cls in entity.__class__.__mro__: - if issubclass(cls, Entity): - self.world[cls].entities.discard(entity) - - def discard(self, entity): - """Remove the entity from the set if it exists, if not, - do nothing - """ - try: - self.remove(entity) - except KeyError: - pass - - -class EntityExtent(object): - """Encapsulates a set of entities queriable by component. Extents - are accessed by using an entity class as a key on the :class:`World`:: - - extent = world[MyEntity] - """ - - entities = None - """The full set of entities in the extent""" - - def __init__(self, world, entities): - self.__world = world - self.entities = entities - - def __getattr__(self, name): - """Return a queriable :class:`ComponentEntitySet` for the named component - - Example:: - - world[MyEntity].movement.velocity > (0, 0) - - Returns a set of entities where the value of the :attr:`velocity` field - of the :attr:`movement` component is greater than ``(0, 0)``. - """ - component = getattr(self.__world.components, name) - return ComponentEntitySet(component, self.entities & component.entities) - - -class Parts(object): - """Maps world parts to attributes. The parts are kept in the - order they are set. Parts may also be inserted out of order. - - Used for: - - - :attr:`World.systems` - - :attr:`World.renderers` - """ - - _world = None - _parts = None - _reserved_names = ('entities', 'entity_id', 'world') - - def __init__(self, world): - self._world = world - self._parts = [] - - def _validate_name(self, name): - if (name in self._reserved_names or name.startswith('_') - or hasattr(self.__class__, name)): - raise ComponentError('illegal part name: %s' % name) - return name - - def __setattr__(self, name, part): - if not hasattr(self.__class__, name): - self._validate_name(name) - if not hasattr(self, name): - self._parts.append(part) - else: - old_part = getattr(self, name) - self._parts[self._parts.index(old_part)] = part - super(Parts, self).__setattr__(name, part) - if hasattr(part, 'set_world'): - part.set_world(self._world) - elif name.startswith("_"): - super(Parts, self).__setattr__(name, part) - else: - raise AttributeError("%s attribute is read only" % name) - - def __delattr__(self, name): - self._validate_name(name) - part = getattr(self, name) - self._parts.remove(part) - super(Parts, self).__delattr__(name) - - def insert(self, name, part, before=None, index=None): - """Add a part with a particular name at a particular index. - If a part by that name already exists, it is replaced. - - :arg name: The name of the part. - :type name: str - - :arg part: The component, system, or renderer part to insert - - :arg before: A part object or name. If specified, the part is - inserted before the specified part in order. - - :arg index: If specified, the part is inserted in the position - specified. You cannot specify both before and index. - :type index: int - """ - assert before is not None or index is not None, ( - "Must specify a value for 'before' or 'index'") - assert before is None or index is None, ( - "Cannot specify both 'before' and 'index' arguments when inserting") - self._validate_name(name) - if before is not None: - if isinstance(before, str): - before = getattr(self, before) - index = self._parts.index(before) - if hasattr(self, name): - old_part = getattr(self, name) - self._parts.remove(old_part) - self._parts.insert(index, part) - super(Parts, self).__setattr__(name, part) - if hasattr(part, 'set_world'): - part.set_world(self._world) - - def __iter__(self): - """Iterate the parts in order""" - return iter(tuple(self._parts)) - - def __len__(self): - return len(self._parts) - - -class ComponentParts(Parts): - """Maps world components to attributes. The components are kept in the - order they are set. Components may also be inserted out of order. - - Used for: :attr:`World.components` - """ - - def join(self, *component_names): - """Join and iterate entity data from multiple components together. - - For each entity in all of the components named, yield a tuple containing - the entity data from each component specified. - - This is useful in systems that pull data from multiple components. - - Typical Usage:: - - for position, movement in world.components.join("position", "movement"): - # Do something with each entity's position and movement data - """ - if component_names: - components = [getattr(self, self._validate_name(name)) - for name in component_names] - if len(components) > 1: - entities = components[0].entities & components[1].entities - for comp in components[2:]: - entities &= comp.entities - else: - entities = components[0].entities - for entity in entities: - yield tuple(comp[entity] for comp in components)
--- a/src/parpg/behaviours/__init__.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -# 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/>. - -from base import BaseBehaviour as Base -from moving import MovingAgentBehaviour as Moving -from npc import NPCBehaviour as NonPlayer -from player import PlayerBehaviour as Player \ No newline at end of file
--- a/src/parpg/behaviours/base.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,116 +0,0 @@ -# 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/>. - -from collections import deque - -from fife import fife - -_AGENT_STATE_NONE, _AGENT_STATE_IDLE, _AGENT_STATE_APPROACH, _AGENT_STATE_RUN, _AGENT_STATE_WANDER, _AGENT_STATE_TALK = xrange(6) - -class BaseBehaviour (fife.InstanceActionListener): - """Fife agent listener""" - def __init__(self): - fife.InstanceActionListener.__init__(self) - self.agent = None - self.state = None - self.animation_queue = deque() - self.nextAction = None - - def attachToLayer(self, agent_ID, layer): - """Attaches to a certain layer - @type agent_ID: String - @param agent_ID: ID of the layer to attach to. - @type layer: Fife layer - @param layer: Layer of the agent to attach the behaviour to - @return: None""" - self.agent = layer.getInstance(agent_ID) - self.agent.addActionListener(self) - self.state = _AGENT_STATE_NONE - - def getX(self): - """Get the NPC's x position on the map. - @rtype: integer" - @return: the x coordinate of the NPC's location""" - return self.agent.getLocation().getLayerCoordinates().x - - def getY(self): - """Get the NPC's y position on the map. - @rtype: integer - @return: the y coordinate of the NPC's location""" - return self.agent.getLocation().getLayerCoordinates().y - - def onNewMap(self, layer): - """Sets the agent onto the new layer.""" - if self.agent is not None: - self.agent.removeActionListener(self) - - self.agent = layer.getInstance(self.parent.general.identifier) - self.agent.addActionListener(self) - self.state = _AGENT_STATE_NONE - - def idle(self): - """@return: None""" - self.state = _AGENT_STATE_IDLE - - def onInstanceActionFinished(self, instance, action): - """@type instance: ??? - @param instance: ??? - @type action: ??? - @param action: ??? - @return: None""" - # First we reset the next behavior - act = self.nextAction - self.nextAction = None - self.idle() - - if act: - act.execute() - try: - animtion = self.animation_queue.popleft() - self.animate(**animtion) - except IndexError: - self.idle() - - def onInstanceActionFrame(self, instance, action, frame): - pass - - def getLocation(self): - """Get the agents position as a fife.Location object. - @rtype: fife.Location - @return: the location of the agent""" - return self.agent.getLocation() - - - def talk(self, pc): - """Makes the agent ready to talk to the PC - @return: None""" - self.state = _AGENT_STATE_TALK - self.pc = pc.behaviour.agent - self.clear_animations() - self.idle() - - def animate(self, action, direction = None, repeating = False): - """Perform an animation""" - direction = direction or self.agent.getFacingLocation() - self.agent.act(action, direction, repeating) - - def queue_animation(self, action, direction = None, repeating = False): - """Add an action to the queue""" - self.animation_queue.append({"action": action, "direction": direction, - "repeating": repeating}) - - def clear_animations(self): - """Remove all actions from the queue""" - self.animation_queue.clear()
--- a/src/parpg/behaviours/moving.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,92 +0,0 @@ -# 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/>. - -from fife import fife -import base -from base import BaseBehaviour - -class MovingAgentBehaviour (BaseBehaviour): - """Fife agent listener""" - def __init__(self): - BaseBehaviour.__init__(self) - self.speed = 0 - self.idle_counter = 1 - - def onNewMap(self, layer): - """Sets the agent onto the new layer.""" - BaseBehaviour.onNewMap(self, layer) - self.idle_counter = 1 - - - def approach(self, location_or_agent, action=None): - """Approaches a location or another agent and then perform an action - (if set). - @type loc: fife.Location - @param loc: the location or agent to approach - @type action: Action - @param action: The action to schedule for execution after the - approach. - @return: None""" - - self.state = base._AGENT_STATE_APPROACH - self.nextAction = action - if isinstance(location_or_agent, fife.Instance): - agent = location_or_agent - self.agent.follow('run', agent, self.speed + 1) - else: - location = location_or_agent - boxLocation = tuple([int(float(i)) for i in location]) - l = fife.Location(self.getLocation()) - l.setLayerCoordinates(fife.ModelCoordinate(*boxLocation)) - self.agent.move('run', l, self.speed + 1) - - def onInstanceActionFinished(self, instance, action): - """@type instance: ??? - @param instance: ??? - @type action: ??? - @param action: ??? - @return: None""" - BaseBehaviour.onInstanceActionFinished(self, instance, action) - - if(action.getId() != 'stand'): - self.idle_counter = 1 - else: - self.idle_counter += 1 - - def idle(self): - """@return: None""" - BaseBehaviour.idle(self) - self.animate('stand') - - def run(self, location): - """Makes the PC run to a certain location - @type location: fife.ScreenPoint - @param location: Screen position to run to. - @return: None""" - self.state = base._AGENT_STATE_RUN - self.clear_animations() - self.nextAction = None - self.agent.move('run', location, self.speed + 1) - - def walk(self, location): - """Makes the PC walk to a certain location. - @type location: fife.ScreenPoint - @param location: Screen position to walk to. - @return: None""" - self.state = base._AGENT_STATE_RUN - self.clear_animations() - self.nextAction = None - self.agent.move('walk', location, self.speed - 1) - \ No newline at end of file
--- a/src/parpg/behaviours/npc.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,108 +0,0 @@ -# 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/>. - -from random import randrange - -from fife import fife - -import base -from moving import MovingAgentBehaviour - -class NPCBehaviour(MovingAgentBehaviour): - """This is a basic NPC behaviour""" - def __init__(self, parent=None): - super(NPCBehaviour, self).__init__() - - self.parent = parent - self.state = base._AGENT_STATE_NONE - self.pc = None - self.target_loc = None - - # hard code these for now - self.distRange = (2, 4) - # these are parameters to lower the rate of wandering - # wander rate is the number of "IDLEs" before a wander step - # this could be set for individual NPCs at load time - # or thrown out altogether. - # HACK: 09.Oct.2011 Beliar - # I increased the wander rate to 900 since the idle method - # gets called way more often now. - self.wanderCounter = 0 - self.wanderRate = 9 - - def getTargetLocation(self): - """@rtype: fife.Location - @return: NPC's position""" - x = self.getX() - y = self.getY() - if self.state == base._AGENT_STATE_WANDER: - """ Random Target Location """ - l = [0, 0] - for i in range(len(l)): - sign = randrange(0, 2) - dist = randrange(self.distRange[0], self.distRange[1]) - if sign == 0: - dist *= -1 - l[i] = dist - x += l[0] - y += l[1] - # Random walk is - # rl = randint(-1, 1);ud = randint(-1, 1);x += rl;y += ud - l = fife.Location(self.agent.getLocation()) - l.setLayerCoordinates(fife.ModelCoordinate(x, y)) - return l - - def onInstanceActionFinished(self, instance, action): - """What the NPC does when it has finished an action. - Called by the engine and required for InstanceActionListeners. - @type instance: fife.Instance - @param instance: self.agent - @type action: ??? - @param action: ??? - @return: None""" - if self.state == base._AGENT_STATE_WANDER: - self.target_loc = self.getTargetLocation() - MovingAgentBehaviour.onInstanceActionFinished(self, instance, action) - - - def idle(self): - """Controls the NPC when it is idling. Different actions - based on the NPC's state. - @return: None""" - if self.state == base._AGENT_STATE_NONE: - self.state = base._AGENT_STATE_IDLE - self.animate('stand') - elif self.state == base._AGENT_STATE_IDLE: - if self.wanderCounter > self.wanderRate: - self.wanderCounter = 0 - self.state = base._AGENT_STATE_WANDER - else: - self.wanderCounter += 1 - self.state = base._AGENT_STATE_NONE - - self.target_loc = self.getTargetLocation() - self.animate('stand') - elif self.state == base._AGENT_STATE_WANDER: - self.wander(self.target_loc) - self.state = base._AGENT_STATE_NONE - elif self.state == base._AGENT_STATE_TALK: - self.animate('stand', self.pc.getLocation()) - - def wander(self, location): - """Nice slow movement for random walking. - @type location: fife.Location - @param location: Where the NPC will walk to. - @return: None""" - self.agent.move('walk', location, self.speed - 1) \ No newline at end of file
--- a/src/parpg/behaviours/player.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,25 +0,0 @@ -# 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/>. - -import moving -from moving import MovingAgentBehaviour - -class PlayerBehaviour (MovingAgentBehaviour): - def __init__(self, parent=None): - super(PlayerBehaviour, self).__init__() - self.parent = parent - self.idle_counter = 1 - self.speed = 0 - self.agent = None \ No newline at end of file
--- a/src/parpg/charactercreationcontroller.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,392 +0,0 @@ -# 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/>. -"""Provides the controller that defines the behaviour of the character creation - screen.""" - -from parpg import vfs -from controllerbase import ControllerBase -from gamescenecontroller import GameSceneController -from gamesceneview import GameSceneView -from parpg.world import World -from parpg.entities import General -from parpg.components import character_statistics - -def getStatCost(offset): - """Gets and returns the cost to increase stat based on the offset""" - - if offset < 0: - offset *= -1 - - if offset < 22: - return 1 - elif offset < 29: - return 2 - elif offset < 32: - return 3 - elif offset < 35: - return 4 - elif offset < 36: - return 5 - elif offset < 38: - return 6 - elif offset < 39: - return 7 - elif offset < 40: - return 8 - elif offset < 41: - return 9 - else: - return 10 - -class CharacterCreationController(ControllerBase, World): - """Controller defining the behaviour of the character creation screen.""" - - #TODO: Change to actual values - MAX_TRAITS = 3 - MIN_AGE = 16 - MAX_AGE = 40 - ORIGINS = {"None": None,} - GENDERS = ["Male", "Female",] - PICTURES = {"Male": ["None",], "Female": ["None",],} - TRAITS = {} - MAX_BULK = 100 - INV_SLOTS = 20 - - def __init__(self, engine, view, model, application): - """Construct a new L{CharacterCreationController} instance. - @param engine: Rendering engine used to display the associated view. - @type engine: L{fife.Engine} - @param view: View used to display the character creation screen. - @type view: L{ViewBase} - @param model: Model of the game state. - @type model: L{GameModel} - @param application: Application used to glue the various MVC - components together. - @type application: - L{fife.extensions.basicapplication.ApplicationBase}""" - ControllerBase.__init__(self, engine, view, model, application) - World.__init__(self) - self.settings = self.model.settings - self.view.start_new_game_callback = self.startNewGame - self.view.cancel_new_game_callback = self.cancelNewGame - - def reset_character(self): - inventory = [] - for x in xrange(self.INV_SLOTS): - inventory.append(None) - self.char_data = General(self, "PlayerCharacter") - self.char_data.description.view_name = "Player" - self.char_data.description.real_name = "Enter name here" - self.char_data.characterstats.gender = self.GENDERS[0] - self.char_data.characterstats.origin = self.ORIGINS.keys()[0] - self.char_data.characterstats.age = 20 - self.char_data.characterstats.picture = ( - self.PICTURES[self.GENDERS[0]][0] - ) - self.model.create_stats(self.char_data) - self.char_data.container.max_bulk = self.MAX_BULK - self.char_data.container.children = inventory - self._stat_points = 200 - - def update_agent(self): - """Updates the player agent data in the model""" - agent_data = ( - self.model.agents[self.model.ALL_AGENTS_KEY]["PlayerCharacter"] - ) - agent_data["Statistics"] = ( - character_statistics.get_stat_values( - self.char_data.characterstats - )["primary"] - ) - - def startNewGame(self): - """Create the new character and start a new game. - @return: None""" - self.update_agent() - view = GameSceneView(self.engine, self.model) - controller = GameSceneController(self.engine, view, self.model, - self.application) - self.application.view = view - self.application.manager.swap_modes(controller) - start_map = self.settings.parpg.Map - self.model.changeMap(start_map) - - def cancelNewGame(self): - """Exit the character creation view and return the to main menu. - @return: None""" - # KLUDGE Technomage 2010-12-24: This is to prevent a circular import - # but a better fix needs to be thought up. - from mainmenucontroller import MainMenuController - from mainmenuview import MainMenuView - view = MainMenuView(self.engine, self.model) - controller = MainMenuController(self.engine, view, self.model, - self.application) - self.application.view = view - self.application.manager.activate_mode(controller) - - def on_activate(self): - self.reset_character() - self.view.show() - - def on_deactivate(self): - """Called when the controller is removed from the list. - @return: None""" - self.view.hide() - - @property - def name(self): - """Returns the name of the character. - @return: Name of the character""" - return self.char_data.description.real_name - - @property - def age(self): - """Returns the age of the character. - @return: Age of the character""" - return self.char_data.characterstats.age - - @property - def gender(self): - """Returns the gender of the character. - @return: Gender of the character""" - return self.char_data.characterstats.gender - - @property - def origin(self): - """Returns the origin of the character. - @return: Origin of the character""" - return self.char_data.characterstats.origin - - @property - def picture(self): - """Returns the ID of the current picture of the character.""" - return self.char_data.characterstats.picture - - def getStatPoints(self): - """Returns the remaining statistic points that can be distributed""" - return self._stat_points - - def increaseStatistic(self, statistic): - """Increases the given statistic by one. - @param statistic: Name of the statistic to increase - @type statistic: string""" - if self.canIncreaseStatistic(statistic): - cost = self.getStatisticIncreaseCost(statistic) - if cost <= self._stat_points: - (self.char_data.characterstats. - primary_stats[statistic].value) += 1 - self._stat_points -= cost - - def getStatisticIncreaseCost(self, statistic): - """Calculate and return the cost to increase the statistic - @param statistic: Name of the statistic to increase - @type statistic: string - @return cost to increase the statistic""" - cur_value = (self.char_data.characterstats. - primary_stats[statistic].value) - new_value = cur_value + 1 - offset = new_value - DEFAULT_STAT_VALUE - return getStatCost(offset) - - def canIncreaseStatistic(self, statistic): - """Checks whether the given statistic can be increased or not. - @param statistic: Name of the statistic to check - @type statistic: string - @return: True if the statistic can be increased, False if not.""" - stat = self.char_data.characterstats.primary_stats[statistic].value - return stat < stat.statistic_type.maximum - - def decreaseStatistic(self, statistic): - """Decreases the given statistic by one. - @param statistic: Name of the statistic to decrease - @type statistic: string""" - if self.canDecreaseStatistic(statistic): - gain = self.getStatisticDecreaseGain(statistic) - self.char_data.characterstats.primary_stats[statistic].value -= 1 - self._stat_points += gain - - def getStatisticDecreaseGain(self, statistic): - """Calculate and return the gain of decreasing the statistic - @param statistic: Name of the statistic to decrease - @type statistic: string - @return cost to decrease the statistic""" - cur_value = (self.char_data.characterstats. - primary_stats[statistic].value) - new_value = cur_value - 1 - offset = new_value - DEFAULT_STAT_VALUE - return getStatCost(offset) - - def canDecreaseStatistic(self, statistic): - """Checks whether the given statistic can be decreased or not. - @param statistic: Name of the statistic to check - @type statistic: string - @return: True if the statistic can be decreased, False if not.""" - stat = self.char_data.characterstats.primary_stats[statistic].value - return stat > stat.statistic_type.minimum - - def getStatisticValue(self, statistic): - """Returns the value of the given statistic. - @param statistic: Name of the primary or secondary statistic - @type statistic: string - @return: Value of the given statistic""" - if self.char_data.characterstats.primary_stats.has_key: - return self.char_data.characterstats.primary_stats[statistic] - else: - return self.char_data.characterstats.secondary_stats[statistic] - - def areAllStatisticsValid(self): - """Checks if all statistics are inside the minimum/maximum values - @return True if all statistics are valid False if not""" - all_stats = self.char_data.characterstats.primary_stats.items() - all_stats.extend(self.char_data.characterstats.secondary_stats.items()) - for stat in all_stats: - if not (stat.value > stat.statistic_type.minumum and\ - stat.value < stat.statistic_type.maximum): - return False - return True - - def setName(self, name): - """Sets the name of the character to the given value. - @param name: New name - @type name: string""" - self.char_data.description.real_name = name - - def isNameValid(self, name): - """Checks whether the name is valid. - @param name: Name to check - @type name: string - @return: True if the name is valid, False if not""" - if name: - return True - return False - - def changeOrigin(self, origin): - """Changes the origin of the character to the given value. - @param origin: New origin - @type origin: string""" - if self.isOriginValid(origin): - self.char_data.characterstats.origin = origin - #TODO: Make changes according to origin - - def isOriginValid(self, origin): - """Checks whether the origin is valid. - @param origin: Origin to check - @type origin: string - @return: True if the origin is valid, False if not""" - return origin in self.ORIGINS - - def changeGender(self, gender): - """Changes the gender of the character to the given value. - @param gender: New gender - @param gender: string""" - if self.isGenderValid(gender): - self.char_data.characterstats.gender = gender - - def isGenderValid(self, gender): - """Checks whether the gender is valid. - @param gender: Gender to check - @type gender: string? - @return: True if the origin is valid, False if not""" - return gender in self.GENDERS - - def changeAge(self, age): - """Sets the age of the character to the given value. - @param age: New age - @type age: integer - """ - if self.isAgeValid(age): - self.char_data.characterstats.age = age - - def isAgeValid(self, age): - """Checks whether the age is valid. - @param age: Age to check - @type age: integer - @return: True if the origin is valid, False if not""" - return age >= self.MIN_AGE and age <= self.MAX_AGE - - def setPicture(self, picture): - """Set picture of the character. - @param picture: ID of the new picture - @type picture: string""" - if self.isPictureValid(picture): - self.char_data.characterstats.picture = picture - - def isPictureValid(self, picture): - """Checks whether the picture is valid. - @param picture: ID of the picture to check - @type picture: string - @return: True if the picture is valid, False if not""" - return picture in self.PICTURES[self.gender] - - def addTrait(self, trait): - """Adds a trait to the character. - @param trait: ID of the trait to add - @type trait: string""" - if self.canAddAnotherTrait() and self.isTraitValid(trait)\ - and not self.hasTrait(trait): - self.char_data.characterstats.traits.append(trait) - - def canAddAnotherTrait(self): - """Checks whether another trait can be added. - @return: True if another trait can be added, False if not""" - return len(self.char_data.characterstats.traits) < self.MAX_TRAITS - - def removeTrait(self, trait): - """Remove trait from character. - @param trait: ID of the trait to remove - @type trait: string""" - if self.hasTrait(trait): - self.char_data.characterstats.traits.remove(trait) - - def hasTrait(self, trait): - """Checks whether the character has the trait. - @param trait: ID of the trait to check - @type trait: string - @return: True if the character has the trait, False if not""" - return trait in self.char_data.characterstats.traits - - def isTraitValid(self, trait): - """Checks whether the trait is valid. - @param trait: ID of the trait to check - @type trait: string - @return: True if the trait is valid, False if not""" - return trait in self.TRAITS - - def areCurrentTraitsValid(self): - """Checks whether the characters traits are valid. - @return: True if the traits are valid, False if not""" - if len(self.char_data.characterstats.traits) > self.MAX_TRAITS: - return False - for trait in self.char_data.characterstats.traits: - if not self.isTraitValid(trait): - return False - return True - - def isCharacterValid(self): - """Checks whether the character as a whole is valid. - @return: True if the character is valid, False if not""" - #Single checks can be disabled by putting a "#" in front of them - if True\ - and self._stat_points >= 0\ - and self.areAllStatisticsValid() \ - and self.areCurrentTraitsValid() \ - and self.isNameValid(self.name)\ - and self.isPictureValid(self.picture)\ - and self.isAgeValid(self.age)\ - and self.isGenderValid(self.gender)\ - and self.isOriginValid(self.origin)\ - : - return True - return False
--- a/src/parpg/charactercreationview.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,79 +0,0 @@ -# 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/>. -"""Provides the view for displaying the character creation screen.""" - -from fife.extensions import pychan - -from parpg import vfs -from viewbase import ViewBase - -class CharacterCreationView(ViewBase): - """View used to display the character creation screen. - @ivar background: Widget displayed as the background. - @type background: L{pychan.Widget} - @ivar start_new_game_callback: Callback attached to the startButton. - @type start_new_game_callback: callable - @ivar cancel_new_game_callback: Callback attached to the cancelButton. - @type cancel_new_game_callback: callable - @ivar character_screen: Widget used to display the character creation - screen. - @type character_screen: L{pychan.Widget}""" - - def __init__(self, engine, model, settings): - """Construct a new L{CharacterCreationView} instance. - @param engine: Rendering engine used to display the view. - @type engine: L{fife.Engine} - @param model: Model of the game state. - @type model: L{GameState}""" - ViewBase.__init__(self, engine, model) - self.settings = settings - xml_file = vfs.VFS.open('gui/main_menu_background.xml') - self.background = pychan.loadXML(xml_file) - screen_mode = self.engine.getRenderBackend().getCurrentScreenMode() - self.background.width = screen_mode.getWidth() - self.background.height = screen_mode.getHeight() - self.start_new_game_callback = None - self.cancel_new_game_callback = None - - xml_file = vfs.VFS.open('gui/character_screen.xml') - self.character_screen = pychan.loadXML(xml_file) - - self.character_screen.adaptLayout() - character_screen_events = {} - character_screen_events['startButton'] = self.startNewGame - character_screen_events['cancelButton'] = self.cancelNewGame - self.character_screen.mapEvents(character_screen_events) - - def show(self): - """Display the view. - @return: None""" - self.background.show() - self.character_screen.show() - - def hide(self): - """Hide the view. - @return: None""" - self.background.hide() - self.character_screen.hide() - - def startNewGame(self): - """Callback tied to the startButton. - @return: None""" - self.start_new_game_callback() - - def cancelNewGame(self): - """Callback tied to the cancelButton. - @return: None""" - self.cancel_new_game_callback()
--- a/src/parpg/characterstatistics.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,168 +0,0 @@ -# This program 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. - -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. -""" -Provides classes that define character stats and traits. -""" - -from abc import ABCMeta, abstractmethod -from weakref import ref as weakref - -from .serializers import SerializableRegistry - -from components import character_statistics - -class AbstractCharacterStatistic(object): - __metaclass__ = ABCMeta - - @abstractmethod - def __init__(self, description, minimum, maximum): - self.description = description - self.minimum = minimum - self.maximum = maximum - - -class PrimaryCharacterStatistic(AbstractCharacterStatistic): - def __init__(self, long_name, short_name, description, minimum=0, - maximum=100): - AbstractCharacterStatistic.__init__(self, description=description, - minimum=minimum, maximum=maximum) - self.long_name = long_name - self.short_name = short_name - -SerializableRegistry.registerClass( - 'PrimaryCharacterStatistic', - PrimaryCharacterStatistic, - init_args=[ - ('long_name', unicode), - ('short_name', unicode), - ('description', unicode), - ('minimum', int), - ('maximum', int), - ], -) - - -class SecondaryCharacterStatistic(AbstractCharacterStatistic): - def __init__(self, name, description, unit, mean, sd, stat_modifiers, - minimum=None, maximum=None): - AbstractCharacterStatistic.__init__(self, description=description, - minimum=minimum, maximum=maximum) - self.name = name - self.unit = unit - self.mean = mean - self.sd = sd - self.stat_modifiers = stat_modifiers - -SerializableRegistry.registerClass( - 'SecondaryCharacterStatistic', - SecondaryCharacterStatistic, - init_args=[ - ('name', unicode), - ('description', unicode), - ('unit', unicode), - ('mean', float), - ('sd', float), - ('stat_modifiers', dict), - ('minimum', float), - ('maximum', float), - ], -) - - -class AbstractStatisticValue(object): - __metaclass__ = ABCMeta - - @abstractmethod - def __init__(self, statistic_type, character): - self.statistic_type = statistic_type - self.character = weakref(character) - - -class PrimaryStatisticValue(AbstractStatisticValue): - def value(): - def fget(self): - return self._value - def fset(self, new_value): - assert 0 <= new_value <= 100 - self._value = new_value - - def __init__(self, statistic_type, character, value): - AbstractStatisticValue.__init__(self, statistic_type=statistic_type, - character=character) - self._value = None - self.value = value - - -class SecondaryStatisticValue(AbstractStatisticValue): - def normalized_value(): - def fget(self): - return self._normalized_value - def fset(self, new_value): - self._normalized_value = new_value - statistic_type = self.statistic_type - mean = statistic_type.mean - sd = statistic_type.sd - self._value = self.calculate_value(mean, sd, new_value) - return locals() - normalized_value = property(**normalized_value()) - - def value(): - def fget(self): - return self._value - def fset(self, new_value): - self._value = new_value - statistic_type = self.statistic_type - mean = statistic_type.mean - sd = statistic_type.sd - self._normalized_value = self.calculate_value(mean, sd, new_value) - return locals() - value = property(**value()) - - def __init__(self, statistic_type, character): - AbstractStatisticValue.__init__(self, statistic_type=statistic_type, - character=character) - mean = statistic_type.mean - sd = statistic_type.sd - normalized_value = self.derive_value(normalized=True) - self._normalized_value = normalized_value - self._value = self.calculate_value(mean, sd, normalized_value) - - def derive_value(self, normalized=True): - """ - Derive the current value - """ - statistic_type = self.statistic_type - stat_modifiers = statistic_type.stat_modifiers - character = self.character() - - value = sum( - character_statistics.get_statistic(character, name).value * - modifier for name, modifier in - stat_modifiers.items() - ) - assert 0 <= value <= 100 - if not normalized: - mean = statistic_type.mean - sd = statistic_type.sd - value = self.calculate_value(mean, sd, value) - return value - - @staticmethod - def calculate_value(mean, sd, normalized_value): - value = sd * (normalized_value - 50) + mean - return value - - @staticmethod - def calculate_normalized_value(mean, sd, value): - normalized_value = ((value - mean) / sd) + 50 - return normalized_value
--- a/src/parpg/common/listeners/command_listener.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -#!/usr/bin/env python -# 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 module contains the CommandListener class for receiving command events""" - - -class CommandListener(object): - """Base class for listeners that receiving command events""" - - def __init__(self, event_listener): - self.event_listener = None - CommandListener.attach(self, event_listener) - - def attach(self, event_listener): - """Attaches the listener to the event""" - event_listener.addListener("Command", self) - self.event_listener = event_listener - - def detach(self): - """Detaches the listener from the event""" - self.event_listener.removeListener("Command", self) - self.event_listener = None - - def onCommand(self, command): - """Called when a command is executed""" - pass
--- a/src/parpg/common/listeners/console_executor.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -#!/usr/bin/env python - -# 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 module contains the ConsoleExecuter class that receives -console events""" - -class ConsoleExecuter(object): - """This class is a base class for listeners receiving console events""" - - def __init__(self, event_listener): - self.event_listener = None - ConsoleExecuter.attach(self, event_listener) - - def attach(self, event_listener): - """Attaches the listener to the event""" - event_listener.addListener("ConsoleCommand", self) - self.event_listener = event_listener - - def detach(self): - """Detaches the listener from the event""" - self.event_listener.removeListener("ConsoleCommand", self) - self.event_listener = None - - def onToolsClick(self): - """Called when the tools button has been clicked""" - pass - - def onConsoleCommand(self, command): - """Called when a console command is executed""" - pass \ No newline at end of file
--- a/src/parpg/common/listeners/event_listener.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,143 +0,0 @@ -#!/usr/bin/env python - -# 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 module contains the EventListener that receives events and distributes -them to PARPG listeners""" -import logging - -from fife import fife -from fife.extensions import pychan - -logger = logging.getLogger('event_listener') - -class EventListener(fife.IKeyListener, - fife.ICommandListener, - fife.IMouseListener, - fife.ConsoleExecuter): - """Class that receives all events and distributes them to the listeners""" - def __init__(self, engine): - """Initialize the instance""" - self.event_manager = engine.getEventManager() - - fife.IKeyListener.__init__(self) - self.event_manager.addKeyListener(self) - fife.ICommandListener.__init__(self) - self.event_manager.addCommandListener(self) - fife.IMouseListener.__init__(self) - self.event_manager.addMouseListener(self) - fife.ConsoleExecuter.__init__(self) - pychan.manager.hook.guimanager.getConsole().setConsoleExecuter(self) - - self.listeners = {"Mouse" : [], - "Key" : [], - "Command" : [], - "ConsoleCommand" : [], - "Widget" : []} - - def addListener(self, listener_type, listener): - """Adds a listener""" - if listener_type in self.listeners.iterkeys(): - if not listener in self.listeners[listener_type]: - self.listeners[listener_type].append(listener) - else: - logger.warning("Listener type " - "'{0}' not supported".format(listener_type)) - - def removeListener(self, listener_type, listener): - """Removes a listener""" - if listener_type in self.listeners.iterkeys(): - self.listeners[listener_type].remove(listener) - else: - logger.warning("Listener type " - "'{0}' not supported".format(listener_type)) - - def mousePressed(self, evt): - """Called when a mouse button is pressed""" - for listeners in self.listeners["Mouse"]: - listeners.mousePressed(evt) - - def mouseReleased(self, evt): - """Called when a mouse button is released""" - for listeners in self.listeners["Mouse"]: - listeners.mouseReleased(evt) - - def mouseEntered(self, evt): - """Called when a mouse enters a region""" - for listeners in self.listeners["Mouse"]: - listeners.mouseEntered(evt) - - def mouseExited(self, evt): - """Called when a mouse exits a region""" - for listeners in self.listeners["Mouse"]: - listeners.mouseExited(evt) - - def mouseClicked(self, evt): - """Called after a mouse button is pressed and released""" - for listeners in self.listeners["Mouse"]: - listeners.mouseClicked(evt) - - def mouseWheelMovedUp(self, evt): - """Called when the mouse wheel has been moved up""" - for listeners in self.listeners["Mouse"]: - listeners.mouseWheelMovedUp(evt) - - def mouseWheelMovedDown(self, evt): - """Called when the mouse wheel has been moved down""" - for listener in self.listeners["Mouse"]: - listener.mouseWheelMovedDown(evt) - - def mouseMoved(self, evt): - """Called when when the mouse has been moved""" - for listener in self.listeners["Mouse"]: - listener.mouseMoved(evt) - - def mouseDragged(self, evt): - """Called when dragging the mouse""" - for listener in self.listeners["Mouse"]: - listener.mouseDragged(evt) - - def keyPressed(self, evt): - """Called when a key is being pressed""" - for listener in self.listeners["Key"]: - listener.keyPressed(evt) - - def keyReleased(self, evt): - """Called when a key is being released""" - for listener in self.listeners["Key"]: - listener.keyReleased(evt) - - def onCommand(self, command): - """Called when a command is executed""" - for listener in self.listeners["Command"]: - listener.onCommand(command) - - def onToolsClick(self): - """Called when the tools button has been clicked""" - for listener in self.listeners["ConsoleCommand"]: - listener.onToolsClick() - - def onConsoleCommand(self, command): - """Called when a console command is executed""" - for listener in self.listeners["ConsoleCommand"]: - listener.onConsoleCommand(command) - - def onWidgetAction(self, evt): - """Called when a widget action is executed""" - for listener in self.listeners["Widget"]: - listener.onWidgetAction(evt) - -
--- a/src/parpg/common/listeners/key_listener.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -#!/usr/bin/env python - -# 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 module contains the KeyListener class for receiving key inputs""" - -class KeyListener(object): - """Base class for listeners receiving keyboard input""" - - def __init__(self, event_listener): - self.event_listener = None - KeyListener.attach(self, event_listener) - - def attach(self, event_listener): - """Attaches the listener to the event""" - event_listener.addListener("Key", self) - self.event_listener = event_listener - - def detach(self): - """Detaches the listener from the event""" - self.event_listener.removeListener("Key", self) - - def keyPressed(self, event): - """Called when a key is being pressed""" - pass - - def keyReleased(self, event): - """Called when a key is being released""" - pass \ No newline at end of file
--- a/src/parpg/common/listeners/mouse_listener.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -#!/usr/bin/env python - -# 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 module contains the MouseListener class for receiving mouse inputs""" - -class MouseListener(object): - """Base class for listeners receiving mouse input""" - - def __init__(self, event_listener): - self.event_listener = None - MouseListener.attach(self, event_listener) - - def attach(self, event_listener): - """Attaches the listener to the event""" - event_listener.addListener("Mouse", self) - self.event_listener = event_listener - - def detach(self): - """Detaches the listener from the event""" - self.event_listener.removeListener("Mouse", self) - self.event_listener = None - - def mousePressed(self, evt): - """Called when a mouse button is pressed""" - pass - - def mouseReleased(self, evt): - """Called when a mouse button is released""" - pass - - def mouseEntered(self, evt): - """Called when a mouse enters a region""" - pass - - def mouseExited(self, evt): - """Called when a mouse exits a region""" - pass - - def mouseClicked(self, evt): - """Called after a mouse button is pressed and released""" - pass - - def mouseWheelMovedUp(self, evt): - """Called when the mouse wheel has been moved up""" - pass - - def mouseWheelMovedDown(self, evt): - """Called when the mouse wheel has been moved down""" - pass - - def mouseMoved(self, evt): - """Called when when the mouse has been moved""" - pass - - def mouseDragged(self, evt): - """Called when dragging the mouse""" - pass
--- a/src/parpg/common/listeners/widget_listener.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,39 +0,0 @@ -#!/usr/bin/env python - -# 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 module contains the WidgetListener class for receiving widget events""" - -class WidgetListener(object): - """A Base class for listeners receiving widget events""" - - def __init__(self, event_listener): - self.event_listener = None - WidgetListener.attach(self, event_listener) - - def attach(self, event_listener): - """Attaches the listener to the event""" - event_listener.addListener("Widget", self) - self.event_listener = event_listener - - def detach(self): - """Detaches the listener from the event""" - self.event_listener.removeListener("Widget", self) - self.event_listener = None - - def onWidgetAction(self, evt): - """Called when a widget action is executed""" - pass \ No newline at end of file
--- a/src/parpg/common/ordereddict.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,127 +0,0 @@ -# Copyright (c) 2009 Raymond Hettinger -# -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -# OTHER DEALINGS IN THE SOFTWARE. - -from UserDict import DictMixin - -class OrderedDict(dict, DictMixin): - - def __init__(self, *args, **kwds): - if len(args) > 1: - raise TypeError('expected at most 1 arguments, got %d' % len(args)) - try: - self.__end - except AttributeError: - self.clear() - self.update(*args, **kwds) - - def clear(self): - self.__end = end = [] - end += [None, end, end] # sentinel node for doubly linked list - self.__map = {} # key --> [key, prev, next] - dict.clear(self) - - def __setitem__(self, key, value): - if key not in self: - end = self.__end - curr = end[1] - curr[2] = end[1] = self.__map[key] = [key, curr, end] - dict.__setitem__(self, key, value) - - def __delitem__(self, key): - dict.__delitem__(self, key) - key, prev, next = self.__map.pop(key) - prev[2] = next - next[1] = prev - - def __iter__(self): - end = self.__end - curr = end[2] - while curr is not end: - yield curr[0] - curr = curr[2] - - def __reversed__(self): - end = self.__end - curr = end[1] - while curr is not end: - yield curr[0] - curr = curr[1] - - def popitem(self, last=True): - if not self: - raise KeyError('dictionary is empty') - if last: - key = reversed(self).next() - else: - key = iter(self).next() - value = self.pop(key) - return key, value - - def __reduce__(self): - items = [[k, self[k]] for k in self] - tmp = self.__map, self.__end - del self.__map, self.__end - inst_dict = vars(self).copy() - self.__map, self.__end = tmp - if inst_dict: - return (self.__class__, (items,), inst_dict) - return self.__class__, (items,) - - def keys(self): - return list(self) - - setdefault = DictMixin.setdefault - update = DictMixin.update - pop = DictMixin.pop - values = DictMixin.values - items = DictMixin.items - iterkeys = DictMixin.iterkeys - itervalues = DictMixin.itervalues - iteritems = DictMixin.iteritems - - def __repr__(self): - if not self: - return '%s()' % (self.__class__.__name__,) - return '%s(%r)' % (self.__class__.__name__, self.items()) - - def copy(self): - return self.__class__(self) - - @classmethod - def fromkeys(cls, iterable, value=None): - d = cls() - for key in iterable: - d[key] = value - return d - - def __eq__(self, other): - if isinstance(other, OrderedDict): - if len(self) != len(other): - return False - for p, q in zip(self.items(), other.items()): - if p != q: - return False - return True - return dict.__eq__(self, other) - - def __ne__(self, other): - return not self == other
--- a/src/parpg/common/utils.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,83 +0,0 @@ -# This program 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. - -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -# Miscellaneous game functions - -import sys -import os -import fnmatch - -from textwrap import dedent -from contextlib import contextmanager -from parpg import vfs - -def addPaths (*paths): - """Adds a list of paths to sys.path. Paths are expected to use forward - slashes, for example '../../engine/extensions'. Slashes are converted - to the OS-specific equivalent. - @type paths: ??? - @param paths: Paths to files? - @return: None""" - for p in paths: - if not p in sys.path: - sys.path.append(os.path.sep.join(p.split('/'))) - -def parseBool(value): - """Parses a string to get a boolean value""" - if (value.isdigit()): - return bool(int(value)) - elif (value.isalpha): - return value.lower()[0] == "t" - return False - -def locateFiles(pattern, root=os.curdir): - """Locate all files matching supplied filename pattern in and below - supplied root directory.""" - filepaths = [] - filenames = vfs.VFS.listFiles(root) - for filename in fnmatch.filter(filenames, pattern): - vfs_file_path = '/'.join([root, filename]) - filepaths.append(vfs_file_path) - dirnames = vfs.VFS.listDirectories(root) - for dirname in dirnames: - subdir_filepaths = locateFiles(pattern, '/'.join([root, dirname])) - filepaths.extend(subdir_filepaths) - return filepaths - -def dedent_chomp(string): - """Remove common leading whitespace and chomp each non-blank line.""" - dedented_string = dedent(string).strip() - lines = dedented_string.splitlines() - formatted_lines = [] - for index in range(len(lines)): - line = lines[index] - if index == len(lines) - 1: - # Don't do anything to the last line. - pass - elif not line or line.isspace(): - line = '\n\n' - else: - line = ''.join([line, ' ']) - formatted_lines.append(line) - result = ''.join(formatted_lines) - return result - -@contextmanager -def cwd(dirname): - cwd = os.getcwd() - - try: - os.chdir(dirname) - yield - finally: - os.chdir(cwd)
--- a/src/parpg/components/__init__.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,31 +0,0 @@ -from character_statistics import CharacterStatistics -from containable import Containable -from container import Container -from description import Description -from dialogue import Dialogue -from fifeagent import FifeAgent -from lockable import Lockable -from usable import Usable -from change_map import ChangeMap -from equipable import Equipable -from equip import Equip -from general import General -from behaviour import Behaviour -from graphics import Graphics - -components = { - "general": General(), - "characterstats": CharacterStatistics(), - "containable": Containable(), - "container": Container(), - "description": Description(), - "dialogue": Dialogue(), - "fifeagent": FifeAgent(), - "lockable": Lockable(), - "usable": Usable(), - "change_map": ChangeMap(), - "equipable": Equipable(), - "equip": Equip(), - "behaviour": Behaviour(), - "graphics": Graphics(), - } \ No newline at end of file
--- a/src/parpg/components/base.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -# This program 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. -# -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -from parpg.bGrease.component import Component - -class Base(Component): - """Base component for PARPG.""" - - @property - def saveable_fields(self): - return self.fields.keys() \ No newline at end of file
--- a/src/parpg/components/behaviour.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# This program 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. -# -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -from base import Base - -class Behaviour(Base): - """Component that stores the values of the behaviour""" - - def __init__(self): - """Constructor""" - Base.__init__(self, behaviour_type=str) - self.behaviour_type = "Base" \ No newline at end of file
--- a/src/parpg/components/change_map.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# This program 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. -# -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -from base import Base -from parpg.bGrease.geometry import Vec2d - -class ChangeMap(Base): - """Component that allows an entity to be contained by Container entity.""" - - def __init__(self): - """Constructor""" - Base.__init__(self, target_map=str, target_position=list) \ No newline at end of file
--- a/src/parpg/components/character_statistics.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,49 +0,0 @@ -# This program 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. -# -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -from base import Base - -class CharacterStatistics(Base): - """Component that defines character statistics.""" - - def __init__(self): - """Constructor""" - Base.__init__(self, gender=str, picture=str, age=int, origin=str, - primary_stats=dict, secondary_stats=dict, traits=list, - ) - @property - def saveable_fields(self): - fields = self.fields.keys() - fields.remove("primary_stats") - fields.remove("secondary_stats") - return fields - -def get_statistic(stats, name): - """Gets the statistic by its name""" - if name in stats.primary_stats: - return stats.primary_stats[name] - elif name in stats.secondary_stats: - return stats.secondary_stats[name] - else: - for stat in stats.primary_stats: - if stat.statistic_type.short_name == name: - return stat - return None - -def get_stat_values(char_stats): - stats = {"primary":{}, "secondary":{}} - for name, stat in char_stats.primary_stats.iteritems(): - stats["primary"][name] = stat.value - for name, stat in char_stats.secondary_stats.iteritems(): - stats["secondary"][name] = stat.value - return stats \ No newline at end of file
--- a/src/parpg/components/containable.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -# This program 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. -# -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -from copy import deepcopy - -from base import Base - -class Containable(Base): - """Component that allows an entity to be contained by Container entity.""" - - def __init__(self): - """Constructor""" - Base.__init__(self, bulk=int, weight=int, item_type=str, image=str, container=object, slot=int) - - @property - def saveable_fields(self): - fields = self.fields.keys() - fields.remove("container") - return fields \ No newline at end of file
--- a/src/parpg/components/container.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,129 +0,0 @@ -# This program 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. -# -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -from copy import deepcopy - -from base import Base - -class Container(Base): - """ - Component that allows an entity to contain one or more child entities. - """ - - def __init__(self): - Base.__init__(self, children=list, max_bulk=int) - - @property - def saveable_fields(self): - fields = self.fields.keys() - fields.remove("children") - return fields - - - -class BulkLimitError(Exception): - """Error that gets raised when the item would exceed the - bulk limit of the container.""" - - def __init__(self, bulk, max_bulk): - self.bulk = bulk - self.max_bulk = max_bulk - - def __str__(self): - return "Item would exceed the bulk limit of the container." - -class NoFreeSlotError(Exception): - """Error that gets raised when the container has no free slots.""" - - def __str__(self): - return "Container can't hold any more items." - -def get_free_slot(container): - """Returns the first slot of the container that is not occupied.""" - index = 0 - for child in container.children: - if not child: - return index - index += 1 - raise NoFreeSlotError - -def get_total_bulk(container): - """Returns the bulk of all items in the container.""" - total_bulk = 0 - for child in container.children: - if child: - total_bulk += child.bulk - return total_bulk - -def get_total_weight(container): - """Returns the weight of all items in the container.""" - total_weight = 0 - for child in container.children: - if child: - total_weight += child.weight - return total_weight - -def get_item(container, slot_or_type): - """Returns the item that is in the slot, or has the given type.""" - if type(slot_or_type) == int: - if len(container.children) >= (slot_or_type + 1): - return container.children[slot_or_type] - else: - for child in container.children: - if child and child.item_type == slot_or_type: - return child - - return None - -def remove_item(container, slot_or_type): - """Removes the item at the given slot, or with the given type.""" - if type(slot_or_type) == int: - item = get_item(container, slot_or_type) - if item: - container.children[slot_or_type] = None - item.container = None - item.slot = -1 - else: - for child in container.children: - if child and child.item_type == slot_or_type: - container.children[child.slot] = None - child.container = None - child.slot = -1 - -def take_item(container, slot_or_type): - """Moves the item at the given slot, or with the given type, - out of the container and returns it.""" - item = get_item(container, slot_or_type) - if item: - remove_item(container, slot_or_type) - return item - -def put_item(container, item, slot=-1): - """Puts the item at the given slot in the container. - Returns the item previously at the slot.""" - if slot == -1: - slot = get_free_slot(container) - total_bulk = get_total_bulk(container) - total_bulk += item.bulk - old_item = get_item(container, slot) - if old_item: - total_bulk -= old_item.bulk - if total_bulk > container.max_bulk: - raise BulkLimitError(total_bulk, container.max_bulk) - remove_item(container, slot) - container.children[slot] = item - if item.container: - remove_item(item.container, item.slot) - item.container = container - item.slot = slot - return old_item \ No newline at end of file
--- a/src/parpg/components/description.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -# This program 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. -# -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -from base import Base - -class Description(Base): - """Component that stores the description of an object""" - - def __init__(self): - """Constructor""" - Base.__init__(self, view_name=str, real_name=str, desc=str)
--- a/src/parpg/components/dialogue.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -# This program 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. -# -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -from copy import deepcopy - -from base import Base - -class Dialogue(Base): - """Component that stores the dialogue""" - - def __init__(self): - """Constructor""" - Base.__init__(self, dialogue=object) - - @property - def saveable_fields(self): - fields = self.fields.keys() - fields.remove("dialogue") - return fields \ No newline at end of file
--- a/src/parpg/components/equip.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,104 +0,0 @@ -# This program 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. -# -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -from base import Base - -class Equip(Base): - """ - Component that stores the equipment (what is being worn/wielded). - """ - - def __init__(self): - Base.__init__(self, head=object, neck=object, body=object, belt=object, - leg=object, feet=object, l_arm=object, r_arm=object) - self.head = None - self.neck = None - self.body = None - self.belt = None - self.leg = None - self.feet = None - self.l_arm = None - self.r_arm = None - - @property - def saveable_fields(self): - return [] - -class SlotInvalidError(Exception): - """Error that gets raised when the slot is invalid.""" - - def __init__(self, slot): - self.slot = slot - - def __str__(self): - return "\"%s\" is not a valid slot." % self.slot - -class AlreadyEquippedError(Exception): - """Error that gets raised when the equipable already has a wearer""" - - def __str__(self): - return "The equipable is already weared." - -class CannotBeEquippedInSlot(Exception): - """Error that gets raised when the equipable can't be equiped in that - slot""" - - def __init__(self, slot, equipable): - self.slot = slot - self.equipable = equipable - - def __str__(self): - return ("%s is not in the equipables slots. (%s)" % - (self.slot, ', '.join(self.equipable.possible_slots)) - ) - - -def equip(wearer, equipable, slot): - """Equip the wearer with the given equipable. - @returns The equipable that was at the given slot, or None""" - if equipable.wearer: - raise AlreadyEquippedError - if slot in equipable.possible_slots: - try: - old_item = getattr(wearer, slot) if hasattr(wearer, slot) else None - setattr(wearer, slot, equipable) - equipable.in_slot = slot - equipable.wearer = wearer - if old_item: - old_item.in_slot = None - old_item.wearer = None - return old_item - except AttributeError: - raise SlotInvalidError(slot) - raise CannotBeEquippedInSlot(slot, equipable) - -def get_equipable(wearer, slot): - """Return the equipable in the given slot""" - if not wearer: - return None - try: - item = getattr(wearer, slot) - return item - except AttributeError: - raise SlotInvalidError(slot) - -def take_equipable(wearer, slot): - """Remove equipable from the given slot and return it""" - item = get_equipable(wearer, slot) - setattr(wearer, slot, None) - if item: - item.in_slot = None - item.wearer = None - return item - - \ No newline at end of file
--- a/src/parpg/components/equipable.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -# This program 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. -# -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -from copy import deepcopy - -from base import Base - -class Equipable(Base): - """ - Component that stores the data for an entity that can be equipped. - """ - - def __init__(self): - Base.__init__(self, possible_slots=list, wearer=object, in_slot=str) - - @property - def saveable_fields(self): - fields = self.fields.keys() - fields.remove("wearer") - return fields \ No newline at end of file
--- a/src/parpg/components/fifeagent.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -# This program 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. -# -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -from copy import deepcopy - -from base import Base - - -class FifeAgent(Base): - """Component that stores the values for a fife agent""" - - def __init__(self): - """Constructor""" - Base.__init__(self, layer=object, behaviour=object) - - @property - def saveable_fields(self): - fields = self.fields.keys() - fields.remove("layer") - fields.remove("behaviour") - return fields - - -def setup_behaviour(agent): - """Attach the behaviour to the layer""" - if agent.behaviour: - agent.behaviour.attachToLayer(agent.entity.getID(), agent.layer) - -def approach(agent, target_or_location, action): - if agent.behaviour: - agent.behaviour.approach(target_or_location, action) - -commands = {"approach":approach} \ No newline at end of file
--- a/src/parpg/components/general.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -# This program 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. -# -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -from base import Base - -class General(Base): - """Component that stores the general values of an parpg entity""" - - def __init__(self): - """Constructor""" - Base.__init__(self, identifier=str) \ No newline at end of file
--- a/src/parpg/components/graphics.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# This program 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. -# -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -from base import Base - - -class Graphics(Base): - """Component that stores the values for the graphical representation""" - - def __init__(self): - """Constructor""" - Base.__init__(self, gfx=str) \ No newline at end of file
--- a/src/parpg/components/lockable.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,47 +0,0 @@ -# This program 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. -# -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -from base import Base - -class Lockable(Base): - """Component that stores the data of a lock""" - - def __init__(self): - """Constructor""" - Base.__init__(self, closed=bool, locked=bool) - -class LockedError(Exception): - - def __str__(self): - return "Is locked" - -class OpenError(Exception): - - def __str__(self): - return "Is open" - -def lock(lock): - if not lock.closed: - raise OpenError - lock.locked = True - -def unlock(lock): - lock.locked = False - -def open(lock): - if lock.locked: - raise LockedError - lock.closed = False - -def close(lock): - lock.closed = True \ No newline at end of file
--- a/src/parpg/components/usable.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# This program 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. -# -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -from base import Base - -class Usable(Base): - """ - Component that stores data about the actions that an object can do. - """ - - def __init__(self): - Base.__init__(self, actions=dict) \ No newline at end of file
--- a/src/parpg/console.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,204 +0,0 @@ -# This program 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. - -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -import re -import os -import sys -from StringIO import StringIO -import code - -class Console: - def __init__(self, app_listener): - """ - @type appListener: ApplicationListener - @param appListener: ApplicationListener object providing a link with - the Controller, the view and the GameModel""" - exit_help = "Terminate application" - grid_help = "Toggle grid display" - run_help = "Toggle player run/walk" - help_help = "Show this help string" - load_help = "Usage: load directory file" - python_help = "Run some python code" - quit_help = "Terminate application" - save_help = "Usage: save directory file" - pause_help = "Pause/Unpause the game" - - self.commands = [ - {"cmd":"exit" ,"callback":self.handleQuit ,"help": exit_help}, - {"cmd":"grid" ,"callback":self.handleGrid ,"help": grid_help}, - {"cmd":"help" ,"callback":self.handleHelp ,"help": help_help}, - {"cmd":"load" ,"callback":self.handleLoad ,"help": load_help}, - {"cmd":"pause" ,"callback":self.handlePause ,"help": pause_help}, - {"cmd":"python","callback":self.handlePython,"help": python_help}, - {"cmd":"run" ,"callback":self.handleRun ,"help": run_help}, - {"cmd":"save" ,"callback":self.handleSave ,"help": save_help}, - {"cmd":"quit" ,"callback":self.handleQuit ,"help": quit_help}, - ] - self.app_listener = app_listener - self.view = self.app_listener.view - self.model = self.app_listener.model - self.game_state = self.app_listener.view.model.game_state - self.console_locals = {"__name__":"__paprg_console__",\ - "__doc__": None,\ - "app_listener":self.app_listener,\ - "model":self.app_listener.model,\ - "view":self.app_listener.view,\ - "engine":self.app_listener.engine} - - self.console = code.InteractiveConsole(self.console_locals) - - def handleQuit(self, command): - """Implements the quit console command - @type command: string - @param command: The command to run - @return: The resultstring""" - self.app_listener.quitGame() - return "Terminating ..." - - def handlePause(self, command): - """Implements the pause console command - @type command: string - @param command: The command to run - @return: The resultstring""" - self.model.togglePause() - return "Game (un)paused" - - def handleGrid(self, command): - """Implements the grid console command - @type command: string - @param command: The command to run - @return: The resultstring""" - self.app_listener.view.active_map.toggleRenderer('GridRenderer') - return "Grid toggled" - - def handleRun(self, command): - """Toggles run/walk mode of the PC player - @type command: string - @param command: The command to run. - @return: The response""" - if self.app_listener.model.pc_run == 1: - self.app_listener.model.pc_run = 0 - return "PC is now walking" - else: - self.app_listener.model.pc_run = 1 - return "PC is now running" - - def handleHelp(self, command): - """Implements the help console command - @type command: string - @param command: The command to run - @return: The resultstring""" - res = "" - for cmd in self.commands: - res += "%10s: %s\n" % (cmd["cmd"], cmd["help"]) - return res - - def handlePython(self,command): - user_code = command[7:len(command)] - - codeOut = StringIO() - - #make stdout and stderr write to our file, not the terminal - sys.stdout = codeOut - sys.stderr = codeOut - - #Workaround it not being possible to enter a blank line in the console - if user_code == " ": - user_code = "" - - #Process the code - self.console.push(user_code) - if len(self.console.buffer) == 0: - output = codeOut.getvalue() - else: - output = "..." - - - #restore stdout and stderr - sys.stdout = sys.__stdout__ - sys.stderr = sys.__stderr__ - - temp_output = output - output = "" - counter = 0 - - #Make the output fit in the console screen - for char in temp_output: - counter += 1 - if char == "\n": - counter = 0 - elif counter == 110: - output += "\n" - counter = 0 - output += char - - return output - - def handleLoad(self, command): - """Implements the load console command - @type command: string - @param command: The command to run - @return: The resultstring""" - result = None - load_regex = re.compile('^load') - l_matches = load_regex.match(command.lower()) - if (l_matches is not None): - end_load = l_matches.end() - try: - l_args = command.lower()[end_load + 1:].strip() - l_path, l_filename = l_args.split(' ') - self.app_listener.model.load_save = True - self.app_listener.model.savegame = os.path.join(l_path, l_filename) - result = "Load triggered" - - except Exception, l_error: - self.app_listener.engine.getGuiManager().getConsole().println('Error: ' + str(l_error)) - result="Failed to load file" - - return result - - def handleSave(self, command): - """Implements the save console command - @type command: string - @param command: The command to run - @return: The resultstring""" - save_regex = re.compile('^save') - s_matches = save_regex.match(command.lower()) - if (s_matches != None): - end_save = s_matches.end() - try: - s_args = command.lower()[end_save+1:].strip() - s_path, s_filename = s_args.split(' ') - self.app_listener.model.save(s_path, s_filename) - result = "Saved to file: " + s_path + s_filename - - except Exception, s_error: - self.app_listener.engine.getGuiManager().getConsole(). \ - println('Error: ' + str(s_error)) - result = "Failed to save file" - return result - - def handleConsoleCommand(self, command): - """Implements the console logic - @type command: string - @param command: The command to run - @return: The response string """ - result = None - for cmd in self.commands: - regex = re.compile('^' + cmd["cmd"]) - if regex.match(command.lower()): - result=cmd["callback"](command) - - if result is None: - result = self.handlePython("python " + command) - return result
--- a/src/parpg/controllerbase.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,91 +0,0 @@ -# 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/>. -from fife import fife - -from parpg.common.listeners.key_listener import KeyListener -from parpg.common.listeners.mouse_listener import MouseListener -from parpg.common.listeners.command_listener import CommandListener -from parpg.mode import FifeMode - -class ControllerBase(FifeMode, KeyListener, MouseListener, CommandListener): - """Base of Controllers""" - 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 - ''' - KeyListener.__init__(self, application.event_listener) - MouseListener.__init__(self, application.event_listener) - CommandListener.__init__(self, application.event_listener) - FifeMode.__init__(self) - self.engine = engine - self.event_manager = engine.getEventManager() - self.view = view - self.model = model - self.application = application - - def pause(self, paused): - """Stops receiving events""" - if paused: - KeyListener.detach(self) - MouseListener.detach(self) - else: - KeyListener.attach(self, self.application.event_listener) - MouseListener.attach(self, self.application.event_listener) - - def setMouseCursor(self, image, dummy_image, mc_type="native"): - """Set the mouse cursor to an image. - @type image: string - @param image: The image you want to set the cursor to - @type dummy_image: string - @param dummy_image: ??? - @type type: string - @param type: ??? - @return: None""" - cursor = self.engine.getCursor() - img_manager = self.engine.getImageManager() - if(mc_type == "target"): - target_cursor_id = img_manager.load(image) - dummy_cursor_id = img_manager.load(dummy_image) - cursor.set(dummy_cursor_id) - cursor.setDrag(target_cursor_id, -16, -16) - else: - cursor_type = fife.CURSOR_IMAGE - zero_cursor_id = img_manager.load(image) - cursor.set(zero_cursor_id) - cursor.setDrag(zero_cursor_id) - - def resetMouseCursor(self): - """Reset cursor to default image. - @return: None""" - image = '/'.join(['gui/cursors/', - self.model.settings.parpg.CursorDefault]) - self.setMouseCursor(image, image) - - def pump(self, dt): - pass
--- a/src/parpg/dialogue.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,192 +0,0 @@ -# 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/>. -""" -Provides classes used to contain and organize dialogue data for use within -in-game dialogues between the player character and NPCs. -""" -try: - from collections import OrderedDict -except ImportError: - # Python version 2.4-2.6 doesn't have the OrderedDict - from parpg.common.ordereddict import OrderedDict - -class Dialogue(object): - """ - Represents a complete dialogue and acts as a container for the dialogue - data belonging to a particular NPC. - """ - __slots__ = ['npc_name', 'avatar_path', 'default_greeting', - 'greetings', 'sections'] - - def __init__(self, npc_name, avatar_path, default_greeting, greetings=None, - sections=None): - """ - Initialize a new L{Dialogue} instance. - - @param npc_name: name displayed for the NPC in the dialogue. - @type npc_name: basestring - @param avatar_path: path to the image that should be displayed as the - NPC's avatar. - @type avatar_path: basestring - @param default_greeting: section of dialogue that should be - displayed when the dialogue is first initiated and no other start - sections are available. - @type default_greeting: L{DialogueSection} - @param greetings: sections of dialogue defining the conditions - under which each should be displayed when the dialogue is first - initiated. - @type greetings: list of - L{RootDialogueSections<DialogueGreeting>} - @param sections: sections of dialogue that make up this - L{Dialogue} instance. - @type sections: list of L{DialogueSections<DialogueSection>} - """ - self.npc_name = npc_name - self.avatar_path = avatar_path - self.default_greeting = default_greeting - self.greetings = greetings if greetings is not None else [] - self.sections = OrderedDict() - all_sections = [default_greeting] - if (greetings is not None): - all_sections += greetings - if (sections is not None): - all_sections += sections - if (__debug__): - section_ids = [section.id for section in all_sections] - for section in all_sections: - # Sanity check: All DialogueResponses should have next_section_id - # attributes that refer to valid DialogueSections in the Dialogue. - if (__debug__): - for response in section.responses: - assert response.next_section_id in section_ids + \ - ['end', 'back'], ('"{0}" does not refer to a ' - 'DialogueSection in this Dialogue')\ - .format(response.next_section_id) - self.sections[section.id] = section - - def __str__(self): - """Return the string representation of a L{Dialogue} instance.""" - string_representation = 'Dialogue(npc_id={0.npc_name})'.format(self) - return string_representation - - -class DialogueNode(object): - """ - Abstract base class that represents a node or related group of attributes - within a Dialogue. - """ - def __init__(self, text, actions=None): - """ - Initialize a new L{DialogueNode} instance. - - @param text: textual content of the L{DialogueNode}. - @type text: basestring - @param actions: dialogue actions associated with the L{DialogueNode}. - @type actions: list of L{DialogueActions<DialogueAction>} - """ - self.text = text - self.actions = actions or [] - - -class DialogueSection(DialogueNode): - """DialogueNode that represents a distinct section of the dialogue.""" - __slots__ = ['id', 'text', 'responses', 'actions'] - - def __init__(self, id_, text, responses=None, actions=None): - """ - Initialize a new L{DialogueSection} instance. - - @param id_: named used to uniquely identify the L{DialogueSection} - within a L{Dialogue}. - @type id_: basestring - @param text: text displayed as the NPC's part of the L{Dialogue}. - @type text: basestring - @param responses: possible responses that the player can choose from. - @type responses: list of L{DialogueResponses<DialogueResponse>} - @param actions: dialogue actions that should be executed when the - L{DialogueSection} is reached. - @type actions: list of L{DialogueActions<DialogueAction>} - """ - DialogueNode.__init__(self, text=text, actions=actions) - self.id = id_ - if (responses is not None): - self.responses = list(responses) - - -class DialogueGreeting(DialogueSection): - """ - Represents a root section of dialogue in a L{Dialogue} along with the - conditional statement used to determine the whether this section should be - displayed first upon dialogue initiation. - - @ivar id: Name used to uniquely identify the L{DialogueSection} to which - the L{DialogueRootSectionReference} points. - @type id: basestring - @ivar condition: Boolean Python expression used to determine if the - L{DialogueSection} referenced is a valid starting section. - @type condition: basestring - """ - __slots__ = ['id', 'condition', 'text', 'actions', 'responses'] - - def __init__(self, id_, condition, text, responses=None, actions=None): - """ - Initialize a new L{DialogueGreeting} instance. - - @param id_: named used to uniquely identify the L{DialogueSection} - within a L{Dialogue}. - @type id_: basestring - @param condition: Boolean Python expression used to determine if this - root dialogue section should be displayed. - @type condition: basestring - @param text: text displayed as the NPC's part of the L{Dialogue}. - @type text: basestring - @param responses: possible responses that the player can choose from. - @type responses: list of L{DialogueResponses<DialogueResponse>} - @param actions: dialogue actions that should be executed when the - L{DialogueSection} is reached. - @type actions: list of L{DialogueActions<DialogueAction>} - """ - DialogueSection.__init__(self, id_=id_, text=text, responses=responses, - actions=actions) - self.condition = condition - - -class DialogueResponse(DialogueNode): - """ - L{DialogueNode} that represents one possible player response to a - particular L{DialogueSection}. - """ - __slots__ = ['text', 'actions', 'condition', 'next_section_id'] - - def __init__(self, text, next_section_id, actions=None, condition=None): - """ - Initialize a new L{DialogueResponse} instance. - - @param text: text displayed as the content of the player's response. - @type text: basestring - @param next_section_id: ID of the L{DialogueSection} that should be - jumped to if this response is chosen by the player. - @type next_section_id: basestring - @param actions: dialogue actions that should be executed if this - response is chosen by the player. - @type actions: list of L{DialogueActions<DialogueAction>} - @param condition: Python expression that when evaluated determines - whether the L{DialogueResponse} should be displayed to the player - as a valid response. - @type condition: basestring - """ - DialogueNode.__init__(self, text=text, actions=actions) - self.condition = condition - self.next_section_id = next_section_id
--- a/src/parpg/dialogueactions.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,410 +0,0 @@ -# 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/>. -""" -Provides classes used to implement dialogue logic and allow dialogues to have -external effects on the game state. -""" -import logging - -from parpg.components import container - -logger = logging.getLogger('dialogueaction') - -class DialogueAction(object): - """ - Abstract base class for subclasses that represent dialogue actions embedded - within a DialogueSection or DialogueResponse. - - Subclasses must define the keyword class variable and implement both the - __init__ and __call__ methods. - - @cvar keyword: keyword used by the L{DialogueParser} to recognize the - L{DialogueAction} in serialized L{Dialogues<Dialogues>}. - @type keyword: basestring - """ - logger = logging.getLogger('dialogueaction.DialogueAction') - registered_actions = {} - - @classmethod - def registerAction(cls, dialogue_action_type): - """ - Register a L{DialogueAction} subclass for easy reference. - - @param dialogue_action_type: dialogue action to register. - @type dialogue_action_type: L{DialogueAction} subclass - """ - cls.registered_actions[dialogue_action_type.keyword] = \ - dialogue_action_type - - def __init__(self, *args, **kwargs): - """ - Initialize a new L{DialogueAction} instance. - - @param args: positional arguments passed by the L{DialogueParser} after - reading a serialized L{Dialogue}. - @type args: list of objects - @param kwargs: keyword arguments passed by the L{DialogueParser} after - reading a serialized L{Dialogue}. - @type kwargs: dict of objects - """ - if (not hasattr(type(self), 'keyword')): - raise AttributeError('DialogueAction subclasses must define the ' - 'keyword class variable.') - self.arguments = (args, kwargs) - - def __call__(self, game_state): - """ - Execute the L{DialogueAction}. - - @param game_state: variables and functions that make up the current - game state. - @type game_state: dict of objects - """ - raise NotImplementedError('subclasses of DialogueAction must ' - 'override __call__') - - -class MeetAction(DialogueAction): - """ - L{DialogueAction} that adds an NPC to the list of NPCs known by the player. - """ - keyword = 'meet' - - def __init__(self, *args, **kwargs): - """ - Initialize a new L{MeetAction} instance. - - @param args: positional arguments. - @type args: list of objects - @param npc_id: identifier of the NPC that the player has met. - @type npc_id: basestring - @param kwargs: keyword arguments (not used). - @type kwargs: dict of objects - """ - DialogueAction.__init__(self, *args, **kwargs) - - def __call__(self, game_state): - """ - Add an NPC to the list of NPCs known by the player. - - @param game_state: variables and functions that make up the current - game state. - @type game_state: dict of objects - """ - npc_id = game_state["npc"].general.identifier - # NOTE Technomage 2010-11-13: This print statement seems overly - # verbose, so I'm logging it as an INFO message instead. -# print("You've met {0}!".format(npc_id)) - self.logger.info("You've met {0}!".format(npc_id)) - game_state['meet'](npc_id) -DialogueAction.registerAction(MeetAction) - - -class InventoryAction(DialogueAction): - """ - Abstract base class for L{DialogueActions<DialogueAction>} used to - manipulate the NPC's and the player's inventory. - """ - def __init__(self, *args, **kwargs): - """ - Initialize a new L{InventoryAction} instance. - - @param args: positional arguments. - @type args: list of objects - @param item_types: item types that should be manipulated. - @type item_types: list of basestrings - @param kwargs: keyword arguments. - @type kwargs: dict of objects - """ - DialogueAction.__init__(self, *args, **kwargs) - self.item_types = args - - -class TakeStuffAction(InventoryAction): - """ - L{InventoryAction} used to move items from the NPC's inventory to the - player's inventory. - """ - keyword = 'take_stuff' - - def __call__(self, game_state): - """ - Move items from the NPC's inventory to the player's inventory. - - @param game_state: variables and functions that make up the current - game state. - @type game_state: dict of objects - """ - item_types = self.item_types - for item_type in item_types: - item = container.take_item(game_state['npc'].container, item_type) - if (item): - container.put_item(game_state['pc'].container, item) - print("{0} gave you the {1}".format(game_state['npc']. - description.view_name, - item_type)) - else: - print("{0} doesn't have the {1}".format(game_state['npc']. - description.view_name, - item_type)) -DialogueAction.registerAction(TakeStuffAction) - - -class GiveStuffAction(InventoryAction): - """ - L{InventoryAction} used to move items from the player's inventory to the - NPC's inventory. - """ - keyword = 'give_stuff' - - def __call__(self, game_state): - """ - Move items from the player's inventory to the NPC's inventory. - - @param game_state: variables and functions that make up the current - game state. - @type game_state: dict of objects - """ - item_types = self.item_types - for item_type in item_types: - item = container.take_item(game_state['pc'].container, item_type) - if (item): - container.put_item(game_state['npc'].container, item) - print("You give the {0} to {1}".format(item_type, - game_state['npc']. - description.view_name)) - else: - print("You don't have the {0}".format(item_type)) -DialogueAction.registerAction(GiveStuffAction) - - -class ReplaceItemAction(InventoryAction): - """ - L{InventoryAction} used to replace an item with another in the player's - inventory. - """ - - keyword = 'replace_item' - - def __call__(self, game_state): - """ - Take an item from the player and place another at its place. - - @param game_state: variables and functions that make up the current - game state. - @type game_state: dict of objects - """ - old_type = self.item_types[0] - new_type = self.item_types[1] - item = container.take_item(game_state['pc'].container, old_type) - if item: - model = game_state['model'] - new_item = model.createItemByType(new_type, new_type, - item.entity.world) - container.put_item(game_state['pc'].container, - new_item.containable, item.slot) - model.deleteObject(item.entity.general.identifier) - item.delete() - print("{0} took the {1} and gave you the {2}".format( - game_state['npc'].description.view_name, old_type, item_type)) - else: - print("You don't have the {0}".format(old_type)) -DialogueAction.registerAction(ReplaceItemAction) - -class QuestAction(DialogueAction): - """ - Abstract base class for quest-related L{DialogueActions<DialogueAction>}. - """ - def __init__(self, *args, **kwargs): - """ - Initialize a new L{QuestAction} instance. - - @param args: positional arguments. - @type args: list of objects - @param quest_id: ID of the quest to manipulate. - @type quest_id: basestring - @param kwargs: keyword arguments (not used). - @type kwargs: dict of objects - """ - DialogueAction.__init__(self, *args, **kwargs) - self.quest_id = kwargs['quest'] if 'quest' in kwargs else args[0] - - -class StartQuestAction(QuestAction): - """L{QuestAction} used to activate a quest.""" - keyword = 'start_quest' - - def __call__(self, game_state): - """ - Activate a quest. - - @param game_state: variables and functions that make up the current - game state. - @type game_state: dict of objects - """ - quest_id = self.quest_id - print("You've picked up the \"{0}\" quest!".format(quest_id)) - game_state['quest'].activateQuest(quest_id) -DialogueAction.registerAction(StartQuestAction) - - -class CompleteQuestAction(QuestAction): - """ - L{QuestAction} used to mark a quest as successfully finished/completed. - """ - keyword = 'complete_quest' - - def __call__(self, game_state): - """ - Successfully complete a quest. - - @param game_state: variables and functions that make up the current - game state. - @type game_state: dict of objects - """ - quest_id = self.quest_id - print("You've finished the \"{0}\" quest".format(quest_id)) - game_state['quest'].finishQuest(quest_id) -DialogueAction.registerAction(CompleteQuestAction) - - -class FailQuestAction(QuestAction): - """L{QuestAction} used to fail an active quest.""" - keyword = 'fail_quest' - - def __call__(self, game_state): - """ - Fail an active quest. - - @param game_state: variables and functions that make up the current - game state. - @type game_state: dict of objects - """ - quest_id = self.quest_id - print("You've failed the \"{0}\" quest".format(quest_id)) - game_state['quest'].failQuest(quest_id) -DialogueAction.registerAction(FailQuestAction) - - -class RestartQuestAction(QuestAction): - """L{QuestAction} used to restart an active quest.""" - keyword = 'restart_quest' - - def __call__(self, game_state): - """ - Restart an active quest. - - @param game_state: variables and functions that make up the current - game state. - @type game_state: dict of objects - """ - quest_id = self.quest_id - print("You've restarted the \"{0}\" quest".format(quest_id)) - game_state['quest'].restartQuest(quest_id) -DialogueAction.registerAction(RestartQuestAction) - - -class QuestVariableAction(QuestAction): - """ - Base class for L{QuestActions<QuestAction>} that modify quest - variables. - """ - def __init__(self, *args, **kwargs): - """ - Initialize a new L{QuestVariableAction} instance. - - @param args: positional arguments (not used). - @type args: list of objects - @param kwargs: keyword arguments. - @type kwargs: dict of objects - @keyword quest: ID of the quest whose variable should be modified. - @type quest: basestring - @keyword variable: name of the quest variable to modify. - @type variable: basestring - @keyword value: new value that should be used to modify the quest - variable. - @type value: object - """ - QuestAction.__init__(self, *args, **kwargs) - self.variable_name = kwargs['variable'] - self.value = kwargs['value'] - - -class IncreaseQuestVariableAction(QuestVariableAction): - """ - L{QuestVariableAction} used to increase the value of a quest variable by a - set amount. - """ - keyword = 'increase_quest_variable' - - def __call__(self, game_state): - """ - Increase a quest variable by a set amount. - - @param game_state: variables and functions that make up the current - game state. - @type game_state: dict of objects - """ - quest_id = self.quest_id - variable_name = self.variable_name - value = self.value - print('Increased {0} by {1}'.format(variable_name, value)) - game_state['quest'][quest_id].increaseValue(variable_name, value) -DialogueAction.registerAction(IncreaseQuestVariableAction) - - -class DecreaseQuestVariableAction(QuestVariableAction): - """ - L{QuestVariableAction} used to decrease the value of a quest variable by a - set amount. - """ - keyword = 'decrease_quest_variable' - - def __call__(self, game_state): - """ - Decrease a quest variable by a set amount. - - @param game_state: variables and functions that make up the current - game state. - @type game_state: dict of objects - """ - quest_id = self.quest_id - variable_name = self.variable_name - value = self.value - print('Decreased {0} by {1}'.format(variable_name, value)) - game_state['quest'][quest_id].decreaseValue(variable_name, value) -DialogueAction.registerAction(DecreaseQuestVariableAction) - - -class SetQuestVariableAction(QuestVariableAction): - """ - L{QuestVariableAction} used to set the value of a quest variable. - """ - keyword = 'set_quest_variable' - - def __call__(self, game_state): - """ - Set the value of a quest variable. - - @param game_state: variables and functions that make up the current - game state. - @type game_state: dict of objects - """ - quest_id = self.quest_id - variable_name = self.variable_name - value = self.value - print('Set {0} to {1}'.format(variable_name, value)) - game_state['quest'][quest_id].setValue(variable_name, value) -DialogueAction.registerAction(SetQuestVariableAction)
--- a/src/parpg/dialoguecontroller.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,63 +0,0 @@ -# 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/>. -from controllerbase import ControllerBase - -class DialogueController(ControllerBase): - """Controller that takes over when a dialogue is started""" - 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 - """ - super(DialogueController, self).__init__(engine, - view, - model, - application) - self.dialogue = None - self.view = view - - def startTalk(self, npc): - if npc.dialogue is not None: - self.model.active_map.centerCameraOnPlayer() - npc.fifeagent.behaviour.talk( - self.model.game_state.\ - getObjectById("PlayerCharacter").fifeagent - ) - self.dialogue = self.view.hud.showDialogue(npc) - self.dialogue.initiateDialogue() - self.model.pause(True) - self.view.hud.enabled = False - - - def pump(self, dt): - ControllerBase.pump(self, dt) - if self.dialogue and not self.dialogue.active: - self.application.manager.pop_mode() - self.model.pause(False) - self.view.hud.enabled = True -
--- a/src/parpg/dialogueparsers.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,666 +0,0 @@ -# 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/>. -""" -Contains classes for parsing and validating L{Dialogues<Dialogue>} and other -dialogue-related data. - -@TODO Technomage 2010-11-13: Exception handling + validation needs work. - Currently YAML files are only crudely validated - the code assumes that - the file contains valid dialogue data, and if that assumption is - violated and causes the code to raise any TypeErrors, AttributeErrors or - ValueErrors the code then raises a DialogueFormatError with the - original (and mostly unhelpful) error message. -@TODO Technomage 2010-11-13: Support reading and writing unicode. -""" -try: - from cStringIO import StringIO -except ImportError: - from StringIO import StringIO -from collections import Sequence -try: - from collections import OrderedDict -except ImportError: - # Python version 2.4-2.6 doesn't have the OrderedDict - from parpg.common.ordereddict import OrderedDict -import re -import textwrap - -import yaml - -from parpg import COPYRIGHT_HEADER -from parpg.dialogue import (Dialogue, DialogueSection, DialogueResponse, - DialogueGreeting) -from parpg.dialogueactions import DialogueAction - -import logging -logger = logging.getLogger('dialogueparser') - -class DialogueFormatError(Exception): - """Exception thrown when the DialogueParser has encountered an error.""" - - -class AbstractDialogueParser(object): - """ - Abstract base class defining the interface for parsers responsible for - constructing a L{Dialogue} from its serialized representation. - """ - def load(self, stream): - """ - Parse a stream and attempt to construct a new L{Dialogue} instance from - its serialized representation. - - @param stream: open stream containing the serialized representation of - a Dialogue. - @type stream: BufferType - """ - raise NotImplementedError('AbstractDialogueParser subclasses must ' - 'override the load method.') - - def dump(self, dialogue, stream): - """ - Serialize a L{Dialogue} instance and dump it to an open stream. - - @param dialogue: dialogue to serialize. - @type dialogue: L{Dialogue} - @param stream: open stream into which the serialized L{Dialogue} should - be dumped. - @type stream: BufferType - """ - raise NotImplementedError('AbstractDialogueParser subclasses must ' - 'override the dump method.') - - def validate(self, stream): - """ - Parse a stream and verify that it contains a valid serialization of a - L{Dialogue instance}. - - @param stream: stream containing the serialized representation of a - L{Dialogue} - @type stream: BufferType - """ - raise NotImplementedError('AbstractDialogueParser subclasses must ' - 'override the validate method.') - - -class YamlDialogueParser(AbstractDialogueParser): - """ - L{AbstractDialogueParser} subclass responsible for parsing dialogues - serialized in YAML. - """ - logger = logging.getLogger('dialogueparser.OldYamlDialogueParser') - - def load(self, stream, loader_class=yaml.Loader): - """ - Parse a YAML stream and attempt to construct a new L{Dialogue} - instance. - - @param stream: stream containing the serialized YAML representation of - a L{Dialogue}. - @type stream: BufferType - @param loader_class: PyYAML loader class to use for reading the - serialization. - @type loader_class: yaml.BaseLoader subclass - """ - loader = loader_class(stream) - dialogue = self._constructDialogue(loader, loader.get_single_node()) - return dialogue - - def dump(self, dialogue, output_stream, dumper_class=yaml.Dumper): - """ - Serialize a L{Dialogue} instance as YAML and dump it to an open stream. - - @param dialogue: dialogue to serialize. - @type dialogue: L{Dialogue} - @param stream: open stream into which the serialized L{Dialogue} should - be dumped. - @type stream: BufferType - @param dumper_class: PyYAML dumper class to use for formatting the - serialization. - @type dumper_class: yaml.BaseDumper subclass - """ - intermediate_stream = StringIO() - # KLUDE Technomage 2010-11-16: The "width" argument seems to be broken, - # as it doesn't take into about current line indentation and fails - # to correctly wrap at word boundaries. - dumper = dumper_class(intermediate_stream, default_flow_style=False, - indent=4, width=99999, line_break='\n', - allow_unicode=True, explicit_start=True, - explicit_end=True, tags=False) - dialogue_node = self._representDialogue(dumper, dialogue) - dumper.open() - dumper.serialize(dialogue_node) - dumper.close() - file_contents = intermediate_stream.getvalue() - - file_contents = re.sub(r'(\n|\r|\r\n)(\s*)(GOTO: .*)', r'\1\2\3\1\2', - file_contents) - lines = file_contents.splitlines() - max_line_length = 76 # 79 - 3 chars for escaping newlines - for i in range(len(lines)): - line = lines[i] - match = re.match( - r'^(\s*(?:-\s+)?)(SAY|REPLY|CONDITION):\s+"(.*)"$', - line - ) - if (match and len(line) > max_line_length): - # Wrap long lines for readability. - initial_indent = len(match.group(1)) - subsequent_indent = initial_indent + 4 - text_wrapper = textwrap.TextWrapper( - max_line_length, - subsequent_indent=' ' * subsequent_indent, - break_long_words=False, - break_on_hyphens=False - ) - new_lines = text_wrapper.wrap(line) - new_lines = ( - new_lines[:1] + [re.sub(r'^(\s*) (.*)$', r'\1\ \2', l) - for l in new_lines[1:]] - ) - lines[i] = '\\\n'.join(new_lines) - - output_stream.write(COPYRIGHT_HEADER) - output_stream.write('\n'.join(lines)) - - - def _representDialogue(self, dumper, dialogue): - dialogue_node = dumper.represent_dict({}) - dialogue_dict = OrderedDict() - dialogue_dict['NPC_NAME'] = dialogue.npc_name - dialogue_dict['AVATAR_PATH'] = dialogue.avatar_path - dialogue_dict['DEFAULT_GREETING'] = \ - self._representDialogueSection(dumper, - dialogue.default_greeting) - # NOTE Technomage 2010-11-16: Dialogue stores its sections in an - # OrderedDict, so a round-trip load, dump, and load will preserve - # the order of DialogueSections. - if (len(dialogue.greetings) > 0): - greetings_list_node = dumper.represent_list([]) - greetings_list = greetings_list_node.value - for greeting in dialogue.greetings: - greeting_node = \ - self._representRootDialogueSection(dumper, greeting) - greetings_list.append(greeting_node) - dialogue_dict['GREETINGS'] = greetings_list_node - if (len(dialogue.setions) > 0): - sections_list_node = dumper.represent_list([]) - sections_list = sections_list_node.value - for section in dialogue.sections.values(): - section_node = self._representDialogueSection(dumper, section) - sections_list.append(section_node) - dialogue_dict['SECTIONS'] = sections_list_node - - for key, value in dialogue_dict.items(): - if (isinstance(key, yaml.Node)): - key_node = key - else: - key_node = dumper.represent_data(key) - if (isinstance(value, yaml.Node)): - value_node = value - else: - value_node = dumper.represent_data(value) - dialogue_node.value.append((key_node, value_node)) - return dialogue_node - - def _representRootDialogueSection(self, dumper, greeting): - greeting_node = dumper.represent_dict({}) - greeting_dict = OrderedDict() - greeting_dict['ID'] = greeting.id - greeting_dict['CONDITION'] = dumper.represent_scalar( - 'tag:yaml.org,2002:str', - greeting.condition, - style='"' - ) - for key, value in greeting_dict.items(): - if (isinstance(key, yaml.Node)): - key_node = key - else: - key_node = dumper.represent_data(key) - if (isinstance(value, yaml.Node)): - value_node = value - else: - value_node = dumper.represent_data(value) - greeting_node.value.append((key_node, value_node)) - return greeting_node - - def _representDialogueSection(self, dumper, dialogue_section): - section_node = dumper.represent_dict({}) - section_dict = OrderedDict() # OrderedDict is required to preserve - # the order of attributes. - section_dict['ID'] = dialogue_section.id - # KLUDGE Technomage 2010-11-16: Hard-coding the tag like this could be - # a problem when writing unicode. - section_dict['SAY'] = dumper.represent_scalar('tag:yaml.org,2002:str', - dialogue_section.text, - style='"') - actions_list_node = dumper.represent_list([]) - actions_list = actions_list_node.value - for action in dialogue_section.actions: - action_node = self._representDialogueAction(dumper, action) - actions_list.append(action_node) - if (actions_list): - section_dict['ACTIONS'] = actions_list_node - responses_list_node = dumper.represent_list([]) - responses_list = responses_list_node.value - for response in dialogue_section.responses: - response_node = self._representDialogueResponse(dumper, response) - responses_list.append(response_node) - section_dict['RESPONSES'] = responses_list_node - - for key, value in section_dict.items(): - if (isinstance(key, yaml.Node)): - key_node = key - else: - key_node = dumper.represent_data(key) - if (isinstance(value, yaml.Node)): - value_node = value - else: - value_node = dumper.represent_data(value) - section_node.value.append((key_node, value_node)) - return section_node - - def _representDialogueResponse(self, dumper, dialogue_response): - response_node = dumper.represent_dict({}) - response_dict = OrderedDict() - # KLUDGE Technomage 2010-11-16: Hard-coding the tag like this could be - # a problem when writing unicode. - response_dict['REPLY'] = dumper.represent_scalar( - 'tag:yaml.org,2002:str', - dialogue_response.text, - style='"') - if (dialogue_response.condition is not None): - response_dict['CONDITION'] = dumper.represent_scalar( - 'tag:yaml.org,2002:str', - dialogue_response.condition, - style='"' - ) - actions_list_node = dumper.represent_list([]) - actions_list = actions_list_node.value - for action in dialogue_response.actions: - action_node = self._representDialogueAction(dumper, action) - actions_list.append(action_node) - if (actions_list): - response_dict['ACTIONS'] = actions_list_node - response_dict['GOTO'] = dialogue_response.next_section_id - - for key, value in response_dict.items(): - if (isinstance(key, yaml.Node)): - key_node = key - else: - key_node = dumper.represent_data(key) - if (isinstance(value, yaml.Node)): - value_node = value - else: - value_node = dumper.represent_data(value) - response_node.value.append((key_node, value_node)) - return response_node - - def _representDialogueAction(self, dumper, dialogue_action): - action_node = dumper.represent_dict({}) - action_dict = OrderedDict() - args, kwargs = dialogue_action.arguments - if (args and not kwargs): - arguments = list(args) - elif (kwargs and not args): - arguments = kwargs - else: - arguments = [list(args), kwargs] - action_dict[dialogue_action.keyword] = arguments - - for key, value in action_dict.items(): - if (isinstance(key, yaml.Node)): - key_node = key - else: - key_node = dumper.represent_data(key) - if (isinstance(value, yaml.Node)): - value_node = value - else: - value_node = dumper.represent_data(value) - action_node.value.append((key_node, value_node)) - return action_node - - def _constructDialogue(self, loader, yaml_node): - npc_name = None - avatar_path = None - default_greeting = None - greetings = [] - sections = [] - - try: - for key_node, value_node in yaml_node.value: - key = key_node.value - if (key == u'NPC_NAME'): - npc_name = loader.construct_object(value_node) - elif (key == u'AVATAR_PATH'): - avatar_path = loader.construct_object(value_node) - elif (key == u'DEFAULT_GREETING'): - default_greeting = \ - self._constructDialogueSection(loader, value_node) - elif (key == u'GREETINGS'): - for greeting_node in value_node.value: - greeting = self._constructRootDialogueSection( - loader, - greeting_node - ) - greetings.append( - greeting - ) - elif (key == u'SECTIONS'): - for section_node in value_node.value: - dialogue_section = self._constructDialogueSection( - loader, - section_node - ) - sections.append(dialogue_section) - except (AttributeError, TypeError, ValueError, - yaml.scanner.ScannerError) as error: - raise DialogueFormatError(error) - - dialogue = Dialogue(npc_name=npc_name, avatar_path=avatar_path, - default_greeting=default_greeting, - greetings=greetings, - sections=sections) - return dialogue - - def _constructRootDialogueSection(self, loader, greeting_node): - id = None - text = None - condition = None - responses = [] - actions = [] - greeting = None - - try: - for key_node, value_node in greeting_node.value: - key = key_node.value - if (key == u'ID'): - id = loader.construct_object(value_node) - elif (key == u'SAY'): - text = loader.construct_object(value_node) - elif (key == u'CONDITION'): - condition = loader.construct_object(value_node) - elif (key == u'RESPONSES'): - for response_node in value_node.value: - dialogue_response = self._constructDialogueResponse( - loader, - response_node - ) - responses.append(dialogue_response) - elif (key == u'ACTIONS'): - for action_node in value_node.value: - action = self._constructDialogueAction(loader, - action_node) - actions.append(action) - except (AttributeError, TypeError, ValueError) as e: - raise DialogueFormatError(e) - else: - greeting = DialogueSection(id=id, text=text, - condition=condition, - responses=responses, - actions=actions) - - return greeting - - def _constructDialogueSection(self, loader, section_node): - id_ = None - text = None - responses = [] - actions = [] - dialogue_section = None - - try: - for key_node, value_node in section_node.value: - key = key_node.value - if (key == u'ID'): - id_ = loader.construct_object(value_node) - elif (key == u'SAY'): - text = loader.construct_object(value_node) - elif (key == u'RESPONSES'): - for response_node in value_node.value: - dialogue_response = self._constructDialogueResponse( - loader, - response_node - ) - responses.append(dialogue_response) - elif (key == u'ACTIONS'): - for action_node in value_node.value: - action = self._constructDialogueAction(loader, - action_node) - actions.append(action) - except (AttributeError, TypeError, ValueError) as e: - raise DialogueFormatError(e) - else: - dialogue_section = DialogueSection(id_=id_, text=text, - responses=responses, - actions=actions) - - return dialogue_section - - def _constructDialogueResponse(self, loader, response_node): - text = None - next_section_id = None - actions = [] - condition = None - - try: - for key_node, value_node in response_node.value: - key = key_node.value - if (key == u'REPLY'): - text = loader.construct_object(value_node) - elif (key == u'ACTIONS'): - for action_node in value_node.value: - action = self._constructDialogueAction(loader, - action_node) - actions.append(action) - elif (key == u'CONDITION'): - condition = loader.construct_object(value_node) - elif (key == u'GOTO'): - next_section_id = loader.construct_object(value_node) - except (AttributeError, TypeError, ValueError) as e: - raise DialogueFormatError(e) - - dialogue_response = DialogueResponse(text=text, - next_section_id=next_section_id, - actions=actions, - condition=condition) - return dialogue_response - - def _constructDialogueAction(self, loader, action_node): - mapping = loader.construct_mapping(action_node, deep=True) - keyword, arguments = mapping.items()[0] - if (isinstance(arguments, dict)): - # Got a dictionary of keyword arguments. - args = () - kwargs = arguments - elif (not isinstance(arguments, Sequence) or - isinstance(arguments, basestring)): - # Got a single positional argument. - args = (arguments,) - kwargs = {} - elif (not len(arguments) == 2 or not isinstance(arguments[1], dict)): - # Got a list of positional arguments. - args = arguments - kwargs = {} - else: - self.logger.error( - '{0} is an invalid DialogueAction argument'.format(arguments) - ) - return None - - action_type = DialogueAction.registered_actions.get(keyword) - if (action_type is None): - self.logger.error( - 'no DialogueAction with keyword "{0}"'.format(keyword) - ) - dialogue_action = None - else: - dialogue_action = action_type(*args, **kwargs) - return dialogue_action - - -class OldYamlDialogueParser(YamlDialogueParser): - """ - L{YAMLDialogueParser} that can read and write dialogues in the old - Techdemo1 dialogue file format. - - @warning: This class is deprecated and likely to be removed in a future - version. - """ - logger = logging.getLogger('dialogueparser.OldYamlDialogueParser') - - def __init__(self): - self.response_actions = {} - - def load(self, stream): - dialogue = YamlDialogueParser.load(self, stream) - # Place all DialogueActions that were in DialogueSections into the - # DialogueResponse that led to the action's original section. - for section in dialogue.sections.values(): - for response in section.responses: - actions = self.response_actions.get(response.next_section_id) - if (actions is not None): - response.actions = actions - return dialogue - - def _constructDialogue(self, loader, yaml_node): - npc_name = None - avatar_path = None - start_section_id = None - sections = [] - - try: - for key_node, value_node in yaml_node.value: - key = key_node.value - if (key == u'NPC'): - npc_name = loader.construct_object(value_node) - elif (key == u'AVATAR'): - avatar_path = loader.construct_object(value_node) - elif (key == u'START'): - start_section_id = loader.construct_object(value_node) - elif (key == u'SECTIONS'): - for id_node, section_node in value_node.value: - dialogue_section = self._constructDialogueSection( - loader, - id_node, - section_node - ) - sections.append(dialogue_section) - except (AttributeError, TypeError, ValueError) as e: - raise DialogueFormatError(e) - - dialogue = Dialogue(npc_name=npc_name, avatar_path=avatar_path, - start_section_id=start_section_id, - sections=sections) - return dialogue - - def _constructDialogueSection(self, loader, id_node, section_node): - id = loader.construct_object(id_node) - text = None - responses = [] - actions = [] - dialogue_section = None - - try: - for node in section_node.value: - key_node, value_node = node.value[0] - key = key_node.value - if (key == u'say'): - text = loader.construct_object(value_node) - elif (key == u'meet'): - action = self._constructDialogueAction(loader, node) - actions.append(action) - elif (key in [u'start_quest', u'complete_quest', u'fail_quest', - u'restart_quest', u'set_value', - u'decrease_value', u'increase_value', - u'give_stuff', u'get_stuff']): - action = self._constructDialogueAction(loader, node) - if (id not in self.response_actions.keys()): - self.response_actions[id] = [] - self.response_actions[id].append(action) - elif (key == u'responses'): - for response_node in value_node.value: - dialogue_response = self._constructDialogueResponse( - loader, - response_node - ) - responses.append(dialogue_response) - except (AttributeError, TypeError, ValueError) as e: - raise DialogueFormatError(e) - else: - dialogue_section = DialogueSection(id=id, text=text, - responses=responses, - actions=actions) - - return dialogue_section - - def _constructDialogueResponse(self, loader, response_node): - text = None - next_section_id = None - actions = [] - condition = None - - try: - text = loader.construct_object(response_node.value[0]) - next_section_id = loader.construct_object(response_node.value[1]) - if (len(response_node.value) == 3): - condition = loader.construct_object(response_node.value[2]) - except (AttributeError, TypeError, ValueError) as e: - raise DialogueFormatError(e) - - dialogue_response = DialogueResponse(text=text, - next_section_id=next_section_id, - actions=actions, - condition=condition) - return dialogue_response - - def _constructDialogueAction(self, loader, action_node): - mapping = loader.construct_mapping(action_node, deep=True) - keyword, arguments = mapping.items()[0] - if (keyword == 'get_stuff'): - # Renamed keyword in new syntax. - keyword = 'take_stuff' - elif (keyword == 'set_value'): - keyword = 'set_quest_value' - elif (keyword == 'increase_value'): - keyword = 'increase_quest_value' - elif (keyword == 'decrease_value'): - keyword = 'decrease_quest_value' - if (isinstance(arguments, dict)): - # Got a dictionary of keyword arguments. - args = () - kwargs = arguments - elif (not isinstance(arguments, Sequence) or - isinstance(arguments, basestring)): - # Got a single positional argument. - args = (arguments,) - kwargs = {} - elif (not len(arguments) == 2 or not isinstance(arguments[1], dict)): - # Got a list of positional arguments. - args = arguments - kwargs = {} - else: - self.logger.error( - '{0} is an invalid DialogueAction argument'.format(arguments) - ) - return None - action_type = DialogueAction.registered_actions.get(keyword) - if (action_type is None): - self.logger.error( - 'no DialogueAction with keyword "{0}"'.format(keyword) - ) - dialogue_action = None - else: - dialogue_action = action_type(*args, **kwargs) - return dialogue_action
--- a/src/parpg/dialogueprocessor.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,378 +0,0 @@ -# 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/>. -""" -Provides the core interface to the dialogue subsystem used to process player -L{Dialogues<Dialogue>} with NPCs. -""" -import logging - -from parpg.common.utils import dedent_chomp - -if (__debug__): - from collections import Sequence, MutableMapping - from parpg.dialogue import Dialogue - -logger = logging.getLogger('dialogueprocessor') - -class DialogueProcessor(object): - """ - Primary interface to the dialogue subsystem used to initiate and process a - L{Dialogue} with an NPC. - - To begin a dialogue with an NPC a L{DialogueProcessor} must first be - instantiated with the dialogue data to process and a dictionary of Python - objects defining the game state for testing of response conditionals. The - L{initiateDialogue} must be called to initialized the L{DialogueProcessor}, - and once it is initialized processing of - L{DialogueSections<DialogueSection>} and - L{DialogueResponses<DialogueResponse>} can be initiated via the - L{continueDialogue} and L{reply} class methods. - - The state of dialogue processing is stored via the - L{dialogue_section_stack} class attribute, which stores a list of - L{DialogueSections<DialogueSection>} that have been or are currently being - processed. Each time L{reply} is called with a L{DialogueResponse} its - next_section_id attribute is used to select a new L{DialogueSection} from - the L{dialogue}. The selected L{DialogueSection} is then pushed - onto the end of the L{dialogue_section_stack}, ready to be processed via - L{continueDialogue}. The exception to this rule occurs when L{reply} is - called with a L{DialogueResponse} whose next_section_id attribute is "end" - or "back". "end" terminates the dialogue as described below, while "back" - removes the last L{DialogueSection} on the L{dialogue_section_stack} - effectively going back to the previous section of dialogue. - - The L{DialogueProcessor} terminates dialogue processing once L{reply} is - called with a L{DialogueResponse} whose next_section_id == 'end'. - Processing can also be manually terminated by calling the L{endDialogue} - class method. - - @note: See the dialogue_demo.py script for a complete example of how the - L{DialogueProcessor} can be used. - - @ivar dialogue: dialogue data currently being processed. - @type dialogue: L{Dialogue} - @ivar dialogue_section_stack: sections of dialogue that have been or are - currently being processed. - @type dialogue_section_stack: list of L{DialogueSections<DialogueSection>} - @ivar game_state: objects defining the game state that should be made - available for testing L{DialogueResponse} conditionals. - @type game_state: dict of Python objects - @ivar in_dialogue: whether a dialogue has been initiated. - @type in_dialogue: Bool - - Usage: - >>> game_state = {'pc': player_character, 'quest': quest_engine} - >>> dialogue_processor = DialogueProcessor(dialogue, game_state) - >>> dialogue_processor.initiateDialogue() - >>> while dialogue_processor.in_dialogue: - ... valid_responses = dialogue_processor.continueDialogue() - ... response = choose_response(valid_responses) - ... dialogue_processor.reply(response) - """ - _logger = logging.getLogger('dialogueengine.DialogueProcessor') - - def dialogue(): - def fget(self): - return self._dialogue - - def fset(self, dialogue): - assert isinstance(dialogue, Dialogue), \ - '{0} does not implement Dialogue interface'.format(dialogue) - self._dialogue = dialogue - - return locals() - dialogue = property(**dialogue()) - - def dialogue_section_stack(): - def fget(self): - return self._dialogue_section_stack - - def fset(self, new_value): - assert isinstance(new_value, Sequence) and not \ - isinstance(new_value, basestring), \ - 'dialogue_section_stack must be a Sequence, not {0}'\ - .format(new_value) - self._dialogue_section_stack = new_value - - return locals() - dialogue_section_stack = property(**dialogue_section_stack()) - - def game_state(): - def fget(self): - return self._game_state - - def fset(self, new_value): - assert isinstance(new_value, MutableMapping),\ - 'game_state must be a MutableMapping, not {0}'\ - .format(new_value) - self._game_state = new_value - - return locals() - game_state = property(**game_state()) - - def in_dialogue(): - def fget(self): - return self._in_dialogue - - def fset(self, value): - assert isinstance(value, bool), '{0} is not a bool'.format(value) - self._in_dialogue = value - - return locals() - in_dialogue = property(**in_dialogue()) - - def __init__(self, dialogue, game_state): - """ - Initialize a new L{DialogueProcessor} instance. - - @param dialogue: dialogue data to process. - @type dialogue: L{Dialogue} - @param game_state: objects defining the game state that should be made - available for testing L{DialogueResponse} conditions. - @type game_state: dict of objects - """ - self._dialogue_section_stack = [] - self._dialogue = dialogue - self._game_state = game_state - self._in_dialogue = False - - def getDialogueGreeting(self): - """ - Evaluate the L{RootDialogueSections<RootDialogueSection>} conditions - and return the valid L{DialogueSection} which should be displayed - first. - - @return: Valid root dialogue section. - @rtype: L{DialogueSection} - - @raise: RuntimeError - evaluation of a DialogueGreeting condition fails - by raising an exception (e.g. due to a syntax error). - """ - dialogue = self.dialogue - dialogue_greeting = None - for greeting in dialogue.greetings: - try: - condition_met = eval(greeting.condition, self.game_state) - except Exception as exception: - error_message = dedent_chomp(''' - exception raised in DialogueGreeting {id} condition: - {exception} - ''').format(id=greeting.id, exception=exception) - self._logger.error(error_message) - if (condition_met): - dialogue_greeting = greeting - if (dialogue_greeting is None): - dialogue_greeting = dialogue.default_greeting - - return dialogue_greeting - - def initiateDialogue(self): - """ - Prepare the L{DialogueProcessor} to process the L{Dialogue} by pushing - the starting L{DialogueSection} onto the L{dialogue_section_stack}. - - @raise RuntimeError: Unable to determine the root L{DialogueSection} - defined by the L{Dialogue}. - """ - if (self.in_dialogue): - self.endDialogue() - dialogue_greeting = self.getDialogueGreeting() - self.dialogue_section_stack.append(dialogue_greeting) - self.in_dialogue = True - self._logger.info('initiated dialogue {0}'.format(self.dialogue)) - - def continueDialogue(self): - """ - Process the L{DialogueSection} at the top of the - L{dialogue_section_stack}, run any L{DialogueActions<DialogueActions>} - it contains and return a list of valid - L{DialogueResponses<DialogueResponses> after evaluating any response - conditionals. - - @returns: valid responses. - @rtype: list of L{DialogueResponses<DialogueResponse>} - - @raise RuntimeError: Any preconditions are not met. - - @precondition: dialogue has been initiated via L{initiateDialogue}. - """ - if (not self.in_dialogue): - error_message = dedent_chomp(''' - dialogue has not be initiated via initiateDialogue yet - ''') - raise RuntimeError(error_message) - current_dialogue_section = self.getCurrentDialogueSection() - self.runDialogueActions(current_dialogue_section) - valid_responses = self.getValidResponses(current_dialogue_section) - - return valid_responses - - def getCurrentDialogueSection(self): - """ - Return the L{DialogueSection} at the top of the - L{dialogue_section_stack}. - - @returns: section of dialogue currently being processed. - @rtype: L{DialogueSection} - - @raise RuntimeError: Any preconditions are not met. - - @precondition: dialogue has been initiated via L{initiateDialogue} and - L{dialogue_section_stack} contains at least one L{DialogueSection}. - """ - if (not self.in_dialogue): - error_message = dedent_chomp(''' - getCurrentDialogueSection called but the dialogue has not been - initiated yet - ''') - raise RuntimeError(error_message) - try: - current_dialogue_section = self.dialogue_section_stack[-1] - except IndexError: - error_message = dedent_chomp(''' - getCurrentDialogueSection called but no DialogueSections are in - the stack - ''') - raise RuntimeError(error_message) - - return current_dialogue_section - - def runDialogueActions(self, dialogue_node): - """ - Execute all L{DialogueActions<DialogueActions>} contained by a - L{DialogueSection} or L{DialogueResponse}. - - @param dialogue_node: section of dialogue or response containing the - L{DialogueActions<DialogueAction>} to execute. - @type dialogue_node: L{DialogueNode} - """ - self._logger.info('processing commands for {0}'.format(dialogue_node)) - for command in dialogue_node.actions: - try: - command(self.game_state) - except (Exception,) as error: - self._logger.error('failed to execute DialogueAction {0}: {1}' - .format(command.keyword, error)) - # TODO Technomage 2010-11-18: Undo previous actions when an - # action fails to execute. - else: - self._logger.debug('ran {0} with arguments {1}' - .format(getattr(type(command), '__name__'), - command.arguments)) - - def getValidResponses(self, dialogue_section): - """ - Evaluate all L{DialogueResponse} conditions for a L{DialogueSection} - and return a list of valid responses. - - @param dialogue_section: section of dialogue containing the - L{DialogueResponses<DialogueResponse>} to process. - @type dialogue_section: L{DialogueSection} - - @return: responses whose conditions were met. - @rtype: list of L{DialogueResponses<DialogueResponse>} - """ - valid_responses = [] - for dialogue_response in dialogue_section.responses: - condition = dialogue_response.condition - try: - condition_met = condition is None or \ - eval(condition, self.game_state) - except (Exception,) as exception: - error_message = dedent_chomp(''' - evaluation of condition {condition} for {response} failed - with error: {exception} - ''').format(condition=dialogue_response.condition, - response=dialogue_response, exception=exception) - self._logger.error(error_message) - else: - self._logger.debug( - 'condition "{0}" for {1} evaluated to {2}' - .format(dialogue_response.condition, dialogue_response, - condition_met) - ) - if (condition_met): - valid_responses.append(dialogue_response) - - return valid_responses - - def reply(self, dialogue_response): - """ - Reply with a L{DialogueResponse}, execute the - L{DialogueActions<DialogueAction>} it contains and push the next - L{DialogueSection} onto the L{dialogue_section_stack}. - - @param dialogue_response: response to reply with. - @type dialogue_response: L{DialogueReponse} - - @raise RuntimeError: Any precondition is not met. - - @precondition: L{initiateDialogue} must be called before this method - is used. - """ - if (not self.in_dialogue): - error_message = dedent_chomp(''' - reply cannot be called until the dialogue has been initiated - via initiateDialogue - ''') - raise RuntimeError(error_message) - self._logger.info('replied with {0}'.format(dialogue_response)) - # FIXME: Technomage 2010-12-11: What happens if runDialogueActions - # raises an error? - self.runDialogueActions(dialogue_response) - next_section_id = dialogue_response.next_section_id - if (next_section_id == 'back'): - if (len(self.dialogue_section_stack) == 1): - error_message = dedent_chomp(''' - attempted to run goto: back action but stack does not - contain a previous DialogueSection - ''') - raise RuntimeError(error_message) - else: - try: - self.dialogue_section_stack.pop() - except (IndexError,): - error_message = dedent_chomp(''' - attempted to run goto: back action but the stack was - empty - ''') - raise RuntimeError(error_message) - else: - self._logger.debug( - 'ran goto: back action, restored last DialogueSection' - ) - elif (next_section_id == 'end'): - self.endDialogue() - self._logger.debug('ran goto: end action, ended dialogue') - else: - try: - next_dialogue_section = \ - self.dialogue.sections[next_section_id] - except KeyError: - error_message = dedent_chomp(''' - {0} is not a recognized goto: action or DialogueSection - identifier - ''').format(next_section_id) - raise RuntimeError(error_message) - else: - self.dialogue_section_stack.append(next_dialogue_section) - - def endDialogue(self): - """ - End the current dialogue and clean up any resources in use by the - L{DialogueProcessor}. - """ - self.dialogue_section_stack = [] - self.in_dialogue = False
--- a/src/parpg/entities/__init__.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,40 +0,0 @@ -# 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/>. - -import sys - -from general import General - -def createEntity(info, identifier, world, extra = None): - """Called when we need to get an actual object. - @type info: dict - @param info: stores information about the object we want to create - @type extra: dict - @param extra: stores additionally required attributes - @return: the object""" - # First, we try to get the world, which every game_obj needs. - extra = extra or {} - - # add the extra info - for key, val in extra.items(): - info[key].update(val) - - # this is for testing purposes - new_ent = General(world, identifier) - for component, data in info.items(): - comp_obj = getattr(new_ent, component) - for key, value in data.items(): - setattr(comp_obj, key, value) - return new_ent
--- a/src/parpg/entities/action.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,630 +0,0 @@ -# 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/>. - -#exceptions - -import logging - -logger = logging.getLogger('action') - -from parpg.gui import drag_drop_data as data_drag -from parpg.dialoguecontroller import DialogueController -from parpg.components import container, lockable - - -class NoSuchQuestException(Exception): - """NoQuestException is used when there is no active quest with the id""" - pass - -#classes - -class Action(object): - """Base Action class, to define the structure""" - - - def __init__(self, controller, commands = None): - """Basic action constructor - @param controller: A reference to the GameSceneController. - @type controller: parpg.GameSceneController - @param commands: Special commands that are executed - @type commands: Dictionary - """ - self.commands = commands or () - self.controller = controller - self.model = controller.model - self.executed = False - - def execute(self): - """To be overwritten""" - #Check if there are special commands and execute them - for command_data in self.commands: - command = command_data["Command"] - if command == "SetQuestVariable": - quest_id = command_data["ID"] - variable = command_data["Variable"] - value = command_data["Value"] - quest_engine = self.model.game_state.quest_engine - if quest_engine.hasQuest(quest_id): - quest_engine[quest_id].setValue(variable, value) - else: - raise NoSuchQuestException - elif command == "ResetMouseCursor": - self.controller.resetMouseCursor() - elif command == "StopDragging": - data_drag.dragging = False - self.executed = True - -class ChangeMapAction(Action): - """A change map scheduled""" - def __init__(self, controller, target_map_name, target_pos, commands=None): - """Initiates a change of the position of the character - possibly flagging a new map to be loaded. - @param controller: A reference to the GameSceneController. - @type controller: parpg.GameSceneController - @param commands: Special commands that are executed - @type commands: Dictionary - @type view: class derived from parpg.ViewBase - @param view: The view - @type target_map_name: String - @param target_map_name: Target map id - @type target_pos: Tuple - @param target_pos: (X, Y) coordinates on the target map. - @return: None""" - super(ChangeMapAction, self).__init__(controller, commands) - self.view = controller.view - self.target_pos = target_pos - self.target_map_name = target_map_name - - def execute(self): - """Executes the map change.""" - self.model.changeMap(self.target_map_name, - self.target_pos) - super(ChangeMapAction, self).execute() - -class OpenAction(Action): - """Open an lockable""" - def __init__(self, controller, lockable, commands=None): - """ - @param controller: A reference to the GameSceneController. - @type controller: parpg.GameSceneController - @param commands: Special commands that are executed - @type commands: Dictionary - @type view: class derived from parpg.ViewBase - @param view: The view - @param lockable: A reference to the lockable - """ - Action.__init__(self, controller, commands) - self.view = controller.view - self.lockable = lockable - - def execute(self): - """Open the lockable.""" - try: - lockable.open(self.lockable.lockable) - self.lockable.fifeagent.behaviour.animate("open") - self.lockable.fifeagent.behaviour.queue_animation("opened", - repeating=True) - except lockable.LockedError: - self.view.hud.createExamineBox(self.lockable.description.view_name, - "Locked") - Action.execute(self) - -class CloseAction(Action): - """Close an lockable""" - def __init__(self, controller, lockable, commands=None): - """ - @param controller: A reference to the GameSceneController. - @type controller: parpg.GameSceneController - @param commands: Special commands that are executed - @type commands: Dictionary - @type view: class derived from parpg.ViewBase - @param view: The view - @param lockable: A reference to the lockable - """ - Action.__init__(self, controller, commands) - self.lockable = lockable - - def execute(self): - """Close the lockable.""" - lockable.close(self.lockable.lockable) - self.lockable.fifeagent.behaviour.animate("close") - self.lockable.fifeagent.behaviour.queue_animation("closed", - repeating=True) - Action.execute(self) - -class UnlockAction(Action): - """Unlocks a lockable.""" - def __init__(self, controller, lockable, commands = None): - """ - @param controller: A reference to the GameSceneController. - @type controller: parpg.GameSceneController - @param commands: Special commands that are executed - @type commands: Dictionary - @param lockable: A reference to the lockable - """ - Action.__init__(self, controller, commands) - self.lockable = lockable - - def execute(self): - """Open the box.""" - lockable.unlock(self.lockable.lockable) - Action.execute(self) - -class LockAction(Action): - """Locks a lockable.""" - def __init__(self, controller, lockable, commands = None): - """ - @param controller: A reference to the GameSceneController. - @type controller: parpg.GameSceneController - @param commands: Special commands that are executed - @type commands: Dictionary - @param lockable: A reference to the lockable - """ - Action.__init__(self, controller, commands) - self.lockable = lockable - self.view = controller.view - - def execute(self): - """Lock the box.""" - try: - lockable.lock(self.lockable.lockable) - except lockable.OpenError: - self.view.hud.createExamineBox(self.lockable.description.view_name, - "Is open") - - Action.execute(self) - - -class ExamineAction(Action): - """Examine an object.""" - def __init__(self, controller, examine_id, examine_name, examine_desc=None, commands=None): - """ - @param controller: A reference to the GameSceneController. - @type controller: parpg.GameSceneController - @param examine_id: An object id - @type examine_id: integer - @param examine_name: An object name - @type examine_name: string - @param examine_desc: A description of the object that will be displayed. - @type examine_desc: string - @param commands: Special commands that are executed - @type commands: Dictionary - """ - super(ExamineAction, self).__init__(controller, commands) - self.view = controller.view - self.examine_id = examine_id - self.examine_name = examine_name - if examine_desc is not None: - self.examine_desc = examine_desc - else: - self.examine_desc = "No Description" - - def execute(self): - """Display the text.""" - action_text = self.examine_desc - self.view.hud.addAction(unicode(action_text)) - logger.debug(action_text) - #this code will cut the line up into smaller lines that will be displayed - place = 25 - while place < len(action_text): - if action_text[place] == ' ': - action_text = action_text[:place] +'\n'+action_text[place:] - place += 26 #plus 1 character to offset the new line - else: place += 1 - self.view.displayObjectText(self.examine_id, unicode(action_text), time=3000) - Action.execute(self) - -class ExamineItemAction(Action): - """Examine an item.""" - def __init__(self, controller, examine_name, examine_desc, commands = None): - """ - @param controller: A reference to the GameSceneController. - @type controller: parpg.GameSceneController - @param commands: Special commands that are executed - @type commands: Dictionary - @type view: class derived from parpg.ViewBase - @param view: The view - @type examine_name: String - @param examine_name: Name of the object to be examined. - @type examine_name: String - @param examine_name: Description of the object to be examined. - """ - super(ExamineItemAction, self).__init__(controller, commands) - self.view = controller.view - self.examine_name = examine_name - self.examine_desc = examine_desc - - def execute(self): - """Display the text.""" - action_text = unicode(self.examine_desc) - self.view.hud.addAction(action_text) - logger.debug(action_text) - Action.execute(self) - -class ExamineContentsAction(Action): - """Examine the contens of an container""" - def __init__(self, controller, container, commands=None): - """ - @param controller: A reference to the GameSceneController. - @type controller: parpg.GameSceneController - @param container: The container - @type container: parpg.entities.General - @param commands: Special commands that are executed - @type commands: Dictionary - """ - Action.__init__(self, controller, commands) - self.view = controller.view - self.container = container - - def execute(self): - """Examine the contents""" - self.view.hud.createBoxGUI(self.container.description.view_name, - self.container.container) - Action.execute(self) - -class ReadAction(Action): - """Read a text.""" - def __init__(self, controller, text_name, text, commands = None): - """ - @param controller: A reference to the GameSceneController. - @type controller: parpg.GameSceneController - @param commands: Special commands that are executed - @type commands: Dictionary - @param view: The view - @type view: class derived from parpg.ViewBase - @param text_name: Name of the object containing the text - @type text_name: String - @param text: Text to be displayied - @type text: String - """ - super(ReadAction, self).__init__(controller, commands) - self.view = controller.view - self.text_name = text_name - self.text = text - - def execute(self): - """Examine the box.""" - action_text = unicode('\n'.join(["You read " + self.text_name + ".", - self.text])) - self.view.hud.addAction(action_text) - logger.debug(action_text) - super(ReadAction, self).execute() - -class TalkAction(Action): - """An action to represent starting a dialogue""" - def __init__(self, controller, npc, commands = None): - """ - @param controller: A reference to the GameSceneController. - @type controller: parpg.GameSceneController - @param commands: Special commands that are executed - @type commands: Dictionary - @type view: class derived from parpg.ViewBase - @param view: The view - @type npc: NonPlayerCharacter - @param npc: NPC to interact with. - """ - super(TalkAction, self).__init__(controller, commands) - self.view = controller.view - self.npc = npc - - def execute(self): - """Talk with the NPC when close enough, otherwise move closer. - @return: None""" - player_char = self.model.game_state.\ - getObjectById("PlayerCharacter").fifeagent - player_char.behaviour.animate( - 'stand', - self.npc.fifeagent.behaviour.getLocation() - ) - - if self.npc.dialogue.dialogue is not None: - dialogue_controller = DialogueController( - self.controller.engine, - self.view, - self.model, - self.controller.application - ) - self.controller.application.manager.push_mode( - dialogue_controller - ) - dialogue_controller.startTalk(self.npc) - else: - self.npc.fifeagent.behaviour.agent.say("Leave me alone!", 1000) - - self.model.game_state.getObjectById("PlayerCharacter").\ - fifeagent.behaviour.idle() - self.model.game_state.getObjectById("PlayerCharacter").\ - fifeagent.behaviour.nextAction = None - super(TalkAction, self).execute() - -class UseAction(Action): - """Action for carryable items. It executes special commands that can be only - used on carryable utens""" - - - def __init__(self, controller, item, commands = None): - """ - @param controller: A reference to the GameSceneController. - @type controller: parpg.GameSceneController - @param item: Item on which the action is called - @type item: CarryableItem - @param commands: Special commands that are executed - @type commands: Dictionary - """ - super(UseAction, self).__init__(controller, commands) - self.view = controller.view - self.item = item - - def execute(self): - #Check if there are special commands and execute them - for command_data in self.commands: - command = command_data["Command"] - if command == "ReplaceItem": - object_id = command_data["ID"] - object_type = command_data["ObjectType"] - containable = self.item.containable - new_item = self.model.createItemByType(object_type, - object_id, - self.item.world) - container.put_item(containable.container, - new_item.containable, - containable.slot) - self.model.deleteObject(self.item.general.identifier) - self.item.delete() - self.view.hud.inventory.updateImages() - super(UseAction, self).execute() - -class PickUpAction(Action): - """Action for picking up items from a map""" - - def __init__(self, controller, item, commands = None): - super(PickUpAction, self).__init__(controller, commands) - self.item = item - self.view = controller.view - - def execute(self): - real_item = self.item.containable - self.item.fifeagent = None - player = self.model.game_state.getObjectById("PlayerCharacter") - self.model.moveObject(self.item.general.identifier, None) - self.model.updateObjectDB(self.item.world) - container.put_item(player.container, real_item) - super(PickUpAction, self).execute() - -class DropItemAction(Action): - """Action for dropping an items on a map""" - def __init__(self, controller, item, commands = None): - super(DropItemAction, self).__init__(controller, commands) - self.item = item - - def execute(self): - map_name = self.model.game_state.current_map_name - identifier = self.item.entity.general.identifier - agent_values = self.model.items[identifier] - coords = (self.model.game_state.getObjectById("PlayerCharacter"). - fifeagent.behaviour.getLocation().getExactLayerCoordinates() - ) - agent_values["Position"] = (coords.x, coords.y) - agent_values["Rotation"] = 0 - agent_values["Map"] = map_name - self.model.deleteObject(identifier) - self.model.addAgent(self.model.ALL_AGENTS_KEY, - {identifier: agent_values}) - self.model.placeAgents(self.item.entity.world) - self.model.updateObjectDB(self.item.entity.world) - super(DropItemAction, self).execute() - -class DropItemFromContainerAction(DropItemAction): - """Action for dropping an items from the Inventory to a map""" - - def __init__(self, controller, item, container_gui, commands = None): - super(DropItemFromContainerAction, self).__init__(controller, item, commands) - self.container_gui = container_gui - - def execute(self): - super(DropItemFromContainerAction, self).execute() - container.remove_item(self.item.container, self.item.slot) - self.container_gui.updateImages() - -class RunScriptAction(Action): - """Action that runs a specific script""" - - def __init__(self, controller, script, commands = None): - """Basic action constructor - @param controller: A reference to the GameSceneController. - @type controller: parpg.GameSceneController - @param script: The name of the script to run. - @type script: string - @param commands: Special commands that are executed - @type commands: Dictionary - """ - Action.__init__(self, controller, commands) - self.script = script - - def execute(self): - self.controller.systems.scripting.runScript(self.script) - Action.execute(self) - -class BrewBeerAction(Action): - """Action for brewing beer in a pot""" - def __init__(self, controller, pot, commands = None): - super(BrewBeerAction, self).__init__(controller, commands) - self.pot = pot.container - self.view = controller.view - - def execute(self): - """Brew the beer""" - has_water = False - has_yeast = False - has_fruit = False - has_wood = False - has_bottle = False - player_character = (self.model.game_state. - getObjectById("PlayerCharacter").container) - for item in self.pot.children: - if not item: - continue - if item.item_type == "Questionable water": - if has_water: - self.view.hud.addAction(unicode(\ - "Please put only 1 water in the pot")) - return - has_water = True - water_type = 1 - water = item - elif item.item_type == "Pure water": - if has_water: - self.view.hud.addAction(unicode(\ - "Please put only 1 water in the pot")) - return - has_water = True - water_type = 2 - water = item - elif item.item_type == "Grain": - if has_fruit: - self.view.hud.addAction(unicode(\ - "Please put only 1 fruit in the pot")) - return - has_fruit = True - fruit_type = 3 - fruit = item - elif item.item_type == "Wild potato": - if has_fruit: - self.view.hud.addAction(unicode(\ - "Please put only 1 fruit in the pot")) - return - has_fruit = True - fruit_type = 2 - fruit = item - elif item.item_type == "Rotten yam": - if has_fruit: - self.view.hud.addAction(unicode(\ - "Please put only 1 fruit in the pot")) - return - has_fruit = True - fruit_type = 1 - fruit = item - elif item.item_type == "Yeast": - if has_yeast: - self.view.hud.addAction(unicode(\ - "Please put only 1 yeast in the pot")) - return - has_yeast = True - yeast = item - else: - self.view.hud.addAction(unicode( - "Item " + (item.entity.description.view_name) + - " is not needed for brewing beer")) - self.view.hud.addAction(unicode(\ - "Please put only ingredients for the beer in the pot.\ - Things like bottles and wood have to be in your inventory")) - return - wood = container.get_item(player_character, "Wood") - if wood: - has_wood = True - bottle = container.get_item(player_character, "Empty beer bottle") - if bottle: - has_bottle = True - if has_water and has_fruit and has_wood and has_bottle: - container.remove_item(self.pot, water.slot) - container.remove_item(self.pot, fruit.slot) - if has_yeast: - container.remove_item(self.pot, yeast.slot) - container.remove_item(player_character, wood.slot) - new_item = (self.model.createItemByType("Beer", "Beer", - self.pot.entity.world) - ) - container.put_item(player_character, new_item.containable) - self.view.hud.inventory.updateImages() - beer_quality = 0 - if water_type == 1: - if fruit_type == 1: - beer_quality = -1 - elif fruit_type == 2: - beer_quality = 2 - elif fruit_type == 3: - beer_quality = 3 - if water_type == 2: - if fruit_type == 1: - beer_quality = 1 - elif fruit_type == 2: - beer_quality = 3 - elif fruit_type == 3: - beer_quality = 4 - if beer_quality > 0 and has_yeast: - beer_quality += 1 - self.model.game_state.quest_engine.quests["beer"].\ - setValue("beer_quality", beer_quality) - else: - self.view.hud.addAction(unicode( - """For brewing beer you need at least: - In the pot: - Fruit (like grain, potato, yam) - Water - Optionally: - Good quality yeast. - Wild yeast will be used if none present. - In the inventory: - Wood - Empty bottle""")) - super(BrewBeerAction, self).execute() - -class SayAction(Action): - """Action that will display a short text over the entity and in the action - box.""" - - def __init__(self, controller, entity, text, commands = None): - """Basic action constructor - @param controller: A reference to the GameSceneController. - @type controller: parpg.GameSceneController - @param entity: The entity that says the text - @type script: parpg.entities.General - @param text: The text to be displayed - @type text: string - @param commands: Special commands that are executed - @type commands: Dictionary - """ - Action.__init__(self, controller, commands) - self.entity = entity - self.text = text - - def execute(self): - if self.entity.fifeagent: - self.entity.fifeagent.behaviour.agent.say(self.text); - if self.entity.description: - self.controller.view.hud.actions_box.addDialog( - self.entity.description.view_name, - self.text) - Action.execute(self) - -ACTIONS = {"ChangeMap":ChangeMapAction, - "Open":OpenAction, - "Close":CloseAction, - "Unlock":UnlockAction, - "Lock":LockAction, - "ExamineItem":ExamineItemAction, - "Examine":ExamineAction, - "Look":ExamineItemAction, - "Read":ReadAction, - "Talk":TalkAction, - "Use":UseAction, - "PickUp":PickUpAction, - "DropFromInventory":DropItemFromContainerAction, - "BrewBeer":BrewBeerAction, - "ExamineContents": ExamineContentsAction, - "RunScript": RunScriptAction, - "Say" : SayAction, - "None": Action, - }
--- a/src/parpg/entities/general.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# This program 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. -# -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -from parpg.bGrease import Entity - -class General(Entity): - - def __init__(self, world, identifier): - self.general.identifier = identifier - - def getID(self): - return self.general.identifier \ No newline at end of file
--- a/src/parpg/font.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,53 +0,0 @@ -import os - -from fife.extensions import pychan -from fife.extensions.pychan.fonts import Font - - -class PARPGFont(Font): - """ Font class for PARPG - This class behaves identical to PyChan's Font class except in - initialization. Ratherthan take a name and a get object, this class - takes a fontdef and settings object as explained below. This class is - necessary because the original Font class was too restrictive on how it - accepted objects - - @param fontdef: defines the font's name, size, type, and optionally - row spacing as well as glyph spacing. - @type fontdef: dictionary - - @param settings: settings object used to dynamically determine the - font's source location - @type settings: parpg.settings.Settings object - """ - def __init__(self, fontdef, settings): - self.font = None - self.name = fontdef['name'] - self.typename = fontdef['typename'] - - if self.typename == 'truetype': - self.filename = '{0}.ttf'.format(self.name.lower().split('_')[0]) - - self.source = '/'.join(['fonts', self.filename]) - self.row_spacing = fontdef.get('row_spacing', 0) - self.glyph_spacing = fontdef.get('glyph_spacing', 0) - - if self.typename == 'truetype': - self.size = fontdef['size'] - self.antialias = fontdef['antialias'] - self.color = fontdef.get('color', [255, 255, 255]) - manager = pychan.manager.hook.guimanager - self.font = manager.createFont(self.source, self.size, '') - - if not self.font: - raise InitializationError('Could not load font ' - '{0}'.format(self.name)) - - self.font.setAntiAlias(self.antialias) - self.font.setColor(*self.color) - else: - raise InitializationError('Unsupported font type ' - '{0}'.format(self.typename)) - - self.font.setRowSpacing(self.row_spacing) - self.font.setGlyphSpacing(self.glyph_spacing)
--- a/src/parpg/gamemap.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,173 +0,0 @@ -# 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/>. - -from fife import fife -from fife.extensions import pychan -from fife.extensions.loaders import loadMapFile - -class GameMap(fife.MapChangeListener): - """Map class used to flag changes in the map""" - def __init__(self, engine, model): - # init mapchange listener - fife.MapChangeListener.__init__(self) - self.map = None - self.engine = engine - self.model = model - self.settings = self.model.settings - - # init map attributes - self.my_cam_id = None - self.cameras = {} - self.agent_layer = None - self.top_layer = None - self.fife_model = engine.getModel() - self.transitions = [] - self.cur_cam2_x = 0 - self.initial_cam2_x = 0 - self.cam2_scrolling_right = True - self.target_rotation = 0 - self.outline_renderer = None - - def reset(self): - """Reset the model to default settings. - @return: None""" - # We have to delete the map in Fife. - if self.map: - self.model.deleteObjects() - self.model.deleteMap(self.map) - - self.transitions = [] - self.map = None - self.agent_layer = None - self.top_layer = None - # We have to clear the cameras in the view as well, or we can't reuse - # camera names like 'main' - #self.view.clearCameras() - self.initial_cam2_x = 0 - self.cam2_scrolling_right = True - #self.cameras = {} - self.cur_cam2_x = 0 - self.target_rotation = 0 - self.outline_renderer = None - - def makeActive(self): - """Makes this map the active one. - @return: None""" - self.cameras[self.my_cam_id].setEnabled(True) - - def load(self, filename): - """Load a map given the filename. - @type filename: String - @param filename: Name of map to load - @return: None""" - self.reset() - - self.map = loadMapFile(filename, self.engine) - - self.agent_layer = self.map.getLayer('ObjectLayer') - self.top_layer = self.map.getLayer('TopLayer') - - # it's possible there's no transition layer - size = len('TransitionLayer') - for layer in self.map.getLayers(): - # could be many layers, but hopefully no more than 3 - if(layer.getId()[:size] == 'TransitionLayer'): - self.transitions.append(self.map.getLayer(layer.getId())) - - """ Initialize the camera. - Note that if we have more than one camera in a map file - we will have to rework how self.my_cam_id works. To make sure - the proper camera is set as the 'main' camera. - At this point we also set the viewport to the current resolution.""" - for cam in self.map.getCameras(): - width = self.settings.fife.ScreenWidth - height = self.settings.fife.ScreenHeight - viewport = fife.Rect(0, 0, width, height) - cam.setViewPort(viewport) - self.my_cam_id = cam.getId() - self.cameras[self.my_cam_id] = cam - cam.resetRenderers() - - self.target_rotation = self.cameras[self.my_cam_id].getRotation() - - self.outline_renderer = ( - fife.InstanceRenderer.getInstance(self.cameras[self.my_cam_id]) - ) - - # set the render text - rend = fife.FloatingTextRenderer.getInstance( - self.cameras[self.my_cam_id] - ) - font = pychan.manager.hook.guimanager.createFont( - 'fonts/rpgfont.png', - 0, - self.settings.fife.FontGlyphs - ) - - rend.setFont(font) - rend.activateAllLayers(self.map) - rend.setEnabled(True) - - # Activate the grid renderer on all layers - rend = self.cameras['map_camera'].getRenderer('GridRenderer') - rend.activateAllLayers(self.map) - - # Activate the grid renderer on all layers - rend = fife.CoordinateRenderer.getInstance( - self.cameras[self.my_cam_id] - ) - rend.setColor(0, 0, 0) - rend.addActiveLayer(self.map.getLayer("GroundLayer")) - - # Make World aware that this is now the active map. - self.model.active_map = self - - def addPC(self): - """Add the player character to the map - @return: None""" - # Update gamestate.player_character - player = self.model.game_state.getObjectById("PlayerCharacter") - player.fifeagent.behaviour.onNewMap(self.agent_layer) - self.centerCameraOnPlayer() - - def toggleRenderer(self, r_name): - """Enable or disable a renderer. - @return: None""" - renderer = self.cameras[self.my_cam_id].getRenderer(str(r_name)) - renderer.setEnabled(not renderer.isEnabled()) - - def isPaused(self): - """Returns wheter the map is currentply paused or not""" - # Time multiplier is a float, never do equals on floats - return not self.map.getTimeMultiplier() >= 1.0 - - def pause(self, paused): - """ Pause/Unpause the game. - @return: nothing""" - if paused: - self.map.setTimeMultiplier(0.0) - if not paused and self.isPaused(): - self.map.setTimeMultiplier(1.0) - - def togglePause(self): - """ Toggle paused state. - @return: nothing""" - self.pause(not self.isPaused()) - - def centerCameraOnPlayer(self): - """Center the camera on the player""" - camera = self.cameras[self.my_cam_id] - player = self.model.game_state.getObjectById("PlayerCharacter") - camera.setLocation(player.fifeagent.behaviour.getLocation())
--- a/src/parpg/gamemodel.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,868 +0,0 @@ -# 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/>. - -# there should be NO references to FIFE here! -import sys -import os.path -import logging -from copy import deepcopy - -from fife import fife -from fife.extensions.serializers.xmlobject import XMLObjectLoader -from parpg.bGrease.geometry import Vec2d -from serializers import XmlSerializer - -from parpg import vfs -from gamestate import GameState -from gamemap import GameMap -from common.utils import locateFiles -from common.utils import parseBool -from parpg.dialogueparsers import YamlDialogueParser, DialogueFormatError -from parpg.entities import createEntity -from parpg import behaviours -from parpg import components -from parpg.components import fifeagent, container, equip, character_statistics -import characterstatistics as char_stats - -try: - import xml.etree.cElementTree as ElementTree -except ImportError: - import xml.etree.ElementTree as ElementTree - -import yaml - -logger = logging.getLogger('gamemodel') - -class GameModel(object): - """GameModel holds the logic for the game. - Since some data (object position and so forth) is held in the - fife, and would be pointless to replicate, we hold a instance of - the fife view here. This also prevents us from just having a - function heavy controller.""" - ALL_AGENTS_KEY = "All" - MAX_ID_NUMBER = 1000 - GENERIC_ITEM_GFX = "generic_item" - DEFAULT_STAT_VALUE = 50 - - def __init__(self, engine, settings): - """Initialize the instance. - @param engine: A fife.Engine object - @type emgome: fife.Engine - @param setting: The applications settigns - @type setting: parpg.settings.Settings object - @return: None""" - self.settings = settings - - self.map_change = False - self.load_saver = False - self.savegame = None - quests_directory = settings.parpg.QuestsPath - self.game_state = GameState(quests_dir=quests_directory) - #self.game_state.quest_engine = - #self.game_state.quest_engine.readQuests() - self.pc_run = 1 - self.target_position = None - self.target_map_name = None - self.object_db = {} - self.active_map = None - self.map_files = {} - self.agents = {} - self.agents[self.ALL_AGENTS_KEY] = {} - self.items = {} - self.engine = engine - self.fife_model = engine.getModel() - - # set values from settings - maps_directory = settings.parpg.MapsPath - self.game_state.maps_file = '/'.join([maps_directory, - settings.parpg.MapsFile]) - self.all_agents_file = '/'.join([maps_directory, - settings.parpg.AllAgentsFile]) - objects_directory = self.settings.parpg.ObjectsPath - self.objects_directory = objects_directory - self.object_db_file = '/'.join([objects_directory, - settings.parpg.ObjectDatabaseFile]) - self.dialogue_directory = settings.parpg.DialoguesPath - self.dialogues = {} - self.agent_import_files = {} - self.obj_loader = XMLObjectLoader(self.engine) - # FIXME M. George Hansen 2011-06-06: character stats scripts aren't - # finished, unfortunately. - # NOTE Beliar 2011-11-05 Activated the stats. Testing needed if it - # works correctly, or if they are still unfinished. - primary_stats_file = ( - vfs.VFS.open('character_scripts/primary_stats.xml') - ) - self.primary_stats = XmlSerializer.deserialize(primary_stats_file) - secondary_stats_file = ( - vfs.VFS.open('character_scripts/secondary_stats.xml') - ) - self.secondary_stats = XmlSerializer.deserialize(secondary_stats_file) - - - def create_stats(self, entity): - for primary_stat in self.primary_stats: - long_name = primary_stat.long_name - entity.characterstats.primary_stats[long_name] = ( - char_stats.PrimaryStatisticValue( - primary_stat, entity.characterstats, - self.DEFAULT_STAT_VALUE) - ) - for secondary_stat in self.secondary_stats: - name = secondary_stat.name - entity.characterstats.secondary_stats[name] = ( - char_stats.SecondaryStatisticValue(secondary_stat, - entity.characterstats - ) - ) - - def checkAttributes(self, attributes, template): - """Checks for attributes that where not given in the map file - and fills them with values from the object database - @param attributes: attributes to check - @type attributes: Dictionary - @param template: Template from which the values will be used - @return: The modified attributes""" - if self.object_db.has_key(template): - db_attributes = deepcopy(self.object_db[template]) - for key in db_attributes.keys(): - if attributes.has_key(key): - tmp_attributes = db_attributes[key] - tmp_attributes.update(attributes[key]) - attributes[key] = tmp_attributes - else: - attributes[key] = db_attributes[key] - return attributes - - def isIDUsed(self, ID): - if self.game_state.hasObject(ID): - return True - for namespace in self.agents: - if ID in self.agents[namespace]: - return True - return False - - def createUniqueID(self, ID): - if self.isIDUsed(ID): - id_number = 1 - while self.isIDUsed(ID + "_" + str(id_number)): - id_number += 1 - if id_number > self.MAX_ID_NUMBER: - raise ValueError( - "Number exceeds MAX_ID_NUMBER:" + - str(self.MAX_ID_NUMBER) - ) - - ID = ID + "_" + str(id_number) - return ID - - def moveObject(self, object_id, new_map): - """Moves the object to a new map, or in a container - @param object_id: ID of the object - @type object_id: str - @param new_map: ID of the new map, or None - @type object_id: str """ - game_object = self.deleteObject(object_id) - self.game_state.addObject(object_id, new_map, game_object) - - def deleteObject(self, object_id): - """Removes an object from the game - @param object_id: ID of the object - @type object_id: str """ - if self.agents["All"].has_key(object_id): - del self.agents["All"][object_id] - else: - del self.items[object_id] - return self.game_state.deleteObject(object_id) - - def save(self, path, filename): - """Writes the saver to a file. - @type filename: string - @param filename: the name of the file to write to - @return: None""" - fname = '/'.join([path, filename]) - try: - save_file = open(fname, 'w') - except(IOError): - sys.stderr.write("Error: Can't create save game: " + fname + "\n") - return - - save_state = {} - save_state["Agents"] = self.agents - save_state["Items"] = self.items - save_state["GameState"] = self.game_state.getStateForSaving() - - yaml.dump(save_state, save_file) - - save_file.close() - - def load(self, path, filename): - """Loads a saver from a file. - @type filename: string - @param filename: the name of the file (including path) to load from - @return: None""" - fname = '/'.join([path, filename]) - - try: - load_file = open(fname, 'r') - except(IOError): - sys.stderr.write("Error: Can't find save game file\n") - return - self.deleteMaps() - self.clearAgents() - - save_state = yaml.load(load_file) - self.game_state.restoreFromState(save_state["GameState"]) - maps = save_state["Agents"] - for map_name in maps: - for agent_name in maps[map_name]: - agent = {agent_name:maps[map_name][agent_name]} - self.addAgent(map_name, agent) - self.items = save_state["Items"] - - load_file.close() - - def teleport(self, agent, position): - """Called when a an agent is moved instantly to a new position. - The setting of position may wan to be created as its own method down - the road. - @type position: String Tuple - @param position: X,Y coordinates passed from engine.changeMap - @return: fife.Location""" - logging.debug(position) - coord = fife.DoublePoint3D(float(position[0]), float(position[1]), 0) - location = fife.Location(self.active_map.agent_layer) - location.setMapCoordinates(coord) - agent.teleport(location) - - def getObjectAtCoords(self, coords): - """Get the object which is at the given coords - @type coords: fife.Screenpoint - @param coords: Coordinates where to check for an object - @rtype: fife.Object - @return: An object or None""" - instances = self.active_map.cameras[ - self.active_map.my_cam_id].\ - getMatchingInstances(coords, self.active_map.agent_layer) - # no object returns an empty tuple - if(instances != ()): - front_y = 0 - - - for obj in instances: - # check to see if this in our list at all - if(self.objectActive(obj.getId())): - # check if the object is on the foreground - obj_map_coords = \ - obj.getLocation().getMapCoordinates() - obj_screen_coords = self.active_map.\ - cameras[self.active_map.my_cam_id]\ - .toScreenCoordinates(obj_map_coords) - - if obj_screen_coords.y > front_y: - #Object on the foreground - front_y = obj_screen_coords.y - return obj - else: - return None - else: - return None - - def getCoords(self, click): - """Get the map location x, y coordinates from the screen coordinates - @type click: fife.ScreenPoint - @param click: Screen coordinates - @rtype: fife.Location - @return: The map coordinates""" - coord = self.active_map.cameras[self.active_map.my_cam_id].\ - toMapCoordinates(click, False) - coord.z = 0 - location = fife.Location(self.active_map.agent_layer) - location.setMapCoordinates(coord) - return location - - def pause(self, paused): - """ Pause/Unpause the game - @return: nothing""" - if self.active_map: - self.active_map.pause(paused) - - def togglePause(self): - """ Toggle paused state. - @return: nothing""" - self.active_map.togglePause() - - def isPaused(self): - """Returns wheter the game is paused or not""" - return self.active_map.isPaused() - - def readMapFiles(self): - """Read all a available map-files and store them""" - maps_file = vfs.VFS.open(self.game_state.maps_file) - self.map_files = yaml.load(maps_file)["Maps"] - - def addAgent(self, namespace, agent): - """Adds an agent to the agents dictionary - @param namespace: the namespace where the agent is to be added to - @type namespace: str - @param agent: The agent to be added - @type agent: dict """ - from fife.extensions.serializers.xml_loader_tools import loadImportFile - if not self.agents.has_key(namespace): - self.agents[namespace] = {} - - agent_values = agent.values()[0] - unique_agent_id = self.createUniqueID(agent.keys()[0]) - del agent[agent.keys()[0]] - agent[unique_agent_id] = agent_values - self.agents[namespace].update(agent) - object_model = "" - if agent_values["Entity"].has_key("graphics") \ - and agent_values["Entity"]["graphics"].has_key("gfx"): - object_model = agent_values["Entity"]["graphics"]["gfx"] - elif agent_values.has_key("Template"): - template = self.object_db[agent_values["Template"]] - object_model = template["graphics"]["gfx"] - else: - object_model = self.GENERIC_ITEM_GFX - import_file = self.agent_import_files[object_model] - loadImportFile(self.obj_loader, import_file, self.engine) - - def readAgentsOfMap(self, map_name): - """Read the agents of the map - @param map_name: Name of the map - @type map_name: str """ - #Get the agents of the map - map_agents_file = self.map_files[map_name].\ - replace(".xml", "_agents.yaml") - agents_data = vfs.VFS.open(map_agents_file) - agents = yaml.load_all(agents_data) - self.agents[map_name] = {} - for agent in agents: - if not agent == None: - self.addAgent(map_name, agent) - - def readScriptsOfMap(self, map_name, world): - """Read the scripts of the map - @param map_name: Name of the map - @type map_name: str - @param world: The current active world - @type world: parpg.world.World""" - map_scripts_file = ( - self.map_files[map_name].replace(".xml", "_scripts.yaml") - ) - if vfs.VFS.exists(map_scripts_file): - scripts_file = vfs.VFS.open(map_scripts_file) - scripts_data = yaml.load(scripts_file) - scripts = (scripts_data["Scripts"]) - conditions = ( - scripts_data["Conditions"] if - scripts_data.has_key("Conditions") else () - ) - scripting = world.systems.scripting - for name, actions in scripts.iteritems(): - scripting.setScript(name, actions) - for condition in conditions: - scripting.addCondition(*condition) - - def readAllAgents(self): - """Read the agents of the all_agents_file and store them""" - agents_file = vfs.VFS.open(self.all_agents_file) - agents = yaml.load_all(agents_file) - for agent in agents: - if agent is not None: - self.addAgent(self.ALL_AGENTS_KEY, agent) - - def getAgentsOfMap(self, map_name): - """Returns the agents that are on the given map - @param map_name: Name of the map - @type map_name: str - @return: A dictionary with the agents of the map""" - if not self.agents.has_key(map_name): - return {} - ret_dict = self.agents[map_name].copy() - for agent_name, agent_value in self.agents[self.ALL_AGENTS_KEY]\ - .iteritems(): - if agent_value["Map"] == map_name: - ret_dict[agent_name] = agent_value - return ret_dict - - def getAgentsOfActiveMap(self): - """Returns the agents that are on active map - @return: A dictionary with the agents of the map """ - return self.getAgentsOfMap(self.active_map.map.getId()) - - def clearAgents(self): - """Resets the agents dictionary""" - self.agents = {} - self.agents[self.ALL_AGENTS_KEY] = {} - - def loadMap(self, map_name): - """Load a new map. - @type map_name: string - @param map_name: Name of the map to load - @return: None""" - if not map_name in self.game_state.maps: - map_file = self.map_files[map_name] - new_map = GameMap(self.engine, self) - self.game_state.maps[map_name] = new_map - new_map.load(map_file) - - def createAgent(self, agent, inst_id, world): - if self.game_state.hasObject(inst_id): - return None - entity_data = deepcopy(agent["Entity"]) - entity_data["fifeagent"] = {} - template = None - if agent.has_key("Template"): - template = agent["Template"] - entity_data = self.checkAttributes(entity_data, template) - object_id = (entity_data["graphics"]["gfx"] - if entity_data.has_key("graphics") and - entity_data["graphics"].has_key("gfx") - else self.GENERIC_ITEM_GFX - ) - map_obj = self.fife_model.getObject(str(object_id), "PARPG") - if not map_obj: - logging.warning("Object with inst_id={0}, ns=PARPG, " - "could not be found. " - "Omitting...".format(str(object_id))) - - x_pos = agent["Position"][0] - y_pos = agent["Position"][1] - z_pos = agent["Position"][2] if len(agent["Position"]) == 3 \ - else 0.0 - stack_pos = agent["Stackposition"] if \ - agent.has_key("StackPosition") \ - else None - inst = self.active_map.agent_layer.\ - createInstance(map_obj, - fife.ExactModelCoordinate(x_pos, - y_pos, - z_pos), - inst_id) - inst.setId(inst_id) - - rotation = agent["Rotation"] - inst.setRotation(rotation) - - fife.InstanceVisual.create(inst) - if (stack_pos): - inst.get2dGfxVisual().setStackPosition(int(stack_pos)) - - if (map_obj.getAction('default')): - target = fife.Location(self.active_map.agent_layer) - inst.act('default', target, True) - - if entity_data.has_key("behaviour"): - entity_data["fifeagent"]["behaviour"] = \ - getattr(behaviours, - entity_data["behaviour"]["behaviour_type"])() - else: - entity_data["fifeagent"]["behaviour"] = behaviours.Base() - if self.dialogues.has_key(inst_id): - entity_data["dialogue"] = {} - entity_data["dialogue"]["dialogue"] = self.dialogues[inst_id] - if (entity_data.has_key("containable") and not - entity_data["containable"].has_key("item_type") - ): - entity_data["containable"]["item_type"] = template - - obj = self.createMapObject(self.active_map.agent_layer, - entity_data, inst_id, world) - - if agent.has_key("Statistics"): - self.create_stats(obj) - for name, val in agent["Statistics"].iteritems(): - obj.characterstats.primary_stats[name].value = val - - if agent.has_key("Inventory"): - inv = agent["Inventory"] - self.createInventoryItems(inv, obj, world) - - if agent.has_key("Equipment"): - for slot, data in agent["Equipment"].iteritems(): - item = None - if data.has_key("type"): - item_type = data["type"] - item_data = {} - item_data = self.checkAttributes(item_data, item_type) - if (item_data.has_key("containable") and - item_data.has_key("equipable")): - item = self.createItem( - self.createUniqueID(data["ID"]), - item_data, world, item_type) - else: - raise Exception( - "Item %s is not containable or equipable." % - item_type - ) - else: - identifier = data["ID"] - if self.game_state.hasObject(identifier): - item = self.game_state.getObjectById(identifier) - else: - item_data = self.items[identifier]["Entity"] - item_type = item_data["containable"]["item_type"] - item = self.createItem(identifier, item_data, - world, item_type) - equip.equip(obj.equip, item.equipable, slot) - if (obj.fifeagent and (obj.lockable and not obj.lockable.closed)): - obj.fifeagent.behaviour.animate("opened", repeating=True) - return obj - - def createInventoryItems(self, inv, obj, world): - slots = inv["Slots"] - obj.container.children = list() - for x in xrange(slots): - obj.container.children.append(None) - items = inv["Items"] if inv.has_key("Items") else list() - for data in items: - item = None - slot = data["Slot"] if data.has_key("Slot") else -1 - if data.has_key("type"): - item_type = data["type"] - item = self.createItemByType(item_type, data["ID"], world) - else: - identifier = data["ID"] - item = self.createItemByID(world, identifier) - - container.put_item(obj.container, item.containable, slot) - - def createItemByID(self, world, identifier): - if self.game_state.hasObject(identifier): - item = self.game_state.getObjectById(identifier) - else: - agent_data = self.items[identifier] - item_data = agent_data["Entity"] - item_type = item_data["containable"]["item_type"] - item = self.createItem(identifier, item_data, - world, item_type) - if item.container and agent_data.has_key("Inventory"): - self.createInventoryItems(agent_data["Inventory"], - item, world) - return item - - def createItemByType(self, item_type, identifier, world): - item_data = {} - item_data = self.checkAttributes(item_data, item_type) - if item_data.has_key("containable"): - return self.createItem( self.createUniqueID(identifier), - item_data, world, item_type) - else: - raise Exception("Item %s is not containable." % item_type) - - def createItem(self, identifier, item_data, world, item_type): - if not item_data["description"].has_key("view_name"): - item_data["description"]["view_name"] = ( - item_data["description"]["real_name"]) - item = createEntity(item_data, identifier, world, None) - item.containable.item_type = item_type - self.game_state.addObject(identifier, None, item) - self.updateObjectDB(world) - return item - - def placeAgents(self, world): - """Places the current maps agents """ - if not self.active_map: - return - agents = self.getAgentsOfMap(self.game_state.current_map_name) - for agent in agents: - if agent == "PlayerCharacter": - continue - if self.active_map.agent_layer.getInstances(agent): - continue - self.createAgent(agents[agent], agent, world) - - def placePC(self, world): - """Places the PlayerCharacter on the map""" - agent = self.agents[self.ALL_AGENTS_KEY]["PlayerCharacter"] - inst_id = "PlayerCharacter" - self.createAgent(agent, inst_id, world) - - # create the PlayerCharacter agent - self.active_map.addPC() - #self.game_state.getObjectById("PlayerCharacter").fifeagent.start() - if agent.has_key("PeopleKnown"): - player = self.game_state.getObjectById("PlayerCharacter") - player.fifeagent.people_i_know = agent["PeopleKnown"] - - def changeMap(self, map_name, target_position = None): - """Registers for a map change on the next pump(). - @type map_name: String - @param map_name: Id of the map to teleport to - @type map_file: String - @param map_file: Filename of the map to teleport to - @type target_position: Tuple - @param target_position: Position of PlayerCharacter on target map. - @return None""" - # set the parameters for the map change if moving to a new map - if map_name != self.game_state.current_map_name: - self.target_map_name = map_name - self.target_position = target_position - # issue the map change - self.map_change = True - - def deleteMaps(self): - """Clear all currently loaded maps from FIFE as well as clear our - local map cache - @return: nothing""" - self.engine.getModel().deleteMaps() - self.engine.getModel().deleteObjects() - self.game_state.clearObjects() - self.game_state.maps = {} - - def setActiveMap(self, map_name, world): - """Sets the active map that is to be rendered. - @type map_name: String - @param map_name: The name of the map to load - @param world: The active world - @type world: parpg.world.World - @return: None""" - # Turn off the camera on the old map before we turn on the camera - # on the new map. - self.active_map.cameras[self.active_map.my_cam_id].setEnabled(False) - # Make the new map active. - self.active_map = self.game_state.maps[map_name] - self.active_map.makeActive() - self.game_state.current_map_name = map_name - if not self.agents.has_key(map_name): - self.readAgentsOfMap(map_name) - - def createMapObject (self, layer, attributes, inst_id, world): - """Create an object and add it to the current map. - @type layer: fife.Layer - @param layer: FIFE layer object exists in - @type attributes: Dictionary - @param attributes: Dictionary of all object attributes - @type instance: fife.Instance - @param instance: FIFE instance corresponding to the object - @return: The created object""" - # create the extra data - extra = {} - if layer is not None: - extra['fifeagent'] = {} - extra['fifeagent']['layer'] = layer - - obj = createEntity(attributes, inst_id, world, extra) - if obj: - self.addObject(layer, obj) - return obj - - def addPC(self, layer, player_char): - """Add the PlayerCharacter to the map - @type layer: fife.Layer - @param layer: FIFE layer object exists in - @type player_char: PlayerCharacter - @param player_char: PlayerCharacter object - @type instance: fife.Instance - @param instance: FIFE instance of PlayerCharacter - @return: None""" - # For now we copy the PlayerCharacter, - # in the future we will need to copy - # PlayerCharacter specifics between the different PlayerCharacter's - player = self.game_state.getObjectById("PlayerCharacter") - player.fifeagent = player_char - player.fifeagent.setup() - player.fifeagent.behaviour.speed = self.settings.parpg.PCSpeed - - - def addObject(self, layer, obj): - """Adds an object to the map. - @type layer: fife.Layer - @param layer: FIFE layer object exists in - @type obj: GameObject - @param obj: corresponding object class - @type instance: fife.Instance - @param instance: FIFE instance of object - @return: None""" - ref = self.game_state.getObjectById(obj.general.identifier, - self.game_state.current_map_name) - if ref is None: - # no, add it to the game state - self.game_state.addObject(obj.general.identifier, - self.game_state.current_map_name, obj) - else: - # yes, use the current game state data - obj.fifeagent.pos.X = ref.X - obj.fifeagent.pos.Y = ref.Y - obj.fifeagent.gfx = ref.gfx - - if obj.fifeagent.behaviour: - obj.fifeagent.behaviour.parent = obj - fifeagent.setup_behaviour(obj.fifeagent) - obj.fifeagent.behaviour.speed = self.settings.parpg.PCSpeed - #Start the behaviour - obj.fifeagent.behaviour.idle() - # create the agent - #obj.setup() - #obj.behaviour.speed = self.settings.parpg.PCSpeed - # create the PlayerCharacter agent - #obj.start() - #if obj.trueAttr("AnimatedContainer"): - # create the agent - #obj.setup() - - def objectActive(self, ident): - """Given the objects ID, pass back the object if it is active, - False if it doesn't exist or not displayed - @type ident: string - @param ident: ID of object - @rtype: boolean - @return: Status of result (True/False)""" - for game_object in \ - self.game_state.getObjectsFromMap(self.game_state.current_map_name): - if (game_object.general.identifier == ident): - # we found a match - return game_object - # no match - return False - - def movePlayer(self, position): - """Code called when the player should move to another location - @type position: fife.ScreenPoint - @param position: Screen position to move to - @return: None""" - player = self.game_state.getObjectById("PlayerCharacter") - if(self.pc_run == 1): - player.fifeagent.behaviour.run(position) - else: - player.fifeagent.behaviour.walk(position) - - def teleportAgent(self, agent, position): - """Code called when an agent should teleport to another location - @type position: fife.ScreenPoint - @param position: Screen position to teleport to - @return: None""" - agent.teleport(position) - self.agents[agent.ID]["Position"] = position - - def readObjectDB(self): - """Reads the Object Information Database from a file. """ - database_file = vfs.VFS.open(self.object_db_file) - database = yaml.load_all(database_file) - for object_info in database: - self.object_db.update(object_info) - - def updateObjectDB(self, world): - """Updates the values in the object database with the worlds values""" - - all_agents = self.agents[self.ALL_AGENTS_KEY] - for entity in world.entities: - identifier = entity.general.identifier - agent_data = {} - map_id = self.game_state.getMapOfObject(identifier) - if map_id: - if all_agents.has_key(identifier): - agent_data = self.agents[self.ALL_AGENTS_KEY][identifier] - else: - agent_data = self.agents[map_id][identifier] - - else: - if not self.items.has_key(identifier): - self.items[identifier] = {} - agent_data = self.items[identifier] - entity_data = {} - entity_data["general"] = {"identifier": identifier} - for name, component in components.components.iteritems(): - if getattr(entity, name): - comp_data = {} - comp_vals = getattr(entity, name) - #Items that are in containers will be saved with them. - for field in component.saveable_fields: - try: - comp_data[field] = getattr(comp_vals, field) - except AttributeError: - #The entity doesn't have this specific value, - #ignore it - pass - if comp_data: - entity_data[name] = comp_data - if name == "fifeagent": - if entity.fifeagent.layer: - layer = entity.fifeagent.layer - inst = layer.getInstance(identifier) - loc = inst.getLocation().getExactLayerCoordinates() - agent_data["Position"] = (loc.x, loc.y, loc.z) - if all_agents.has_key(identifier): - agent_data["Map"] = map_id - agent_data["Rotation"] = inst.getRotation() - elif name == "characterstats": - agent_data["Statistics"] = ( - character_statistics.get_stat_values( - entity.characterstats - )["primary"] - ) - elif name == "container" and hasattr(comp_vals, - "children"): - inventory_data = {} - inventory_data["Slots"] = len(comp_vals.children) - items = [] - for child in comp_vals.children: - if not child: - continue - items.append( - {"ID": child.entity.general.identifier, - "Slot": child.slot} - ) - inventory_data["Items"] = items - agent_data["Inventory"] = inventory_data - elif name == "equip": - equip_data = {} - for field in component.fields: - if(hasattr(comp_vals, field)): - equipable = getattr(comp_vals, field) - if equipable: - equip_data[field] = { - "ID": - equipable.entity.general.identifier - } - agent_data["Equipment"] = equip_data - agent_data["Entity"] = entity_data - - def getAgentImportFiles(self): - """Searches the agents directory for import files """ - filepaths = locateFiles("*.xml", self.objects_directory) - for filepath in filepaths: - try: - xml_file = vfs.VFS.open(filepath) - root = ElementTree.parse(xml_file).getroot() - if root.tag == "object": - self.agent_import_files[root.attrib["id"]] = filepath - except SyntaxError as error: - logging.error("Error parsing file {0}: {1}".format(filepath, - error)) - - def getDialogues(self): - """Searches the dialogue directory for dialogues """ - files = locateFiles("*.yaml", self.dialogue_directory) - dialogue_parser = YamlDialogueParser() - for dialogue_filepath in files: - # Note Technomage 2010-11-13: the new DialogueEngine uses its own - # parser now, YamlDialogueParser. -# dialogues = yaml.load_all(file(dialogue_file, "r")) - dialogue_file = vfs.VFS.open(dialogue_filepath) - try: - dialogue = dialogue_parser.load(dialogue_file) - except DialogueFormatError as error: - logging.error('unable to load dialogue file {0}: {1}' - .format(dialogue_filepath, error)) - else: - self.dialogues[dialogue.npc_name] = dialogue - # Note Technomage 2010-11-13: the below code is used to load - # multiple dialogues from a single file. Is this functionality - # used/necessary? -# for dialogue in dialogues: -# self.dialogues[dialogue["NPC"]] = dialogue
--- a/src/parpg/gamescenecontroller.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,581 +0,0 @@ -# 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 entities.action import (ChangeMapAction, ExamineAction, TalkAction, - OpenAction, CloseAction, UnlockAction, LockAction, - PickUpAction, DropItemAction, - ExamineContentsAction, - ) - -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) - self.systems.scripting.game_state = self.model.game_state - - #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"}) - player_char = (self.model.game_state. - getObjectById("PlayerCharacter")) - action = DropItemAction(self, - data_drag.dragged_item, - commands) - player_char.fifeagent.behaviour.approach([coord.x, coord.y], - action) - 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.setupScripts(self.model.target_map_name) - - self.model.setActiveMap(self.model.target_map_name, self) - - self.model.placeAgents(self) - self.model.placePC(self) - self.model.updateObjectDB(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 setupScripts(self, map_name): - """Read scripts for the current map""" - self.systems.scripting.reset() - self.model.readScriptsOfMap(map_name, self) - - 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_behaviour = npc.fifeagent.behaviour - npc_pos = npc_behaviour.getLocation().getLayerCoordinates() - self.model.game_state.getObjectById("PlayerCharacter").fifeagent.\ - behaviour.approach(npc_behaviour.agent, - 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() - agent = obj.fifeagent.behaviour.agent - 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, - agent, - 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, - agent, - ChangeMapAction(self, obj.change_map.target_map, - obj.change_map.target_position)]) - - if obj.lockable: - if obj.lockable.closed: - if not obj.lockable.locked: - actions.append(["Open", "Open", - player.fifeagent.behaviour.approach, - agent, - OpenAction(self, obj)]) - else: - actions.append(["Close", "Close", - player.fifeagent.behaviour.approach, - agent, - CloseAction(self, obj)]) - if obj.lockable.locked: - actions.append(["Unlock", "Unlock", - player.fifeagent.behaviour.approach, - agent, - UnlockAction(self, obj)]) - else: - if obj.lockable.closed: - actions.append(["Lock", "Lock", - player.fifeagent.behaviour.approach, - agent, - LockAction(self, obj)]) - if obj.container: - if obj.characterstats: - #TODO: This is reserved for a possible "Steal" action. - pass - elif not obj.lockable or not obj.lockable.closed: - actions.append(["Examine contents", "Examine Contents", - player.fifeagent.behaviour.approach, - agent, - ExamineContentsAction(self, obj)]) - if obj.containable: - actions.append(["Pick Up", "Pick Up", - player.fifeagent.behaviour.approach, - agent, - 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.setupScripts(self.model.game_state.current_map_name) - 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() - - 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) - World.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,)
--- a/src/parpg/gamesceneview.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,159 +0,0 @@ -# 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/>. - -from sounds import SoundEngine -from viewbase import ViewBase -from fife import fife - -class GameSceneView(ViewBase): - """GameSceneView is responsible for drawing the scene""" - def __init__(self, engine, model): - """Constructor for GameSceneView - @param engine: A fife.Engine instance - @type engine: fife.Engine - @param model: a script.GameModel instance - @type model: script.GameModel - """ - super(GameSceneView, self).__init__(engine, model) - - # init the sound - self.sounds = SoundEngine(engine) - - self.hud = None - - # The current highlighted object - self.highlight_obj = None - - # faded objects in top layer - self.faded_objects = set() - - def displayObjectText(self, obj_id, text, time=1000): - """Display on screen the text of the object over the object. - @type obj_id: id of fife.instance - @param obj: id of object to draw over - @type text: String - @param text: text to display over object - @return: None""" - try: - if obj_id: - obj = self.model.active_map.agent_layer.getInstance(obj_id) - else: - obj = None - except RuntimeError as error: - if error.args[0].split(',')[0].strip() == "_[NotFound]_": - obj = None - else: - raise - if obj: - obj.say(str(text), time) - - def onWalk(self, click): - """Callback sample for the context menu.""" - self.hud.hideContainer() - self.model.game_state.getObjectById("PlayerCharacter").fifeagent.behaviour.run(click) - - def refreshTopLayerTransparencies(self): - """Fade or unfade TopLayer instances if the PlayerCharacter - is under them.""" - if not self.model.active_map: - return - - # get the PlayerCharacter's screen coordinates - camera = self.model.active_map.cameras[self.model.active_map.my_cam_id] - point = self.model.game_state.getObjectById("PlayerCharacter").fifeagent.\ - behaviour.agent.getLocation() - scr_coords = camera.toScreenCoordinates(point.getMapCoordinates()) - - # find all instances on TopLayer that fall on those coordinates - instances = camera.getMatchingInstances(scr_coords, - self.model.active_map.top_layer) - instance_ids = [ instance.getId() for instance in instances ] - faded_objects = self.faded_objects - - # fade instances - for instance_id in instance_ids: - if instance_id not in faded_objects: - faded_objects.add(instance_id) - self.model.active_map.top_layer.getInstance(instance_id).\ - get2dGfxVisual().setTransparency(128) - - # unfade previously faded instances - for instance_id in faded_objects.copy(): - if instance_id not in instance_ids: - faded_objects.remove(instance_id) - self.model.active_map.top_layer.getInstance(instance_id).\ - get2dGfxVisual().setTransparency(0) - - - #def removeHighlight(self): - - - def highlightFrontObject(self, mouse_coords): - """Highlights the object that is at the - current mouse coordinates""" - if not self.model.active_map: - return - if mouse_coords: - front_obj = self.model.getObjectAtCoords(mouse_coords) - if front_obj != None: - if self.highlight_obj == None \ - or front_obj.getId() != \ - self.highlight_obj: - if self.model.game_state.hasObject(front_obj.getId()): - self.displayObjectText(self.highlight_obj, "") - self.model.active_map.outline_renderer.removeAllOutlines() - self.highlight_obj = front_obj.getId() - self.model.active_map.outline_renderer.addOutlined( - front_obj, - 0, - 137, 255, 2) - # get the text - item = self.model.objectActive(self.highlight_obj) - if item is not None: - self.displayObjectText(self.highlight_obj, - item.description.view_name) - else: - self.model.active_map.outline_renderer.removeAllOutlines() - self.highlight_obj = None - - - def moveCamera(self, direction): - """Move the camera in the given direction. - @type direction: list of two integers - @param direction: the two integers can be 1, -1, or 0 - @return: None """ - - if 'cameras' in dir(self.model.active_map): - cam = self.model.active_map.cameras[self.model.active_map.my_cam_id] - location = cam.getLocation() - position = location.getMapCoordinates() - - #how many pixls to move by each call - move_by = 1 - #create a new DoublePoint3D and add it to position DoublePoint3D - new_x, new_y = move_by * direction[0], move_by * direction[1] - - position_offset = fife.DoublePoint3D(int(new_x), int(new_y)) - position += position_offset - - #give location the new position - location.setMapCoordinates(position) - - #detach the camera from any objects - cam.detach() - #move the camera to the new location - cam.setLocation(location) - -
--- a/src/parpg/gamestate.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,167 +0,0 @@ -# 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/>. - -from parpg.quest_engine import QuestEngine - -class GameState(object): - """This class holds the current state of the game.""" - def __init__(self, quests_dir = None): - self.player_character = None - self.quest_engine = QuestEngine(quests_dir) - self.quest_engine.readQuests() - self.objects = {} - self.object_ids = {} - self.current_map_name = None - self.maps = {} - self.npcs_met = set() - self.funcs = { - "meet":self.meet, - "met":self.met - } - - - def addObject(self, object_id, map_id, game_object): - """Adds an object to the objects and object_ids - dictionaries. - @param object_id: ID of the object - @type object_id: str - @param map_id: ID of the map the object is on. - If the object is in a container this has to be None - @type map_id: str or None - @param object: object to be added - @type object: GameObject - """ - if not self.object_ids.has_key(object_id): - if not self.objects.has_key(map_id): - self.objects[map_id] = {} - self.objects[map_id][object_id] = game_object - self.object_ids[object_id] = map_id - - def deleteObject(self, object_id): - """Removes an object from the dictionaries - @param object_id: ID of the object - @type object_id: str - @returns The deleted object - """ - if self.hasObject(object_id): - map_id = self.getMapOfObject(object_id) - if map_id: - inst = self.maps[map_id].agent_layer.getInstance(object_id) - self.maps[map_id].agent_layer.deleteInstance(inst) - obj = self.objects[map_id][object_id] - del self.objects[map_id][object_id] - del self.object_ids[object_id] - return obj - return None - - - def getObjectsFromMap(self, map_id): - """Gets all objects that are currently on the given map. - @type map: String - @param map: The map name. - @returns: The list of objects on this map. Or an empty list""" - return [i for i in self.getObjectDictOfMap(map_id).values() - if map_id in self.objects] - - - def getObjectDictOfMap(self, map_id): - if map_id in self.objects: - return self.objects[map_id] - return {} - - def deleteObjectsFromMap(self, map_id): - """Deletes all objects of the given map. - @type map: String - @param map: The map name. - @returns: None""" - deleted_objs = [] - if map_id in self.objects: - for obj in self.objects[map_id].copy(): - deleted_objs.append(self.deleteObject(obj)) - return deleted_objs - - def hasObject(self, object_id): - """Check if an object with the given id is present - @param object_id: ID of the object - @type object_id: str - @return: True if there is an object False if not - """ - return self.object_ids.has_key(object_id) - - def getMapOfObject(self, object_id): - """Returns the map the object is on. - @param object_id: ID of the object - @type object_id: str - @return: Name of the map the object is on. - If there is no such object or the object is in a container None is returned - """ - if self.object_ids.has_key(object_id): - return self.object_ids[object_id] - return None - - def getObjectById(self, obj_id, map_id = None): - """Gets an object by its object id and map id - @type obj_id: String - @param obj_id: The id of the object. - @type map_id: String - @param map_id: It id of the map containing the object. - @returns: The object or None.""" - if not map_id: - map_id = self.getMapOfObject(obj_id) - if not map_id in self.objects: - self.objects[map_id] = {} - if obj_id in self.objects[map_id]: - return self.objects[map_id][obj_id] - - def clearObjects(self): - """Delete all objects from the state - """ - self.objects = {} - self.object_ids = {} - - def getStateForSaving(self): - """Prepares state for saving - @type state: dictionary - @param state: State of the object - """ - ret_dict = {} - ret_dict["CurrentMap"] = self.current_map_name - ret_dict["Quests"] = self.quest_engine.getStateForSaving() - ret_dict["NPCsMet"] = self.npcs_met - return ret_dict - - def restoreFromState(self, state): - """Restores the state""" - self.current_map_name = state["CurrentMap"] - self.npcs_met = state["NPCsMet"] - self.quest_engine.readQuests() - self.quest_engine.restoreFromState(state["Quests"]) - - def meet(self, npc): - """Record that the PC has met a certain NPC - @type npc: str - @param npc: The NPC's name or id""" - if npc in self.npcs_met: - # we could raise an error here, but should probably be a warn - # raise RuntimeError("I already know %s" % npc) - return - self.npcs_met.add(npc) - - def met(self, npc): - """Indicate whether the PC has met this npc before - @type npc: str - @param npc: The NPC's name or id - @return: None""" - return npc in self.npcs_met \ No newline at end of file
--- a/src/parpg/gui/__init__.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -from fife.extensions import pychan - -from .inventorygui import EquipmentSlot, InventoryGrid -from .spinners import Spinner, IntSpinner -from .tabwidget import TabWidget - -pychan.registerWidget(EquipmentSlot) -pychan.registerWidget(InventoryGrid) -pychan.registerWidget(Spinner) -pychan.registerWidget(IntSpinner) -pychan.registerWidget(TabWidget) \ No newline at end of file
--- a/src/parpg/gui/actionsbox.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,73 +0,0 @@ -# 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/>. - -"""Widget for displaying actions""" - -from fife.extensions import pychan -from fife.extensions.pychan import ScrollArea -from fife.extensions.pychan import VBox - -class ActionsBox(ScrollArea): - def __init__(self, **kwargs): - ScrollArea.__init__(self, **kwargs) - self.ContentBox = VBox(name = "ActionsContentBox", is_focusable=False) - self.addChild(self.ContentBox) - - def refresh(self): - """Refresh the actions box so that it displays the contents of - self.actions_text - @return: None""" - self.adaptLayout() - self.vertical_scroll_amount = self.getVerticalMaxScroll() - - def addAction(self, action): - """Add an action to the actions box. - @type action: (unicode) string - @param action: The text that you want to display in the actions box - @return: None""" - - if not type(action) is unicode: - action = unicode(action) - action_label = pychan.widgets.Label(text = action, wrap_text = True) - action_label.max_width = self.ContentBox.width - self.ContentBox.addChild(action_label) - self.refresh() - - def addDialog(self, name, text): - """Add a dialog text to the actions box. Prints first the name and then, indented to the right, the text. - @type name: (unicode) string - @param action: The name of the character that spoke - @type text:: (unicode) string - @param text: The text that was said - @return: None""" - if not type(name) is unicode: - name = unicode(name) - if not type(text) is unicode: - text = unicode(text) - - - name_label = pychan.widgets.Label(text = name, wrap_text = True) - self.ContentBox.addChild(name_label) - text_box = pychan.widgets.HBox() - spacer = pychan.widgets.Label() - spacer.min_width = int(self.ContentBox.width * 0.05) - spacer.max_width = int(self.ContentBox.width * 0.05) - text_box.addChild(spacer) - text_label = pychan.widgets.Label(text = text, wrap_text = True) - text_label.max_width = int(self.ContentBox.width * 0.95) - text_box.addChild(text_label) - self.ContentBox.addChild(text_box) - self.refresh() -
--- a/src/parpg/gui/charactercreationview.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -from fife.extensions import pychan -from fife.extensions.pychan.widgets import Label, HBox - -from parpg import vfs -from parpg.gui.spinner import IntSpinner - -class CharacterCreationView(object): - def __init__(self, xml_script_path='gui/character_creation.xml'): - xml_file = vfs.VFS.open(xml_script_path) - self.gui = pychan.loadXML(xml_file) - - def createStatisticList(self, statistics): - statistics_list = self.gui.findChild(name='statisticsList') - # Start with an empty list. - statistics_list.removeAllChildren() - for statistic in statistics: - name = statistic.long_name - hbox = HBox() - hbox.opaque = 0 - label = Label(text=name) - spinner = IntSpinner(lower_limit=0, upper_limit=100) - hbox.addChildren(label, spinner) - statistics_list.addChildren(hbox) - - def createTraitsList(self, traits): - pass - - def updateMessageArea(self, message): - message_area = self.gui.findChild(name='messageArea') - message_area.text = unicode(message) \ No newline at end of file
--- a/src/parpg/gui/containergui.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,137 +0,0 @@ -# This program 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. - -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. -from fife.extensions.pychan.tools import callbackWithArguments as cbwa - -from parpg.gui.containergui_base import ContainerGUIBase -from parpg.gui import drag_drop_data as data_drag -from parpg.components import container - -class ContainerGUI(ContainerGUIBase): - def __init__(self, controller, title, container): - """A class to create a window showing the contents of a container. - @param controller: The current Controller - @type controller: Class derived from ControllerBase - @param title: The title of the window - @type title: string - @param container: A container to represent - @type container: parpg.components.Container - @return: None""" - super(ContainerGUI, self).__init__(controller, "gui/container_base.xml") - self.gui.findChild(name="topWindow").title = title - - self.empty_images = dict() - self.container = container - self.events_to_map = {} - self.buttons = ("Slot1", "Slot2", "Slot3", - "Slot4", "Slot5", "Slot6", - "Slot7", "Slot8", "Slot9") - - def updateImages(self): - for index, button in enumerate(self.buttons): - widget = self.gui.findChild(name=button) - widget.item = container.get_item(self.container, index) - self.updateImage(widget) - - def updateImage(self, button): - if (button.item == None): - image = self.empty_images[button.name] - else: - image = button.item.image - button.up_image = image - button.down_image = image - button.hover_image = image - - def dragObject(self, obj): - """Drag the selected object. - @type obj: string - @param obj: The name of the object within - the dictionary 'self.buttons' - @return: None""" - # get the widget from the gui with the name obj - drag_widget = self.gui.findChild(name = obj) - drag_item = drag_widget.item - # only drag if the widget is not empty - if (drag_item != None): - # get the item that the widget is 'storing' - data_drag.dragged_item = drag_widget.item - # get the up and down images of the widget - up_image = drag_widget.up_image - down_image = drag_widget.down_image - self.setDragData(drag_widget.item, down_image, up_image) - container.take_item(self.container, drag_widget.item.slot) - - # after dragging the 'item', set the widgets' images - # so that it has it's default 'empty' images - drag_widget.item = None - self.updateImage(drag_widget) - - def dropObject(self, obj): - """Drops the object being dropped - @type obj: string - @param obj: The name of the object within - the dictionary 'self.buttons' - @return: None""" - try: - drop_widget = self.gui.findChild(name = obj) - drop_index = drop_widget.index - replace_item = None - - if data_drag.dragging: - drag_item = data_drag.dragged_item - #this will get the replacement item and the data for drag_drop if - ## there is an item all ready occupying the slot - replace_item = ( - container.put_item(self.container, drag_item, drop_index) - ) - - #if there was no item the stop dragging and reset cursor - if replace_item: - up_image = drop_widget.up_image - down_image = drop_widget.down_image - self.setDragData(replace_item, down_image, up_image) - else: - data_drag.dragging = False - #reset the mouse cursor to the normal cursor - self.controller.resetMouseCursor() - drop_widget.item = drag_item - self.updateImage(drop_widget) - except (container.BulkLimitError): - #Do we want to notify the player why the item can't be dropped? - pass - - def showContainer(self): - """Show the container - @return: None""" - # Prepare slots 1 through 9 - empty_image = "gui/inv_images/inv_backpack.png" - slot_count = 9 - for counter in range(1, slot_count+1): - slot_name = "Slot%i" % counter - index = counter - 1 - self.empty_images[slot_name] = empty_image - widget = self.gui.findChild(name=slot_name) - widget.item = container.get_item(self.container, index) - widget.index = index - self.updateImage(widget) - self.events_to_map[slot_name] = cbwa(self.dragDrop, slot_name) - self.events_to_map[slot_name + "/mouseReleased"] = \ - self.showContextMenu - - self.gui.mapEvents(self.events_to_map) - self.gui.show() - - def hideContainer(self): - """Hide the container - @return: None""" - if self.gui.isVisible(): - self.gui.hide()
--- a/src/parpg/gui/containergui_base.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,150 +0,0 @@ -# This program 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. - -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. -from copy import deepcopy -from types import StringTypes - -from fife import fife -from fife.extensions import pychan - -from parpg import vfs -from parpg.gui import drag_drop_data as data_drag -from parpg.entities.action import ACTIONS -from parpg.entities import General - -class ContainerGUIBase(object): - """ - Base class for windows that show the content of a container - """ - - - def __init__(self, controller, gui_file): - self.controller = controller - if isinstance(gui_file, pychan.Widget): - self.gui = gui_file - elif isinstance(gui_file, StringTypes): - xml_file = vfs.VFS.open(gui_file) - self.gui = pychan.loadXML(xml_file) - else: - self.gui = pychan.loadXML(gui_file) - - def dragDrop(self, obj): - """Decide whether to drag or drop the image. - @type obj: string - @param obj: The name of the object within - the dictionary 'self.buttons' - @return: None""" - if(data_drag.dragging == True): - self.dropObject(obj) - elif(data_drag.dragging == False): - self.dragObject(obj) - - def dragObject(self, obj): - """Drag the selected object. - @type obj: string - @param obj: The name of the object within - the dictionary 'self.buttons' - @return: None""" - pass - - def dropObject(self, obj): - """Drops the object being dropped - @type obj: string - @param obj: The name of the object within - the dictionary 'self.buttons' - @return: None""" - pass - - - def setDragData(self, drag_item, down_image, up_image): - """Set the dragging data""" - # set the mouse cursor to be the widget's image - self.controller.setMouseCursor(up_image.source, down_image.source) - data_drag.dragged_item = drag_item - data_drag.dragged_image = up_image.source - data_drag.dragging = True - - def createMenuItems(self, item, actions): - """Creates context menu items for all classes based on ContainerGUI""" - assert(isinstance(actions, dict)) - assert(isinstance(item, General)) - menu_actions = [] - item_name = item.description.view_name - if not actions.has_key("Look"): - look_action = actions["Look"] = {} - look_action["text"] = item.description.desc - if item.container and not actions.has_key("ExamineContents"): - actions["ExamineContents"] = {} - for action_name in actions: - display_name = action_name - if action_name in ACTIONS: - param_dict = {} - param_dict["controller"] = self.controller - param_dict["commands"] = {} - if action_name == "Look": - param_dict["examine_name"] = item_name - param_dict["examine_desc"] = actions[action_name].\ - pop("text") - if action_name == "Read": - param_dict["text_name"] = item_name - param_dict["text"] = "" - if action_name == "Use": - param_dict["item"] = item - display_name = actions[action_name].pop("text") - if action_name == "ExamineContents": - param_dict["container"] = item - display_name = "Examine Contents" - if action_name == "BrewBeer": - param_dict["pot"] = item - display_name = "Brew beer" - if actions[action_name]: - param_dict.update(actions[action_name]) - menu_actions.append([action_name, - display_name, - self.executeMenuItem, - ACTIONS[action_name]\ - (**param_dict)]) - return menu_actions - - def showContextMenu(self, event, widget): - """Decide whether to drag or drop the image. - @type obj: string - @param obj: The name of the object within - the dictionary 'self.buttons' - @return: None""" - if event.getButton() == event.RIGHT: - item = widget.item - if item: - if not isinstance(item, General): - item = item.entity - actions = {} - if item.usable: - actions = deepcopy(item.usable.actions) - x_pos, y_pos = widget.getAbsolutePos() - x_pos += event.getX() - y_pos += event.getY() - menu_actions = self.createMenuItems(item, actions) - self.controller.view.hud.hideContextMenu() - self.controller.view.hud.showContextMenu(menu_actions, - (x_pos, - y_pos) - ) - - def executeMenuItem(self, action): - """Executes the items action - @param action: The action to run - @type action: Class derived from parpg.entities.action.Action - """ - action.execute() - - def updateImages(self): - pass
--- a/src/parpg/gui/dialogs.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -# 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/>. -from fife.extensions import pychan - -from parpg import vfs - -class RestartDialog(object): - def __init__(self, settings): - self.settings = settings - xml_file = vfs.VFS.open('gui/restart_dialog.xml') - self.window = pychan.loadXML(xml_file) - self.window.mapEvents({'closeButton': self.hide}) - - def hide(self): - self.window.hide() - - def show(self): - self.window.show()
--- a/src/parpg/gui/dialoguegui.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,178 +0,0 @@ -# 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/>. -import logging - -from fife import fife -from fife.extensions import pychan -from fife.extensions.pychan import widgets - -from parpg import vfs -from parpg.dialogueprocessor import DialogueProcessor - -logger = logging.getLogger('dialoguegui') - -class DialogueGUI(object): - """Window that handles the dialogues.""" - _logger = logging.getLogger('dialoguegui.DialogueGUI') - - def __init__(self, controller, npc, quest_engine, met_fnc, meet_fnc, - has_fnc, player_character): - self.active = False - self.controller = controller - xml_file = vfs.VFS.open('gui/dialogue.xml') - self.dialogue_gui = pychan.loadXML(xml_file) - self.npc = npc - # TODO Technomage 2010-11-10: the QuestEngine should probably be - # a singleton-like object, which would avoid all of this instance - # handling. - self.quest_engine = quest_engine - self.player_character = player_character - self.met_fnc = met_fnc - self.meet_fnc = meet_fnc - self.pc_has_fnc = lambda slot_or_type:\ - has_fnc(player_character.container, slot_or_type) - self.npc_has_fnc = lambda slot_or_type:\ - has_fnc(npc.container, slot_or_type) - - def initiateDialogue(self): - """Callback for starting a quest""" - self.active = True - stats_label = self.dialogue_gui.findChild(name='stats_label') - stats_label.text = u'Name: John Doe\nAn unnamed one' - events = { - 'end_button': self.handleEnd - } - self.dialogue_gui.mapEvents(events) - self.dialogue_gui.show() - self.setNpcName(self.npc.description.view_name) - self.setAvatarImage(self.npc.dialogue.dialogue.avatar_path) - - game_state = {'npc': self.npc, 'pc': self.player_character, - 'quest': self.quest_engine, - 'met': self.met_fnc, 'meet': self.meet_fnc, - 'pc_has': self.pc_has_fnc, 'npc_has': self.npc_has_fnc, - 'model': self.controller.model, - } - try: - self.dialogue_processor = DialogueProcessor(self.npc.dialogue.dialogue, - game_state) - self.dialogue_processor.initiateDialogue() - except (TypeError) as error: - self._logger.error(str(error)) - else: - self.continueDialogue() - - def setDialogueText(self, text): - """Set the displayed dialogue text. - @param text: text to display.""" - text = unicode(text) - speech = self.dialogue_gui.findChild(name='speech') - # to append text to npc speech box, uncomment the following line - #speech.text = speech.text + "\n-----\n" + unicode(say) - speech.text = text - self._logger.debug('set dialogue text to "{0}"'.format(text)) - - def continueDialogue(self): - """Display the dialogue text and responses for the current - L{DialogueSection}.""" - dialogue_processor = self.dialogue_processor - dialogue_text = dialogue_processor.getCurrentDialogueSection().text - self.setDialogueText(dialogue_text) - self.responses = dialogue_processor.continueDialogue() - self.setResponses(self.responses) - - def handleEntered(self, *args): - """Callback for when user hovers over response label.""" - pass - - def handleExited(self, *args): - """Callback for when user hovers out of response label.""" - pass - - def handleClicked(self, *args): - """Handle a response being clicked.""" - response_n = int(args[0].name.replace('response', '')) - response = self.responses[response_n] - dialogue_processor = self.dialogue_processor - dialogue_processor.reply(response) - if (not dialogue_processor.in_dialogue): - self.handleEnd() - else: - self.continueDialogue() - - def handleEnd(self): - """Handle the end of the conversation being reached, either from the - GUI or from within the conversation itself.""" - self.dialogue_gui.hide() - self.responses = [] - self.npc.fifeagent.behaviour.state = 1 - self.npc.fifeagent.behaviour.idle() - self.active = False - - def setNpcName(self, name): - """Set the NPC name to display on the dialogue GUI. - @param name: name of the NPC to set - @type name: basestring""" - name = unicode(name) - stats_label = self.dialogue_gui.findChild(name='stats_label') - try: - (first_name, desc) = name.split(" ", 1) - stats_label.text = u'Name: ' + first_name + "\n" + desc - except ValueError: - stats_label.text = u'Name: ' + name - - self.dialogue_gui.title = name - self._logger.debug('set NPC name to "{0}"'.format(name)) - - def setAvatarImage(self, image_path): - """Set the NPC avatar image to display on the dialogue GUI - @param image_path: filepath to the avatar image - @type image_path: basestring""" - avatar_image = self.dialogue_gui.findChild(name='npc_avatar') - avatar_image.image = image_path - - def setResponses(self, dialogue_responses): - """Creates the list of clickable response labels and sets their - respective on-click callbacks. - @param responses: list of L{DialogueResponses} from the - L{DialogueProcessor} - @type responses: list of L{DialogueResponses}""" - choices_list = self.dialogue_gui.findChild(name='choices_list') - choices_list.removeAllChildren() - for index, response in enumerate(dialogue_responses): - button = widgets.Label( - name="response{0}".format(index), - text=unicode(response.text), - hexpand="1", - min_size=(100,16), - max_size=(490,48), - position_technique='center:center' - ) - button.margins = (5, 5) - button.background_color = fife.Color(0, 0, 0) - button.color = fife.Color(0, 255, 0) - button.border_size = 0 - button.wrap_text = 1 - button.capture(lambda button=button: self.handleEntered(button), - event_name='mouseEntered') - button.capture(lambda button=button: self.handleExited(button), - event_name='mouseExited') - button.capture(lambda button=button: self.handleClicked(button), - event_name='mouseClicked') - choices_list.addChild(button) - self.dialogue_gui.adaptLayout(True) - self._logger.debug( - 'added {0} to response choice list'.format(response) - )
--- a/src/parpg/gui/drag_drop_data.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# 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 contains the data that tells the GUI whether something is being dragged, dropped etc. -It is in one place to allow communication between multiple windows -""" -dragging = False -dragged_image = None -dragged_item = None
--- a/src/parpg/gui/filebrowser.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,162 +0,0 @@ -# This program 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. - -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. -import sys -import os -import logging - -from fife.extensions import pychan -from fife.extensions.pychan import widgets - -from parpg import vfs - -logger = logging.getLogger('filebrowser') - -def u2s(string): - # TODO: cryptic function name - return string.encode(sys.getfilesystemencoding()) - -class FileBrowser(object): - """FileBrowser displays directory and file listings from the vfs. - The file_selected parameter is a callback invoked when a file selection - has been made; its signature must be file_selected(path,filename). If - select_dir is set, file_selected's filename parameter should be optional. - The save_file option provides a box for supplying a new filename that - doesn't exist yet. The select_dir option allows directories to be - selected as well as files.""" - def __init__(self, engine, settings, file_selected, gui_xml_path, - close_callback=None, save_file=False, select_dir=False, - extensions=('.dat',)): - self.engine = engine - self.settings = settings - self.file_selected = file_selected - - self._widget = None - self.save_file = save_file - self.select_dir = select_dir - self.close_callback = close_callback - self.gui_xml_path = gui_xml_path - - self.extensions = extensions - # FIXME M. George Hansen 2011-06-06: Not sure that user_path is set - # correctly atm. Plus, I don't think that this should be - # hard-coded. - self.path = os.path.join(self.settings.user_path, 'saves') - self.dir_list = [] - self.file_list = [] - - def close(self): - """Closes the browser""" - self._widget.hide() - if self.close_callback: - self.close_callback() - - def showBrowser(self): - """Shows the file dialog browser""" - if self._widget: - self._widget.show() - return - xml_file = vfs.VFS.open(self.gui_xml_path) - self._widget = pychan.loadXML(xml_file) - self._widget.mapEvents({ - 'dirList' : self._setPath, - 'selectButton' : self._selectFile, - 'closeButton' : self.close - }) - self._setPath() - if self.save_file: - self._file_entry = widgets.TextField(name='saveField', text=u'') - self._widget.findChild(name="fileColumn").\ - addChild(self._file_entry) - self._widget.show() - - def _setPath(self): - """Path change callback.""" - selection = self._widget.collectData('dirList') - if not (selection < 0): - new_dir = u2s(self.dir_list[selection]) - lst = self.path.split('/') - if new_dir == '..' and lst[-1] != '..' and lst[-1] != '.': - lst.pop() - else: - lst.append(new_dir) - self.path = '/'.join(lst) - - def decodeList(list): - fs_encoding = sys.getfilesystemencoding() - if fs_encoding is None: fs_encoding = "ascii" - - new_list = [] - for i in list: - try: - new_list.append(unicode(i, fs_encoding)) - except: - new_list.append(unicode(i, fs_encoding, 'replace')) - logger.debug("WARNING: Could not decode item:\n" - "{0}".format(i)) - return new_list - - - - self.dir_list = [] - self.file_list = [] - - dir_list = ('..',) + filter(lambda d: not d.startswith('.'), \ - self.engine.getVFS().\ - listDirectories(self.path)) - file_list = filter(lambda f: f.split('.')[-1] in self.extensions, \ - self.engine.getVFS().listFiles(self.path)) - - self.dir_list = decodeList(dir_list) - self.file_list = decodeList(file_list) - self._widget.distributeInitialData({ - 'dirList' : self.dir_list, - 'fileList' : self.file_list - }) - - def _selectFile(self): - """ File selection callback. """ - self._widget.hide() - selection = self._widget.collectData('fileList') - - if self.save_file: - data = self._widget.collectData('saveField') - if data: - if (data.endswith(".dat")): - self.file_selected(self.path, \ - u2s(self._widget.collectData('saveField'))) - else: - self.file_selected(self.path, - u2s(self._widget.collectData('saveField')) + '.dat') - return - - - if selection >= 0 and selection < len(self.file_list): - self.file_selected(self.path, u2s(self.file_list[selection])) - return - - if self.select_dir: - self.file_selected(self.path) - return - - logger.error('no selection') - - def _warningMessage(self): - """Shows the warning message dialog when a file with a - faulty extension was selected.""" - window = widgets.Window(title="Warning") - text = "Please save the file as a .dat" - label = widgets.Label(text=text) - ok_button = widgets.Button(name="ok_button", text="Ok") - window.addChildren([label, ok_button]) - window.mapEvents({'ok_button':window.hide}) - window.show()
--- a/src/parpg/gui/hud.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,514 +0,0 @@ -# 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/>. - -import os -import logging - -from fife.extensions import pychan -from fife.extensions.pychan.tools import callbackWithArguments as cbwa - -from parpg import vfs -from parpg.gui.filebrowser import FileBrowser -from parpg.gui.menus import ContextMenu, SettingsMenu -from parpg.gui.popups import ExaminePopup -from parpg.gui.containergui import ContainerGUI -from parpg.gui.dialoguegui import DialogueGUI -from parpg.gui import drag_drop_data as data_drag -from parpg.gui.inventorygui import CharacterGUI -from actionsbox import ActionsBox -from parpg.entities.action import DropItemAction -from parpg.components import container - -logger = logging.getLogger('hud') -class Hud(object): - """Main Hud class""" - def __init__(self, controller, settings, callbacks): - """Initialise the instance. - @type controller: Class derived from ControllerBase - @param controller: The current controller - @type settings: settings.Setting - @param settings: The settings - @type inv_model: dict - @type callbacks: dict - @param callbacks: a dict of callbacks - saveGame: called when the user clicks on Save - loadGame: called when the user clicks on Load - quitGame: called when the user clicks on Quit - @return: None""" - - # TODO: perhaps this should not be hard-coded here - self.settings = settings - pychan.registerWidget(ActionsBox) - - xml_file = vfs.VFS.open('gui/hud.xml') - self.hud = pychan.loadXML(xml_file) - - self.controller = controller - self.engine = controller.engine - self.model = controller.model - self.inventory = None - self.character_screen = None - - self.save_game_callback = callbacks['saveGame'] - self.load_game_callback = callbacks['loadGame'] - self.quit_callback = callbacks['quitGame'] - - self.box_container = None - self.examine_box = None - self.context_menu = None - self.help_dialog = None - self.events_to_map = None - self.main_menu = None - self.menu_events = None - self.quit_window = None - self.bottom_panel = self.hud.findChild(name="mainHudWindow") - - self.actions_box = self.hud.findChild(name="actionsBox") - self.menu_displayed = False - self.inventory_storage = None - self.initializeHud() - self.initializeMainMenu() - self.initializeContextMenu() - self.initializeHelpMenu() - self.initializeEvents() - self.initializeQuitDialog() - self.initializeSettingsMenu() - - def _getEnabled(self): - """"Returns whether the gui widget is enabled or not""" - return self.hud.real_widget.isEnabled() - - def _setEnabled(self, enabled): - """"Sets whether the gui widget is enabled or not""" - self.hud.real_widget.setEnabled(enabled) - childs = self.hud.getNamedChildren() - for child_list in childs.itervalues(): - for child in child_list: - child.real_widget.setEnabled(enabled) - - enabled = property(_getEnabled, _setEnabled) - - def initializeHud(self): - """Initialize and show the main HUD - @return: None""" - self.events_to_map = {"menuButton":self.displayMenu, } - self.hud.mapEvents(self.events_to_map) - # set HUD size according to screen size - screen_width = self.engine.getSettings().getScreenWidth() - self.hud.findChild(name="mainHudWindow").size = (screen_width, 65) - self.hud.findChild(name="inventoryButton").position = \ - (screen_width-59, 7) - # add ready slots - ready1 = self.hud.findChild(name='hudReady1') - ready2 = self.hud.findChild(name='hudReady2') - ready3 = self.hud.findChild(name='hudReady3') - ready4 = self.hud.findChild(name='hudReady4') - - if (screen_width <=800) : - gap = 0 - else : - gap = 40 - ready1.position = (160+gap, 7) - ready2.position = (220+gap, 7) - ready3.position = (screen_width-180-gap, 7) - ready4.position = (screen_width-120-gap, 7) - self.actions_box.position = (280+gap, 5) - actions_width = screen_width - 470 - 2*gap - - self.actions_box.ContentBox.min_width = actions_width - self.actions_box.ContentBox.max_width = actions_width - - # and finally add an actions box - self.actions_box.min_size = (actions_width, 55) - self.actions_box.max_size = (actions_width, 55) - # now it should be OK to display it all - self.showHUD() - - def addAction(self, action): - """Add an action to the actions box. - @type action: (unicode) string - @param action: The text that you want to display in the actions box - @return: None""" - self.actions_box.addAction(action) - - def showHUD(self): - """Show the HUD. - @return: None""" - self.hud.show() - self.enabled = True - - def hideHUD(self): - """Hide the HUD. - @return: None""" - self.hud.hide() - self.enabled = False - - def initializeInventory(self): - """Initialize the inventory""" - if not self.inventory: - xml_file = vfs.VFS.open('gui/inventory.xml') - player = self.model.game_state.getObjectById("PlayerCharacter") - self.inventory = CharacterGUI(self.controller, xml_file, - player.container, player.equip, None) -# inv_callbacks = { -# 'refreshReadyImages': self.refreshReadyImages, -# 'toggleInventoryButton': self.toggleInventoryButton, -# } -# self.inventory_storage = \ -# self.model.game_state.getObjectById("PlayerCharacter").fifeagent.inventory -# if self.inventory == None: -# self.inventory = inventorygui.InventoryGUI(self.controller, -# self.inventory_storage, -# inv_callbacks) -# else: -# self.inventory.inventory_storage = self.inventory_storage -# self.refreshReadyImages() - - def initializeCharacterScreen(self): - """Initialize the character screen.""" - # TODO Technomage 2010-12-24: - if not self.character_screen: - xml_file = vfs.VFS.open('gui/character_screen.xml') - self.character_screen = pychan.loadXML(xml_file) - - def initializeContextMenu(self): - """Initialize the Context Menu - @return: None""" - self.context_menu = ContextMenu(self.engine, [], (0, 0)) - - def showContextMenu(self, data, pos): - """Display the Context Menu with model at pos - @type model: list - @param model: model to pass to context menu - @type pos: tuple - @param pos: tuple of x and y coordinates - @return: None""" - self.context_menu = ContextMenu(self.engine, data, pos) - self.context_menu.show() - - def hideContextMenu(self): - """Hides the context menu - @return: None""" - self.context_menu.hide() - - def initializeMainMenu(self): - """Initalize the main menu. - @return: None""" - - xml_file = vfs.VFS.open('gui/hud_pause_menu.xml') - self.main_menu = pychan.loadXML(xml_file) - - #TODO: find more suitalbe place for onOptilonsPress implementation - self.menu_events = {"resumeButton": self.hideMenu, - "settingsButton": self.displaySettings, - "helpButton": self.displayHelp} - self.main_menu.mapEvents(self.menu_events) - - def displayMenu(self): - """Displays the main in-game menu. - @return: None""" - self.stopActions() - if (self.menu_displayed == False): - self.main_menu.show() - self.menu_displayed = True - self.model.pause(True) - self.controller.pause(True) - self.enabled = False - elif (self.menu_displayed == True): - self.hideMenu() - - def hideMenu(self): - """Hides the main in-game menu. - @return: None""" - self.main_menu.hide() - self.menu_displayed = False - self.model.pause(False) - self.controller.pause(False) - self.enabled = True - - def initializeSettingsMenu(self): - self.settings_menu = SettingsMenu(self.engine, self.settings) - - def displaySettings(self): - self.settings_menu.show() - - def initializeHelpMenu(self): - """Initialize the help menu - @return: None""" - - xml_file = vfs.VFS.open('gui/help.xml') - self.help_dialog = pychan.loadXML(xml_file) - - help_events = {"closeButton":self.help_dialog.hide} - self.help_dialog.mapEvents(help_events) - main_help_text = u"Welcome to Post-Apocalyptic RPG or PARPG![br][br]"\ - "This game is still in development, so please expect for there to be "\ - "bugs and[br]feel free to tell us about them at "\ - "http://www.forums.parpg.net.[br]This game uses a "\ - "\"Point 'N' Click\" interface, which means that to move around,[br]"\ - "just click where you would like to go and your character will move "\ - "there.[br]PARPG also utilizes a context menu. To access this, just "\ - "right click anywhere[br]on the screen and a menu will come up. This "\ - "menu will change depending on[br]what you have clicked on, hence "\ - "it's name \"context menu\".[br][br]" - - k_text = u" Keybindings" - k_text += "[br] A : Add a test action to the actions display" - k_text += "[br] I : Toggle the inventory screen" - k_text += "[br] F7 : Take a screenshot" - k_text += "[br] (Saves to screenshots directory)" - k_text += "[br] F10 : Toggle console" - k_text += "[br] PAUSE : (Un)Pause the game" - k_text += "[br] Q : Quit the game" - self.help_dialog.distributeInitialData({ - "MainHelpText":main_help_text, - "KeybindText":k_text - }) - - def displayHelp(self): - """Display the help screen. - @return: None""" - self.help_dialog.show() - - def saveGame(self): - """ Called when the user wants to save the game. - @return: None""" - self.stopActions() - xml_path = 'gui/savebrowser.xml' - save_browser = FileBrowser(self.engine, - self.settings, - self.save_game_callback, - xml_path, - self.loadsave_close, - save_file=True, - extensions=('.dat')) - save_browser.showBrowser() - self.controller.pause(True) - self.model.pause(True) - self.enabled = False - - def stopActions(self): - """This method stops/resets actions that are currently performed - like dragging an item. - This is done to be able to savely perform other actions that might - interfere with current running ones.""" - #Reset dragging - move item back to its old container - if data_drag.dragging: - drag_item = data_drag.dragged_item - data_drag.dragging = False - data_drag.dragged_item = None - DropItemAction(self.controller, drag_item).execute() - if self.inventory: - self.inventory.closeInventory() - - def newGame(self): - """Called when user request to start a new game. - @return: None""" - self.stopActions() - logger.info('new game') - - def loadsave_close(self): - """Called when the load/save filebrowser was closed without a file selected""" - if not self.menu_displayed: - self.enabled = True - self.model.pause(False) - self.controller.pause(False) - - def loadGame(self): - """ Called when the user wants to load a game. - @return: None""" - self.stopActions() - xml_path = 'gui/loadbrowser.xml' - load_browser = FileBrowser(self.engine, - self.settings, - self.load_game_callback, - xml_path, - close_callback = self.loadsave_close, - save_file=False, - extensions=('.dat')) - load_browser.showBrowser() - self.model.pause(True) - self.controller.pause(True) - self.enabled = False - - def initializeQuitDialog(self): - """Creates the quit confirmation dialog - @return: None""" - self.quit_window = pychan.widgets.Window(title=unicode("Quit?"), \ - min_size=(200,0)) - - hbox = pychan.widgets.HBox() - are_you_sure = "Are you sure you want to quit?" - label = pychan.widgets.Label(text=unicode(are_you_sure)) - yes_button = pychan.widgets.Button(name="yes_button", - text=unicode("Yes"), - min_size=(90,20), - max_size=(90,20)) - no_button = pychan.widgets.Button(name="no_button", - text=unicode("No"), - min_size=(90,20), - max_size=(90,20)) - - self.quit_window.addChild(label) - hbox.addChild(yes_button) - hbox.addChild(no_button) - self.quit_window.addChild(hbox) - - events_to_map = { "yes_button": self.quit_callback, - "no_button": self.quit_window.hide } - - self.quit_window.mapEvents(events_to_map) - - - def quitGame(self): - """Called when user requests to quit game. - @return: None""" - self.stopActions() - self.quit_window.show() - - def toggleInventoryButton(self): - """Manually toggles the inventory button. - @return: None""" - button = self.hud.findChild(name="inventoryButton") - if button.toggled == 0: - button.toggled = 1 - else: - button.toggled = 0 - - def toggleInventory(self, toggle_image=True): - """Displays the inventory screen - @return: None""" - if self.inventory is None: - self.initializeInventory() - - self.inventory.toggleInventory(toggle_image) - - def toggleCharacterScreen(self): - if self.characcter_screen is None: - self.initializeCharacterScreen() - - if not self.character_screen.isVisible(): - self.character_screen.show() - else: - self.character_screen.hide() - - def refreshReadyImages(self): - """Make the Ready slot images on the HUD be the same as those - on the inventory - @return: None""" - for ready in range(1, 5): - button = self.hud.findChild(name=("hudReady%d" % ready)) - if self.inventory_storage == None : - origin = None - else: - origin = self.inventory_storage.getItemsInSlot('ready', ready-1) - if origin == None: - self.setImages(button, - self.inventory.slot_empty_images['ready']) - else: - self.setImages(button, origin.getInventoryThumbnail()) - - def setImages(self, widget, image): - """Set the up, down, and hover images of an Imagebutton. - @type widget: pychan.widget - @param widget: widget to set - @type image: string - @param image: image to use - @return: None""" - widget.up_image = image - widget.down_image = image - widget.hover_image = image - - def initializeEvents(self): - """Intialize Hud events - @return: None""" - events_to_map = {} - - # when we click the toggle button don't change the image - events_to_map["inventoryButton"] = cbwa(self.toggleInventory, False) - events_to_map["saveButton"] = self.saveGame - events_to_map["loadButton"] = self.loadGame - - hud_ready_buttons = ["hudReady1", "hudReady2", \ - "hudReady3", "hudReady4"] - - for item in hud_ready_buttons: - events_to_map[item] = cbwa(self.readyAction, item) - - self.hud.mapEvents(events_to_map) - - menu_events = {} - menu_events["newButton"] = self.newGame - menu_events["quitButton"] = self.quitGame - menu_events["saveButton"] = self.saveGame - menu_events["loadButton"] = self.loadGame - self.main_menu.mapEvents(menu_events) - - def readyAction(self, ready_button): - """ Called when the user selects a ready button from the HUD """ - text = "Used the item from %s" % ready_button - self.addAction(text) - - def createBoxGUI(self, title, container): - """Creates a window to display the contents of a box - @type title: string - @param title: The title for the window - @param items: The box to display - @return: A new ContainerGui""" - events = {'takeAllButton':self.hideContainer, - 'closeButton':self.hideContainer} - #hide previous container if any, to avoid orphaned dialogs - self.hideContainer() - - self.box_container = ContainerGUI(self.controller, - unicode(title), container) - self.box_container.gui.mapEvents(events) - self.box_container.showContainer() - return self.box_container - - def hideContainer(self): - """Hide the container box - @return: None""" - if self.box_container: - self.box_container.hideContainer() - self.box_container = None - - def createExamineBox(self, title, desc): - """Create an examine box. It displays some textual description of an - object - @type title: string - @param title: The title of the examine box - @type desc: string - @param desc: The main body of the examine box - @return: None""" - if self.examine_box is not None: - self.examine_box.closePopUp() - self.examine_box = ExaminePopup(self.engine, title, desc) - self.examine_box.showPopUp() - - def showDialogue(self, npc): - """Show the NPC dialogue window - @type npc: actors.NonPlayerCharacter - @param npc: the npc that we are having a dialogue with - @return: The dialogue""" - self.stopActions() - dialogue = DialogueGUI( - self.controller, - npc, - self.model.game_state.quest_engine, - self.model.game_state.met, self.model.game_state.meet, - container.get_item, - self.model.game_state.getObjectById("PlayerCharacter")) - return dialogue
--- a/src/parpg/gui/inventorygui.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,391 +0,0 @@ -# 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/>. -import logging -from types import StringTypes - -from fife.extensions.pychan.tools import callbackWithArguments as cbwa -from fife.extensions import pychan -from fife.extensions.pychan.attrs import UnicodeAttr - -from parpg.gui import drag_drop_data as data_drag -from parpg.gui.containergui_base import ContainerGUIBase -from parpg.entities.action import ACTIONS -from parpg.components import equip, equipable, container -from parpg.entities import General - -logger = logging.getLogger('action') - -class EquipmentSlot(pychan.VBox): - def __init__(self, min_size=(50, 50), - max_size=(50, 50), margins=None, - **kwargs): - pychan.VBox.__init__(self, min_size=min_size, max_size=max_size, - **kwargs) - self.background_image = 'gui/inv_images/inv_background.png' - icon = pychan.Icon(name="Icon") - self.addChild(icon) - self.adaptLayout() - if self.parent is not None: - self.beforeShow() - - @property - def image(self): - icon = self.findChildByName("Icon") - return icon.image - - @image.setter - def image(self, image): - icon = self.findChildByName("Icon") - icon.image = image - -class InventoryGrid(pychan.VBox): - ATTRIBUTES = pychan.VBox.ATTRIBUTES + [pychan.attrs.PointAttr('grid_size')] - - def _setNColumns(self, n_columns): - n_rows = self.grid_size[1] - self.grid_size = (n_columns, n_rows) - - def _getNColumns(self): - n_columns = self.grid_size[0] - return n_columns - n_columns = property(fget=_getNColumns, fset=_setNColumns) - - def _setNRows(self, n_rows): - n_columns = self.grid_size[0] - self.grid_size = (n_columns, n_rows) - - def _getNRows(self): - n_rows = self.grid_size[1] - return n_rows - n_rows = property(fget=_getNRows, fset=_getNColumns) - - def _setGridSize(self, grid_size): - n_columns, n_rows = grid_size - self.removeAllChildren() - for row_n in xrange(n_rows): - row_size = (n_columns * 50, 50) - row = pychan.HBox(min_size=row_size, max_size=row_size, - padding=self.padding) - row.border_size = 1 - row.opaque = 0 - for column_n in xrange(n_columns): - index = (row_n * n_columns + column_n) - slot = pychan.Icon(min_size=(50, 50), max_size=(50, 50)) - slot.border_size = 1 - slot.name = "Slot_%d" % index - slot.index = index - row.addChild(slot) - self.addChild(row) - self.min_size = ((n_columns * 50) + 2, (n_rows * 50) + 2) - self.max_size = self.min_size - - def _getGridSize(self): - n_rows = len(self.children) - n_columns = len(self.children[0].children) - return (n_rows, n_columns) - grid_size = property(fget=_getGridSize, fset=_setGridSize) - - def getSlot(self, row_or_index, col=None): - if col: - index = row * self.n_columns + col - else: - index = row_or_index - return self.findChildByName("Slot_%d" % index) - - def __init__(self, grid_size=(2, 2), padding=0, **kwargs): - pychan.VBox.__init__(self, padding=padding, **kwargs) - self.opaque = 0 - self.grid_size = grid_size - self.border_size = 1 - - -class EquipmentGUI(ContainerGUIBase): - def __init__(self, controller, gui, equip, callbacks): - ContainerGUIBase.__init__(self, controller, gui) - self.equip = equip - self.equip_to_gui = { - "head": "headSlot", - "neck": "neckSlot", - "body": "shirtSlot", - "belt": "beltSlot", - "leg": "pantsSlot", - "feet": "bootsSlot", - "l_arm": "leftHandSlot", - "r_arm": "rightHandSlot", - } - self.setSlotEvents() - - def updateImages(self): - for eq_slot, gui_slot in self.equip_to_gui.iteritems(): - widget = self.gui.findChildByName(gui_slot) - equipable = equip.get_equipable(self.equip, eq_slot) - widget.item = equipable.entity if equipable else None - self.updateImage(widget) - - def updateImage(self, slot): - assert(isinstance(slot, EquipmentSlot)) - if (slot.item): - image = slot.item.containable.image - else: - image = None - slot.image = image - - def dragObject(self, obj): - """Drag the selected object. - @type obj: string - @param obj: The name of the object - @return: None""" - # get the widget from the gui with the name obj - drag_widget = self.gui.findChildByName(obj) - drag_item = drag_widget.item - # only drag if the widget is not empty - if (drag_item != None): - if isinstance(drag_item, General): - drag_item = drag_item.containable - elif isinstance(drag_item, equipable): - drag_item = drag_item.entity.containable - drag_eq = drag_item.entity.equipable - # get the image of the widget - image = drag_widget.image - self.setDragData(drag_item, image, image) - equip.take_equipable(self.equip, drag_eq.in_slot) - - # after dragging the 'item', set the widgets' images - # so that it has it's default 'empty' images - drag_widget.item = None - self.updateImage(drag_widget) - - def dropObject(self, obj): - """Drops the object being dropped - @type obj: string - @param obj: The name of the object - @return: None""" - try: - drop_widget = self.gui.findChildByName(obj) - drop_slot = drop_widget.slot - replace_item = None - - if data_drag.dragging: - drag_item = data_drag.dragged_item.entity - if drag_item.equipable: - drag_item = drag_item.equipable - else: - return - #this will get the replacement item and the data for drag_drop - #if there is an item all ready occupying the slot - replace_item = ( - equip.equip(self.equip, drag_item, drop_slot) - ) - - #if there was no item the stop dragging and reset cursor - if replace_item: - image = drop_widget.image - self.setDragData(replace_item.entity.containable, image, image) - else: - data_drag.dragging = False - #reset the mouse cursor to the normal cursor - self.controller.resetMouseCursor() - drop_widget.item = drag_item.entity - self.updateImage(drop_widget) - except (equip.AlreadyEquippedError, equip.CannotBeEquippedInSlot): - #Do we want to notify the player why the item can't be dropped? - pass - - def mousePressedOnSlot(self, event, widget): - if event.getButton() == event.LEFT: - self.dragDrop(widget.name) - - def setSlotEvents(self): - events_to_map = {} - for eq_slot, gui_slot in self.equip_to_gui.iteritems(): - widget = self.gui.findChildByName(gui_slot) - slot_name = widget.name - widget.slot = eq_slot - events_to_map[slot_name + "/mousePressed"] = ( - self.mousePressedOnSlot - ) - events_to_map[slot_name + "/mouseReleased"] = self.showContextMenu - - self.gui.mapEvents(events_to_map) - -class InventoryGUI(ContainerGUIBase): - def __init__(self, controller, gui, container, callbacks): - ContainerGUIBase.__init__(self, controller, gui) - self.grid = self.gui.findChildByName("Grid") - assert(isinstance(self.grid, InventoryGrid)) - self.container = container - self.setSlotEvents() - - def setSlotEvents(self): - slot_count = self.grid.n_rows * self.grid.n_columns - events_to_map = {} - for counter in xrange(0, slot_count): - widget = self.grid.getSlot(counter) - slot_name = widget.name - widget.index = counter - events_to_map[slot_name + "/mousePressed"] = ( - self.mousePressedOnSlot - ) - events_to_map[slot_name + "/mouseReleased"] = self.showContextMenu - - self.grid.mapEvents(events_to_map) - - def updateImages(self): - for index, child in enumerate(self.container.children): - slot = self.grid.getSlot(index) - if child: - slot.item = child.entity - else: - slot.item = None - self.updateImage(slot) - - def updateImage(self, slot): - assert(isinstance(slot, pychan.Icon)) - if (slot.item): - image = slot.item.containable.image - else: - image = None - slot.image = image - - def mousePressedOnSlot(self, event, widget): - if event.getButton() == event.LEFT: - self.dragDrop(widget.name) - - def dragObject(self, obj): - """Drag the selected object. - @type obj: string - @param obj: The name of the object - @return: None""" - # get the widget from the gui with the name obj - drag_widget = self.gui.findChild(name = obj) - drag_item = drag_widget.item - # only drag if the widget is not empty - if (drag_item != None): - if isinstance(drag_item, General): - drag_item = drag_item.containable - # get the image of the widget - image = drag_widget.image - self.setDragData(drag_item, image, image) - container.take_item(self.container, drag_item.slot) - - # after dragging the 'item', set the widgets' images - # so that it has it's default 'empty' images - drag_widget.item = None - self.updateImage(drag_widget) - - def dropObject(self, obj): - """Drops the object being dropped - @type obj: string - @param obj: The name of the object - @return: None""" - try: - drop_widget = self.gui.findChildByName(obj) - drop_index = drop_widget.index - replace_item = None - - if data_drag.dragging: - drag_item = data_drag.dragged_item - #this will get the replacement item and the data for drag_drop if - ## there is an item all ready occupying the slot - replace_item = ( - container.put_item(self.container, drag_item, drop_index) - ) - - #if there was no item the stop dragging and reset cursor - if replace_item: - image = drop_widget.image - self.setDragData(replace_item, image, image) - else: - data_drag.dragging = False - #reset the mouse cursor to the normal cursor - self.controller.resetMouseCursor() - drop_widget.item = drag_item.entity - self.updateImage(drop_widget) - except (container.BulkLimitError): - #Do we want to notify the player why the item can't be dropped? - pass - - def createMenuItems(self, item, actions): - """Creates context menu items for the InventoryGUI""" - menu_actions = ContainerGUIBase.createMenuItems(self, item, actions) - param_dict = {} - param_dict["controller"] = self.controller - param_dict["commands"] = {} - param_dict["item"] = item.containable - param_dict["container_gui"] = self - menu_actions.append(["Drop", - "Drop", - self.executeMenuItem, - ACTIONS["DropFromInventory"](**param_dict)]) - return menu_actions - -class CharacterGUI(object): - def __init__(self, controller, gui, container, equip, callbacks): - self.engine = controller.engine - self.inventory_shown = False - if isinstance(gui, pychan.Widget): - self.gui = gui - elif isinstance(gui, StringTypes): - xml_file = vfs.VFS.open(gui) - self.gui = pychan.loadXML(xml_file) - else: - self.gui = pychan.loadXML(gui) - - render_backend = self.engine.getRenderBackend() - screen_mode = render_backend.getCurrentScreenMode() - screen_width, screen_height = (screen_mode.getWidth(), - screen_mode.getHeight()) - widget_width, widget_height = self.gui.size - self.gui.position = ((screen_width - widget_width) / 2, - (screen_height - widget_height) / 2) - self.equip_gui = EquipmentGUI( - controller, - self.gui.findChildByName("equipmentPage"), - equip, callbacks - ) - self.inv_gui = InventoryGUI( - controller, - self.gui.findChildByName("inventoryPage"), - container, callbacks - ) - - def toggleInventory(self, toggleImage=True): - """Pause the game and enter the inventory screen, or close the - inventory screen and resume the game. - @type toggleImage: bool - @param toggleImage: - Call toggleInventoryCallback if True. Toggling via a - keypress requires that we toggle the Hud inventory image - explicitly. Clicking on the Hud inventory button toggles the - image implicitly, so we don't change it. - @return: None""" - if not self.inventory_shown: - self.showInventory() - self.inventory_shown = True - else: - self.closeInventory() - self.inventory_shown = False - - def updateImages(self): - self.equip_gui.updateImages() - self.inv_gui.updateImages() - - def showInventory(self): - self.updateImages() - self.gui.show() - - def closeInventory(self): - self.gui.hide() - \ No newline at end of file
--- a/src/parpg/gui/menus.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,172 +0,0 @@ -# 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/>. - -import logging - -from fife.extensions import pychan - -from parpg import vfs -from dialogs import RestartDialog - -logger = logging.getLogger('menus') - -class ContextMenu(object): - def __init__(self, engine, menu_items, pos): - """@type engine: engine.Engine - @param engine: An instance of the class Engine from engine.py - @type menu_items: list - @param menu_items: A list of items containing the name and - text for the menu item and callback - i.e. [["menu", "Some text", Callback] - @type pos: (int, int) - @param pos: Screen position to use - @return: None""" - self.vbox = pychan.widgets.VBox(position=pos) - events_to_map = {} - for item in menu_items: - p = pychan.widgets.Button(name=item[0], text=unicode(item[1])) - self.vbox.addChild(p) - events_to_map [item[0]] = self.actionDecorator(*item[2:]) - self.vbox.mapEvents(events_to_map) - - def show(self): - """Shows the context menu""" - self.vbox.show() - def hide(self): - """Hides the context menu""" - self.vbox.hide() - - def actionDecorator (self,func, *args, **kwargs): - """This function is supposed to add some generic that should be - executed before and/or after an action is fired through the - context menu. - - @type func: Any callable - @param func: The original action function - @param args: Unpacked list of positional arguments - @param kwargs: Unpacked list of keyword arguments - @return: A wrapped version of func""" - def decoratedFunc (): - """ This is the actual wrapped version of func, that is returned. - It takes no external arguments, so it can safely be passed around - as a callback.""" - # some stuff that we do before the actual action is executed - self.hide() - # run the action function, and record the return value - ret_val = func (*args,**kwargs) - # we can eventually add some post-action code here, if needed (e.g. logging) - pass - # return the value, as if the original function was called - return ret_val - return decoratedFunc - -class SettingsMenu(object): - def __init__(self, engine, settings): - self.engine = engine - self.settings = settings - - width = self.settings.fife.ScreenWidth - height = self.settings.fife.ScreenHeight - - # available options - screen_modes = self.engine.getDeviceCaps().getSupportedScreenModes() - resolutions = list(set([(mode.getWidth(), mode.getHeight()) - for mode in screen_modes])) - self.resolutions = ["{0}x{1}".format(item[0], item[1]) - for item in sorted(resolutions)[1:]] - - self.render_backends = ['OpenGL', 'SDL'] - self.lighting_models = range(3) - - # selected options - self.resolution = "{0}x{1}".format(width, height) - self.render_backend = self.settings.fife.RenderBackend - self.lighting_model = self.settings.fife.Lighting - self.fullscreen = self.settings.fife.FullScreen - self.sound = self.settings.fife.EnableSound - self.scroll_speed = self.settings.parpg.ScrollSpeed - - xml_file = vfs.VFS.open('gui/settings_menu.xml') - self.window = pychan.loadXML(xml_file) - self.restart_dialog = RestartDialog(self.settings) - self.window.mapEvents({'okButton': self.save, - 'cancelButton': self.hide, - 'defaultButton': self.reset, - 'scroll_speed': self.update}) - self.initializeWidgets() - self.fillWidgets() - - def initializeWidgets(self): - scroll_speed = unicode(self.scroll_speed) - initial_data = {'screen_resolution': self.resolutions, - 'render_backend': self.render_backends, - 'lighting_model': self.lighting_models, - 'scroll_speed_value': scroll_speed} - - self.window.distributeInitialData(initial_data) - - - def fillWidgets(self): - resolution = self.resolutions.index(self.resolution) - backend = self.render_backends.index(self.render_backend) - lighting = self.lighting_models.index(self.lighting_model) - - self.window.distributeData({'screen_resolution': resolution, - 'render_backend': backend, - 'lighting_model': lighting, - 'enable_fullscreen': self.fullscreen, - 'enable_sound': self.sound}) - - def update(self): - """updates lables to show realtime data""" - #collects the data from the widgets - (scroll_speed) = self.window.collectData('scroll_speed') - - #alter the data note:pychan insists that all lables be given - # unicode text - #the slice rounds the number displayed - scroll_speed = unicode(scroll_speed)[:3] - - #adds the data to the proper widgets - self.window.distributeInitialData({'scroll_speed_value': scroll_speed}) - - def show(self): - self.window.show() - - def hide(self): - self.window.hide() - - def reset(self): - self.settings.read(self.settings.system_path) - self.fillWidgets() - - def save(self): - (resolution, backend, lighting, fullscreen, sound, scroll_speed) = \ - self.window.collectData('screen_resolution', 'render_backend', - 'lighting_model', 'enable_fullscreen', - 'enable_sound', 'scroll_speed') - - width, height = self.resolutions[resolution].split('x') - self.settings.fife.ScreenWidth = width - self.settings.fife.ScreenHeight = height - self.settings.fife.RenderBackend = self.render_backends[backend] - self.settings.fife.Lighting = self.lighting_models[lighting] - self.settings.fife.FullScreen = fullscreen - self.settings.fife.EnableSound = sound - self.settings.parpg.ScrollSpeed = scroll_speed - self.settings.write() - - self.restart_dialog.show() - self.hide()
--- a/src/parpg/gui/popups.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,60 +0,0 @@ -#/usr/bin/python - -# 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/>. - -from fife.extensions import pychan - -class ExaminePopup(): - """Create a popup for when you click examine on an object""" - def __init__(self, engine, object_title, desc): - """Initialize the popup - @type engine: fife.Engine - @param engine: an instance of the fife engine - @type object_title: string - @param object_title: The title for the window, probably should just - be the name of the object - @type desc: string - @param desc: The description of the object - @return: None""" - self.engine = engine - - self.examine_window = pychan.widgets.\ - Window(title=unicode(object_title), - position_technique="center:center", - min_size=(175,175)) - - self.scroll = pychan.widgets.ScrollArea(name='scroll', size=(150,150)) - self.description = pychan.widgets.Label(name='descText', - text=unicode(desc), - wrap_text=True) - self.description.max_width = 170 - self.scroll.addChild(self.description) - self.examine_window.addChild(self.scroll) - - self.close_button = pychan.widgets.Button(name='closeButton', - text=unicode('Close')) - self.examine_window.addChild(self.close_button) - - self.examine_window.mapEvents({'closeButton':self.examine_window.hide}) - - def closePopUp(self): - # TODO: missing function information - if self.examine_window.isVisible(): - self.examine_window.hide() - - def showPopUp(self): - # TODO: missing function information - self.examine_window.show()
--- a/src/parpg/gui/spinners.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,234 +0,0 @@ -from fife.extensions.pychan.widgets import (ImageButton, TextField, HBox, - Spacer) -from fife.extensions.pychan.attrs import Attr, IntAttr, BoolAttr - -class ListAttr(Attr): - def parse(self, value): - list_ = value.split(',') - return list_ - - -class Spinner(HBox): - ATTRIBUTES = HBox.ATTRIBUTES + [ - ListAttr('items'), - IntAttr('default_item_n'), - BoolAttr('circular'), - ] - - def default_item_n(): - def fget(self): - return self._default_item_n - - def fset(self, index): - if len(self.items) -1 >= index: - self._default_item_n = index - self.text_field.text = self.items[index] - else: - error_message = \ - 'default_item_n exceeds number of items in spinner' - raise ValueError(error_message) - - return locals() - default_item_n = property(**default_item_n()) - - def items(): - def fget(self): - return self._items - - def fset(self, items): - self._items = map(unicode, items) - if self.default_item_n > len(items) - 1: - self.default_item_n = 0 - self.text_field.text = self.items[self.default_item_n] if \ - len(self.items) > 0 else u'' - - return locals() - items = property(**items()) - - def background_color(): - def fget(self): - return self.text_field.background_color - - def fset(self, background_color): - self.text_field.background_color = background_color - - return locals() - background_color = property(**background_color()) - - def font(): - def fget(self): - return self.text_field.font - - def fset(self, font): - self.text_field.font = font - - return locals() - font = property(**font()) - - def background_color(): - def fget(self): - return self.text_field.background_color - - def fset(self, background_color): - self.text_field.background_color = background_color - - return locals() - background_color = property(**background_color()) - - def min_size(): - def fget(self): - return self._min_size - - def fset(self, min_size): - self._min_size = min_size - self.decrease_button.capture(self.previousItem) - increase_button_width, increase_button_height = \ - self.increase_button.size - decrease_button_width, decrease_button_height = \ - self.decrease_button.size - text_field_width = min_size[0] - (2 * self.padding) - \ - (increase_button_width + decrease_button_width) - self.text_field.min_width = text_field_width - self.text_field.max_width = text_field_width - self.text_field.min_height = min_size[1] - - return locals() - min_size = property(**min_size()) - - - def max_size(): - def fget(self): - return self._max_size - - def fset(self, max_size): - self._max_size = max_size - self.decrease_button.capture(self.previousItem) - increase_button_width, increase_button_height = \ - self.increase_button.size - decrease_button_width, decrease_button_height = \ - self.decrease_button.size - text_field_width = max_size[0] - (2 * self.padding) - \ - (increase_button_width + decrease_button_width) - self.text_field.max_width = text_field_width - self.text_field.max_height = max_size[1] - - return locals() - max_size = property(**max_size()) - - def __init__(self, items=None, default_item_n=0, circular=True, - min_size=(50, 14), max_size=(50, 14), font=None, background_color=None, **kwargs): - self._current_index = 0 - self._items = map(unicode, items) if items is not None else [] - self._default_item_n = default_item_n - self._min_size = min_size - self.circular = circular - padding = 1 - self.text_field = TextField(background_color=background_color) - self.decrease_button = ImageButton( - up_image='gui/buttons/left_arrow_up.png', - down_image='gui/buttons/left_arrow_down.png', - hover_image='gui/buttons/left_arrow_hover.png', - ) - # FIXME Technomage 2011-03-05: This is a hack to prevent the button - # from expanding width-wise and skewing the TextField orientation. - # Max size shouldn't be hard-coded like this though... - self.decrease_button.max_size = (12, 12) - self.decrease_button.capture(self.previousItem) - self.increase_button = ImageButton( - up_image='gui/buttons/right_arrow_up.png', - down_image='gui/buttons/right_arrow_down.png', - hover_image='gui/buttons/right_arrow_hover.png', - ) - self.increase_button.capture(self.nextItem) - increase_button_width, increase_button_height = \ - self.increase_button.size - decrease_button_width, decrease_button_height = \ - self.decrease_button.size - self.text_field = TextField(font=font) - text_field_width = min_size[0] - (2 * padding) - \ - (increase_button_width + decrease_button_width) - self.text_field.min_width = text_field_width - self.text_field.max_width = text_field_width - self.text_field.min_height = min_size[1] - self.text_field.text = self.items[default_item_n] if \ - len(self.items) > 0 else u'' - HBox.__init__(self, **kwargs) - self.opaque = 0 - self.padding = padding - self.margins = (0, 0) - self.addChildren(self.decrease_button, self.text_field, - self.increase_button) - - def nextItem(self, event, widget): - if self.circular: - if self._current_index < len(self.items) - 1: - self._current_index += 1 - else: - self._current_index = 0 - self.text_field.text = self.items[self._current_index] - elif self._current_index < len(self.items) - 1: - self._current_index += 1 - self.text_field.text = self.items[self._current_index] - - def previousItem(self, event, widget): - if self.circular: - if self._current_index > 0: - self._current_index -= 1 - else: - self._current_index = len(self.items) - 1 - self.text_field.text = self.items[self._current_index] - elif self._current_index > 0: - self._current_index -= 1 - self.text_field.text = self.items[self._current_index] - - -class IntSpinner(Spinner): - ATTRIBUTES = Spinner.ATTRIBUTES + [ - IntAttr('lower_limit'), - IntAttr('upper_limit'), - IntAttr('step_size'), - ] - - def lower_limit(): - def fget(self): - return self._lower_limit - - def fset(self, lower_limit): - self._lower_limit = lower_limit - integers = range(lower_limit, self.upper_limit + 1, self.step_size) - self.items = integers - - return locals() - lower_limit = property(**lower_limit()) - - def upper_limit(): - def fget(self): - return self._upper_limit - - def fset(self, upper_limit): - self._upper_limit = upper_limit - integers = range(self.lower_limit, upper_limit + 1, self.step_size) - self.items = integers - - return locals() - upper_limit = property(**upper_limit()) - - def step_size(): - def fget(self): - return self._step_size - - def fset(self, step_size): - self._step_size = step_size - integers = range(self.lower_limit, self.upper_limit + 1, step_size) - self.items = integers - - return locals() - step_size = property(**step_size()) - - def __init__(self, lower_limit=0, upper_limit=100, step_size=1, **kwargs): - self._lower_limit = lower_limit - self._upper_limit = upper_limit - self._step_size = step_size - integers = range(lower_limit, upper_limit + 1, step_size) - Spinner.__init__(self, items=integers, **kwargs) -
--- a/src/parpg/gui/tabwidget.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,105 +0,0 @@ -from fife.extensions.pychan.widgets import VBox, HBox, ScrollArea, Button -from fife.extensions.pychan.tools import callbackWithArguments - -class TabWidget(VBox): - def min_size(): - def fget(self): - return self._min_size - - def fset(self, min_size): - self._min_size = min_size - self.view.min_size = min_size - self.adaptLayout() - - return locals() - min_size = property(**min_size()) - - def max_size(): - def fget(self): - return self._max_size - - def fset(self, max_size): - self._max_size = max_size - self.view.max_size = max_size - self.adaptLayout() - - return locals() - max_size = property(**max_size()) - - def opaque(): - def fget(self): - return self._getOpaque() - - def fset(self, opaque): - self._setOpaque(opaque) - self.view.opaque = opaque - base_color = self.view.base_color - base_red = base_color.r - base_green = base_color.g - base_blue = base_color.b - background_color = self.view.background_color - background_red = background_color.r - background_green = background_color.g - background_blue = background_color.b - alpha = 255 if opaque else 0 - self.view.base_color = (base_red, base_green, base_blue, alpha) - self.view.background_color = (background_red, background_green, - background_blue, alpha) - - return locals() - opaque = property(**opaque()) - - def border_size(): - def fget(self): - frame = self.findChild(name='frame') - return frame.border_size - - def fset(self, border_size): - frame = self.findChild(name='frame') - frame.border_size = border_size - - return locals() - border_color = property(**border_size()) - - def __init__(self, min_size=(0, 0), max_size=(9999, 9999), border_size=1, - **kwargs): - self._min_size = min_size - self._max_size = max_size - self.views = {} - tab_bar = HBox(name='tabBar') - tab_bar.min_size = (0, 20) - tab_bar.max_size = (9999, 20) - self.view = ScrollArea(name='view') - self.view.min_size = self._min_size - self.view.max_size = self._max_size - self.view.border_size = border_size - frame = VBox(name='frame') - frame.border_size = border_size - frame.opaque = 0 - frame.addChild(self.view) - VBox.__init__(self, **kwargs) - self.padding = 0 - VBox.addChild(self, tab_bar) - VBox.addChild(self, frame) - self.adaptLayout() - - def addTab(self, text): - text = unicode(text) - tab = Button(text=text) - tab_bar = self.findChild(name='tabBar') - tab_bar.addChild(tab) - tab.capture(callbackWithArguments(self.showView, text)) - self.adaptLayout() - - def addChild(self, child): - name = child.name or unicode(str(child)) - self.addTab(name) - self.views[name] = child - if len(self.views) == 1: - # Show the first view by default. - self.showView(name) - - def showView(self, name): - view = self.views[name] - self.view.content = view - self.adaptLayout()
--- a/src/parpg/inventory.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,174 +0,0 @@ -# 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/>. - -import copy - -# TODO: many missing function definitions in this code - -class Inventory(Container): - """The class to represent inventory 'model': allow operations with - inventory contents, perform weight/bulk calculations, etc""" - def __init__(self, **kwargs): - """Initialise instance""" - Container.__init__(self, **kwargs) - self.items = {"head": Slot(), "neck": Slot(), - "shoulders": Slot(), "chest": Slot(), - "abdomen": Slot(), "left_arm": Slot(), - "right_arm": Slot(),"groin": Slot(), - "hips": Slot(), "left_leg": Slot(), - "right_leg": Slot(), "left_hand": Slot(), - "right_hand": Slot(), "ready": Container(), - "backpack": Container()} - for key, item in self.items.iteritems(): - item.name = key - kwargs = {} - kwargs["container"] = item - item.setScript("onPlaceItem", self.onChildPlaceItem, kwargs = kwargs) - self.item_lookup = {} - - def onChildPlaceItem(self, container): - for item in container.items.itervalues(): - self.item_lookup[item.ID] = container.name - - def placeItem(self, item, index=None): - self.items["backpack"].placeItem(item, index) - #self.item_lookup[item.ID] = "backpack" - - def takeItem(self, item): - if not item.ID in self.item_lookup: - raise ValueError ('I do not contain this item: %s' % item) - self.items[self.item_lookup[item.ID]].takeItem(item) - del self.item_lookup[item.ID] - - def removeItem(self, item): - if not item.ID in self.item_lookup: - raise ValueError ('I do not contain this item: %s' % item) - self.items[self.item_lookup[item.ID]].removeItem(item) - del self.item_lookup[item.ID] - - def replaceItem(self, old_item, new_item): - """Replaces the old item with the new one - @param old_item: Old item which is removed - @type old_item: Carryable - @param new_item: New item which is added - @type new_item: Carryable - """ - if not old_item.ID in self.item_lookup: - raise ValueError ('I do not contain this item: %s' % old_item) - self.items[self.item_lookup[old_item.ID]]\ - .replaceItem(old_item, new_item) - - def getWeight(self): - """Total weight of all items in container + container's own weight""" - return sum((item.weight for item in self.items.values())) - - def setWeightDummy(self, weight): - pass - - weight = property(getWeight, setWeightDummy, "Total weight of container") - - - def count(self, item_type = ""): - return sum(item.count(item_type) for item in self.items.values()) - - def takeOff(self, item): - return self.moveItemToSlot(item, "backpack") - - def moveItemToSlot(self,item,slot,index=None): - if not slot in self.items: - raise(ValueError("%s: No such slot" % slot)) - - if item.ID in self.item_lookup: - self.items[self.item_lookup[item.ID]].takeItem(item) - try: - self.items[slot].placeItem(item, index) - except Container.SlotBusy: - if index == None : - offending_item = self.items[slot].items[0] - else : - offending_item = self.items[slot].items[index] - self.items[slot].takeItem(offending_item) - self.items[slot].placeItem(item, index) - self.placeItem(offending_item) - self.item_lookup[item.ID] = slot - - def getItemsInSlot(self, slot, index=None): - if not slot in self.items: - raise(ValueError("%s: No such slot" % slot)) - if index != None: - return self.items[slot].items.get(index) - else: - return copy.copy(self.items[slot].items) - - def isSlotEmpty(self, slot, index=None): - if not slot in self.items: - raise(ValueError("%s: No such slot" % slot)) - if index == None: - return self.items[slot].count() == 0 - else: - return not index in self.items[slot].items - - - def has(self, item_ID): - return item_ID in self.item_lookup - - def findItemByID(self, ID): - if ID not in self.item_lookup: - return None - return self.items[self.item_lookup[ID]].findItemByID(ID) - - def findItem(self, **kwargs): - """Find an item in inventory by various attributes. All parameters - are optional. - @type name: String - @param name: Object name. If the name is non-unique, - first matching object is returned - @type kind: String - @param kind: One of the possible object kinds like "openable" or - "weapon" (see base.py) - @return: The item matching criteria or None if none was found""" - for slot in self.items: - item_found = self.items[slot].findItem(**kwargs) - if item_found != None: - return item_found - return None - - def __repr__(self): - return "[Inventory contents: " + \ - reduce((lambda a,b: str(a) - + ', ' + str(b)), - self.items.values()) + " ]" - - def serializeInventory(self): - """Returns the inventory items as a list""" - inventory = [] - inventory.extend(self.items["backpack"].serializeItems()) - for key, slot in self.items.iteritems(): - if key == "ready" or key == "backpack": - continue - elif len(slot.items) > 0: - item = slot.items[0] - item_dict = item.getStateForSaving() - item_dict["slot"] = key - item_dict["type"] = type(item).__name__ - inventory.append(item_dict) - return inventory - - def getStateForSaving(self): - """Returns state for saving - """ - state = {} - state["Inventory"] = self.serializeInventory() - return state
--- a/src/parpg/main.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -#!/usr/bin/env python2 -# This program 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. - -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. -import logging -import sys -from os.path import abspath - -from parpg.settings import Settings - - -def main(args, opts): - settings = Settings(*args) - - settings.parpg.DataPath = abspath(settings.parpg.DataPath) - - levels = {'debug': logging.DEBUG, - 'info': logging.INFO, - 'warning': logging.WARNING, - 'error': logging.ERROR, - 'critical': logging.CRITICAL} - - #TODO: setup formating - logging.basicConfig(filename=opts.logfile, level=levels[opts.loglevel]) - logger = logging.getLogger('parpg') - - try: - old_path = sys.path - sys.path = [settings.parpg.FifePath] - import fife - except AttributeError: - logger.warning('[parpg] section has no FifePath option') - except ImportError: - logger.critical("Could not import fife module. Please install fife or add " - "'FifePath' to the [parpg] section of your settings file") - sys.exit(1) - finally: - sys.path = old_path - - from parpg.application import PARPGApplication - from parpg.common import utils - - # enable psyco if available and in settings file - try: - import psyco - psyco_available = True - except ImportError: - logger.warning('Psyco Acceleration unavailable') - psyco_available = False - - if settings.fife.UsePsyco: - if psyco_available: - psyco.full() - logger.info('Psyco Acceleration enabled') - else: - logger.warning('Please install psyco before attempting to use it' - 'Psyco Acceleration disabled') - else: - logger.info('Psycho Acceleration disabled') - - # run the game - app = PARPGApplication(settings) - app.run()
--- a/src/parpg/mainmenucontroller.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,95 +0,0 @@ -# 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/ - -from controllerbase import ControllerBase -from charactercreationview import CharacterCreationView -from charactercreationcontroller import CharacterCreationController -from gamescenecontroller import GameSceneController -from gamesceneview import GameSceneView - -#For debugging/code analysis -if False: - from parpg.mainmenuview import MainMenuView - from fife import fife - from gamemodel import GameModel - from parpg import PARPGApplication - -class MainMenuController(ControllerBase): - """Controller for handling the main menu state""" - - def __init__(self, engine, view, model, application): - """Constructor""" - super(MainMenuController, self).__init__(engine, view, model, - application) - - #this can be helpful for IDEs code analysis - if False: - assert(isinstance(self.engine, fife.Engine)) - assert(isinstance(self.view, MainMenuView)) - assert(isinstance(self.model, GameModel)) - assert(isinstance(self.application, PARPGApplication)) - assert(isinstance(self.event_manager, fife.EventManager)) - - self.view.quit_callback = self.quitGame - self.view.new_game_callback = self.newGame - self.view.initalizeMainMenu(self.newGame, self.loadGame, self.quitGame) - self.view.showMenu() - self.resetMouseCursor() - - def newGame(self): - """Start a new game and switch to the character creation controller.""" - view = CharacterCreationView(self.engine, self.model, - self.model.settings) - controller = CharacterCreationController(self.engine, view, self.model, - self.application) - self.application.view = view - self.application.manager.swap_modes(controller) - -# def newGame(self): -# """Starts a new game""" -# view = GameSceneView(self.engine, -# self.model) -# controller = GameSceneController(self.engine, -# view, -# self.model, -# self.application) -# self.application.view = view -# self.application.manager.swap_modes(controller) -# start_map = self.model.settings.get("PARPG", "Map") -# self.model.changeMap(start_map) - - def loadGame(self, *args, **kwargs): - """Loads the game state - @return: None""" - - view = GameSceneView(self.engine, - self.model) - controller = GameSceneController(self.engine, - view, - self.model, - self.application) - self.application.view = view - self.application.manager.swap_modes(controller) - controller.loadGame(*args, **kwargs) - - def on_deactivate(self): - """Called when the controller is removed from the list""" - self.view.hideMenu() - - - def quitGame(self): - """Quits the game - @return: None""" - self.application.listener.quitGame()
--- a/src/parpg/mainmenuview.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,134 +0,0 @@ -# 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/ - -from fife.extensions import pychan - -from parpg import vfs -from viewbase import ViewBase -from parpg.gui.filebrowser import FileBrowser -from parpg.gui.menus import SettingsMenu - -class MainMenuView(ViewBase): - """View that is used to display the main menu""" - - def __init__(self, engine, model): - """Constructor for MainMenuView - @param engine: A fife.Engine instance - @type engine: fife.Engine - @param model: a script.GameModel instance - @type model: script.GameModel - """ - ViewBase.__init__(self, engine, model) - self.quit_window = None - self.new_game_callback = None - self.load_game_callback = None - self.quit_callback = None - self.main_menu = None - self.character_screen = None - - def showMenu(self): - """"Shows the main menu""" - self.main_menu_background.show() - self.main_menu.show() - - def hideMenu(self): - """"Hides the main menu""" - self.main_menu.hide() - self.main_menu_background.hide() - - def initalizeMainMenu(self, new_game, load_game, quit_game): - """Initialized the main menu and sets the callbacks""" - # Set a simple background to display the main screen. - xml_file = vfs.VFS.open('gui/main_menu_background.xml') - self.main_menu_background = pychan.loadXML(xml_file) - - # Initialize the main menu screen. - screen_mode = self.engine.getRenderBackend().getCurrentScreenMode() - self.main_menu_background.width = screen_mode.getWidth() - self.main_menu_background.height = screen_mode.getHeight() - - xml_file = vfs.VFS.open('gui/main_menu.xml') - self.main_menu = pychan.loadXML(xml_file) - - self.main_menu.adaptLayout() - self.new_game_callback = new_game - self.load_game_callback = load_game - self.quit_callback = quit_game - menu_events = {} - menu_events["newButton"] = self.newGame - menu_events["loadButton"] = self.loadGame - menu_events["settingsButton"] = self.displaySettings - menu_events["quitButton"] = self.quitGame - self.main_menu.mapEvents(menu_events) - - self.initializeQuitDialog() - self.initializeSettingsMenu() - - def newGame(self): - """Called when user request to start a new game. - @return: None""" - self.new_game_callback() - - def loadGame(self): - """ Called when the user wants to load a game. - @return: None""" - load_browser = FileBrowser(self.engine, - self.model.settings, - self.load_game_callback, - gui_xml_path='gui/loadbrowser.xml', - save_file=False, - extensions=('.dat')) - load_browser.showBrowser() - - def initializeQuitDialog(self): - """Creates the quit confirmation dialog - @return: None""" - - self.quit_window = pychan.widgets.Window(title=unicode("Quit?"), \ - min_size=(200,0)) - - hbox = pychan.widgets.HBox() - are_you_sure = "Are you sure you want to quit?" - label = pychan.widgets.Label(text=unicode(are_you_sure)) - yes_button = pychan.widgets.Button(name="yes_button", - text=unicode("Yes"), - min_size=(90,20), - max_size=(90,20)) - no_button = pychan.widgets.Button(name="no_button", - text=unicode("No"), - min_size=(90,20), - max_size=(90,20)) - - self.quit_window.addChild(label) - hbox.addChild(yes_button) - hbox.addChild(no_button) - self.quit_window.addChild(hbox) - - events_to_map = { "yes_button": self.quit_callback, - "no_button": self.quit_window.hide } - - self.quit_window.mapEvents(events_to_map) - - - def quitGame(self): - """Called when user requests to quit game. - @return: None""" - self.quit_window.show() - - def initializeSettingsMenu(self): - self.settings_menu = SettingsMenu(self.engine, self.model.settings) - - def displaySettings(self): - self.settings_menu.show()
--- a/src/parpg/mode.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ - -from parpg.bGrease.mode import * -import abc - -class FifeManager(BaseManager): - - def __init__(self): - self.modes = [] - - def _pump(self): - if self.current_mode: - self.current_mode.pump(self.current_mode.engine.getTimeManager().getTimeDelta() / 1000.0) - -class FifeMode(BaseMode): - - def __init__(self): - BaseMode.__init__(self) - - @abc.abstractmethod - def pump(self, dt): - """Performs actions every frame""" \ No newline at end of file
--- a/src/parpg/quest_engine.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,260 +0,0 @@ -# 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/>. - -import yaml - -from parpg.common.utils import locateFiles -from parpg import vfs - -class Quest(object): - """Class that holds the information for a quest""" - def __init__(self, quest_id, quest_giver_id, quest_name, description, - variables): - self.quest_id = quest_id - self.quest_giver_id = quest_giver_id - self.quest_name = quest_name - self.description = description - self.quest_variables = variables - - def setValue(self, variable_name, value): - """Set the value of a quest variable - @param variable_name: the name of the variable to set - @param value: the value you want to assign to the variable - @return: True on success - @return: False when it failes""" - - if self.quest_variables.has_key(variable_name): - self.quest_variables[variable_name]["value"] = value - return True - else: - return False - - def getValue(self, variable_name): - """Get the value of a quest_variable - @param variable_name: the name of the variable to set - @return: the value of the quest_variable""" - if self.quest_variables.has_key(variable_name): - return self.quest_variables[variable_name]["value"] - else: - return False - - def getGoalValue(self, variable_name): - """Get the goal value of a quest_variable - @param variable_name: the name of the variable to set - @return: the goal value of the quest variable""" - if self.quest_variables.has_key(variable_name): - return self.quest_variables[variable_name]["goal_value"] - else: - return False - - def increaseValue(self, variable_name, value): - """Increase a variable by a specified value - @param variable_name: the name of the variable to set - @param value: the value you want to increase the variable with - @return: True on success - @return: False when it fails""" - if self.quest_variables.has_key(variable_name): - self.quest_variables[variable_name]["value"] += value - return True - else: - return False - - def decreaseValue(self, variable_name, value): - """Decrease a variable by a specified value - @param variable_name: the name of the variable to set - @param value: the value you want to decrease the variable with - @return: True on success - @return: False when it failes""" - if self.quest_variables.has_key(variable_name): - self.quest_variables[variable_name]["value"] -= value - return True - else: - return False - - def isGoalValue(self, variable_name): - """Check if the variable has reached it's goal value - @param variable_name: the name of the variable to check - @return: True when the variable has reached the goal value - @return: False when it has not reached the goal value""" - if self.quest_variables.has_key(variable_name): - return self.quest_variables[variable_name]["value"] == \ - self.quest_variables[variable_name]["goal_value"] - else: - return False - - def isEqualOrBiggerThanGoalValue(self, variable_name): - """Check if the variable is equil or bigger then it's goal value - @param variable_name: the name of the variable to set - @return: True when it has reached or exceeded the goal value - @return: False when it has not reached or exceeded the goal value """ - if variable_name in self.quest_variables: - return self.quest_variables[variable_name]["value"] >= \ - self.quest_variables[variable_name]["goal_value"] - else: - return False - - def restartQuest(self): - """Restarts the quest. This sets all values to the reset values, - if there is a reset value present """ - for variable in self.quest_variables.itervalues(): - if variable.has_key("reset_value"): - variable["value"] = variable["reset_value"] - -class QuestEngine(dict): - def __init__(self, quest_dir): - """Create a quest engine object""" - dict.__init__(self) - self.empty_quest = Quest(None, None, None, None, {}) - self.quests = {} - self.active_quests = [] - self.finished_quests = [] - self.failed_quests = [] - self.quest_dir = quest_dir - - def __str__(self): - return self.quests.__str__() - - def __getitem__(self, key): - try: - return self.quests.__getitem__(key) - except KeyError: - return self.empty_quest - - def items(self): - return self.quests.items() - - def values(self): - return self.quests.values() - - def keys(self): - return self.quests.keys() - - def readQuests(self): - """Reads in the quests in the quest directory""" - filepaths = locateFiles("*.yaml", self.quest_dir) - self.quests = {} - self.active_quests = [] - self.finished_quests = [] - self.failed_quests = [] - for filepath in filepaths: - quest_file = vfs.VFS.open(filepath) - tree = yaml.load(quest_file) - quest_properties = tree["QUEST_PROPERTIES"] - variable_defines = tree["DEFINES"] - - self.quests[quest_properties["quest_id"]] = \ - Quest(quest_properties["quest_id"], - quest_properties["quest_giver_id"], - quest_properties["quest_name"], - quest_properties["description"], - variable_defines) - - def activateQuest(self, quest_id): - """Add a quest to the quest log - @param quest: the quest id of the quest to add to the quest log - @return: True if succesfully added - @return: False if failed to add""" - - if quest_id in self.quests \ - and not (quest_id in self.active_quests \ - or quest_id in self.finished_quests): - self.active_quests.append(quest_id) - return True - return False - - def finishQuest(self, quest_id): - """Move a quest to the finished quests log - @param quest_id: The id of the quest you want to move - @return: True on success - @return: False when it failes""" - if quest_id in self.active_quests: - self.finished_quests.append(quest_id) - self.active_quests.remove(quest_id) - return True - return False - - def restartQuest(self, quest_id): - """Restart a quest - @param quest_id: ID of the quest you want to restart - @return: True on success - @return: False when it failes""" - if quest_id in self.active_quests: - self.quests[quest_id].restartQuest() - - def failQuest(self, quest_id): - """Set a quest to failed - @param quest_id: ID of the quest you want to fail - @return: True on success - @return: False when it failes""" - if quest_id in self.active_quests: - self.failed_quests.append(quest_id) - self.active_quests.remove(quest_id) - return True - return False - - def hasQuest(self, quest_id): - """Check whether a quest is present in the quest_list. - It doesn't matter which state the quest is, or even if its - started. - @param quest_id: ID of the quest you want to check - @return: True on when the quest is in the quest log - @return: False when it's not in the quest log""" - return quest_id in self.quests - - def hasActiveQuest(self, quest_id): - """Check whether a quest is in the quest log - @param quest_id: ID of the quest you want to check - @return: True on when the quest is in the quest log - @return: False when it's not in the quest log""" - return quest_id in self.active_quests - - def hasFinishedQuest(self, quest_id): - """Check whether a quest is in the finished quests log - @param quest_id: ID of the quest you want to check - @return: True on when the quest is in the finished quests log - @return: False when it's not in the finished quests log""" - return quest_id in self.finished_quests - - def hasFailedQuest(self, quest_id): - """Check whether a quest is in the failed quests log - @param quest_id: ID of the quest you want to check - @return: True on when the quest is in the failed quests log - @return: False when it's not in the failed quests log""" - return quest_id in self.failed_quests - - def getStateForSaving(self): - """Prepares state for saving - @type state: dictionary - @param state: State of the object""" - ret_dict = {} - variables_dict = ret_dict["Variables"] = {} - for quest in self.quests.itervalues(): - quest_dict = variables_dict[quest.quest_id] = {} - for variable, data in quest.quest_variables.iteritems(): - quest_dict[variable] = data["value"] - ret_dict["ActiveQuests"] = self.active_quests - ret_dict["FinishedQuests"] = self.finished_quests - ret_dict["FailedQuests"] = self.failed_quests - return ret_dict - - def restoreFromState(self, state): - """Restores the state""" - variables_dict = state["Variables"] - for quest_id, variables in variables_dict.iteritems(): - for variable, value in variables.iteritems(): - self.quests[quest_id].setValue(variable, value) - self.active_quests = state["ActiveQuests"] - self.finished_quests = state["FinishedQuests"] - self.failed_quests = state["FailedQuests"]
--- a/src/parpg/serializers.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,156 +0,0 @@ -# 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/>. -""" -Provides classes used to serialize and deserialize Python classes. -""" - -from abc import ABCMeta, abstractmethod -try: - from xml.etree import cElementTree as ElementTree -except ImportError: - from xml.etree import ElementTree -try: - from collections import OrderedDict -except ImportError: - from .common.ordereddict import OrderedDict - -from .common.utils import dedent_chomp - -import logging - -logger = logging.getLogger('serializers') - -class Serializable(object): - def __init__(self, class_, init_args=None, attributes=None): - self.class_ = class_ - if init_args is not None: - self.init_args = OrderedDict(init_args) - else: - self.init_args = OrderedDict() - if attributes is not None: - self.attributes = OrderedDict(attributes) - else: - self.attributes = OrderedDict() - - -class SerializableRegistry(object): - """ - Class holding the data used to serialize and deserialize a particular - Python object. - """ - registered_classes = {} - - @classmethod - def registerClass(cls, name, class_, init_args=None, attributes=None): - serializable = Serializable(class_, init_args, attributes) - cls.registered_classes[name] = serializable - - -class AbstractSerializer(object): - __metaclass__ = ABCMeta - - @abstractmethod - def serialize(self, object_, stream): - pass - - @abstractmethod - def deserialize(self, stream): - pass - - -class XmlSerializer(AbstractSerializer): - def serialize(self, statistic, stream): - pass - - @classmethod - def deserialize(cls, stream): - element_tree = ElementTree.parse(stream) - root_element = element_tree.getroot() - object_ = cls.construct_object(root_element) - return object_ - - @classmethod - def construct_object(cls, element): - element_name = element.tag - if element_name in SerializableRegistry.registered_classes.keys(): - object_ = cls.construct_registered_class(element) - elif len(element) > 0: - # Element contains subelements, so we'll treat it as an - # OrderedDict. - if element_name == 'list': - object_ = cls.construct_list(element) - else: - object_ = cls.construct_ordered_dict(element) - else: - object_ = cls.construct_primitive(element) - return object_ - - @classmethod - def construct_registered_class(cls, element): - element_name = element.tag - serializable = SerializableRegistry.registered_classes[element_name] - class_ = serializable.class_ - init_args = OrderedDict() - for subelement in element: - arg = cls.construct_object(subelement) - subelement_name = subelement.tag - init_args[subelement_name] = arg - try: - object_ = class_(**init_args) - except (TypeError, ValueError) as exception: - logger.error(init_args) - error_message = \ - 'unable to deserialize tag {0}: {1}'.format(element_name, - exception) - raise ValueError(error_message) - return object_ - - @classmethod - def construct_ordered_dict(cls, element): - object_ = OrderedDict() - for subelement in element: - child = cls.construct_object(subelement) - name = subelement.tag - object_[name] = child - return object_ - - @classmethod - def construct_list(cls, element): - object_ = [] - for subelement in element: - child = cls.construct_object(subelement) - object_.append(child) - return object_ - - @classmethod - def construct_primitive(cls, element): - text = element.text - # Interpret the element's text as unicode by default. - element_type = element.attrib.get('type', 'unicode') - if element_type == 'unicode': - formatted_text = dedent_chomp(text) - object_ = unicode(formatted_text) - elif element_type == 'str': - formatted_text = dedent_chomp(text) - object_ = str(formatted_text) - elif element_type == 'int': - object_ = int(text) - elif element_type == 'float': - object_ = float(text) - else: - error_message = '{0!r} is not a recognized primitive type' - error_message.format(element_type) - raise ValueError(error_message) - return object_
--- a/src/parpg/settings.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,491 +0,0 @@ -#!/usr/bin/env python2 - -# Copyright (C) 2011 Edwin Marshall <emarshall85@gmail.com> - -# 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/>. - -""" Provides a class used for reading and writing various configurable options - throughout the game - - This class produces an INI formated settings file as opposed to an XML - formatted one. The reason that python's built-in ConfigurationParser isn't - sufficient is because comments aren't preserved when writing a settings - file, the order in which the options are written isn't preserved, and the - interface used with this class is arguably more convenient that - ConfigParser's. - - Default Settings may be generated by envoking this module from the - command line: - python -m settings.py [system] [data_directory] - - where [system] is one of local, windows, or linux (mac coming soon), - and data_directory is the base path for the data files to be loaded. - - Both [system] and [data_directory] are option. If omitted, both - default to whichever what is reasonable based on the system settings.py - is run on -""" - -import os -import sys -import platform - -#TODO: add logging to replace print statements -class Section(object): - """ An object that represents a section in a settings file. - - Options can be added to a section by simply assigning a value to an - attribute: - section.foo = baz - would produce: - [section] - foo = baz - in the settings file. Options that do not exist on assignment - are created dynamcially. - - Values are automatically converted to the appropriate python type. - Options that begin and end with brackets([, ]) are converted to lists, - and options that are double-quoted (") are converted to strings. - Section also recognizes booleans regardless of case, in addition to the - literals 'yes' and 'no' of any case. Except in the case of - double-quoted strings, extra white-space is trimmed, so you need not - worry. For example: - foo = bar - is equivalent to : - foo = baz - """ - def __init__(self, name): - """ Initialize a new section. - - @param name: name of the section. In the INI file, sections are surrounded - by brackets ([name]) - @type name: string - """ - self.name = name - - def __setattr__(self, option, value): - """ Assign a value to an option, converting types when appropriate. - - @param option: name of the option to assign a value to. - @type option: string @param value: value to be assigned to the option. - @type value: int, float, string, boolean, or list - """ - value = str(value) - if value.startswith('[') and value.endswith(']'): - value = [item.strip() for item in value[1:-1].split(',')] - elif value.lower() == 'true' or value.lower() == 'yes': - value = True - elif value.lower() == 'false' or value.lower() == 'no': - value = False - elif value.isdigit(): - value = int(value) - else: - try: - value = float(value) - except ValueError: - # leave as string - pass - - self.__dict__[option] = value - - def __getattribute__(self, option): - """ Returns the option's value""" - # Remove leading and trailing quotes from strings that have them - return_value = object.__getattribute__(self, option) - try: - for key, value in return_value.iteritems(): - if (hasattr(value, 'split') and - value.startswith("\"") and value.endswith("\"")): - return_value[key] = value[1:-1] - except AttributeError: - pass - - return return_value - - @property - def options(self): - """ Returns a dictionary of existing options """ - options = self.__dict__ - # get rid of properties that aren't actually options - if options.has_key('name'): - options.pop('name') - - return options - -class Settings(object): - """ An object that represents a settings file, its sectons, - and the options defined within those sections. - """ - def __init__(self, settings_path='', system_path='', user_path='', - filename='parpg.cfg'): - """ initializes a new settings object. If no paths are given, they are - guessed based on whatever platform the script was run on. - - Examples: - paths = ['/etc/parpg', '/home/user_name/.config/parpg'] - settings = Settings(*paths) - - paths = {'system': '/etc/parpg', - 'user': '/home/user_name/.config/parpg'} - settings = Settings(**paths) - - settings = Settings('.') - - settigns = Settings() - - @param system_path: Path to the system settings file. - @type system_path: string (must be a valid path) - - @param user_path: Path to the user settings file. Options that - are missing from this file are propogated - from the system settings file and saved on - request - @type user_path: string (must be a valid path) - - @param suffix: Suffix of the settings file that will be generated. - @type suffix: string - """ - self.filename = filename - self.settings_file = '' - - self.paths = {} - if not system_path and not user_path and not settings_path: - # use platform-specific values as paths - (self.paths['system'], self.paths['user'], - self.paths['settings']) = self.platform_paths() - else: - # convert supplied paths to absolute paths - abs_paths = [os.path.expanduser(path) - for path in [system_path, user_path, settings_path]] - (self.paths['system'], self.paths['user'], - self.paths['settings']) = abs_paths - - self.read() - - - def __getattr__(self, name): - """ Returns a Section object to be used for assignment, creating one - if it doesn't exist. - - @param name: name of section to be retrieved - @type name: string - """ - if name in ['get', 'set']: - raise AttributeError("{0} is deprecated. Please consult Settings' " - "documentation for information on how to " - "create/modify sections and their respective " - "options".format(name)) - else: - if not self.__dict__.has_key(name): - setattr(self, name, Section(name)) - - return getattr(self, name) - - def platform_paths(self, system=None): - if system is None: - system = platform.system().lower() - - if system == 'linux': - return (os.path.join(os.sep, 'usr', 'share', 'parpg'), - os.path.join(os.environ['XDG_CONFIG_HOME'], 'parpg'), - os.path.join(os.sep, 'etc', 'parpg')) - elif system == 'windows': - return (os.path.join(os.environ['PROGRAMFILES'], 'PARPG'), - os.path.join(os.environ['USERDATA'], 'PARPG'), - os.path.join(os.environ['PROGRAMFILES'], 'PARPG')) - else: - # TODO: determine values for Mac - return None - - def read(self, filenames=None): - """ Reads a settings file and populates the settings object - with its sections and options. Calling this method without - any arguments simply re-reads the previously defined filename - and paths - - @param filenames: name of files to be parsed. - @type path: string or list - """ - - if filenames is None: - filenames = [os.path.join(self.paths['settings'], self.filename), - os.path.join(self.paths['user'], self.filename)] - elif hasattr(filenames, 'split'): - filenames = [filenames] - - for filename in filenames: - section = None - if os.path.exists(filename): - try: - self.settings_file = open(filename, 'r').readlines() - except IOError as (errno, strerror): - if errno == 2: - if os.path.basename(filename).startswith('system'): - print ('{0} could not be found. Please supply a ' - 'different path or generate a system settings ' - 'file with:\n' - 'python2 -m parpg.settings').format(filename) - sys.exit(1) - else: - print 'Error No. {0}: {1} {2}'.format(errno, filename, strerror) - sys.exit(1) - - for line in self.settings_file: - if line.startswith('#') or line.strip() == '': - continue - elif line.startswith('[') and line.endswith(']\n'): - getattr(self, line[1:-2]) - section = line[1:-2] - else: - option, value = [item.strip() - for item in line.split('=', 1)] - setattr(getattr(self, section), option, value) - - def write(self, filename=None): - """ Writes a settings file based on the settings object's - sections and options - - @param filename: Name of file to save to. By default, this is - the user settings file. - @type path: string - """ - if filename is None: - filename = os.path.join(self.paths['user'], - 'user{0}'.format(self.suffix)) - - for section in self.sections: - - if '[{0}]\n'.format(section) not in self.settings_file: - self.settings_file.append('\n[{0}]\n'.format(section)) - for option, value in getattr(self, section).options.iteritems(): - template = '{0} = {1}\n'.format(option, value) - self.settings_file.append(template) - else: - start_of_section = (self.settings_file - .index('[{0}]\n'.format(section)) + 1) - - for option, value in getattr(self, - section).options.iteritems(): - if hasattr(value, 'sort'): - value = '[{0}]'.format(', '.join(value)) - - new_option = False - template = '{0} = {1}\n'.format(option, value) - for index, line in enumerate(self.settings_file[:]): - if option in line: - new_option = False - if str(value) not in line: - self.settings_file[index] = template - - break - else: - new_option = True - if new_option: - while self.settings_file[start_of_section].startswith('#'): - start_of_section += 1 - - self.settings_file.insert(start_of_section, template) - - with open(filename, 'w') as out_stream: - for line in self.settings_file: - out_stream.write(line) - - @property - def sections(self): - """ Returns a list of existing sections""" - sections = self.__dict__.keys() - sections.pop(sections.index('settings_file')) - sections.pop(sections.index('paths')) - sections.pop(sections.index('suffix')) - sections.pop(sections.index('filename')) - - return sections - - @property - def system_path(self): - return self.paths['system'] - - @property - def user_path(self): - return self.paths['user'] - - @property - def settings_path(self): - return self.paths['settings'] - -DEFAULT_SETTINGS = """\ -[fife] -#------------------------------------------------------------------------------ -# Options marked with ? are untested/unknown - -# Game window's title (string) DO NOT EDIT! -WindowTitle = PARPG Techdemo 2 - -# Icon to use for the game window's border (filename) DO NOT EDIT! -WindowIcon = window_icon.png - -# Video driver to use. (?) -VideoDriver = "" - -# Backend to use for graphics (OpenGL|OpenGLe|SDL) -RenderBackend = OpenGL - -# Run the game in fullscreen mode or not. (True|False) -FullScreen = False - -# Screen Resolution's width. Not used if FullScreen is set to False -# (800|1024|etc) -ScreenWidth = 1024 - -# Screen Resolution's height. Not used if FullScreen is set to False -# (600|768|etc) -ScreenHeight = 768 - -# Screen DPI? (?) -BitsPerPixel = 0 - -# ? (?) -SDLRemoveFakeAlpha = 1 - -# Use Framebuffer Objects (True|False) -GLUseFramebuffer = True - -# Use NPOT textures (True|False) -GLUseNPOT = True - -# The Sensitivity of the mouse. (-0.99 to 10.0) -MouseSensitivity = 0.0 - -# Sets whether the mouse should be accelerated. (True|False) -MouseAcceleration = False - -# Subdirectory to load icons from (path) -IconsPath = icons - -# ? ([R, G, B]) -ColorKey = [250, 0, 250] - -# ? (True|False) -ColorKeyEnabled = False - -# Turn on sound effects and music (True|False) -EnableSound = True - -# Initial volume of sound effects and music (0.0-100.0?) -InitialVolume = 5.0 - -# Characters to use to render fonts. DO NOT EDIT! -FontGlyphs = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,!?-+/():;%&`'*#=[]\"" - -# Subdirectory to load fronts from (path) -FontsPath = fonts - -# Font to load when game starts -Font = oldtypewriter.ttf - -# Size of in-game fonts -DefaultFontSize = 12 - -# ? (?) -LogModules = [controller] - -# ? (?) -PychanDebug = False - -# use Psyco Acceperation (True|False) -UsePsyco = False - -# ? (?) -ProfilingOn = False - -# Lighting Model to use (0-2) -Lighting = 0 - -[parpg] -#------------------------------------------------------------------------------ - -# System subdirectory to load maps from (path) -MapsPath = maps - -# YAML file that contains the available maps (filename) -MapsFile = maps.yaml - -# Map to load when game starts (filename) -Map = Mall - -# ? (filename) -AllAgentsFile = all_agents.yaml - -# System subdirectory to load objects from (path) -ObjectsPath = objects - -# YAML file that contains the database of availabel objects (filename) -ObjectDatabaseFile = object_database.yaml - -# System subdirectory to load dialogues from (path) -DialoguesPath = dialogue - -# System subdirectory to load quests from (path) -QuestsPath = quests - -# User subdirectory to save screenshots to -ScreenshotsPath = screenshots - -# User subdirectory to save games to -SavesPath = saves - -# System subdirectory where gui files are loaded from (path) -GuiPath = gui - -# System subdirectory where cursors are loaded from (path) -CursorPath = cursors - -# File to use for default cursor (filename) -CursorDefault = cursor_plain.png - -# File to use for up cursor (filename) -CursorUp = cursor_up.png - -# File to use for right cursor (filename) -CursorRight = cursor_right.png - -# File to use for down cursor (filename) -CursorDown = cursor_down.png - -# File to use for left cursor (filename) -CursorLeft = cursor_left.png - -# how many pixles to move the camera per time frame (digit) -ScrollSpeed = 1.0 - -# Player walk speed (digit) -PCSpeed = 3\ -""" - -if __name__ == '__main__': - from optparse import OptionParser - - usage = "usage: %prog [options] system[, system, ...]" - parser = OptionParser(usage=usage) - - parser.add_option('-f', '--filename', default='system.cfg', - help='Filename of output configuration file') - - opts, args = parser.parse_args() - - with open(opts.filename, 'w') as f: - for line in DEFAULT_SETTINGS: - f.write(line)
--- a/src/parpg/sounds.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -# 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/>. - -# sounds.py holds the object code to play sounds and sound effects -from fife import fife - -class SoundEngine: - def __init__(self, fife_engine): - """Initialise the SoundEngine instance - @type fife_engine: fine.Engine - @param fife_engine: Instance of the Fife engine - @return: None""" - self.engine = fife_engine - self.sound_engine = self.engine.getSoundManager() - self.sound_engine.init() - # set up the sound - self.music = self.sound_engine.createEmitter() - self.music_on = False - self.music_init = False - - def playMusic(self, sfile=None): - """Play music, with the given file if passed - @type sfile: string - @param sfile: Filename to play - @return: None""" - if(sfile is not None): - # setup the new sound - sound = self.engine.getSoundClipManager().load(sfile) - self.music.setSoundClip(sound) - self.music.setLooping(True) - self.music_init = True - self.music.play() - self.music_on = True - - def pauseMusic(self): - """Stops current playback - @return: None""" - if(self.music_init == True): - self.music.pause() - self.music_on = False - - def toggleMusic(self): - """Toggle status of music, either on or off - @return: None""" - if((self.music_on == False)and(self.music_init == True)): - self.playMusic() - else: - self.pauseMusic() - - def setVolume(self, volume): - """Set the volume of the music - @type volume: integer - @param volume: The volume wanted, 0 to 100 - @return: None""" - self.sound_engine.setVolume(0.01 * volume) -
--- a/src/parpg/systems/__init__.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -from scriptingsystem import ScriptingSystem \ No newline at end of file
--- a/src/parpg/systems/gamerulessystem.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -# This program 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. -# -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -from parpg.bGrease import System - -class GameRulesSystem(System): - """ - System responsible for defining the game rules and mechanics by which - characters interact with the world. - """ - - pass
--- a/src/parpg/systems/scriptingsystem.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,155 +0,0 @@ -# This program 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. -# -# This program 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 this program. If not, see <http://www.gnu.org/licenses/>. - -from collections import deque -from copy import deepcopy - -from parpg.bGrease import System - -class Script(object): - """Script object""" - - def __init__(self, actions, system): - """Constructor""" - assert(isinstance(actions, deque)) - self.actions = actions - assert(isinstance(system, ScriptingSystem)) - self.system = system - self.reset() - - def reset(self): - """Resets the state of the script""" - self.running_actions = deepcopy(self.actions) - self.running = False - self.finished = False - self.time = 0 - self.wait = 0 - self.cur_action = None - - def update(self, time): - """Advance the script""" - if not self.running: - return - if self.cur_action and not self.cur_action.executed: - return - self.time += time - if self.wait <= self.time: - self.time = 0 - try: - action_data = self.running_actions.popleft() - action = self.system.actions[action_data[0]] - action_params = eval(action_data[1], - self.system.funcs, - self.system.vals - ) - if not (isinstance(action_params, list) - or isinstance(action_params, tuple)): - action_params = [action_params] - self.cur_action = action(self.system.world, *action_params) - self.wait = action_data[2] - if len(action_data) >= 4: - vals = ( - eval(action_data[4], self.system.funcs, self.system.vals) - if len(action_data) > 4 - else () - ) - command = action_data[3] - self.system.commands[command]( - *vals, - action=self.cur_action - ) - else: - self.cur_action.execute() - except IndexError: - self.finished = True - self.running = False - - -class ScriptingSystem(System): - """ - System responsible for managing scripts attached to entities to define - their behavior. - """ - - def __init__(self, commands, actions): - """Constructor""" - self.funcs = {} - self.vals = {} - self.commands = commands - self.actions = actions - self.game_state = None - self.reset() - - def reset(self): - """Resets the script and condition collections""" - self.scripts = {} - self.conditions = [] - - - def step(self, dt): - """Execute a time step for the system. Must be defined - by all system classes. - - :param dt: Time since last step invocation - :type dt: float - """ - self.vals.clear() - self.vals.update( - self.game_state.getObjectDictOfMap( - self.game_state.current_map_name) - ) - self.funcs.clear() - self.funcs.update(self.game_state.funcs) - for condition_data in self.conditions: - condition = condition_data[0] - script_name = condition_data[1] - if not self.scripts.has_key(script_name): - return - script = self.scripts[script_name] - if eval(condition, self.funcs, self.vals) and not script.running: - script.running = True - for script in self.scripts.itervalues(): - assert(isinstance(script, Script)) - if script.finished: - script.reset() - elif script.running: - script.update(dt) - - def setScript(self, name, actions): - """Sets a script. - @param name: The name of the script - @param actions: What the script does - @type actions: deque or iterable - """ - if not(isinstance(actions, deque)): - actions = deque(actions) - self.scripts[name] = Script(actions, - self - ) - - def addCondition(self, condition, script_name): - """Adds a condition. - @param condition: Condition which will be evaluated - @param script_name: Name of the script that will be executed if the - condition evaluates to True. - """ - self.conditions.append((condition, script_name)) - - - def runScript(self, name): - """Runs a script with the given name - @param name: The name of the script""" - if self.scripts.has_key(name): - self.scripts[name].running = True - - \ No newline at end of file
--- a/src/parpg/vfs.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -# 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/>. - -# KLUDGE M. George Hansen 2011-06-04: Die, global variable, die! -VFS = None \ No newline at end of file
--- a/src/parpg/viewbase.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -# 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/>. - -class ViewBase(object): - """Base class for views""" - def __init__(self, engine, model): - """Constructor for engine - @param engine: A fife.Engine instance - @type engine: fife.Engine - @param model: a script.GameModel instance - @type model: script.GameModel - """ - self.engine = engine - self.model = model
--- a/src/parpg/world.py Thu Jan 12 18:01:28 2012 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,28 +0,0 @@ -from parpg.bGrease.world import * -from parpg.bGrease.component import Component - -from parpg.mode import FifeMode -from parpg import components -from parpg.components.fifeagent import commands -from parpg.systems import ScriptingSystem -from parpg.entities.action import ACTIONS - -class World(FifeMode, BaseWorld): - - def __init__(self): - FifeMode.__init__(self) - BaseWorld.__init__(self) - - def configure(self): - """Configure the game world's components, systems and renderers""" - for name, component in components.components.iteritems(): - setattr(self.components, name, component) - self.systems.scripting = ScriptingSystem(commands, ACTIONS) - - def pump(self, dt): - for component in self.components: - if hasattr(component, "step"): - component.step(dt) - for system in self.systems: - if hasattr(system, "step"): - system.step(dt) \ No newline at end of file