# HG changeset patch
# User KarstenBock@gmx.net
# Date 1326392009 -3600
# Node ID 92bb014104ee3c68eac9d0e5edb9b11751e0a33f
# Parent 7747b0f73694c494f7b83b95acba688ff3c46163
Added parpg-source subrepo in parpg directory,
which replaces the src directory.
diff -r 7747b0f73694 -r 92bb014104ee .hgsub
--- 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
diff -r 7747b0f73694 -r 92bb014104ee .hgsubstate
--- 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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg.py
--- 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 .
-
-#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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/__init__.py
--- 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 .
-
-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 .
-"""
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/application.py
--- 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 .
-"""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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/__init__.py
--- 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 `.
- """
- __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 `.
- """
- __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.
- """
-
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/collision.py
--- 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)
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/color.py
--- 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)
-
-
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/component/__init__.py
--- 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 `.
-"""
-
-__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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/component/base.py
--- 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()
-
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/component/field.py
--- 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)
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/component/general.py
--- 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__)
-
-
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/component/schema.py
--- 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 @@
-
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/controller/__init__.py
--- 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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/controller/integrator.py
--- 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
-
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/entity.py
--- 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 `.
-"""
-
-__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)
-
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/geometry.py
--- 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()
-
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/impl/__init__.py
--- 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
-
-
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/impl/controls.py
--- 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 `.
- """
- 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()
-
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/impl/mode.py
--- 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 `.
-"""
-
-__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)
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/impl/world.py
--- 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 `.
-"""
-
-__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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/mode.py
--- 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 `.
-"""
-
-__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)
-
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/renderer/__init__.py
--- 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 `
-"""
-
-__all__ = ('Vector', 'Camera')
-
-from parpg.bGrease.renderer.vector import Vector
-from parpg.bGrease.renderer.camera import Camera
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/renderer/camera.py
--- 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)
-
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/renderer/vector.py
--- 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)]
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/bGrease/world.py
--- 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 `.
-"""
-
-__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)
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/behaviours/__init__.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/behaviours/base.py
--- 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 .
-
-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()
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/behaviours/moving.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/behaviours/npc.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/behaviours/player.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/charactercreationcontroller.py
--- 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 .
-"""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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/charactercreationview.py
--- 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 .
-"""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()
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/characterstatistics.py
--- 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 .
-"""
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/common/__init__.py
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/common/listeners/__init__.py
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/common/listeners/command_listener.py
--- 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 .
-
-"""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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/common/listeners/console_executor.py
--- 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 .
-
-"""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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/common/listeners/event_listener.py
--- 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 .
-
-"""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)
-
-
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/common/listeners/key_listener.py
--- 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 .
-
-"""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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/common/listeners/mouse_listener.py
--- 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 .
-
-"""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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/common/listeners/widget_listener.py
--- 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 .
-
-"""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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/common/ordereddict.py
--- 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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/common/utils.py
--- 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 .
-
-# 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)
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/components/__init__.py
--- 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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/components/base.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/components/behaviour.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/components/change_map.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/components/character_statistics.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/components/containable.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/components/container.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/components/description.py
--- 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 .
-
-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)
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/components/dialogue.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/components/equip.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/components/equipable.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/components/fifeagent.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/components/general.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/components/graphics.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/components/lockable.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/components/usable.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/console.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/controllerbase.py
--- 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 .
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/dialogue.py
--- 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 .
-"""
-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}
- @param sections: sections of dialogue that make up this
- L{Dialogue} instance.
- @type sections: list of L{DialogueSections}
- """
- 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}
- """
- 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}
- @param actions: dialogue actions that should be executed when the
- L{DialogueSection} is reached.
- @type actions: list of L{DialogueActions}
- """
- 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}
- @param actions: dialogue actions that should be executed when the
- L{DialogueSection} is reached.
- @type actions: list of L{DialogueActions}
- """
- 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}
- @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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/dialogueactions.py
--- 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 .
-"""
-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}.
- @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} 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}.
- """
- 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} 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)
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/dialoguecontroller.py
--- 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 .
-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
-
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/dialogueparsers.py
--- 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 .
-"""
-Contains classes for parsing and validating L{Dialogues} 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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/dialogueprocessor.py
--- 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 .
-"""
-Provides the core interface to the dialogue subsystem used to process player
-L{Dialogues} 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} and
- L{DialogueResponses} 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} 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}
- @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} 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}
- it contains and return a list of valid
- L{DialogueResponses after evaluating any response
- conditionals.
-
- @returns: valid responses.
- @rtype: list of L{DialogueResponses}
-
- @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} contained by a
- L{DialogueSection} or L{DialogueResponse}.
-
- @param dialogue_node: section of dialogue or response containing the
- L{DialogueActions} 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} to process.
- @type dialogue_section: L{DialogueSection}
-
- @return: responses whose conditions were met.
- @rtype: list of L{DialogueResponses}
- """
- 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} 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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/entities/__init__.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/entities/action.py
--- 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 .
-
-#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,
- }
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/entities/general.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/font.py
--- 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)
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gamemap.py
--- 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 .
-
-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())
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gamemodel.py
--- 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 .
-
-# 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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gamescenecontroller.py
--- 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 .
-"""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,)
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gamesceneview.py
--- 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 .
-
-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)
-
-
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gamestate.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gui/__init__.py
--- 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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gui/actionsbox.py
--- 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 .
-
-"""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()
-
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gui/charactercreationview.py
--- 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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gui/containergui.py
--- 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 .
-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()
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gui/containergui_base.py
--- 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 .
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gui/dialogs.py
--- 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 .
-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()
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gui/dialoguegui.py
--- 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 .
-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)
- )
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gui/drag_drop_data.py
--- 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 .
-
-"""
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gui/filebrowser.py
--- 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 .
-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()
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gui/hud.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gui/inventorygui.py
--- 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 .
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gui/menus.py
--- 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 .
-
-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()
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gui/popups.py
--- 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 .
-
-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()
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gui/spinners.py
--- 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)
-
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/gui/tabwidget.py
--- 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()
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/inventory.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/main.py
--- 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 .
-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()
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/mainmenucontroller.py
--- 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 .
-
-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"]
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/serializers.py
--- 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 .
-"""
-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_
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/settings.py
--- 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
-
-# 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 .
-
-""" 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)
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/sounds.py
--- 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 .
-
-# 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)
-
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/systems/__init__.py
--- 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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/systems/gamerulessystem.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/systems/scriptingsystem.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/vfs.py
--- 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 .
-
-# KLUDGE M. George Hansen 2011-06-04: Die, global variable, die!
-VFS = None
\ No newline at end of file
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/viewbase.py
--- 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 .
-
-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
diff -r 7747b0f73694 -r 92bb014104ee src/parpg/world.py
--- 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