changeset 0:7a89ea5404b1

Initial commit of parpg-core.
author M. George Hansen <technopolitica@gmail.com>
date Sat, 14 May 2011 01:12:35 -0700
parents
children 4912a6f97c52
files __init__.py application.py charactercreationcontroller.py charactercreationview.py characterstatistics.py common/__init__.py common/listeners/__init__.py common/listeners/command_listener.py common/listeners/console_executor.py common/listeners/event_listener.py common/listeners/key_listener.py common/listeners/mouse_listener.py common/listeners/widget_listener.py common/ordereddict.py common/utils.py console.py controllerbase.py dialogue.py dialogueactions.py dialoguecontroller.py dialogueparsers.py dialogueprocessor.py font.py gamemap.py gamemodel.py gamescenecontroller.py gamesceneview.py gamestate.py gui/__init__.py gui/actionsbox.py gui/charactercreationview.py gui/containergui.py gui/containergui_base.py gui/dialogs.py gui/dialoguegui.py gui/drag_drop_data.py gui/filebrowser.py gui/hud.py gui/inventorygui.py gui/menus.py gui/popups.py gui/spinners.py gui/tabwidget.py inventory.py main.py mainmenucontroller.py mainmenuview.py objects/__init__.py objects/action.py objects/actors.py objects/base.py objects/composed.py objects/containers.py objects/doors.py objects/items.py quest_engine.py serializers.py settings.py sounds.py viewbase.py
diffstat 58 files changed, 10695 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/__init__.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,31 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+COPYRIGHT_HEADER = """\
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/application.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,230 @@
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""This module contains the main Application class 
+and the basic Listener for PARPG """
+
+import os
+import sys
+
+from fife import fife
+from fife.extensions import pychan
+from fife.extensions.serializers.xmlanimation import XMLAnimationLoader
+from fife.extensions.basicapplication import ApplicationBase
+
+from parpg import console
+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
+
+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.engine = fife.Engine()
+        self.loadSettings()
+        self.engine.init()
+        self._animationloader = XMLAnimationLoader(self.engine.getImagePool(),
+                                                   self.engine.getVFS())
+        self.engine.getAnimationPool().addResourceLoader(self._animationloader)
+
+        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()
+        self.view = MainMenuView(self.engine, self.model)
+        self.loadFonts()
+        self.event_listener = EventListener(self.engine)
+        self.controllers = []
+        controller = MainMenuController(self.engine, self.view, self.model, 
+                                        self)
+        #controller.initHud()
+        self.controllers.append(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.system_path, 
+                                        self._setting.fife.FontsPath))
+        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()
+        engineSetting.setDefaultFontGlyphs(self._setting.fife.FontGlyphs)
+        engineSetting.setDefaultFontPath(os.path.join(self._setting.system_path,
+                                                   self._setting.fife.FontsPath,
+                                                   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.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.setColorKey(*[int(digit) 
+                                    for digit in self._setting.fife.ColorKey])
+
+        engineSetting.setWindowTitle(self._setting.fife.WindowTitle)
+        engineSetting.setWindowIcon(os.path.join(self._setting.system_path, 
+                                                 self._setting.fife.IconsPath,
+                                                 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 pushController(self, controller):
+        """Adds a controller to the list to be the current active one."""
+        self.controllers[-1].pause(True)
+        self.controllers.append(controller)
+    
+    def popController(self):
+        """Removes and returns the current active controller, unless its the last one"""
+        ret_controller = None
+        if self.controllers.count > 1:
+            ret_controller = self.controllers.pop()
+            self.controllers[-1].pause(False)
+        ret_controller.onStop()
+        return ret_controller
+    
+    def switchController(self, controller):
+        """Clears the controller list and adds a controller to be the current active one"""
+        for old_controller in self.controllers:
+            old_controller.onStop()
+        self.controllers = []
+        self.controllers.append(controller)
+    
+    def _pump(self):
+        """Main game loop.
+           There are in fact 2 main loops, this one and the one in GameSceneView.
+           @return: None"""
+        if self.listener.quit:
+            self.breakRequested = True #pylint: disable-msg=C0103
+        else:
+            for controller in self.controllers:
+                controller.pump()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/charactercreationcontroller.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,394 @@
+#   This file is part of PARPG.
+#
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+"""Provides the controller that defines the behaviour of the character creation
+   screen."""
+
+import characterstatistics as char_stats
+from serializers import XmlSerializer
+from controllerbase import ControllerBase
+from gamescenecontroller import GameSceneController
+from gamesceneview import GameSceneView
+from parpg.inventory import Inventory
+
+DEFAULT_STAT_VALUE = 50
+
+
+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    
+
+#TODO: Should be replaced with the real character class once its possible
+class SimpleCharacter(object):
+    """This is a simple class that is used to store the data during the
+    character creation"""
+    
+    def __init__(self, name, gender, origin, age, picture, traits,
+                 primary_stats, secondary_stats, inventory):
+        self.name = name
+        self.gender = gender
+        self.origin = origin
+        self.age = age
+        self.picture = picture
+        self.traits = traits
+        self.statistics = {}
+        for primary_stat in primary_stats:
+            short_name = primary_stat.short_name
+            self.statistics[short_name] = char_stats.PrimaryStatisticValue(
+                                                    primary_stat,
+                                                    self,
+                                                    DEFAULT_STAT_VALUE)
+            long_name = primary_stat.long_name
+            self.statistics[long_name] = char_stats.PrimaryStatisticValue(
+                                                    primary_stat,
+                                                    self,
+                                                    DEFAULT_STAT_VALUE)
+        for secondary_stat in secondary_stats:
+            name = secondary_stat.name            
+            self.statistics[name] = char_stats.SecondaryStatisticValue(
+                                                    secondary_stat,
+                                                    self)
+        self.inventory = inventory
+            
+class CharacterCreationController(ControllerBase):
+    """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 = {}
+    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)
+        self.view.start_new_game_callback = self.startNewGame
+        self.view.cancel_new_game_callback = self.cancelNewGame
+        self.view.show()
+        #TODO: Maybe this should not be hardcoded
+        stream = file("character_scripts/primary_stats.xml")        
+        prim_stats = XmlSerializer.deserialize(stream)
+        stream = file("character_scripts/secondary_stats.xml")        
+        sec_stats = XmlSerializer.deserialize(stream)
+        self.char_data = SimpleCharacter("",
+                                              self.GENDERS[0],
+                                              self.ORIGINS.keys()[0],
+                                              20,
+                                              self.PICTURES[self.GENDERS[0]][0],
+                                              [],
+                                              prim_stats,
+                                              sec_stats,
+                                              Inventory())
+        self._stat_points = 200
+  
+       
+    def startNewGame(self):
+        """Create the new character and start a new game.
+           @return: None"""
+        view = GameSceneView(self.engine, self.model)
+        controller = GameSceneController(self.engine, view, self.model,
+                                         self.application)
+        self.application.view = view
+        self.application.switchController(controller)
+        start_map = self.model.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.switchController(controller)
+    
+    def onStop(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.name
+    
+    @property
+    def age(self):
+        """Returns the age of the character.
+        @return: Age of the character"""
+        return self.char_data.age
+    
+    @property
+    def gender(self):
+        """Returns the gender of the character.
+        @return: Gender of the character"""
+        return self.char_data.gender
+    
+    @property
+    def origin(self):
+        """Returns the origin of the character.
+        @return: Origin of the character"""
+        return self.char_data.origin
+    
+    @property
+    def picture(self):
+        """Returns the ID of the current picture of the character."""
+        return self.char_data.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.statistics[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.statistics[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.statistics[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.statistics[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.statistics[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.statistics[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"""
+        return self.char_data.statistics[statistic]
+    
+    def areAllStatisticsValid(self):
+        """Checks if all statistics are inside the minimum/maximum values
+        @return True if all statistics are valid False if not"""
+        for stat in self.char_data.statistics.items():
+            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.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.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.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.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.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.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.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.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.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.traits) > self.MAX_TRAITS:
+            return False 
+        for trait in self.char_data.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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/charactercreationview.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,80 @@
+#   This file is part of PARPG.
+#
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+"""Provides the view for displaying the character creation screen."""
+
+import os
+
+from fife.extensions import pychan
+
+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
+        gui_path = os.path.join(self.settings.system_path,
+                                self.settings.parpg.GuiPath)
+        self.background = pychan.loadXML(os.path.join(gui_path, 
+                                                    'main_menu_background.xml'))
+        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
+        self.character_screen = pychan.loadXML(os.path.join(gui_path,
+                                                       'character_screen.xml'))
+        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()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/characterstatistics.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,165 @@
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""
+Provides classes that define character stats and traits.
+"""
+
+from abc import ABCMeta, abstractmethod
+from weakref import ref as weakref
+
+from .serializers import SerializableRegistry
+
+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[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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/listeners/command_listener.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+"""This module contains the CommandListener class for receiving command events"""
+
+
+class CommandListener(object):
+    """Base class for listeners that receiving command events"""
+
+    def __init__(self, event_listener):
+        self.event_listener = None
+        CommandListener.attach(self, event_listener)
+    
+    def attach(self, event_listener):
+        """Attaches the listener to the event"""
+        event_listener.addListener("Command", self)
+        self.event_listener = event_listener
+        
+    def detach(self):
+        """Detaches the listener from the event"""
+        self.event_listener.removeListener("Command", self)
+        self.event_listener = None
+
+    def onCommand(self, command):
+        """Called when a command is executed"""
+        pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/listeners/console_executor.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+"""This module contains the ConsoleExecuter class that receives 
+console events"""
+
+class ConsoleExecuter(object):
+    """This class is a base class for listeners receiving console events"""
+
+    def __init__(self, event_listener):
+        self.event_listener = None
+        ConsoleExecuter.attach(self, event_listener)
+    
+    def attach(self, event_listener):
+        """Attaches the listener to the event"""
+        event_listener.addListener("ConsoleCommand", self)
+        self.event_listener = event_listener
+        
+    def detach(self):
+        """Detaches the listener from the event"""
+        self.event_listener.removeListener("ConsoleCommand", self)
+        self.event_listener = None
+
+    def onToolsClick(self):
+        """Called when the tools button has been clicked"""
+        pass
+    
+    def onConsoleCommand(self, command):
+        """Called when a console command is executed"""
+        pass
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/listeners/event_listener.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+"""This module contains the EventListener that receives events and distributes 
+them to PARPG listeners"""
+
+from fife import fife
+import logging
+
+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)
+        engine.getGuiManager().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)
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/listeners/key_listener.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+"""This module contains the KeyListener class for receiving key inputs"""
+
+class KeyListener(object):
+    """Base class for listeners receiving keyboard input"""
+
+    def __init__(self, event_listener):
+        self.event_listener = None
+        KeyListener.attach(self, event_listener)
+    
+    def attach(self, event_listener):
+        """Attaches the listener to the event"""
+        event_listener.addListener("Key", self)
+        self.event_listener = event_listener
+        
+    def detach(self):
+        """Detaches the listener from the event"""
+        self.event_listener.removeListener("Key", self)
+        
+    def keyPressed(self, event):
+        """Called when a key is being pressed"""
+        pass
+    
+    def keyReleased(self, event):
+        """Called when a key is being released"""
+        pass
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/listeners/mouse_listener.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+"""This module contains the MouseListener class for receiving mouse inputs"""
+
+class MouseListener(object):
+    """Base class for listeners receiving mouse input"""
+    
+    def __init__(self, event_listener):
+        self.event_listener = None
+        MouseListener.attach(self, event_listener)
+    
+    def attach(self, event_listener):
+        """Attaches the listener to the event"""
+        event_listener.addListener("Mouse", self)
+        self.event_listener = event_listener
+        
+    def detach(self):
+        """Detaches the listener from the event"""
+        self.event_listener.removeListener("Mouse", self)
+        self.event_listener = None
+        
+    def mousePressed(self, evt):
+        """Called when a mouse button is pressed"""
+        pass
+
+    def mouseReleased(self, evt):
+        """Called when a mouse button is released"""
+        pass
+
+    def mouseEntered(self, evt):
+        """Called when a mouse enters a region"""
+        pass
+
+    def mouseExited(self, evt):
+        """Called when a mouse exits a region"""
+        pass
+
+    def mouseClicked(self, evt):
+        """Called after a mouse button is pressed and released"""
+        pass
+
+    def mouseWheelMovedUp(self, evt):
+        """Called when the mouse wheel has been moved up"""
+        pass
+
+    def mouseWheelMovedDown(self, evt):
+        """Called when the mouse wheel has been moved down"""
+        pass
+
+    def mouseMoved(self, evt):
+        """Called when when the mouse has been moved"""
+        pass
+
+    def mouseDragged(self, evt):
+        """Called when dragging the mouse"""
+        pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/listeners/widget_listener.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+"""This module contains the WidgetListener class for receiving widget events"""
+
+class WidgetListener(object):
+    """A Base class for listeners receiving widget events"""
+    
+    def __init__(self, event_listener):
+        self.event_listener = None
+        WidgetListener.attach(self, event_listener)
+    
+    def attach(self, event_listener):
+        """Attaches the listener to the event"""
+        event_listener.addListener("Widget", self)
+        self.event_listener = event_listener
+        
+    def detach(self):
+        """Detaches the listener from the event"""
+        self.event_listener.removeListener("Widget", self)
+        self.event_listener = None
+
+    def onWidgetAction(self, evt):
+        """Called when a widget action is executed"""
+        pass
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/ordereddict.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,127 @@
+# 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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/utils.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,61 @@
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Miscellaneous game functions
+
+import os, sys, fnmatch
+from textwrap import dedent
+
+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."""
+    for path, _, files in os.walk(os.path.abspath(root)):
+        for filename in fnmatch.filter(files, pattern):
+            yield os.path.join(path, filename)
+
+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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/console.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,204 @@
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import re
+import os
+import sys
+from StringIO import StringIO
+import code
+
+class Console:
+    def __init__(self, app_listener):
+        """
+        @type appListener: ApplicationListener
+        @param appListener: ApplicationListener object providing a link with
+        the Controller, the view and the GameModel"""
+        exit_help   = "Terminate application"
+        grid_help   = "Toggle grid display"
+        run_help    = "Toggle player run/walk"
+        help_help   = "Show this help string"
+        load_help   = "Usage: load directory file"
+        python_help = "Run some python code"
+        quit_help   = "Terminate application"
+        save_help   = "Usage: save directory file"
+        pause_help  = "Pause/Unpause the game"
+
+        self.commands = [
+            {"cmd":"exit"  ,"callback":self.handleQuit  ,"help": exit_help},
+            {"cmd":"grid"  ,"callback":self.handleGrid  ,"help": grid_help},
+            {"cmd":"help"  ,"callback":self.handleHelp  ,"help": help_help},
+            {"cmd":"load"  ,"callback":self.handleLoad  ,"help": load_help},
+            {"cmd":"pause" ,"callback":self.handlePause ,"help": pause_help},
+            {"cmd":"python","callback":self.handlePython,"help": python_help},
+            {"cmd":"run"   ,"callback":self.handleRun   ,"help": run_help},
+            {"cmd":"save"  ,"callback":self.handleSave  ,"help": save_help},
+            {"cmd":"quit"  ,"callback":self.handleQuit  ,"help": quit_help},
+        ]
+        self.app_listener = app_listener
+        self.view = self.app_listener.view 
+        self.model = self.app_listener.model
+        self.game_state = self.app_listener.view.model.game_state
+        self.console_locals = {"__name__":"__paprg_console__",\
+                               "__doc__": None,\
+                               "app_listener":self.app_listener,\
+                               "model":self.app_listener.model,\
+                               "view":self.app_listener.view,\
+                               "engine":self.app_listener.engine}
+
+        self.console = code.InteractiveConsole(self.console_locals)
+
+    def handleQuit(self, command):
+        """Implements the quit console command 
+           @type command: string
+           @param command: The command to run
+           @return: The resultstring"""
+        self.app_listener.quitGame()
+        return "Terminating ..."
+
+    def handlePause(self, command):
+        """Implements the pause console command
+           @type command: string
+           @param command: The command to run
+           @return: The resultstring"""
+        self.model.togglePause()
+        return "Game (un)paused"
+
+    def handleGrid(self, command):
+        """Implements the grid console command 
+           @type command: string
+           @param command: The command to run
+           @return: The resultstring"""
+        self.app_listener.view.active_map.toggleRenderer('GridRenderer')
+        return "Grid toggled"
+
+    def handleRun(self, command):
+        """Toggles run/walk mode of the PC player
+           @type command: string
+           @param command: The command to run.
+           @return: The response"""
+        if self.app_listener.model.pc_run == 1:
+            self.app_listener.model.pc_run = 0
+            return "PC is now walking"
+        else:
+            self.app_listener.model.pc_run = 1
+            return "PC is now running"
+
+    def handleHelp(self, command):
+        """Implements the help console command 
+           @type command: string
+           @param command: The command to run
+           @return: The resultstring"""
+        res = ""
+        for cmd in self.commands:
+            res += "%10s: %s\n" % (cmd["cmd"], cmd["help"])
+        return res
+
+    def handlePython(self,command):
+        user_code = command[7:len(command)]
+
+        codeOut = StringIO()
+
+        #make stdout and stderr write to our file, not the terminal
+        sys.stdout = codeOut
+        sys.stderr = codeOut
+
+        #Workaround it not being possible to enter a blank line in the console
+        if user_code == " ":
+            user_code = ""
+
+        #Process the code
+        self.console.push(user_code)
+        if len(self.console.buffer) == 0:
+            output = codeOut.getvalue()
+        else:
+            output =  "..."
+
+
+        #restore stdout and stderr
+        sys.stdout = sys.__stdout__
+        sys.stderr = sys.__stderr__
+
+        temp_output = output
+        output = ""
+        counter = 0
+
+        #Make the output fit in the console screen
+        for char in temp_output:
+            counter += 1
+            if char == "\n":
+                counter = 0
+            elif counter == 110:
+                output += "\n"
+                counter = 0
+            output += char
+
+        return output
+
+    def handleLoad(self, command):
+        """Implements the load console command 
+           @type command: string
+           @param command: The command to run
+           @return: The resultstring"""
+        result = None
+        load_regex = re.compile('^load')
+        l_matches = load_regex.match(command.lower())
+        if (l_matches is not None):
+            end_load = l_matches.end()
+            try:
+                l_args = command.lower()[end_load + 1:].strip()
+                l_path, l_filename = l_args.split(' ')
+                self.app_listener.model.load_save = True
+                self.app_listener.model.savegame = os.path.join(l_path, l_filename)
+                result = "Load triggered"
+
+            except Exception, l_error:
+                self.app_listener.engine.getGuiManager().getConsole().println('Error: ' + str(l_error))
+                result="Failed to load file"
+
+        return result
+
+    def handleSave(self, command):
+        """Implements the save console command 
+           @type command: string
+           @param command: The command to run
+           @return: The resultstring"""
+        save_regex = re.compile('^save')
+        s_matches = save_regex.match(command.lower())
+        if (s_matches != None):
+            end_save = s_matches.end()
+            try:
+                s_args = command.lower()[end_save+1:].strip()
+                s_path, s_filename = s_args.split(' ')
+                self.app_listener.model.save(s_path, s_filename)
+                result = "Saved to file: " + s_path + s_filename
+
+            except Exception, s_error:
+                self.app_listener.engine.getGuiManager().getConsole(). \
+                    println('Error: ' + str(s_error))
+                result = "Failed to save file"
+        return result 
+
+    def handleConsoleCommand(self, command):
+        """Implements the console logic 
+           @type command: string
+           @param command: The command to run
+           @return: The response string """
+        result = None        
+        for cmd in self.commands:
+            regex = re.compile('^' + cmd["cmd"])
+            if regex.match(command.lower()):
+                result=cmd["callback"](command)
+
+        if result is None:
+            result = self.handlePython("python " + command) 
+        return result
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/controllerbase.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,99 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+import os
+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
+
+class ControllerBase(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)
+        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()
+        cursor_type = fife.CURSOR_IMAGE
+        img_pool = self.engine.getImagePool()
+        if(mc_type == "target"):
+            target_cursor_id = img_pool.addResourceFromFile(image)  
+            dummy_cursor_id = img_pool.addResourceFromFile(dummy_image)
+            cursor.set(cursor_type, dummy_cursor_id)
+            cursor.setDrag(cursor_type, target_cursor_id, -16, -16)
+        else:
+            cursor_type = fife.CURSOR_IMAGE
+            zero_cursor_id = img_pool.addResourceFromFile(image)
+            cursor.set(cursor_type, zero_cursor_id)
+            cursor.setDrag(cursor_type, zero_cursor_id)
+
+    def resetMouseCursor(self):
+        """Reset cursor to default image.
+           @return: None"""
+        image = os.path.join(self.model.settings.system_path,
+                             self.model.settings.parpg.GuiPath,
+                             self.model.settings.parpg.CursorPath,
+                             self.model.settings.parpg.CursorDefault)
+        self.setMouseCursor(image, image)
+        
+    def onStop(self):
+        """Called when the controller is removed from the list"""
+        pass 
+                
+    def pump(self):
+        """This method gets called every frame"""
+        pass
+    
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogue.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,192 @@
+#   This file is part of PARPG.
+#
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+"""
+Provides classes used to contain and organize dialogue data for use within
+in-game dialogues between the player character and NPCs.
+"""
+try:
+    from collections import OrderedDict
+except ImportError:
+    # Python version 2.4-2.6 doesn't have the OrderedDict
+    from parpg.common.ordereddict import OrderedDict
+
+class Dialogue(object):
+    """
+    Represents a complete dialogue and acts as a container for the dialogue
+    data belonging to a particular NPC.
+    """
+    __slots__ = ['npc_name', 'avatar_path', 'default_greeting',
+                 'greetings', 'sections']
+    
+    def __init__(self, npc_name, avatar_path, default_greeting, greetings=None,
+                 sections=None):
+        """
+        Initialize a new L{Dialogue} instance.
+        
+        @param npc_name: name displayed for the NPC in the dialogue.
+        @type npc_name: basestring
+        @param avatar_path: path to the image that should be displayed as the
+            NPC's avatar.
+        @type avatar_path: basestring
+        @param default_greeting: section of dialogue that should be
+            displayed when the dialogue is first initiated and no other start
+            sections are available.
+        @type default_greeting: L{DialogueSection}
+        @param greetings: sections of dialogue defining the conditions
+            under which each should be displayed when the dialogue is first
+            initiated.
+        @type greetings: list of 
+            L{RootDialogueSections<DialogueGreeting>}
+        @param sections: sections of dialogue that make up this
+            L{Dialogue} instance.
+        @type sections: list of L{DialogueSections<DialogueSection>}
+        """
+        self.npc_name = npc_name
+        self.avatar_path = avatar_path
+        self.default_greeting = default_greeting
+        self.greetings = greetings if greetings is not None else []
+        self.sections = OrderedDict()
+        all_sections = [default_greeting]
+        if (greetings is not None):
+            all_sections += greetings
+        if (sections is not None):
+            all_sections += sections
+        if (__debug__):
+            section_ids = [section.id for section in all_sections]
+        for section in all_sections:
+            # Sanity check: All DialogueResponses should have next_section_id
+            # attributes that refer to valid DialogueSections in the Dialogue.
+            if (__debug__):
+                for response in section.responses:
+                    assert response.next_section_id in section_ids + \
+                        ['end', 'back'], ('"{0}" does not refer to a ' 
+                                          'DialogueSection in this Dialogue')\
+                        .format(response.next_section_id)
+            self.sections[section.id] = section
+    
+    def __str__(self):
+        """Return the string representation of a L{Dialogue} instance."""
+        string_representation = 'Dialogue(npc_id={0.npc_name})'.format(self)
+        return string_representation
+
+
+class DialogueNode(object):
+    """
+    Abstract base class that represents a node or related group of attributes
+    within a Dialogue.
+    """
+    def __init__(self, text, actions=None):
+        """
+        Initialize a new L{DialogueNode} instance.
+        
+        @param text: textual content of the L{DialogueNode}.
+        @type text: basestring
+        @param actions: dialogue actions associated with the L{DialogueNode}.
+        @type actions: list of L{DialogueActions<DialogueAction>}
+        """
+        self.text = text
+        self.actions = actions or []
+
+
+class DialogueSection(DialogueNode):
+    """DialogueNode that represents a distinct section of the dialogue."""
+    __slots__ = ['id', 'text', 'responses', 'actions']
+    
+    def __init__(self, id_, text, responses=None, actions=None):
+        """
+        Initialize a new L{DialogueSection} instance.
+        
+        @param id_: named used to uniquely identify the L{DialogueSection}
+            within a L{Dialogue}.
+        @type id_: basestring
+        @param text: text displayed as the NPC's part of the L{Dialogue}.
+        @type text: basestring
+        @param responses: possible responses that the player can choose from.
+        @type responses: list of L{DialogueResponses<DialogueResponse>}
+        @param actions: dialogue actions that should be executed when the
+            L{DialogueSection} is reached.
+        @type actions: list of L{DialogueActions<DialogueAction>}
+        """
+        DialogueNode.__init__(self, text=text, actions=actions)
+        self.id = id_
+        if (responses is not None):
+            self.responses = list(responses)
+
+
+class DialogueGreeting(DialogueSection):
+    """
+    Represents a root section of dialogue in a L{Dialogue} along with the
+    conditional statement used to determine the whether this section should be
+    displayed first upon dialogue initiation.
+    
+    @ivar id: Name used to uniquely identify the L{DialogueSection} to which
+        the L{DialogueRootSectionReference} points.
+    @type id: basestring
+    @ivar condition: Boolean Python expression used to determine if the
+        L{DialogueSection} referenced is a valid starting section.
+    @type condition: basestring
+    """
+    __slots__ = ['id', 'condition', 'text', 'actions', 'responses']
+    
+    def __init__(self, id_, condition, text, responses=None, actions=None):
+        """
+        Initialize a new L{DialogueGreeting} instance.
+        
+        @param id_: named used to uniquely identify the L{DialogueSection}
+            within a L{Dialogue}.
+        @type id_: basestring
+        @param condition: Boolean Python expression used to determine if this
+            root dialogue section should be displayed.
+        @type condition: basestring
+        @param text: text displayed as the NPC's part of the L{Dialogue}.
+        @type text: basestring
+        @param responses: possible responses that the player can choose from.
+        @type responses: list of L{DialogueResponses<DialogueResponse>}
+        @param actions: dialogue actions that should be executed when the
+            L{DialogueSection} is reached.
+        @type actions: list of L{DialogueActions<DialogueAction>}
+        """
+        DialogueSection.__init__(self, id_=id_, text=text, responses=responses,
+                                 actions=actions)
+        self.condition = condition
+
+
+class DialogueResponse(DialogueNode):
+    """
+    L{DialogueNode} that represents one possible player response to a
+    particular L{DialogueSection}.
+    """
+    __slots__ = ['text', 'actions', 'condition', 'next_section_id']
+    
+    def __init__(self, text, next_section_id, actions=None, condition=None):
+        """
+        Initialize a new L{DialogueResponse} instance.
+        
+        @param text: text displayed as the content of the player's response.
+        @type text: basestring
+        @param next_section_id: ID of the L{DialogueSection} that should be
+            jumped to if this response is chosen by the player.
+        @type next_section_id: basestring
+        @param actions: dialogue actions that should be executed if this
+            response is chosen by the player.
+        @type actions: list of L{DialogueActions<DialogueAction>}
+        @param condition: Python expression that when evaluated determines
+            whether the L{DialogueResponse} should be displayed to the player
+            as a valid response.
+        @type condition: basestring
+        """
+        DialogueNode.__init__(self, text=text, actions=actions)
+        self.condition = condition
+        self.next_section_id = next_section_id
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogueactions.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,373 @@
+#   This file is part of PARPG.
+#
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+"""
+Provides classes used to implement dialogue logic and allow dialogues to have
+external effects on the game state.
+"""
+import logging
+
+logger = logging.getLogger('dialogueaction')
+
+class DialogueAction(object):
+    """
+    Abstract base class for subclasses that represent dialogue actions embedded
+    within a DialogueSection or DialogueResponse.
+    
+    Subclasses must define the keyword class variable and implement both the
+    __init__ and __call__ methods.
+    
+    @cvar keyword: keyword used by the L{DialogueParser} to recognize the
+        L{DialogueAction} in serialized L{Dialogues<Dialogues>}.
+    @type keyword: basestring
+    """
+    logger = logging.getLogger('dialogueaction.DialogueAction')
+    registered_actions = {}
+    
+    @classmethod
+    def registerAction(cls, dialogue_action_type):
+        """
+        Register a L{DialogueAction} subclass for easy reference.
+        
+        @param dialogue_action_type: dialogue action to register.
+        @type dialogue_action_type: L{DialogueAction} subclass
+        """
+        cls.registered_actions[dialogue_action_type.keyword] = \
+            dialogue_action_type
+    
+    def __init__(self, *args, **kwargs):
+        """
+        Initialize a new L{DialogueAction} instance.
+        
+        @param args: positional arguments passed by the L{DialogueParser} after
+            reading a serialized L{Dialogue}.
+        @type args: list of objects
+        @param kwargs: keyword arguments passed by the L{DialogueParser} after
+            reading a serialized L{Dialogue}.
+        @type kwargs: dict of objects
+        """
+        if (not hasattr(type(self), 'keyword')):
+            raise AttributeError('DialogueAction subclasses must define the '
+                                 'keyword class variable.')
+        self.arguments = (args, kwargs)
+    
+    def __call__(self, game_state):
+        """
+        Execute the L{DialogueAction}.
+        
+        @param game_state: variables and functions that make up the current
+            game state.
+        @type game_state: dict of objects
+        """
+        raise NotImplementedError('subclasses of DialogueAction must '
+                                  'override __call__')
+
+
+class MeetAction(DialogueAction):
+    """
+    L{DialogueAction} that adds an NPC to the list of NPCs known by the player.
+    """
+    keyword = 'meet'
+    
+    def __init__(self, *args, **kwargs):
+        """
+        Initialize a new L{MeetAction} instance.
+        
+        @param args: positional arguments.
+        @type args: list of objects
+        @param npc_id: identifier of the NPC that the player has met.
+        @type npc_id: basestring
+        @param kwargs: keyword arguments (not used).
+        @type kwargs: dict of objects
+        """
+        DialogueAction.__init__(self, *args, **kwargs)
+        self.npc_id = args[0]
+    
+    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 = self.npc_id
+        # 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['pc'].meet(npc_id)
+DialogueAction.registerAction(MeetAction)
+
+
+class InventoryAction(DialogueAction):
+    """
+    Abstract base class for L{DialogueActions<DialogueAction>} used to
+    manipulate the NPC's and the player's inventory.
+    """
+    def __init__(self, *args, **kwargs):
+        """
+        Initialize a new L{InventoryAction} instance.
+        
+        @param args: positional arguments.
+        @type args: list of objects
+        @param item_types: item types that should be manipulated.
+        @type item_types: list of basestrings
+        @param kwargs: keyword arguments.
+        @type kwargs: dict of objects
+        """
+        DialogueAction.__init__(self, *args, **kwargs)
+        self.item_types = args
+
+
+class TakeStuffAction(InventoryAction):
+    """
+    L{InventoryAction} used to move items from the NPC's inventory to the
+    player's inventory.
+    """
+    keyword = 'take_stuff'
+    
+    def __call__(self, game_state):
+        """
+        Move items from the NPC's inventory to the player's inventory.
+        
+        @param game_state: variables and functions that make up the current
+            game state.
+        @type game_state: dict of objects
+        """
+        item_types = self.item_types
+        for item_type in item_types:
+            item = game_state['npc'].inventory.findItem(item_type=item_type)
+            if (item):
+                game_state['npc'].give(item, game_state['pc'])
+                print("{0} gave you the {1}".format(game_state['npc'].name,
+                                                    item_type))
+            else:
+                print("{0} doesn't have the {1}".format(game_state['npc'].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 = game_state['npc'].inventory.findItem(item_type = item_type)
+            if (item):
+                game_state['pc'].give(item, game_state['npc'])
+                print("You give the {0} to {1}".format(item_type,
+                                                       game_state['npc'].name))
+            else:
+                print("You don't have the {0}".format(item_type))
+DialogueAction.registerAction(GiveStuffAction)
+
+
+class QuestAction(DialogueAction):
+    """
+    Abstract base class for quest-related L{DialogueActions<DialogueAction>}.
+    """
+    def __init__(self, *args, **kwargs):
+        """
+        Initialize a new L{QuestAction} instance.
+        
+        @param args: positional arguments.
+        @type args: list of objects
+        @param quest_id: ID of the quest to manipulate.
+        @type quest_id: basestring
+        @param kwargs: keyword arguments (not used).
+        @type kwargs: dict of objects
+        """
+        DialogueAction.__init__(self, *args, **kwargs)
+        self.quest_id = kwargs['quest'] if 'quest' in kwargs else args[0]
+
+
+class StartQuestAction(QuestAction):
+    """L{QuestAction} used to activate a quest."""
+    keyword = 'start_quest'
+    
+    def __call__(self, game_state):
+        """
+        Activate a quest.
+        
+        @param game_state: variables and functions that make up the current
+            game state.
+        @type game_state: dict of objects
+        """
+        quest_id = self.quest_id
+        print("You've picked up the \"{0}\" quest!".format(quest_id))
+        game_state['quest'].activateQuest(quest_id)
+DialogueAction.registerAction(StartQuestAction)
+
+
+class CompleteQuestAction(QuestAction):
+    """
+    L{QuestAction} used to mark a quest as successfully finished/completed.
+    """
+    keyword = 'complete_quest'
+    
+    def __call__(self, game_state):
+        """
+        Successfully complete a quest.
+        
+        @param game_state: variables and functions that make up the current
+            game state.
+        @type game_state: dict of objects
+        """
+        quest_id = self.quest_id
+        print("You've finished the \"{0}\" quest".format(quest_id))
+        game_state['quest'].finishQuest(quest_id)
+DialogueAction.registerAction(CompleteQuestAction)
+
+
+class FailQuestAction(QuestAction):
+    """L{QuestAction} used to fail an active quest."""
+    keyword = 'fail_quest'
+    
+    def __call__(self, game_state):
+        """
+        Fail an active quest.
+        
+        @param game_state: variables and functions that make up the current
+            game state.
+        @type game_state: dict of objects
+        """
+        quest_id = self.quest_id
+        print("You've failed the \"{0}\" quest".format(quest_id))
+        game_state['quest'].failQuest(quest_id)
+DialogueAction.registerAction(FailQuestAction)
+
+
+class RestartQuestAction(QuestAction):
+    """L{QuestAction} used to restart an active quest."""
+    keyword = 'restart_quest'
+    
+    def __call__(self, game_state):
+        """
+        Restart an active quest.
+        
+        @param game_state: variables and functions that make up the current
+            game state.
+        @type game_state: dict of objects
+        """
+        quest_id = self.quest_id
+        print("You've restarted the \"{0}\" quest".format(quest_id))
+        game_state['quest'].restartQuest(quest_id)
+DialogueAction.registerAction(RestartQuestAction)
+
+
+class QuestVariableAction(QuestAction):
+    """
+    Base class for L{QuestActions<QuestAction>} that modify quest
+    variables.
+    """
+    def __init__(self, *args, **kwargs):
+        """
+        Initialize a new L{QuestVariableAction} instance.
+        
+        @param args: positional arguments (not used).
+        @type args: list of objects
+        @param kwargs: keyword arguments.
+        @type kwargs: dict of objects
+        @keyword quest: ID of the quest whose variable should be modified.
+        @type quest: basestring
+        @keyword variable: name of the quest variable to modify.
+        @type variable: basestring
+        @keyword value: new value that should be used to modify the quest
+            variable.
+        @type value: object
+        """
+        QuestAction.__init__(self, *args, **kwargs)
+        self.variable_name = kwargs['variable']
+        self.value = kwargs['value']
+
+
+class IncreaseQuestVariableAction(QuestVariableAction):
+    """
+    L{QuestVariableAction} used to increase the value of a quest variable by a
+    set amount.
+    """
+    keyword = 'increase_quest_variable'
+    
+    def __call__(self, game_state):
+        """
+        Increase a quest variable by a set amount.
+        
+        @param game_state: variables and functions that make up the current
+            game state.
+        @type game_state: dict of objects
+        """
+        quest_id = self.quest_id
+        variable_name = self.variable_name
+        value = self.value
+        print('Increased {0} by {1}'.format(variable_name, value))
+        game_state['quest'][quest_id].increaseValue(variable_name, value)
+DialogueAction.registerAction(IncreaseQuestVariableAction)
+
+
+class DecreaseQuestVariableAction(QuestVariableAction):
+    """
+    L{QuestVariableAction} used to decrease the value of a quest variable by a
+    set amount.
+    """
+    keyword = 'decrease_quest_variable'
+    
+    def __call__(self, game_state):
+        """
+        Decrease a quest variable by a set amount.
+        
+        @param game_state: variables and functions that make up the current
+            game state.
+        @type game_state: dict of objects
+        """
+        quest_id = self.quest_id
+        variable_name = self.variable_name
+        value = self.value
+        print('Decreased {0} by {1}'.format(variable_name, value))
+        game_state['quest'][quest_id].decreaseValue(variable_name, value)
+DialogueAction.registerAction(DecreaseQuestVariableAction)
+
+
+class SetQuestVariableAction(QuestVariableAction):
+    """
+    L{QuestVariableAction} used to set the value of a quest variable.
+    """
+    keyword = 'set_quest_variable'
+    
+    def __call__(self, game_state):
+        """
+        Set the value of a quest variable.
+        
+        @param game_state: variables and functions that make up the current
+            game state.
+        @type game_state: dict of objects
+        """
+        quest_id = self.quest_id
+        variable_name = self.variable_name
+        value = self.value
+        print('Set {0} to {1}'.format(variable_name, value))
+        game_state['quest'][quest_id].setValue(variable_name, value)
+DialogueAction.registerAction(SetQuestVariableAction)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dialoguecontroller.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,59 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+from controllerbase import ControllerBase
+
+class DialogueController(ControllerBase):
+    """Controller that takes over when a dialogue is started"""
+    def __init__(self, 
+                 engine, 
+                 view, 
+                 model, 
+                 application):
+        """
+        Constructor
+        @param engine: Instance of the active fife engine
+        @type engine: fife.Engine
+        @param view: Instance of a GameSceneView
+        @param type: parpg.GameSceneView
+        @param model: The model that has the current gamestate
+        @type model: parpg.GameModel
+        @param application: The application that created this controller
+        @type application: parpg.PARPGApplication
+        @param settings: The current settings of the application
+        @type settings: fife.extensions.fife_settings.Setting
+        """
+        super(DialogueController, self).__init__(engine,
+                                                  view,
+                                                  model,
+                                                  application)
+        self.dialogue = None
+        self.view = view
+        
+    def startTalk(self, npc):
+        if npc.dialogue is not None:
+            self.model.active_map.centerCameraOnPlayer()            
+            npc.talk(self.model.game_state.player_character)
+            self.dialogue = self.view.hud.showDialogue(npc)
+            self.dialogue.initiateDialogue()
+            self.model.pause(True)
+            self.view.hud.enabled = False
+
+            
+    def pump(self):
+        if self.dialogue and not self.dialogue.active:
+            self.application.popController()
+            self.model.pause(False)
+            self.view.hud.enabled = True
+            
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogueparsers.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,669 @@
+#   This file is part of PARPG.
+#
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+"""
+Contains classes for parsing and validating L{Dialogues<Dialogue>} and other
+dialogue-related data.
+
+@TODO Technomage 2010-11-13: Exception handling + validation needs work.
+    Currently YAML files are only crudely validated - the code assumes that
+    the file contains valid dialogue data, and if that assumption is
+    violated and causes the code to raise any TypeErrors, AttributeErrors or
+    ValueErrors the code then raises a DialogueFormatError with the
+    original (and mostly unhelpful) error message.
+@TODO Technomage 2010-11-13: Support reading and writing unicode.
+"""
+try:
+    from cStringIO import StringIO
+except ImportError:
+    from StringIO import StringIO
+from collections import Sequence
+try:
+    from collections import OrderedDict
+except ImportError:
+    # Python version 2.4-2.6 doesn't have the OrderedDict
+    from parpg.common.ordereddict import OrderedDict
+import re
+import textwrap
+
+import yaml
+
+from parpg import COPYRIGHT_HEADER
+from parpg.dialogue import (Dialogue, DialogueSection, DialogueResponse,
+    DialogueGreeting)
+from parpg.dialogueactions import DialogueAction
+
+import logging
+logger = logging.getLogger('dialogueparser')
+
+class DialogueFormatError(Exception):
+    """Exception thrown when the DialogueParser has encountered an error."""
+
+
+class AbstractDialogueParser(object):
+    """
+    Abstract base class defining the interface for parsers responsible for
+    constructing a L{Dialogue} from its serialized representation.
+    """
+    def load(self, stream):
+        """
+        Parse a stream and attempt to construct a new L{Dialogue} instance from
+        its serialized representation.
+        
+        @param stream: open stream containing the serialized representation of
+            a Dialogue.
+        @type stream: BufferType
+        """
+        raise NotImplementedError('AbstractDialogueParser subclasses must '
+                                  'override the load method.')
+    
+    def dump(self, dialogue, stream):
+        """
+        Serialize a L{Dialogue} instance and dump it to an open stream.
+        
+        @param dialogue: dialogue to serialize.
+        @type dialogue: L{Dialogue}
+        @param stream: open stream into which the serialized L{Dialogue} should
+            be dumped.
+        @type stream: BufferType
+        """
+        raise NotImplementedError('AbstractDialogueParser subclasses must '
+                                  'override the dump method.')
+    
+    def validate(self, stream):
+        """
+        Parse a stream and verify that it contains a valid serialization of a
+        L{Dialogue instance}.
+        
+        @param stream: stream containing the serialized representation of a
+            L{Dialogue}
+        @type stream: BufferType
+        """
+        raise NotImplementedError('AbstractDialogueParser subclasses must '
+                                  'override the validate method.')
+
+
+class YamlDialogueParser(AbstractDialogueParser):
+    """
+    L{AbstractDialogueParser} subclass responsible for parsing dialogues
+    serialized in YAML.
+    """
+    logger = logging.getLogger('dialogueparser.OldYamlDialogueParser')
+    
+    def load(self, stream, loader_class=yaml.Loader):
+        """
+        Parse a YAML stream and attempt to construct a new L{Dialogue}
+        instance.
+        
+        @param stream: stream containing the serialized YAML representation of
+            a L{Dialogue}.
+        @type stream: BufferType
+        @param loader_class: PyYAML loader class to use for reading the
+            serialization.
+        @type loader_class: yaml.BaseLoader subclass
+        """
+        loader = loader_class(stream)
+        try:
+            dialogue = \
+                self._constructDialogue(loader, loader.get_single_node())
+        except (AssertionError,) as error:
+            raise DialogueFormatError(str(error))
+        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) as e:
+            raise DialogueFormatError(e)
+        
+        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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dialogueprocessor.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,378 @@
+#   This file is part of PARPG.
+#
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+"""
+Provides the core interface to the dialogue subsystem used to process player
+L{Dialogues<Dialogue>} with NPCs.
+"""
+import logging
+
+from parpg.common.utils import dedent_chomp
+
+if (__debug__):
+    from collections import Sequence, MutableMapping
+    from parpg.dialogue import Dialogue
+
+logger = logging.getLogger('dialogueprocessor')
+
+class DialogueProcessor(object):
+    """
+    Primary interface to the dialogue subsystem used to initiate and process a
+    L{Dialogue} with an NPC.
+    
+    To begin a dialogue with an NPC a L{DialogueProcessor} must first be
+    instantiated with the dialogue data to process and a dictionary of Python
+    objects defining the game state for testing of response conditionals. The
+    L{initiateDialogue} must be called to initialized the L{DialogueProcessor},
+    and once it is initialized processing of
+    L{DialogueSections<DialogueSection>} and
+    L{DialogueResponses<DialogueResponse>} can be initiated via the
+    L{continueDialogue} and L{reply} class methods.
+    
+    The state of dialogue processing is stored via the
+    L{dialogue_section_stack} class attribute, which stores a list of
+    L{DialogueSections<DialogueSection>} that have been or are currently being
+    processed. Each time L{reply} is called with a L{DialogueResponse} its
+    next_section_id attribute is used to select a new L{DialogueSection} from
+    the L{dialogue}. The selected L{DialogueSection} is then pushed
+    onto the end of the L{dialogue_section_stack}, ready to be processed via
+    L{continueDialogue}. The exception to this rule occurs when L{reply} is
+    called with a L{DialogueResponse} whose next_section_id attribute is "end"
+    or "back". "end" terminates the dialogue as described below, while "back"
+    removes the last L{DialogueSection} on the L{dialogue_section_stack}
+    effectively going back to the previous section of dialogue.
+    
+    The L{DialogueProcessor} terminates dialogue processing once L{reply} is
+    called with a L{DialogueResponse} whose next_section_id == 'end'.
+    Processing can also be manually terminated by calling the L{endDialogue}
+    class method.
+    
+    @note: See the dialogue_demo.py script for a complete example of how the
+        L{DialogueProcessor} can be used.
+    
+    @ivar dialogue: dialogue data currently being processed.
+    @type dialogue: L{Dialogue}
+    @ivar dialogue_section_stack: sections of dialogue that have been or are
+        currently being processed.
+    @type dialogue_section_stack: list of L{DialogueSections<DialogueSection>}
+    @ivar game_state: objects defining the game state that should be made
+        available for testing L{DialogueResponse} conditionals.
+    @type game_state: dict of Python objects
+    @ivar in_dialogue: whether a dialogue has been initiated.
+    @type in_dialogue: Bool
+    
+    Usage:
+    >>> game_state = {'pc': player_character, 'quest': quest_engine}
+    >>> dialogue_processor = DialogueProcessor(dialogue, game_state)
+    >>> dialogue_processor.initiateDialogue()
+    >>> while dialogue_processor.in_dialogue:
+    ...     valid_responses = dialogue_processor.continueDialogue()
+    ...     response = choose_response(valid_responses)
+    ...     dialogue_processor.reply(response)
+    """
+    _logger = logging.getLogger('dialogueengine.DialogueProcessor')
+    
+    def dialogue():
+        def fget(self):
+            return self._dialogue
+        
+        def fset(self, dialogue):
+            assert isinstance(dialogue, Dialogue), \
+                '{0} does not implement Dialogue interface'.format(dialogue)
+            self._dialogue = dialogue
+        
+        return locals()
+    dialogue = property(**dialogue())
+    
+    def dialogue_section_stack():
+        def fget(self):
+            return self._dialogue_section_stack
+        
+        def fset(self, new_value):
+            assert isinstance(new_value, Sequence) and not \
+                   isinstance(new_value, basestring), \
+                   'dialogue_section_stack must be a Sequence, not {0}'\
+                   .format(new_value)
+            self._dialogue_section_stack = new_value
+        
+        return locals()
+    dialogue_section_stack = property(**dialogue_section_stack())
+    
+    def game_state():
+        def fget(self):
+            return self._game_state
+        
+        def fset(self, new_value):
+            assert isinstance(new_value, MutableMapping),\
+                   'game_state must be a MutableMapping, not {0}'\
+                   .format(new_value)
+            self._game_state = new_value
+        
+        return locals()
+    game_state = property(**game_state())
+    
+    def in_dialogue():
+        def fget(self):
+            return self._in_dialogue
+        
+        def fset(self, value):
+            assert isinstance(value, bool), '{0} is not a bool'.format(value)
+            self._in_dialogue = value
+        
+        return locals()
+    in_dialogue = property(**in_dialogue())
+    
+    def __init__(self, dialogue, game_state):
+        """
+        Initialize a new L{DialogueProcessor} instance.
+        
+        @param dialogue: dialogue data to process.
+        @type dialogue: L{Dialogue}
+        @param game_state: objects defining the game state that should be made
+            available for testing L{DialogueResponse} conditions.
+        @type game_state: dict of objects
+        """
+        self._dialogue_section_stack = []
+        self._dialogue = dialogue
+        self._game_state = game_state
+        self._in_dialogue = False
+    
+    def getDialogueGreeting(self):
+        """
+        Evaluate the L{RootDialogueSections<RootDialogueSection>} conditions
+        and return the valid L{DialogueSection} which should be displayed
+        first.
+        
+        @return: Valid root dialogue section.
+        @rtype: L{DialogueSection}
+        
+        @raise: RuntimeError - evaluation of a DialogueGreeting condition fails
+            by raising an exception (e.g. due to a syntax error).
+        """
+        dialogue = self.dialogue
+        dialogue_greeting = None
+        for greeting in dialogue.greetings:
+            try:
+                condition_met = eval(greeting.condition, self.game_state)
+            except Exception as exception:
+                error_message = dedent_chomp('''
+                    exception raised in DialogueGreeting {id} condition:
+                    {exception}
+                ''').format(id=greeting.id, exception=exception)
+                self._logger.error(error_message)
+            if (condition_met):
+                dialogue_greeting = greeting
+        if (dialogue_greeting is None):
+            dialogue_greeting = dialogue.default_greeting
+        
+        return dialogue_greeting
+    
+    def initiateDialogue(self):
+        """
+        Prepare the L{DialogueProcessor} to process the L{Dialogue} by pushing
+        the starting L{DialogueSection} onto the L{dialogue_section_stack}.
+        
+        @raise RuntimeError: Unable to determine the root L{DialogueSection}
+            defined by the L{Dialogue}.
+        """
+        if (self.in_dialogue):
+            self.endDialogue()
+        dialogue_greeting = self.getDialogueGreeting()
+        self.dialogue_section_stack.append(dialogue_greeting)
+        self.in_dialogue = True
+        self._logger.info('initiated dialogue {0}'.format(self.dialogue))
+    
+    def continueDialogue(self):
+        """
+        Process the L{DialogueSection} at the top of the
+        L{dialogue_section_stack}, run any L{DialogueActions<DialogueActions>}
+        it contains and return a list of valid
+        L{DialogueResponses<DialogueResponses> after evaluating any response
+        conditionals.
+        
+        @returns: valid responses.
+        @rtype: list of L{DialogueResponses<DialogueResponse>}
+        
+        @raise RuntimeError: Any preconditions are not met.
+        
+        @precondition: dialogue has been initiated via L{initiateDialogue}.
+        """
+        if (not self.in_dialogue):
+            error_message = dedent_chomp('''
+                dialogue has not be initiated via initiateDialogue yet
+            ''')
+            raise RuntimeError(error_message)
+        current_dialogue_section = self.getCurrentDialogueSection()
+        self.runDialogueActions(current_dialogue_section)
+        valid_responses = self.getValidResponses(current_dialogue_section)
+        
+        return valid_responses
+    
+    def getCurrentDialogueSection(self):
+        """
+        Return the L{DialogueSection} at the top of the
+        L{dialogue_section_stack}.
+        
+        @returns: section of dialogue currently being processed.
+        @rtype: L{DialogueSection}
+        
+        @raise RuntimeError: Any preconditions are not met.
+        
+        @precondition: dialogue has been initiated via L{initiateDialogue} and
+            L{dialogue_section_stack} contains at least one L{DialogueSection}.
+        """
+        if (not self.in_dialogue):
+            error_message = dedent_chomp('''
+                getCurrentDialogueSection called but the dialogue has not been
+                initiated yet
+            ''')
+            raise RuntimeError(error_message)
+        try:
+            current_dialogue_section = self.dialogue_section_stack[-1]
+        except IndexError:
+            error_message = dedent_chomp('''
+                getCurrentDialogueSection called but no DialogueSections are in
+                the stack
+            ''')
+            raise RuntimeError(error_message)
+        
+        return current_dialogue_section
+    
+    def runDialogueActions(self, dialogue_node):
+        """
+        Execute all L{DialogueActions<DialogueActions>} contained by a
+        L{DialogueSection} or L{DialogueResponse}.
+        
+        @param dialogue_node: section of dialogue or response containing the
+            L{DialogueActions<DialogueAction>} to execute.
+        @type dialogue_node: L{DialogueNode}
+        """
+        self._logger.info('processing commands for {0}'.format(dialogue_node))
+        for command in dialogue_node.actions:
+            try:
+                command(self.game_state)
+            except (Exception,) as error:
+                self._logger.error('failed to execute DialogueAction {0}: {1}'
+                                   .format(command.keyword, error))
+                # TODO Technomage 2010-11-18: Undo previous actions when an
+                #     action fails to execute.
+            else:
+                self._logger.debug('ran {0} with arguments {1}'
+                                   .format(getattr(type(command), '__name__'),
+                                           command.arguments))
+    
+    def getValidResponses(self, dialogue_section):
+        """
+        Evaluate all L{DialogueResponse} conditions for a L{DialogueSection}
+        and return a list of valid responses.
+        
+        @param dialogue_section: section of dialogue containing the
+            L{DialogueResponses<DialogueResponse>} to process.
+        @type dialogue_section: L{DialogueSection}
+        
+        @return: responses whose conditions were met.
+        @rtype: list of L{DialogueResponses<DialogueResponse>}
+        """
+        valid_responses = []
+        for dialogue_response in dialogue_section.responses:
+            condition = dialogue_response.condition
+            try:
+                condition_met = condition is None or \
+                                eval(condition, self.game_state)
+            except (Exception,) as exception:
+                error_message = dedent_chomp('''
+                    evaluation of condition {condition} for {response} failed
+                    with error: {exception}
+                ''').format(condition=dialogue_response.condition,
+                            response=dialogue_response, exception=exception)
+                self._logger.error(error_message)
+            else:
+                self._logger.debug(
+                    'condition "{0}" for {1} evaluated to {2}'
+                    .format(dialogue_response.condition, dialogue_response,
+                            condition_met)
+                )
+                if (condition_met):
+                    valid_responses.append(dialogue_response)
+        
+        return valid_responses
+    
+    def reply(self, dialogue_response):
+        """
+        Reply with a L{DialogueResponse}, execute the
+        L{DialogueActions<DialogueAction>} it contains and push the next
+        L{DialogueSection} onto the L{dialogue_section_stack}.
+        
+        @param dialogue_response: response to reply with.
+        @type dialogue_response: L{DialogueReponse}
+        
+        @raise RuntimeError: Any precondition is not met.
+        
+        @precondition: L{initiateDialogue} must be called before this method
+            is used.
+        """
+        if (not self.in_dialogue):
+            error_message = dedent_chomp('''
+                reply cannot be called until the dialogue has been initiated
+                via initiateDialogue
+            ''')
+            raise RuntimeError(error_message)
+        self._logger.info('replied with {0}'.format(dialogue_response))
+        # FIXME: Technomage 2010-12-11: What happens if runDialogueActions
+        #     raises an error?
+        self.runDialogueActions(dialogue_response)
+        next_section_id = dialogue_response.next_section_id
+        if (next_section_id == 'back'):
+            if (len(self.dialogue_section_stack) == 1):
+                error_message = dedent_chomp('''
+                    attempted to run goto: back action but stack does not
+                    contain a previous DialogueSection
+                ''')
+                raise RuntimeError(error_message)
+            else:
+                try:
+                    self.dialogue_section_stack.pop()
+                except (IndexError,):
+                    error_message = dedent_chomp('''
+                        attempted to run goto: back action but the stack was
+                        empty
+                    ''')
+                    raise RuntimeError(error_message)
+                else:
+                    self._logger.debug(
+                        'ran goto: back action, restored last DialogueSection'
+                    )
+        elif (next_section_id == 'end'):
+            self.endDialogue()
+            self._logger.debug('ran goto: end action, ended dialogue')
+        else:
+            try:
+                next_dialogue_section = \
+                    self.dialogue.sections[next_section_id]
+            except KeyError:
+                error_message = dedent_chomp('''
+                    {0} is not a recognized goto: action or DialogueSection
+                    identifier
+                ''').format(next_section_id)
+                raise RuntimeError(error_message)
+            else:
+                self.dialogue_section_stack.append(next_dialogue_section)
+    
+    def endDialogue(self):
+        """
+        End the current dialogue and clean up any resources in use by the
+        L{DialogueProcessor}.
+        """
+        self.dialogue_section_stack = []
+        self.in_dialogue = False
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/font.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,54 @@
+import os
+
+from fife.extensions.pychan.fonts import Font
+from fife.extensions.pychan.internal import get_manager
+
+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 = os.path.join(settings.system_path,
+                                   settings.fife.FontsPath,
+                                   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 = get_manager().hook.engine.getGuiManager()
+            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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gamemap.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,167 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+from fife import fife
+
+from fife.extensions.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
+
+        # 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.model.settings.fife.ScreenWidth
+            height = self.model.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
+                                                                  ])
+        text = self.engine.getGuiManager().\
+                        createFont('fonts/rpgfont.png', 0, \
+                                   self.model.settings.fife.FontGlyphs)
+        rend.changeDefaultFont(text)
+        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
+        self.model.game_state.player_character.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_agent = self.model.game_state.player_character.behaviour.agent
+        camera.setLocation(player_agent.getLocation())
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gamemodel.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,775 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+# there should be NO references to FIFE here!
+import sys
+import os.path
+import logging
+from copy import deepcopy
+
+from fife import fife
+from fife.extensions.serializers.xmlobject import XMLObjectLoader 
+
+from gamestate import GameState
+from objects import createObject
+from objects.composed import CarryableItem, CarryableContainer
+from gamemap import GameMap
+from common.utils import locateFiles
+from common.utils import parseBool
+from inventory import Inventory
+from parpg.dialogueparsers import YamlDialogueParser, DialogueFormatError
+
+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
+    
+    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 = os.path.join(self.settings.system_path,
+                                        self.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.engine = engine
+        self.fife_model = engine.getModel()
+
+        # set values from settings
+        maps_file = os.path.join(self.settings.system_path, 
+                                 self.settings.parpg.MapsPath,
+                                 self.settings.parpg.MapsFile)
+        self.game_state.maps_file = maps_file
+        all_agents_file = os.path.join(self.settings.system_path,
+                                       self.settings.parpg.MapsPath,
+                                       self.settings.parpg.AllAgentsFile)
+        self.all_agents_file = all_agents_file
+        objects_dir = os.path.join(self.settings.system_path,
+                                    self.settings.parpg.ObjectsPath)
+        self.objects_directory = objects_dir
+        object_db_file = os.path.join(self.objects_directory,
+                                      self.settings.parpg.ObjectDatabaseFile)
+        self.object_db_file = object_db_file
+        dialogues_dir = os.path.join(self.settings.system_path, 
+                                     self.settings.parpg.DialoguesPath)
+        self.dialogues_directory = dialogues_dir
+        self.dialogues = {}
+        self.agent_import_files = {}
+        self.obj_loader = XMLObjectLoader(
+                                          self.engine.getImagePool(), 
+                                          self.engine.getAnimationPool(), 
+                                          self.engine.getModel(),
+                                          self.engine.getVFS() 
+                                          )
+
+    def checkAttributes(self, attributes):
+        """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
+        @return: The modified attributes""" 
+        if attributes.has_key("object_type"):
+            class_name = attributes.pop("object_type")
+        else:
+            class_name = attributes["type"]
+        if not attributes.has_key("type"):
+            attributes["type"] = class_name
+        if self.object_db.has_key(class_name):
+            db_attributes = deepcopy(self.object_db[class_name])
+            for key in db_attributes.keys():
+                if attributes.has_key(key):
+                    attributes[key] = attributes[key] or db_attributes[key]
+                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 createContainerItems(self, container_objs):
+        """Create the items of a container from a dictionary
+        @param container_objs: Dictionary containing the items
+        @type container_objs: dict"""
+        items = []
+        for container_obj in container_objs:
+            items.append(self.createContainerObject(container_obj))
+        
+        return items
+
+    def createContainerObject(self, attributes):
+        """Create an object that can be stored in 
+        an container and return it
+        @param attributes: Dictionary of all object attributes
+        @type attributes: Dictionary
+        @return: The created object """
+        # create the extra data
+        extra = {}
+        extra['controller'] = self
+        attributes = self.checkAttributes(attributes)
+        
+        info = {}
+        info.update(attributes)
+        info.update(extra)
+        ID = info.pop("id") if info.has_key("id") else info.pop("ID")
+        if not info.has_key("item_type"):
+            info["item_type"] = info["type"]
+        ID = self.createUniqueID(ID)
+        if info.has_key("attributes"):
+            attributes = info["attributes"]
+            if "Container" in attributes:
+                info["actions"]["Open"] = ""
+                if info.has_key("Items"):
+                    inventory_objs = info["Items"]
+                    info["items"] = self.createContainerItems(inventory_objs)
+                
+                new_item = CarryableContainer(ID = ID, **info) 
+            else:
+                new_item = CarryableItem(ID = ID, **info) 
+        else:
+            new_item = CarryableItem(ID = ID, **info) 
+        self.game_state.addObject(None, new_item)
+        return new_item
+      
+    def createInventoryObject(self, container, attributes):
+        """Create an inventory object and place it into a container
+           @type container: base.Container
+           @param container: Container where the item is on
+           @type attributes: Dictionary
+           @param attributes: Dictionary of all object attributes
+           @return: None"""
+        index = attributes.pop("index") if attributes.has_key("index") else None
+        slot = attributes.pop("slot") if attributes.has_key("slot") else None
+        obj = self.createContainerObject(attributes)        
+        #obj = createObject(attributes, extra)
+        if slot:
+            container.moveItemToSlot(obj, slot)
+        else:
+            container.placeItem(obj, index)
+    
+    def deleteObject(self, object_id):
+        """Removes an object from the game
+        @param object_id: ID of the object
+        @type object_id: str """
+        del self.agents["All"][object_id]
+        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"] = {}
+        for map_name in self.agents:
+            if map_name == self.ALL_AGENTS_KEY:
+                continue
+            agents_dict = {}
+            for agent in self.agents[map_name]:
+                agent_obj = self.game_state.getObjectById(agent, map_name)
+                agent_inst = self.game_state.maps[map_name].\
+                                    agent_layer.getInstance(agent)
+                agent_dict = self.agents[map_name][agent]
+                agent_dict.update(agent_obj.getStateForSaving())
+                agent_dict["Rotation"] = agent_inst.getRotation()
+                agents_dict[agent] = agent_dict
+            save_state["Agents"][map_name] = agents_dict
+        agents_dict = {}
+        for agent in self.agents["All"]:
+            map_name = self.agents["All"][agent]["Map"]
+            agent_dict = self.agents["All"][agent]
+            agent_obj = None
+            if agent == "PlayerCharacter":
+                agent_obj = self.game_state.player_character
+            else:
+                agent_obj = self.game_state.getObjectById(agent, map_name)
+            if agent_obj:
+                agent_inst = self.game_state.maps[map_name].\
+                                    agent_layer.getInstance(agent)
+                agent_dict.update(agent_obj.getStateForSaving())
+                agent_dict["Rotation"] = agent_inst.getRotation()
+                agent_dict["MapName"] = map_name
+            agents_dict[agent] = agent_dict
+        save_state["Agents"]["All"] = agents_dict
+        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)
+                
+        # Load the current map
+        if self.game_state.current_map_name:
+            self.loadMap(self.game_state.current_map_name)         
+        load_file.close()
+        
+
+        # Recreate all the behaviours. These can't be saved because FIFE
+        # objects cannot be pickled
+        
+        self.placeAgents()
+        self.placePC()
+      
+        # In most maps we'll create the PlayerCharacter Instance internally. 
+        # In these cases we need a target position
+         
+    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_data = file(self.game_state.maps_file)
+        self.map_files = yaml.load(maps_data)["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.has_key("ObjectModel"): 
+            object_model =  agent_values["ObjectModel"]
+        elif agent_values["ObjectType"] == "MapItem":
+            object_data = self.object_db[agent_values["ItemType"]]
+            object_model = object_data["gfx"] if object_data.has_key("gfx") \
+                        else "generic_item"
+        else:
+            object_model = self.object_db[agent_values["ObjectType"]]["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 = file(map_agents_file)
+        agents = yaml.load_all(agents_data)
+        for agent in agents:
+            if not agent == None:
+                self.addAgent(map_name, agent)  
+    
+    def readAllAgents(self):
+        """Read the agents of the all_agents_file and store them"""
+        agents_data = file(self.all_agents_file)
+        agents = yaml.load_all(agents_data)
+        for agent in agents:
+            if not agent == 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):
+        object_type = agent["ObjectType"]
+        object_id = agent["ObjectModel"] \
+                                if agent.has_key("ObjectModel") \
+                                else None
+        if object_id == None:
+            if object_type == "MapItem":
+                object_data = self.object_db[agent["ItemType"]]
+                object_id = object_data["gfx"] if object_data.has_key("gfx") \
+                            else "generic_item"
+            else:
+                object_id = self.object_db[object_type]["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(obj_id)))
+
+        x_pos = agent["Position"][0]
+        y_pos = agent["Position"][1]
+        z_pos = agent["Position"][2] if len(agent["Position"]) == 3 \
+                                        else -0.1 if object_type == "MapItem" \
+                                        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)
+            
+        inst_dict = {}
+        inst_dict["id"] = inst_id
+        inst_dict["type"] = object_type
+        inst_dict["xpos"] = x_pos
+        inst_dict["ypos"] = y_pos
+        inst_dict["gfx"] = object_id
+        inst_dict["is_open"] = parseBool(agent["Open"]) \
+                                if agent.has_key("Open") \
+                                else False
+        inst_dict["locked"] = parseBool(agent["Locked"]) \
+                                if agent.has_key("Locked") \
+                                else False
+        inst_dict["name"] = agent["ViewName"]
+        inst_dict["real_name"] = agent["RealName"] \
+                                    if agent.has_key("RealName") \
+                                    else agent["ViewName"]
+        inst_dict["text"] = agent["Text"] \
+                                    if agent.has_key("Text") \
+                                    else None
+        if self.dialogues.has_key(inst_id):
+            inst_dict["dialogue"] = self.dialogues[inst_id]
+        inst_dict["target_map_name"] = agent["TargetMap"] \
+                                        if agent.\
+                                            has_key("TargetMap") \
+                                        else None
+        inst_dict["target_x"] = agent["TargetPosition"][0] \
+                                    if agent.\
+                                        has_key("TargetPosition") \
+                                    else None
+        inst_dict["target_y"] = agent["TargetPosition"][1] \
+                                    if agent.\
+                                        has_key("TargetPosition") \
+                                    else None
+        if agent.has_key("Inventory"):
+            inventory = Inventory()
+            inventory_objs = agent["Inventory"]
+            for inventory_obj in inventory_objs:
+                self.createInventoryObject(inventory,
+                                           inventory_obj 
+                                           )
+            inst_dict["inventory"] = inventory
+
+        if agent.has_key("Items"):
+            container_objs = agent["Items"]
+            items = self.createContainerItems(container_objs)
+            inst_dict["items"] = items
+            
+        if agent.has_key("ItemType"):
+            if not agent.has_key("item"):
+                item_data = {}
+                item_data["type"] = agent["ItemType"]
+                item_data["ID"] = inst_id 
+                item_data = self.createContainerObject(item_data)
+            else:
+                item_data = agent["item"]
+            inst_dict["item"] = item_data
+            inst_dict["item_type"] = agent["ItemType"]
+
+        self.createMapObject(self.active_map.agent_layer, inst_dict)
+    
+    def placeAgents(self):
+        """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)
+
+    def placePC(self):
+        """Places the PlayerCharacter on the map"""
+        agent = self.agents[self.ALL_AGENTS_KEY]["PlayerCharacter"]
+        inst_id = "PlayerCharacter"
+        self.createAgent(agent, inst_id)
+        
+        # create the PlayerCharacter agent
+        self.active_map.addPC()
+        self.game_state.player_character.start()
+        if agent.has_key("PeopleKnown"):
+            self.game_state.player_character.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):
+        """Sets the active map that is to be rendered.
+           @type map_name: String
+           @param map_name: The name of the map to load
+           @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
+
+    def createMapObject (self, layer, attributes):
+        """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: None"""
+        # create the extra data
+        extra = {}
+        if layer is not None:
+            extra['agent_layer'] = layer
+        attributes = self.checkAttributes(attributes)
+        
+        obj = createObject(attributes, extra)
+        
+        if obj.trueAttr("PC"):
+            self.addPC(layer, obj)
+        else:
+            self.addObject(layer, 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
+        self.game_state.player_character = player_char
+        self.game_state.player_character.setup()        
+        self.game_state.player_character.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.ID, \
+                                            self.game_state.current_map_name) 
+        if ref is None:
+            # no, add it to the game state
+            self.game_state.addObject(self.game_state.current_map_name, obj)
+        else:
+            # yes, use the current game state data
+            obj.X = ref.X
+            obj.Y = ref.Y
+            obj.gfx = ref.gfx  
+             
+        if obj.trueAttr("NPC"):
+            # create the agent
+            obj.setup()
+            obj.behaviour.speed = self.settings.parpg.PCSpeed - 1
+            # 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.ID == 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"""
+        if(self.pc_run == 1):
+            self.game_state.player_character.run(position)
+        else:
+            self.game_state.player_character.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 = file(self.object_db_file, "r")
+        database = yaml.load_all(database_file)
+        for object_info in database:
+            self.object_db.update(object_info)
+
+    def getAgentImportFiles(self):
+        """Searches the agents directory for import files """
+        files = locateFiles("*.xml", self.objects_directory)
+        for xml_file in files:
+            xml_file = os.path.relpath(xml_file).replace("\\", "/")
+            try:
+                root = ElementTree.parse(xml_file).getroot()
+                if root.tag == "object":
+                    self.agent_import_files[root.attrib["id"]] = xml_file
+            except SyntaxError as error:
+                assert(isinstance(error, SyntaxError))
+                logging.critical("Error parsing file {0}: "
+                                       "{1}".format(xml_file, error.msg))
+                sys.exit(1)
+    
+    def getDialogues(self):
+        """Searches the dialogue directory for dialogues """
+        files = locateFiles("*.yaml", self.dialogues_directory)
+        dialogue_parser = YamlDialogueParser()
+        for dialogue_filepath in files:
+            dialogue_filepath = os.path.relpath(dialogue_filepath) \
+                                .replace("\\", "/")
+            # Note Technomage 2010-11-13: the new DialogueEngine uses its own
+            #     parser now, YamlDialogueParser.
+#            dialogues = yaml.load_all(file(dialogue_file, "r"))
+            with file(dialogue_filepath, 'r') as dialogue_file:
+                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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gamescenecontroller.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,461 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+"""This file contains the GameSceneController that handles input when the game
+   is exploring a scene"""
+
+
+from datetime import datetime
+import random
+import glob
+import os
+
+from fife import fife
+from fife import extensions
+
+from controllerbase import ControllerBase
+from parpg.gui.hud import Hud
+from parpg.gui import drag_drop_data as data_drag
+from objects.action import ChangeMapAction, ExamineAction, OpenBoxAction, \
+                           UnlockBoxAction, LockBoxAction, TalkAction, \
+                           PickUpAction, DropItemAction
+
+#For debugging/code analysis
+if False:
+    from gamesceneview import GameSceneView
+    from gamemodel import GameModel
+    from parpg import PARPGApplication
+
+import logging
+
+logger = logging.getLogger('gamescenecontroller')
+
+class GameSceneController(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)
+        #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_direction = [0, 0]
+        self.scroll_timer = extensions.fife_timer.Timer(100,
+                                          lambda: self.view.moveCamera \
+                                                   (self.scroll_direction))    
+        
+        #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_file = random.choice(glob.glob(os.path.join(
+                                                                  "music", 
+                                                                  "*.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
+            screenshot_directory = os.path.join(settings.user_path,
+                                           settings.parpg.ScreenshotsPath)
+            # 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() 
+    
+    def mouseReleased(self, evt):
+        """If a mouse button is released, fife calls this routine.
+           We want to wait until the button is released, because otherwise
+           pychan captures the release if a menu is opened.
+           @type evt: fife.event
+           @param evt: The event that fife caught
+           @return: None"""
+        self.view.hud.hideContextMenu()
+        scr_point = fife.ScreenPoint(evt.getX(), evt.getY())
+        if(evt.getButton() == fife.MouseEvent.LEFT):
+            if(data_drag.dragging):
+                coord = self.model.getCoords(scr_point)\
+                                    .getExactLayerCoordinates()
+                commands = ({"Command": "ResetMouseCursor"}, 
+                            {"Command": "StopDragging"})
+                self.model.game_state.player_character.approach([coord.x, 
+                                                                 coord.y],
+                                    DropItemAction(self, 
+                                                   data_drag.dragged_item, 
+                                                   commands))
+            else:
+                self.model.movePlayer(self.model.getCoords(scr_point))
+        elif(evt.getButton() == fife.MouseEvent.RIGHT):
+            # is there an object here?
+            tmp_active_map = self.model.active_map
+            instances = tmp_active_map.cameras[tmp_active_map.my_cam_id].\
+                            getMatchingInstances(scr_point,
+                                                 tmp_active_map.agent_layer)
+            info = None
+            for inst in instances:
+                # check to see if this is an active item
+                if(self.model.objectActive(inst.getId())):
+                    # yes, get the model
+                    info = self.getItemActions(inst.getId())
+                    break
+
+            # take the menu items returned by the engine or show a
+            # default menu if no items
+            data = info or \
+                [["Walk", "Walk here", self.view.onWalk, 
+                  self.model.getCoords(scr_point)]]
+            # show the menu
+            self.view.hud.showContextMenu(data, (scr_point.x, scr_point.y))
+    
+        
+    def updateMouse(self):
+        """Updates the mouse values"""
+        if self.paused:
+            return
+        cursor = self.engine.getCursor()
+        #this can be helpful for IDEs code analysis
+        if False:
+            assert(isinstance(cursor, fife.Cursor))
+        self.last_mousecoords = fife.ScreenPoint(cursor.getX(), 
+                                                 cursor.getY())        
+        self.view.highlightFrontObject(self.last_mousecoords)       
+        
+        #set the trigger area in pixles
+        pixle_edge = 20
+        
+        mouse_x = self.last_mousecoords.x
+        screen_width = self.model.engine.getSettings().getScreenWidth()
+        mouse_y = self.last_mousecoords.y
+        screen_height = self.model.engine.getSettings().getScreenHeight()
+        
+        image = None
+        settings = self.model.settings
+        
+        
+        #edge logic
+        self.scroll_direction = [0, 0]
+        if self.has_mouse_focus:
+            direction = self.scroll_direction
+            #up
+            if mouse_y <= pixle_edge: 
+                direction[0] += 1
+                direction[1] -= 1
+                image = os.path.join(settings.system_path,
+                                     settings.parpg.GuiPath,
+                                     settings.parpg.CursorPath,
+                                     settings.parpg.CursorUp)
+                
+            #right
+            if mouse_x >= screen_width - pixle_edge:
+                direction[0] += 1
+                direction[1] += 1
+                image = os.path.join(settings.system_path,
+                                     settings.parpg.GuiPath,
+                                     settings.parpg.CursorPath,
+                                     settings.parpg.CursorRight)
+                
+            #down
+            if mouse_y >= screen_height - pixle_edge:
+                direction[0] -= 1
+                direction[1] += 1
+                image = os.path.join(settings.system_path,
+                                     settings.parpg.GuiPath,
+                                     settings.parpg.CursorPath,
+                                     settings.parpg.CursorDown)
+                
+            #left
+            if mouse_x <= pixle_edge:
+                direction[0] -= 1
+                direction[1] -= 1
+                image = os.path.join(settings.system_path,
+                                     settings.parpg.GuiPath,
+                                     settings.parpg.CursorPath,
+                                     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:
+                player_char = self.model.game_state.player_character
+                self.model.game_state.player_character = None
+                pc_agent = self.model.agents[self.model.ALL_AGENTS_KEY]\
+                                                ["PlayerCharacter"]
+                pc_agent.update(player_char.getStateForSaving())
+                pc_agent["Map"] = self.model.target_map_name 
+                pc_agent["Position"] = self.model.target_position
+                pc_agent["Inventory"] = \
+                        player_char.inventory.serializeInventory()
+                player_agent = self.model.active_map.\
+                                    agent_layer.getInstance("PlayerCharacter")
+                self.model.active_map.agent_layer.deleteInstance(player_agent)
+            self.model.loadMap(self.model.target_map_name)
+            self.model.setActiveMap(self.model.target_map_name)          
+            self.model.readAgentsOfMap(self.model.target_map_name)
+            self.model.placeAgents()
+            self.model.placePC()
+            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 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.ID,
+                                            self.model.game_state.\
+                                                current_map_name)
+        self.model.game_state.player_character.approach([npc.getLocation().\
+                                     getLayerCoordinates().x,
+                                     npc.getLocation().\
+                                     getLayerCoordinates().y],
+                                    TalkAction(self, npc))
+
+    def getItemActions(self, obj_id):
+        """Given the objects ID, return the text strings and callbacks.
+           @type obj_id: string
+           @param obj_id: ID of object
+           @rtype: list
+           @return: List of text and callbacks"""
+        actions = []
+        # note: ALWAYS check NPC's first!
+        obj = self.model.game_state.\
+                        getObjectById(obj_id,
+                                      self.model.game_state.current_map_name)
+        
+        if obj is not None:
+            if obj.trueAttr("NPC"):
+                # keep it simple for now, None to be replaced by callbacks
+                actions.append(["Talk", "Talk", self.initTalk, obj])
+                actions.append(["Attack", "Attack", self.nullFunc, obj])
+            else:
+                actions.append(["Examine", "Examine",
+                                self.model.game_state.\
+                                player_character.approach, 
+                                [obj.X, obj.Y],
+                                ExamineAction(self, 
+                                              obj_id, obj.name, 
+                                              obj.text)])
+                # is it a Door?
+                if obj.trueAttr("door"):
+                    actions.append(["Change Map", "Change Map",
+                       self.model.game_state.player_character.approach, 
+                       [obj.X, obj.Y],
+                       ChangeMapAction(self, obj.target_map_name,
+                                       obj.target_pos)])
+                # is it a container?
+                if obj.trueAttr("container"):
+                    actions.append(["Open", "Open", 
+                                    self.model.game_state.\
+                                        player_character.approach,
+                                    [obj.X, obj.Y],
+                                    OpenBoxAction(self, obj)])
+                    actions.append(["Unlock", "Unlock", 
+                                    self.model.game_state.\
+                                        player_character.approach,
+                                    [obj.X, obj.Y],
+                                    UnlockBoxAction(self, obj)])
+                    actions.append(["Lock", "Lock", 
+                                    self.model.game_state.\
+                                        player_character.approach,
+                                    [obj.X, obj.Y],
+                                    LockBoxAction(self, obj)])
+                # can you pick it up?
+                if obj.trueAttr("carryable"):
+                    actions.append(["Pick Up", "Pick Up", 
+                                    self.model.game_state.\
+                                        player_character.approach,
+                                    [obj.X, obj.Y],
+                                    PickUpAction(self, obj)])
+
+        return actions
+    
+    def saveGame(self, *args, **kwargs):
+        """Saves the game state, delegates call to engine.Engine
+           @return: None"""
+        self.model.pause(False)
+        self.pause(False)
+        self.view.hud.enabled = True
+        self.model.save(*args, **kwargs)
+
+    def loadGame(self, *args, **kwargs):
+        """Loads the game state, delegates call to engine.Engine
+           @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()
+        self.view.hud.inventory = None
+
+        self.model.load(*args, **kwargs)
+        self.view.hud.initializeInventory()          
+
+    def quitGame(self):
+        """Quits the game
+           @return: None"""
+        self.application.listener.quitGame()
+    
+    def pause(self, paused):
+        """Pauses the controller"""
+        super(GameSceneController, self).pause(paused)
+        self.paused = paused
+        if paused:
+            self.scroll_timer.stop()
+            self.resetMouseCursor()
+    
+    def onCommand(self, command):
+        if(command.getCommandType() == fife.CMD_MOUSE_FOCUS_GAINED):
+            self.has_mouse_focus = True
+        elif(command.getCommandType() == fife.CMD_MOUSE_FOCUS_LOST):
+            self.has_mouse_focus = False
+      
+    def pump(self):
+        """Routine called during each frame. Our main loop is in ./run.py"""
+        # uncomment to instrument
+        # t0 = time.time()
+        if self.paused: 
+            return
+        self.updateMouse()
+        if self.model.active_map:
+            self.view.highlightFrontObject(self.last_mousecoords)
+            self.view.refreshTopLayerTransparencies()
+            if self.scroll_direction != [0, 0]:
+                self.scroll_timer.start()
+            else: 
+                self.scroll_timer.stop()
+                if not data_drag.dragging:
+                    self.resetMouseCursor()
+                
+        self.handleCommands()
+        # print "%05f" % (time.time()-t0,)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gamesceneview.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,159 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+from sounds import SoundEngine
+from viewbase import ViewBase
+from fife import fife
+
+class GameSceneView(ViewBase):
+    """GameSceneView is responsible for drawing the scene"""
+    def __init__(self, engine, model):
+        """Constructor for GameSceneView
+           @param engine: A fife.Engine instance
+           @type engine: fife.Engine
+           @param model: a script.GameModel instance
+           @type model: script.GameModel 
+           """
+        super(GameSceneView, self).__init__(engine, model)
+
+        # init the sound
+        self.sounds = SoundEngine(engine)
+
+        self.hud = None        
+
+        # The current highlighted object
+        self.highlight_obj = None
+     
+        # faded objects in top layer
+        self.faded_objects = set()
+
+    def displayObjectText(self, obj_id, text, time=1000):
+        """Display on screen the text of the object over the object.
+           @type obj_id: id of fife.instance
+           @param obj: id of object to draw over
+           @type text: String
+           @param text: text to display over object
+           @return: None"""
+        try:
+            if obj_id:
+                obj = self.model.active_map.agent_layer.getInstance(obj_id)
+            else:
+                obj = None
+        except RuntimeError as error:
+            if error.args[0].split(',')[0].strip() == "_[NotFound]_":
+                obj = None
+            else:
+                raise
+        if obj:
+            obj.say(str(text), time)
+
+    def onWalk(self, click):
+        """Callback sample for the context menu."""
+        self.hud.hideContainer()
+        self.model.game_state.player_character.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.player_character.\
+                                        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.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)
+            
+            
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gamestate.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,124 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+from parpg.quest_engine import QuestEngine
+
+class GameState(object):
+    """This class holds the current state of the game."""
+    def __init__(self, quests_dir = None):
+        self.player_character = None
+        self.quest_engine = QuestEngine(quests_dir)
+        self.quest_engine.readQuests()
+        self.objects = {}
+        self.object_ids = {}
+        self.current_map_name = None
+        self.maps = {}
+        
+        
+    def addObject(self, map_id, game_object):
+        """Adds an object to the objects and object_ids
+        dictionaries.
+        @param map_id: ID of the map the object is on. 
+        If the object is in a container this has to be None
+        @param object: object to be added
+        @type object: GameObject
+        @type map_id: str or None
+        """
+        object_id = game_object.ID
+        if not self.object_ids.has_key(object_id):
+            if 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
+        """
+        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)
+                del self.objects[map_id][object_id]
+            del self.object_ids[object_id]
+            
+            
+    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"""
+        if map_id in self.objects:
+            return [i for i in self.objects[map_id].values() \
+                                        if map_id in self.objects]
+        
+        return {}
+    
+    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()
+        return ret_dict
+
+    def restoreFromState(self, state):
+        """Restores the state"""
+        self.current_map_name = state["CurrentMap"]
+        self.quest_engine.readQuests()
+        self.quest_engine.restoreFromState(state["Quests"])
+        
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/__init__.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,10 @@
+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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/actionsbox.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,73 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Widget for displaying actions"""
+
+from fife.extensions import pychan
+from fife.extensions.pychan import ScrollArea
+from fife.extensions.pychan import VBox
+
+class ActionsBox(ScrollArea):
+    def __init__(self, **kwargs):
+        ScrollArea.__init__(self, **kwargs)
+        self.ContentBox = VBox(name = "ActionsContentBox", is_focusable=False)
+        self.addChild(self.ContentBox)
+    
+    def refresh(self):
+        """Refresh the actions box so that it displays the contents of
+        self.actions_text
+        @return: None"""
+        self.adaptLayout()
+        self.vertical_scroll_amount = self.getVerticalMaxScroll()
+
+    def addAction(self, action):
+        """Add an action to the actions box.
+        @type action: (unicode) string
+        @param action: The text that you want to display in the actions box
+        @return: None"""      
+      
+        if not type(action) is unicode:
+            action = unicode(action)
+        action_label = pychan.widgets.Label(text = action, wrap_text = True)
+        action_label.max_width = self.ContentBox.width
+        self.ContentBox.addChild(action_label)
+        self.refresh()
+    
+    def addDialog(self, name, text):
+        """Add a dialog text to the actions box. Prints first the name and then, indented to the right, the text.
+        @type name: (unicode) string
+        @param action: The name of the character that spoke
+        @type text:: (unicode) string
+        @param text: The text that was said
+        @return: None"""        
+        if not type(name) is unicode:
+            name = unicode(name)
+        if not type(text) is unicode:
+            text = unicode(text)
+        
+        
+        name_label = pychan.widgets.Label(text = name, wrap_text = True)
+        self.ContentBox.addChild(name_label)
+        text_box = pychan.widgets.HBox()
+        spacer = pychan.widgets.Label()
+        spacer.min_width = int(self.ContentBox.width * 0.05)
+        spacer.max_width = int(self.ContentBox.width * 0.05)
+        text_box.addChild(spacer)
+        text_label = pychan.widgets.Label(text = text, wrap_text = True)
+        text_label.max_width = int(self.ContentBox.width * 0.95)
+        text_box.addChild(text_label)
+        self.ContentBox.addChild(text_box)
+        self.refresh()
+        
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/charactercreationview.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,28 @@
+from fife.extensions import pychan
+from fife.extensions.pychan.widgets import Label, HBox
+
+from parpg.gui.spinner import IntSpinner
+
+class CharacterCreationView(object):
+    def __init__(self, xml_script_path='gui/character_creation.xml'):
+        self.gui = pychan.loadXML(xml_script_path)
+    
+    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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/containergui.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,139 @@
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+from fife.extensions.pychan.tools import callbackWithArguments as cbwa
+
+from parpg.gui.containergui_base import ContainerGUIBase
+from parpg.gui import drag_drop_data as data_drag
+from parpg.objects.base 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: 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 = self.container.getItemAt(index)
+            self.updateImage(widget) 
+               
+    def updateImage(self, button):
+        if (button.item == None):
+            image = self.empty_images[button.name]
+        else:
+            image = button.item.getInventoryThumbnail()
+        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
+            # set the mouse cursor to be the widget's image
+            self.controller.setMouseCursor(up_image.source, down_image.source)
+            data_drag.dragged_image = up_image.source
+            data_drag.dragging = True
+            data_drag.dragged_widget = drag_widget
+            data_drag.source_container = self.container
+
+            self.container.takeItem(drag_widget.item)
+            
+            # 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 = drop_widget.item
+    
+            if data_drag.dragging:
+                container = self.container
+                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
+                if replace_item != None:
+                    self.dragObject(obj)
+                container.placeItem(drag_item, drop_index)
+                
+            drop_widget.item = drag_item
+            self.updateImage(drop_widget)
+            #if there was no item the stop dragging and reset cursor
+            if replace_item == None:
+                data_drag.dragging = False
+                #reset the mouse cursor to the normal cursor
+                self.controller.resetMouseCursor()
+        except (Container.SlotBusy, Container.TooBig, Container.ItemSelf):
+            #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
+            self.empty_images[slot_name] = empty_image
+            widget = self.gui.findChild(name=slot_name)
+            widget.item = self.container.items.get(counter-1)
+            widget.index = counter-1
+            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()      
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/containergui_base.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,122 @@
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from fife import fife
+from fife.extensions import pychan
+
+from parpg.gui import drag_drop_data as data_drag
+from parpg.objects.action import ACTIONS
+from copy import deepcopy
+
+class ContainerGUIBase(object):
+    """
+    Base class for windows that show the content of a container
+    """
+
+
+    def __init__(self, controller, gui_file):
+        self.controller = controller
+        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 createMenuItems(self, item, actions):
+        """Creates context menu items for all classes based on ContainerGUI"""
+        menu_actions = []
+        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 == "Open":
+                    param_dict["container"] = item
+                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 and item.trueAttr("usable"):
+                actions = deepcopy(item.actions)
+                if not actions:
+                    return
+                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.objects.action.Action
+        """
+        action.execute()
+    
+    def updateImages(self):
+        pass
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/dialogs.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,32 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+import os
+
+from fife.extensions import pychan
+
+class RestartDialog(object):
+    def __init__(self, settings):
+        self.settings = settings
+        self.window = pychan.loadXML(os.path.join(self.settings.system_path,
+                                                  self.settings.parpg.GuiPath,
+                                                  'restart_dialog.xml'))
+        self.window.mapEvents({'closeButton': self.hide})
+
+    def hide(self):
+        self.window.hide()
+
+    def show(self):
+        self.window.show()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/dialoguegui.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,165 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+import logging
+
+from fife import fife
+from fife.extensions import pychan
+from fife.extensions.pychan import widgets
+
+from parpg.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, player_character):
+        self.active = False
+        self.controller = controller
+        self.dialogue_gui = pychan.loadXML("gui/dialogue.xml")
+        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
+    
+    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.name)
+        self.setAvatarImage(self.npc.dialogue.avatar_path)
+        
+        game_state = {'npc': self.npc, 'pc': self.player_character,
+                      'quest': self.quest_engine}
+        try:
+            self.dialogue_processor = DialogueProcessor(self.npc.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.behaviour.state = 1
+        self.npc.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)
+            )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/drag_drop_data.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,25 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+This contains the data that tells the GUI whether something is being dragged, dropped etc.
+It is in one place to allow communication between multiple windows
+"""
+dragging = False
+dragged_image = None
+dragged_type = None
+dragged_item = None
+dragged_widget = None
+source_container = None
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/filebrowser.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,159 @@
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from fife.extensions import pychan
+from fife.extensions.pychan import widgets
+
+import sys
+import os
+import logging
+
+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
+        print self.settings.parpg.SavesPath
+        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
+        self.path = os.path.join(self.settings.user_path,
+                                 self.settings.parpg.SavesPath)
+        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
+        self._widget = pychan.loadXML(self.gui_xml_path)
+        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()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/hud.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,505 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import logging
+
+from fife.extensions import pychan
+from fife.extensions.pychan.tools import callbackWithArguments as cbwa
+
+from parpg.gui.filebrowser import FileBrowser
+from parpg.gui.menus import ContextMenu, SettingsMenu
+from parpg.gui import inventorygui
+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 actionsbox import ActionsBox
+
+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
+        pychan.registerWidget(ActionsBox)
+        self.hud = pychan.loadXML("gui/hud.xml")
+        self.controller = controller
+        self.engine = controller.engine
+        self.model = controller.model
+        self.settings = settings
+        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:
+            self.inventory = inventorygui.InventoryGUI(self.controller,
+                                                       None,
+                                                       None)
+#        inv_callbacks = {
+#            'refreshReadyImages': self.refreshReadyImages,
+#            'toggleInventoryButton': self.toggleInventoryButton,
+#        }
+#        self.inventory_storage = \
+#            self.model.game_state.player_character.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:
+            self.character_screen = pychan.loadXML('gui/character_screen.xml')
+        
+    
+    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"""
+        self.main_menu = pychan.loadXML("gui/hud_pause_menu.xml")
+        #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"""
+        self.help_dialog = pychan.loadXML("gui/help.xml")
+        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 = os.path.join(self.settings.system_path,
+                                    self.settings.parpg.GuiPath,
+                                    '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:
+            data_drag.source_container.placeItem(data_drag.dragged_item)
+            data_drag.dragging = False
+            data_drag.dragged_item = None
+        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 = os.path.join(self.settings.system_path,
+                                    self.settings.parpg.GuiPath,
+                                    '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 == None:
+            self.initializeInventory()
+        self.inventory.toggleInventory(toggle_image)
+    
+    def toggleCharacterScreen(self):
+        if not self.character_screen:
+            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()
+            #TODO: Move the close() call into OpenBoxAction(). This can be done 
+            # after createBoxGUI becomes a blocking call or it's otherwise
+            # possible to wait till the box GUI is closed.
+            if self.box_container.container.trueAttr("openable"):
+                self.box_container.container.close()
+            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.player_character)
+        return dialogue
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/inventorygui.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,365 @@
+#!/usr/bin/env python
+
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+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.objects.base import Container
+from parpg.gui.containergui_base import ContainerGUIBase
+from parpg.objects.action import ACTIONS
+
+import logging
+
+logger = logging.getLogger('action')
+
+class EquipmentSlot(pychan.VBox):
+    ATTRIBUTES = pychan.VBox.ATTRIBUTES + [UnicodeAttr('label_text')]
+    
+    def _setLabelText(self, text):
+        label = self.findChild()
+        label.text = unicode(text)
+        label.resizeToContent()
+        self.margins = (
+            int((self.width - label.width) / 2.0),
+            int((self.height - label.height) / 2.0)
+        )
+    
+    def _getLabelText(self):
+        label = self.findChild()
+        return label.text
+    
+    label_text = property(fget=_getLabelText, fset=_setLabelText)
+    
+    def __init__(self, label_text=u'equipment', min_size=(50, 50),
+                 max_size=(50, 50), margins=None,
+                 background_image="gui/inv_images/inv_background.png",
+                 **kwargs):
+        pychan.VBox.__init__(self, min_size=min_size, max_size=max_size,
+                             **kwargs)
+        self.background_image = background_image
+        label = pychan.Label(text=unicode(label_text))
+        self.addChild(label)
+        self.label_text = label_text
+        self.adaptLayout()
+        if self.parent is not None:
+            self.beforeShow()
+
+
+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 range(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 range(n_columns):
+                slot = pychan.Icon(min_size=(50, 50), max_size=(50, 50))
+                slot.border_size = 1
+                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 __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 InventoryGUI(ContainerGUIBase):
+    def __init__(self, controller, inventory, callbacks):
+        super(InventoryGUI, self).__init__(controller, "gui/inventory.xml")
+        self.engine = controller.engine
+        self.inventory_shown = False
+        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)
+    
+    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 showInventory(self):
+        self.gui.show()
+    
+    def closeInventory(self):
+        self.gui.hide()
+
+
+class _InventoryGUI(ContainerGUIBase):
+    """Inventory GUI class"""
+    def __init__(self, controller, inventory, callbacks):
+        """Initialise the instance.
+           @param controller: Current Controller
+           @type controller: Class derived from ControllerBase
+           @type inventory: Inventory
+           @param inventory: An inventory object to be displayed and manipulated
+           @type callbacks: dict
+           @param callbacks: a dict of callbacks
+               refreshReadyImages:
+                   Function that will make the ready slots on the HUD
+                   reflect those within the inventory
+               toggleInventoryButton:
+                   Function that will toggle the state of the inventory button
+           @return: None"""
+        super(InventoryGUI, self).__init__(controller, "gui/inventory.xml")
+        self.engine = controller.engine
+        self.readyCallback = callbacks['refreshReadyImages']
+        self.toggleInventoryButtonCallback = callbacks['toggleInventoryButton']
+        self.original_cursor_id = self.engine.getCursor().getId()
+
+        self.inventory_shown = False
+        events_to_map = {}
+        self.inventory_storage = inventory
+        
+        # Buttons of inventory arranged by slots
+
+        self.slot_buttons = {'head': ('Head',), 'chest': ('Body',),
+                             'left_arm': ('LeftHand',),
+                             'right_arm': ('RightHand',),
+                             'hips' : ('Belt',), 'left_leg': ('LeftFoot',),
+                             'right_leg': ('RightFoot',),
+                             'left_hand': ('LeftHeld',),
+                             'right_hand': ('RightHeld',),
+                             'backpack': ('A1', 'A2', 'A3', 'A4', 'A5',
+                                          'B1', 'B2', 'B3', 'B4', 'B5',
+                                          'C1', 'C2', 'C3', 'C4', 'C5',
+                                          'D1', 'D2', 'D3', 'D4', 'D5'),
+                             'ready': ('Ready1', 'Ready2', 'Ready3', 'Ready4')
+        }
+        # the images that should be used for the buttons when they are "empty"
+        self.slot_empty_images = {'head':'gui/inv_images/inv_head.png',
+                                  'chest':'gui/inv_images/inv_torso.png',
+                                  'left_arm':'gui/inv_images/inv_lhand.png',
+                                  'right_arm':'gui/inv_images/inv_rhand.png',
+                                  'hips':'gui/inv_images/inv_belt.png',
+                                  'left_leg':'gui/inv_images/inv_lfoot.png',
+                                  'right_leg':'gui/inv_images/inv_rfoot.png',
+                                  'left_hand':'gui/inv_images/inv_litem.png',
+                                  'right_hand':'gui/inv_images/inv_ritem.png',
+                                  'backpack':'gui/inv_images/inv_backpack.png',
+                                  'ready':'gui/inv_images/inv_belt_pouches.png',
+                                  }
+        self.updateInventoryButtons()
+
+        for slot in self.slot_buttons:
+            for _, button in enumerate(self.slot_buttons[slot]):
+                events_to_map[button] = cbwa(self.dragDrop, button)
+                events_to_map[button + "/mouseReleased"] = \
+                                                self.showContextMenu
+        events_to_map['close_button'] = self.closeInventoryAndToggle
+        self.gui.mapEvents(events_to_map)
+        # TODO: Why the commented out code?
+        # self.resetMouseCursor()
+
+    def updateImages(self):
+        self.updateInventoryButtons()
+    
+    def updateInventoryButtons (self):
+        for slot in self.slot_buttons:
+            for index, button in enumerate(self.slot_buttons[slot]):
+                widget = self.gui.findChild(name=button)
+                widget.slot = slot
+                widget.index = index
+                widget.item = self.inventory_storage.getItemsInSlot(widget.slot,
+                                                                   widget.index)
+                self.updateImage(widget)
+                
+    def updateImage(self, button):
+        if (button.item == None):
+            image = self.slot_empty_images[button.slot]
+        else:
+            image = button.item.getInventoryThumbnail()
+        button.up_image = image
+        button.down_image = image
+        button.hover_image = image
+
+    def closeInventory(self):
+        """Close the inventory.
+           @return: None"""
+        self.gui.hide()
+
+    def closeInventoryAndToggle(self):
+        """Close the inventory screen.
+           @return: None"""
+        self.closeInventory()
+        self.toggleInventoryButtonCallback()
+        self.inventory_shown = False
+
+    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
+
+        if toggleImage:
+            self.toggleInventoryButtonCallback()
+
+    def showInventory(self):
+        """Show the inventory.
+           @return: None"""
+        self.updateInventoryButtons()
+        self.gui.show()                
+                
+    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 inventory 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
+            # set the mouse cursor to be the widget's image
+            self.controller.setMouseCursor(up_image.source,down_image.source)
+            data_drag.dragged_image = up_image.source
+            data_drag.dragging = True
+            data_drag.dragged_widget = drag_widget
+            data_drag.source_container = self.inventory_storage
+            
+            self.inventory_storage.takeItem(drag_widget.item)
+            # 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"""
+        drop_widget = self.gui.findChild(name = obj)
+        drop_slot, drop_index = drop_widget.slot, drop_widget.index
+        replace_item = None
+        try :
+            if data_drag.dragging:
+                inventory = self.inventory_storage
+                drag_item = data_drag.dragged_item
+                #this will get the replacement item and data for drag_drop if
+                ## there is an item All ready occupying the slot
+                if not inventory.isSlotEmpty(drop_slot, drop_index):
+                    #get the item and then remove it from the inventory
+                    replace_item = inventory.getItemsInSlot \
+                                                (drop_slot, drop_index)
+                    self.dragObject(obj)
+                self.inventory_storage.moveItemToSlot(drag_item,
+                                                      drop_slot,
+                                                      drop_index)
+                    
+            if drop_widget.slot == 'ready':
+                self.readyCallback()
+            
+            if replace_item == None:
+                self.controller.resetMouseCursor()
+                data_drag.dragging = False
+        except Container.TooBig :
+            logger.warning("%s too big to fit "
+                                 "into %s" % (data_drag.dragged_item,
+                                              drop_widget.slot))
+        except (Container.SlotBusy, Container.ItemSelf):
+            pass
+        self.updateInventoryButtons()
+              
+    def createMenuItems(self, item, actions):
+        """Creates context menu items for the InventoryGUI"""
+        menu_actions = super(InventoryGUI, self).createMenuItems(item, actions)
+        param_dict = {}
+        param_dict["controller"] = self.controller
+        param_dict["commands"] = {}
+        param_dict["item"] = item
+        param_dict["container_gui"] = self
+        menu_actions.append(["Drop",
+                             "Drop", 
+                             self.executeMenuItem, 
+                             ACTIONS["DropFromInventory"](**param_dict)])        
+        return menu_actions
+    
+    def getImage(self, name):
+        """Return a current image from the inventory
+           @type name: string
+           @param name: name of image to get
+           @return: None"""
+        return self.gui.findChild(name = name)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/menus.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,156 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+import os
+import logging
+
+from fife.extensions import pychan
+from 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.window = pychan.loadXML(os.path.join(self.settings.system_path,
+                                                 self.settings.parpg.GuiPath,
+                                                 'settings_menu.xml'))
+        self.restart_dialog = RestartDialog(self.settings)
+        self.window.mapEvents({'okButton': self.save,
+                               'cancelButton': self.hide,
+                               'defaultButton': self.reset})
+        self.initializeWidgets()
+        self.fillWidgets()
+
+    def initializeWidgets(self):
+        initial_data = {'screen_resolution': self.resolutions,
+                        'render_backend': self.render_backends,
+                        'lighting_model': self.lighting_models}
+
+        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 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) = self.window.collectData('screen_resolution',
+                                                      'render_backend',
+                                                      'lighting_model', 
+                                                      'enable_fullscreen',
+                                                      'enable_sound')
+
+        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.write()
+       
+        self.restart_dialog.show()
+        self.hide()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/popups.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,60 @@
+#/usr/bin/python
+
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+from fife.extensions import pychan
+
+class ExaminePopup():
+    """Create a popup for when you click examine on an object"""
+    def __init__(self, engine, object_title, desc):
+        """Initialize the popup  
+           @type engine: fife.Engine
+           @param engine: an instance of the fife engine
+           @type object_title: string
+           @param object_title: The title for the window, probably should just
+                                be the name of the object
+           @type desc: string
+           @param desc: The description of the object
+           @return: None"""
+        self.engine = engine
+
+        self.examine_window = pychan.widgets.\
+                                Window(title=unicode(object_title),
+                                       position_technique="center:center",
+                                       min_size=(175,175))
+
+        self.scroll = pychan.widgets.ScrollArea(name='scroll', size=(150,150))
+        self.description = pychan.widgets.Label(name='descText',
+                                                text=unicode(desc),
+                                                wrap_text=True)
+        self.description.max_width = 170
+        self.scroll.addChild(self.description)
+        self.examine_window.addChild(self.scroll)
+        
+        self.close_button = pychan.widgets.Button(name='closeButton',
+                                                  text=unicode('Close'))
+        self.examine_window.addChild(self.close_button)
+
+        self.examine_window.mapEvents({'closeButton':self.examine_window.hide})
+
+    def closePopUp(self):
+        # TODO: missing function information
+        if self.examine_window.isVisible():
+            self.examine_window.hide()
+    
+    def showPopUp(self):
+        # TODO: missing function information
+        self.examine_window.show()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/spinners.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,234 @@
+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)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gui/tabwidget.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,105 @@
+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()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/inventory.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,177 @@
+# This file is part of PARPG.
+# 
+# PARPG is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+# 
+# PARPG is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+# 
+# You should have received a copy of the GNU General Public License
+# along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+from parpg.objects.base import Container
+from parpg.objects.composed import SingleItemContainer as Slot
+
+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
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,96 @@
+#!/usr/bin/env python2 
+#   This program is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+#TODO: Modularize this script
+import sys
+import logging
+
+from optparse import OptionParser
+
+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 system.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')
+parser.add_option('-m', '--module',
+                  help='location of the parpg module')
+opts, args = parser.parse_args()
+
+if not args:
+    parser.print_help()
+    sys.exit(1)
+            
+
+# initialize settings
+if opts.module:
+    print('added ' + opts.module)
+    sys.path.insert(0, opts.module)
+
+from parpg.settings import Settings
+
+settings = Settings(*args)
+
+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:
+    sys.path.insert(0, settings.parpg.FifePath) 
+except AttributeError:
+    logger.warning('[parpg] section has no FifePath option')
+
+try:
+    from fife import fife
+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)
+
+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()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mainmenucontroller.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,95 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/
+
+from controllerbase import ControllerBase
+from charactercreationview import CharacterCreationView
+from charactercreationcontroller import CharacterCreationController
+from gamescenecontroller import GameSceneController
+from gamesceneview import GameSceneView
+
+#For debugging/code analysis
+if False:
+    from parpg.mainmenuview import MainMenuView
+    from fife import fife
+    from gamemodel import GameModel
+    from parpg import PARPGApplication
+
+class MainMenuController(ControllerBase):
+    """Controller for handling the main menu state"""
+
+    def __init__(self, engine, view, model, application):
+        """Constructor"""
+        super(MainMenuController, self).__init__(engine, view, model, 
+                                                 application)
+    
+        #this can be helpful for IDEs code analysis
+        if False:
+            assert(isinstance(self.engine, fife.Engine))
+            assert(isinstance(self.view, MainMenuView))
+            assert(isinstance(self.model, GameModel))
+            assert(isinstance(self.application, PARPGApplication))
+            assert(isinstance(self.event_manager, fife.EventManager))
+        
+        self.view.quit_callback = self.quitGame
+        self.view.new_game_callback = self.newGame
+        self.view.initalizeMainMenu(self.newGame, self.loadGame, self.quitGame)
+        self.view.showMenu()
+        self.resetMouseCursor()
+    
+    def newGame(self):
+        """Start a new game and switch to the character creation controller."""
+        view = CharacterCreationView(self.engine, self.model,
+                                     self.model.settings)
+        controller = CharacterCreationController(self.engine, view, self.model,
+                                                 self.application)
+        self.application.view = view
+        self.application.switchController(controller)
+    
+#    def newGame(self):
+#        """Starts a new game"""
+#        view = GameSceneView(self.engine,
+#                             self.model)
+#        controller = GameSceneController(self.engine,
+#                                         view,
+#                                         self.model,
+#                                         self.application)        
+#        self.application.view = view
+#        self.application.switchController(controller)
+#        start_map = self.model.settings.get("PARPG", "Map")
+#        self.model.changeMap(start_map)
+
+    def loadGame(self, *args, **kwargs):
+        """Loads the game state
+           @return: None"""
+
+        view = GameSceneView(self.engine,
+                             self.model)
+        controller = GameSceneController(self.engine,
+                                         view,
+                                         self.model,
+                                         self.application)        
+        self.application.view = view
+        self.application.switchController(controller)
+        controller.loadGame(*args, **kwargs)
+        
+    def onStop(self):
+        """Called when the controller is removed from the list"""
+        self.view.hideMenu()
+                                         
+    
+    def quitGame(self):
+        """Quits the game
+           @return: None"""
+        self.application.listener.quitGame()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mainmenuview.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,151 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/
+
+import os
+
+from fife.extensions import pychan
+
+from viewbase import ViewBase
+from parpg.gui.filebrowser import FileBrowser
+from parpg.gui.menus import SettingsMenu
+
+class MainMenuView(ViewBase):
+    """View that is used to display the main menu"""
+
+    def __init__(self, engine, model):
+        """Constructor for MainMenuView
+           @param engine: A fife.Engine instance
+           @type engine: fife.Engine
+           @param model: a script.GameModel instance
+           @type model: script.GameModel 
+           """        
+        ViewBase.__init__(self, engine, model)
+        self.quit_window = None
+        self.new_game_callback = None
+        self.load_game_callback = None
+        self.quit_callback = None
+        self.main_menu = None
+        self.character_screen = None
+        self.gui_path = os.path.join(self.model.settings.system_path,
+                                     self.model.settings.parpg.GuiPath)
+        
+    def showMenu(self):
+        """"Shows the main menu"""
+        self.main_menu_background.show()
+        self.main_menu.show()
+        
+    def hideMenu(self):
+        """"Hides the main menu"""
+        self.main_menu.hide()
+        self.main_menu_background.hide()
+
+    def initalizeMainMenu(self, new_game, load_game, quit_game):
+        """Initialized the main menu and sets the callbacks"""
+        # Set a simple background to display the main screen.
+        self.main_menu_background = pychan.loadXML(os.path.join(self.gui_path,
+                                                   'main_menu_background.xml'))
+        
+        # Initialize the main menu screen.
+        screen_mode = self.engine.getRenderBackend().getCurrentScreenMode()
+        self.main_menu_background.width = screen_mode.getWidth()
+        self.main_menu_background.height = screen_mode.getHeight()
+        self.main_menu = pychan.loadXML(os.path.join(self.gui_path,
+                                                     'main_menu.xml'))
+
+        # Setup images for variables widgets 
+        self.main_menu.background_image = os.path.join(self.gui_path,
+                                                       'notebook',
+                                                       'notebook_background.png')
+        quit_button = self.main_menu.findChild(name='quitButton')
+        quit_button.up_image = os.path.join(self.gui_path, 'notebook', 'tabs',
+                                            'tab2_bg_dark_bottom.png')
+        quit_button.hover_image = os.path.join(self.gui_path, 'notebook',
+                                               'tabs',
+                                               'tab2_bg_normal_bottom.png')
+        quit_button.down_image = os.path.join(self.gui_path, 'notebook',
+                                              'tabs',
+                                              'tab2_bg_normal_bottom.png')
+
+        self.main_menu.adaptLayout()
+        self.new_game_callback = new_game
+        self.load_game_callback = load_game
+        self.quit_callback = quit_game
+        menu_events = {}
+        menu_events["newButton"] = self.newGame
+        menu_events["loadButton"] = self.loadGame
+        menu_events["settingsButton"] = self.displaySettings
+        menu_events["quitButton"] = self.quitGame
+        self.main_menu.mapEvents(menu_events)
+        
+        self.initializeQuitDialog()
+        self.initializeSettingsMenu()
+    
+    def newGame(self):
+        """Called when user request to start a new game.
+           @return: None"""
+        self.new_game_callback()
+    
+    def loadGame(self):
+        """ Called when the user wants to load a game.
+            @return: None"""
+        load_browser = FileBrowser(self.engine,
+                                   self.model.settings,
+                                   self.load_game_callback,
+                                   gui_xml_path=os.path.join(self.gui_path,
+                                                             'loadbrowser.xml'),
+                                   save_file=False,
+                                   extensions=('.dat'))
+        load_browser.showBrowser()
+    
+    def initializeQuitDialog(self):
+        """Creates the quit confirmation dialog
+           @return: None"""
+        
+        self.quit_window = pychan.widgets.Window(title=unicode("Quit?"), \
+                                                 min_size=(200,0))
+
+        hbox = pychan.widgets.HBox()
+        are_you_sure = "Are you sure you want to quit?"
+        label = pychan.widgets.Label(text=unicode(are_you_sure))
+        yes_button = pychan.widgets.Button(name="yes_button", 
+                                           text=unicode("Yes"),
+                                           min_size=(90,20),
+                                           max_size=(90,20))
+        no_button = pychan.widgets.Button(name="no_button",
+                                          text=unicode("No"),
+                                          min_size=(90,20),
+                                          max_size=(90,20))
+
+        self.quit_window.addChild(label)
+        hbox.addChild(yes_button)
+        hbox.addChild(no_button)
+        self.quit_window.addChild(hbox)
+
+        events_to_map = { "yes_button": self.quit_callback,
+                          "no_button":  self.quit_window.hide }
+        
+        self.quit_window.mapEvents(events_to_map)
+
+
+    def quitGame(self):
+        """Called when user requests to quit game.
+           @return: None"""
+        self.quit_window.show()
+
+    def initializeSettingsMenu(self):
+        self.settings_menu = SettingsMenu(self.engine, self.model.settings)
+
+    def displaySettings(self):
+        self.settings_menu.show()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/objects/__init__.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,54 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+import containers
+import doors
+import actors
+import items
+import sys
+
+OBJECT_MODULES = [containers, actors, doors, items]
+
+def getAllObjects ():
+    """Returns a dictionary with the names of the concrete game object classes
+       mapped to the classes themselves"""
+    result = {}
+    for module in OBJECT_MODULES:
+        for class_name in module.__all__:
+            result[class_name] = getattr (module, class_name)
+    return result
+
+def createObject(info, 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 type and ID, which every game_obj needs.
+    extra = extra or {}
+    try:
+        obj_type = info.pop('type')
+        ID = info.pop('id')
+    except KeyError:
+        sys.stderr.write("Error: Game object missing type or id.")
+        sys.exit(False)
+
+    # add the extra info
+    for key, val in extra.items():
+        info[key] = val
+
+    # this is for testing purposes
+    return getAllObjects()[obj_type](ID, **info)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/objects/action.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,548 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+#exceptions
+
+import logging
+
+logger = logging.getLogger('action')
+
+from parpg.gui import drag_drop_data as data_drag
+
+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
+    
+    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
+                
+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 a container"""
+    def __init__(self, controller, container, 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 container: A reference to the container
+        """
+        super(OpenAction, self).__init__(controller, commands)
+        self.view = controller.view
+        self.container = container
+    def execute(self):
+        """Open the box."""
+        self.view.hud.createBoxGUI(self.container.name, \
+                                              self.container)
+        super(OpenAction, self).execute()
+       
+       
+class OpenBoxAction(OpenAction):
+    """Open a box. Needs to be more generic, but will do for now."""
+    def __init__(self, controller, container, 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 container: A reference to the container
+        """
+        super(OpenBoxAction, self).__init__(controller, commands)
+        self.view = controller.view
+        self.container = container
+        
+    def execute(self):
+        """Open the box."""
+        try:
+            self.container.open()
+            super(OpenBoxAction, self).execute()
+
+        except ValueError:
+            self.view.hud.createExamineBox(self.container.name, \
+                                                  "The container is locked")
+        
+class UnlockBoxAction(Action):
+    """Unlocks a box. Needs to be more generic, but will do for now."""
+    def __init__(self, controller, container, commands = None):
+        """
+        @param controller: A reference to the GameSceneController.
+        @type controller: parpg.GameSceneController
+        @param commands: Special commands that are executed
+        @type commands: Dictionary 
+        @param container: A reference to the container
+        """
+        super(UnlockBoxAction, self).__init__(controller, commands)
+        self.container = container
+    
+    def execute(self):
+        """Open the box."""
+        self.container.unlock()
+        super(UnlockBoxAction, self).execute()
+        
+class LockBoxAction(Action):
+    """Locks a box. Needs to be more generic, but will do for now."""
+    def __init__(self, controller, container, commands = None):
+        """
+        @param controller: A reference to the GameSceneController.
+        @type controller: parpg.GameSceneController
+        @param commands: Special commands that are executed
+        @type commands: Dictionary 
+        @param container: A reference to the container
+        """
+        super(LockBoxAction, self).__init__(controller, commands)
+        self.container = container
+        
+    def execute(self):
+        """Lock the box."""
+        self.container.lock()
+        super(LockBoxAction, self).execute()
+
+
+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 
+        @type view: class derived from parpg.ViewBase
+        @param view: The view
+        
+        """
+        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)
+
+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)
+
+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"""
+        from parpg.dialoguecontroller import DialogueController
+        
+        player_char = self.model.game_state.player_character
+        npc_coordinates = self.npc.getLocation().getLayerCoordinates()
+        pc_coordinates = player_char.behaviour.agent.\
+                            getLocation().getLayerCoordinates()
+        
+        distance_squared = (npc_coordinates.x - pc_coordinates.x) *\
+                           (npc_coordinates.x - pc_coordinates.x) +\
+                           (npc_coordinates.y - pc_coordinates.y) *\
+                           (npc_coordinates.y - pc_coordinates.y)
+        
+        # If we are too far away, we approach the NPC again
+        if distance_squared > 2:
+            player_char.approach([self.npc.getLocation().
+                         getLayerCoordinates().x,
+                         self.npc.getLocation().
+                         getLayerCoordinates().y], 
+                        TalkAction(self.controller,
+                                   self.npc, self.commands))        
+        else:
+            player_char.behaviour.agent.act('stand', self.npc.getLocation())
+    
+            if self.npc.dialogue is not None:
+                dialogue_controller = DialogueController(self.controller.engine,
+                                                         self.view,
+                                                         self.model,
+                                                         self.controller.application)
+                self.controller.application.pushController(dialogue_controller)
+                dialogue_controller.startTalk(self.npc)
+            else:
+                self.npc.behaviour.agent.say("Leave me alone!", 1000)
+                
+            self.model.game_state.player_character.behaviour.idle()
+            self.model.game_state.player_character.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"]
+                container = self.item.in_container
+                inst_dict = {}
+                inst_dict["ID"] = object_id
+                inst_dict["object_type"] = object_type
+                new_item = self.model.createContainerObject(inst_dict)
+                container.replaceItem(self.item, new_item)
+                self.view.hud.inventory.updateInventoryButtons()
+        super(UseAction, self).execute()
+
+class PickUpAction(Action):
+    """Action for picking up items from a map"""
+
+    def __init__(self, controller, map_item, commands = None):
+        super(PickUpAction, self).__init__(controller, commands)
+        self.map_item = map_item
+        self.view = controller.view
+        
+    def execute(self):
+        real_item = self.map_item.item
+        self.model.deleteObject(self.map_item.ID)
+        self.model.game_state.player_character.\
+                                inventory.placeItem(real_item)
+        self.view.hud.inventory.updateInventoryButtons()
+        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
+        map_item_values = {}
+        map_item_values["ViewName"] = self.item.name
+        map_item_values["ObjectType"] = "MapItem"
+        map_item_values["ItemType"] = self.item.item_type
+        map_item_values["Map"] = map_name
+        coords = self.model.game_state.player_character.\
+                                        getLocation().getExactLayerCoordinates()
+        map_item_values["Position"] = (coords.x, coords.y)
+        map_item_values["Rotation"] = 0
+        map_item_values["item"] = self.item
+        agent = {}
+        agent[self.item.ID] = map_item_values
+        self.model.addAgent("All", agent)
+        self.model.placeAgents()
+        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()
+        self.item.in_container.takeItem(self.item)
+        self.container_gui.updateImages()
+        
+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
+        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.player_character
+        for item in self.pot.items.itervalues():
+            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.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 = player_character.hasItem("Wood")
+        if wood:
+            has_wood = True        
+        bottle = player_character.hasItem("Empty beer bottle")
+        if bottle:
+            has_bottle = True        
+        if has_water and has_fruit and has_wood and has_bottle:
+            self.pot.removeItem(water)
+            self.pot.removeItem(fruit)
+            if has_yeast:
+                self.pot.removeItem(yeast)
+            player_character.inventory.removeItem(wood)
+            inst_dict = {}
+            inst_dict["ID"] = "Beer"
+            inst_dict["object_type"] = "Beer"
+            new_item = self.model.createContainerObject(inst_dict)
+            player_character.inventory.placeItem(new_item)
+            self.view.hud.inventory.updateInventoryButtons()
+            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()
+
+ACTIONS = {"ChangeMap":ChangeMapAction, 
+           "Open":OpenAction,
+           "OpenBox":OpenBoxAction, 
+           "Unlock":UnlockBoxAction,
+           "Lock":LockBoxAction,
+           "ExamineItem":ExamineItemAction,
+           "Examine":ExamineAction,
+           "Look":ExamineItemAction,
+           "Read":ReadAction,
+           "Talk":TalkAction,
+           "Use":UseAction,
+           "PickUp":PickUpAction,
+           "DropFromInventory":DropItemFromContainerAction,
+           "BrewBeer":BrewBeerAction}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/objects/actors.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,414 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+from random import randrange
+
+
+from fife import fife
+
+from base import GameObject, Living, Scriptable, CharStats
+from composed import CarryableItem
+from parpg.inventory import Inventory
+
+"""All actors go here. Concrete classes only."""
+
+__all__ = ["PlayerCharacter", "NonPlayerCharacter", ]
+
+_AGENT_STATE_NONE, _AGENT_STATE_IDLE, _AGENT_STATE_APPROACH, _AGENT_STATE_RUN, _AGENT_STATE_WANDER, _AGENT_STATE_TALK = xrange(6)
+
+class ActorBehaviour (fife.InstanceActionListener):
+    """Fife agent listener"""
+    def __init__(self, layer):
+        fife.InstanceActionListener.__init__(self)
+        self.layer = layer
+        self.agent = None
+        self.state = None
+        self.speed = 0
+        self.idle_counter = 1
+    
+    def attachToLayer(self, agent_ID):
+        """Attaches to a certain layer
+           @type agent_ID: String
+           @param agent_ID: ID of the layer to attach to.
+           @return: None"""
+        self.agent = self.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.ID)
+        self.agent.addActionListener(self)
+        self.state = _AGENT_STATE_NONE
+        self.idle_counter = 1
+    
+    def idle(self):
+        """@return: None"""
+        self.state = _AGENT_STATE_IDLE
+        self.agent.act('stand', self.agent.getFacingLocation())
+
+    def onInstanceActionFinished(self, instance, action):
+        pass
+
+class PCBehaviour (ActorBehaviour):
+    def __init__(self, parent=None, layer=None):
+        super(PCBehaviour, self).__init__(layer)        
+        self.parent = parent
+        self.idle_counter = 1
+        self.speed = 0
+        self.nextAction = None
+        self.agent = None
+        
+    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()
+            
+        if(action.getId() != 'stand'):
+            self.idle_counter = 1
+        else:
+            self.idle_counter += 1            
+
+
+class NPCBehaviour(ActorBehaviour):
+    def __init__(self, Parent=None, Layer=None):
+        super(NPCBehaviour, self).__init__(Layer)
+        
+        self.parent = Parent
+        self.state = _AGENT_STATE_NONE
+        self.pc = None
+        self.target_loc = None
+        self.nextAction = 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.
+        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 == _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 (the NPC listener is listening for this
+                                        instance)
+           @type action: ???
+           @param action: ???
+           @return: None"""
+        if self.state == _AGENT_STATE_WANDER:
+            self.target_loc = self.getTargetLocation()
+        self.idle()
+        
+    
+    def idle(self):
+        """Controls the NPC when it is idling. Different actions
+           based on the NPC's state.
+           @return: None"""
+        if self.state == _AGENT_STATE_NONE:
+            self.state = _AGENT_STATE_IDLE
+            self.agent.act('stand', self.agent.getFacingLocation())
+        elif self.state == _AGENT_STATE_IDLE:
+            if self.wanderCounter > self.wanderRate:
+                self.wanderCounter = 0
+                self.state = _AGENT_STATE_WANDER
+            else:
+                self.wanderCounter += 1
+                self.state = _AGENT_STATE_NONE
+            
+            self.target_loc = self.getTargetLocation()
+            self.agent.act('stand', self.agent.getFacingLocation())
+        elif self.state == _AGENT_STATE_WANDER:
+            self.parent.wander(self.target_loc)
+            self.state = _AGENT_STATE_NONE
+        elif self.state == _AGENT_STATE_TALK:
+            self.agent.act('stand', self.pc.getLocation())
+            
+class CharacterBase(GameObject, CharStats, Living):
+    """Base class for Characters"""
+    def __init__(self, ID, agent_layer=None, inventory=None, text="",
+                 primary_stats=None, secondary_stats=None, **kwargs):
+        GameObject.__init__(self, ID, text=text, **kwargs)
+        CharStats.__init__(self, **kwargs)
+        Living.__init__(self, **kwargs)
+        self.statistics = {}
+        if primary_stats is not None:
+            for primary_stat in primary_stats:
+                name = primary_stat.name
+                self.statistics[name] = primary_stat
+        if secondary_stats is not None:
+            for secondary_stat in primary_stats:
+                long_name = secondary_stat.long_name
+                self.statistics[long_name] = secondary_stat
+                short_name = secondary_stat.short_name
+                self.statistics[short_name] = secondary_stat
+                secondary_stat.attach(self)
+        self.behaviour = None
+        if inventory == None:
+            self.inventory = Inventory()
+        else:
+            self.inventory = inventory
+        self.state = _AGENT_STATE_NONE
+        self.layer_id = agent_layer.getId()
+        self.createBehaviour(agent_layer)
+    
+    def createBehaviour(self, layer):
+        """Creates the behaviour for this actor.
+           @return: None"""
+        pass
+    
+    def setup(self):
+        """@return: None"""
+        self.behaviour.attachToLayer(self.ID)
+
+    def start(self):
+        """@return: None"""
+        self.behaviour.idle()
+
+    def teleport(self, location):
+        """Teleports a Character instantly to the given location.
+           @type location: fife.Location
+           @param location: Target coordinates for Character.
+           @return: None"""
+        self.state = _AGENT_STATE_IDLE
+        self.behaviour.nextAction = None 
+        self.behaviour.agent.setLocation(location)
+
+    def give (self, item, actor):
+        """Gives the specified item to the different actor. Raises an exception if the item was invalid or not found
+           @type item: Carryable
+           @param item: The item object to give
+           @param actor: Person to give item to"""
+        if item == None: 
+            raise ValueError("I don't have %s" % item.name)
+        self.inventory.takeItem(item)
+        actor.inventory.placeItem(item)           
+        
+    def hasItem(self, item_type):
+        """Returns wether an item is present in the players inventory or not
+        @param item_type: ID of the item
+        @type item_type: str
+        @return: True when the item is present, False when not"""
+        return self.inventory.findItem(item_type=item_type)
+
+    def itemCount(self, item_type=""):
+        """Returns number of all items or items specified by item_type 
+        the player has.
+        @param item_type: ID of the item, can be empty
+        @type item_type: str
+        @return: Number of items"""
+        return self.inventory.count(item_type)
+
+    def getLocation(self):
+        """Get the NPC's position as a fife.Location object. Basically a
+           wrapper.
+           @rtype: fife.Location
+           @return: the location of the NPC"""
+        return self.behaviour.agent.getLocation()
+    
+    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 = _AGENT_STATE_RUN
+        self.behaviour.nextAction = None
+        self.behaviour.agent.move('run', location, self.behaviour.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 = _AGENT_STATE_RUN
+        self.behaviour.nextAction = None 
+        self.behaviour.agent.move('walk', location, self.behaviour.speed - 1)
+
+    def getStateForSaving(self):
+        """Returns state for saving
+        """
+        ret_dict = GameObject.getStateForSaving(self)
+        ret_dict["Inventory"] = self.inventory.serializeInventory()
+        return ret_dict
+
+    def _getCoords(self):
+        """Get-er property function"""
+        return (self.getLocation().getMapCoordinates().x,
+                self.getLocation().getMapCoordinates().y)
+    
+    def _setCoords(self, coords):
+        """Set-er property function"""
+        map_coords = self.getLocation().getMapCoordinates()
+        map_coords.X, map_coords.Y = float(coords[0]), float (coords[1])
+        self.teleport(map_coords)
+    
+    coords = property (_getCoords, _setCoords,
+        doc="Property allowing you to get and set the object's \
+                coordinates via tuples")
+           
+class PlayerCharacter (CharacterBase):
+    """PC class"""
+    def __init__ (self, ID, agent_layer=None, inventory=None,
+                  text="Its you. Who would've thought that?", **kwargs):
+        if inventory == None:
+            inventory = Inventory()
+            inventory.placeItem(CarryableItem(ID=456, name="Dagger123"))
+            inventory.placeItem(CarryableItem(ID=555, name="Beer"))
+            inventory.placeItem(CarryableItem(ID=616,
+                                    name="Pamphlet",
+                                    image="/gui/inv_images/inv_pamphlet.png"))
+        CharacterBase.__init__(self, ID, agent_layer, inventory, text, **kwargs)
+        self.people_i_know = set()
+        self.attributes.append("PC")
+  
+    def getStateForSaving(self):
+        """Returns state for saving
+        """
+        ret_dict = super(PlayerCharacter, self).getStateForSaving()
+        ret_dict["PeopleKnown"] = self.people_i_know
+        return ret_dict
+    
+    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.people_i_know:
+            # we could raise an error here, but should probably be a warn
+            # raise RuntimeError("I already know %s" % npc)
+            return
+        self.people_i_know.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.people_i_know
+
+    def createBehaviour(self, layer):
+        """Creates the behaviour for this actor.
+           @return: None"""
+        self.behaviour = PCBehaviour(self, layer)
+ 
+    def approach(self, location, action=None):
+        """Approaches a location and then perform an action (if set).
+           @type loc: fife.Location
+           @param loc: the location to approach
+           @type action: Action
+           @param action: The action to schedule for execution after the approach.
+           @return: None"""
+        self.state = _AGENT_STATE_APPROACH
+        self.behaviour.nextAction = action
+        boxLocation = tuple([int(float(i)) for i in location])
+        l = fife.Location(self.behaviour.agent.getLocation())
+        l.setLayerCoordinates(fife.ModelCoordinate(*boxLocation))
+        self.behaviour.agent.move('run', l, self.behaviour.speed + 1)
+    
+class NonPlayerCharacter(CharacterBase, Scriptable):
+    """NPC class"""
+    def __init__(self, ID, agent_layer=None, name='NPC', \
+                 text='A nonplayer character', inventory=None,
+                 real_name='NPC', dialogue=None, **kwargs):
+        # init game object
+        CharacterBase.__init__(self, ID, agent_layer=agent_layer,
+                               inventory=inventory, name=name,
+                               real_name=real_name, text=text, **kwargs)
+        Scriptable.__init__(self, **kwargs)
+
+        self.attributes.append("NPC")
+        self.dialogue = dialogue
+
+    def prepareStateForSaving(self, state):
+        """Prepares state for saving
+        @type state: dictionary
+        @param state: State of the object  
+        """
+        CharacterBase.prepareStateForSaving(self, state)
+        del state["behaviour"]
+
+    def getStateForSaving(self):
+        """Returns state for saving
+        """
+        ret_dict = CharacterBase.getStateForSaving(self)
+        ret_dict["Lives"] = self.lives
+        ret_dict["State"] = self.behaviour.state
+        return ret_dict
+
+    def createBehaviour(self, layer):
+        """Creates the behaviour for this actor.
+           @return None """
+        self.behaviour = NPCBehaviour(self, layer)
+
+    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.behaviour.agent.move('walk', location, self.behaviour.speed - 1)
+
+    def talk(self, pc):
+        """Makes the NPC ready to talk to the PC
+           @return: None"""
+        self.behaviour.state = _AGENT_STATE_TALK
+        self.behaviour.pc = pc.behaviour.agent
+        self.behaviour.idle()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/objects/base.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,508 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Containes classes defining the base properties of all interactable in-game 
+   objects (such as Carryable, Openable, etc. These are generally independent 
+   classes, which can be combined in almost any way and order. 
+
+   Some rules that should be followed when CREATING base property classes:
+   
+   1. If you want to support some custom initialization arguments, 
+      always define them as keyword ones. Only GameObject would use 
+      positional arguments.
+   2. In __init__() **ALWAYS** call the parent's __init__(**kwargs), preferably 
+      *at the end* of your __init__() (makes it easier to follow)
+   3. There should always be an attributes.append(x) call on __init__ 
+      (where X is the name of the class)
+
+   EXAMPLE:
+
+   class Openable(object):
+       def __init__ (self, is_open = True, **kwargs):
+           self.attribbutes.append("openable")
+           self.is_open = is_open
+           super(Openable,self).__init__ (**kwargs)
+        
+
+   Some rules are to be followed when USING the base classes to make composed 
+   ones:
+
+   1. The first parent should always be the base GameObject class
+   2. Base classes other than GameObject can be inherited in any order
+   3. The __init__ functoin of the composed class should always invoke the
+      parent's __init__() *before* it starts customizing any variables.
+
+   EXAMPLE:
+
+   class TinCan (GameObject, Container, Scriptable, Destructable, Carryable):
+       def __init__ (self, *args, **kwargs):
+           super(TinCan,self).__init__ (*args, **kwargs)
+           self.name = 'Tin Can'"""
+         
+class BaseObject(object):
+    """A base class that supports dynamic attributes functionality"""
+    def __init__ (self):
+        if not self.__dict__.has_key("attributes"):
+            self.attributes = []
+    
+    def trueAttr(self, attr):
+        """Method that checks if the instance has an attribute"""
+        return attr in self.attributes
+
+    def getStateForSaving(self):
+        """Returns state for saving
+        """
+        state = {}
+        state["attributes"] = self.attributes
+        return state
+
+class DynamicObject (BaseObject):
+    """Class with basic attributes"""
+    def __init__ (self, name="Dynamic object", real_name=None, image=None, **kwargs):
+        """Initialise minimalistic set of data
+           @type name: String
+           @param name: Object display name
+           @type image: String or None
+           @param name: Filename of image to use in inventory"""
+        BaseObject.__init__(self)
+        self.name = name
+        self.real_name = real_name or name
+        self.image = image
+
+    def prepareStateForSaving(self, state):
+        """Prepares state for saving
+        @type state: dictionary
+        @param state: State of the object  
+        """
+        pass
+    
+    def restoreState(self, state):
+        """Restores a state from a saved state
+        @type state: dictionary
+        @param state: Saved state  
+        """
+        self.__dict__.update(state)
+
+    def __getstate__(self):
+        odict = self.__dict__.copy()
+        self.prepareStateForSaving(odict)
+        return odict
+    
+    def __setstate__(self, state):
+        self.restoreState(state)
+    
+    def getStateForSaving(self):
+        """Returns state for saving
+        """
+        state = BaseObject.getStateForSaving(self)
+        state["Name"] = self.name
+        state["RealName"] = self.real_name
+        state["Image"] = self.image
+        return state
+
+class GameObject (DynamicObject):
+    """A base class to be inherited by all game objects. This must be the
+       first class (left to right) inherited by any game object."""
+    def __init__ (self, ID, gfx = None, xpos = 0.0, ypos = 0.0, map_id = None, 
+                  blocking=True, name="Generic object", real_name="Generic object", text="Item description",
+                  desc="Detailed description", **kwargs):
+        """Set the basic values that are shared by all game objects.
+           @type ID: String
+           @param ID: Unique object identifier. Must be present.
+           @type gfx: Dictionary
+           @param gfx: Dictionary with graphics for the different contexts       
+           @type coords 2-item tuple
+           @param coords: Initial coordinates of the object.
+           @type map_id: String
+           @param map_id: Identifier of the map where the object is located
+           @type blocking: Boolean
+           @param blocking: Whether the object blocks character movement
+           @type name: String
+           @param name: The display name of this object (e.g. 'Dirty crate')
+           @type text: String
+           @param text: A longer description of the item
+           @type desc: String
+           @param desc: A long description of the item that is displayed when it is examined
+           """
+        DynamicObject.__init__(self, name, real_name, **kwargs)
+        self.ID = ID
+        self.gfx = gfx or {}
+        self.X = xpos
+        self.Y = ypos
+        self.map_id = map_id
+        self.blocking = True
+        self.text = text
+        self.desc = desc
+        
+    def _getCoords(self):
+        """Get-er property function"""
+        return (self.X, self.Y)
+    
+    def _setCoords(self, coords):
+        """Set-er property function"""
+        self.X, self.Y = float(coords[0]), float (coords[1])
+        
+    coords = property (_getCoords, _setCoords, 
+        doc = "Property allowing you to get and set the object's \
+                coordinates via tuples")
+    
+    def __repr__(self):
+        """A debugging string representation of the object"""
+        return "<%s:%s>" % (self.name, self.ID)
+
+    def getStateForSaving(self):
+        """Returns state for saving
+        """
+        state = super(GameObject, self).getStateForSaving()
+        state["ObjectModel"] = self.gfx
+        state["Text"] = self.text
+        state["Desc"] = self.desc
+        state["Position"] = list(self.coords)
+        return state
+
+
+class Scriptable (BaseObject):
+    """Allows objects to have predefined parpg executed on certain events"""
+    def __init__ (self, parpg = None, **kwargs):
+        """Init operation for scriptable objects
+           @type parpg: Dictionary
+           @param parpg: Dictionary where the event strings are keys. The 
+           values are 3-item tuples (function, positional_args, keyword_args)"""
+        BaseObject.__init__(self)
+        self.attributes.append("scriptable")
+        self.parpg = parpg or {}
+        
+    def runScript (self, event):
+        """Runs the script for the given event"""
+        if event in self.parpg and self.parpg[event]:
+            func, args, kwargs = self.parpg[event]
+            func (*args, **kwargs)
+            
+    def setScript (self, event, func, args = None , kwargs = None):
+        """Sets a script to be executed for the given event."""
+        args = args or {}
+        kwargs = kwargs or {}
+        self.parpg[event] = (func, args, kwargs)
+
+class Openable(DynamicObject, Scriptable):
+    """Adds open() and .close() capabilities to game objects
+       The current state is tracked by the .is_open variable"""
+    def __init__(self, is_open = True, **kwargs):
+        """Init operation for openable objects
+           @type is_open: Boolean
+           @param is_open: Keyword boolean argument sets the initial state."""
+        DynamicObject.__init__(self, **kwargs)
+        Scriptable.__init__(self, **kwargs)
+        self.attributes.append("openable")
+        self.is_open = is_open
+    
+    def open(self):
+        """Opens the object, and runs an 'onOpen' script, if present"""
+        self.is_open = True
+        try:
+            if self.trueAttr ('scriptable'):
+                self.runScript('onOpen')
+        except AttributeError :
+            pass
+            
+    def close(self):
+        """Opens the object, and runs an 'onClose' script, if present"""
+        self.is_open = False
+        try:
+            if self.trueAttr ('scriptable'):
+                self.runScript('onClose')
+        except AttributeError :
+            pass
+        
+class Lockable (Openable):
+    """Allows objects to be locked"""
+    def __init__ (self, locked = False, is_open = True, **kwargs):
+        """Init operation for lockable objects
+           @type locked: Boolean
+           @param locked: Keyword boolen argument sets the initial locked state.
+           @type is_open: Boolean
+           @param is_open: Keyword boolean argument sets the initial open state.
+                           It is ignored if locked is True -- locked objects
+                           are always closed."""
+        self.attributes.append("lockable")
+        self.locked = locked
+        if locked :
+            is_open = False
+        Openable.__init__( self, is_open, **kwargs )
+        
+    def unlock (self):
+        """Handles unlocking functionality"""
+        self.locked = False      
+        
+    def lock (self):
+        """Handles  locking functionality"""
+        self.close()
+        self.locked = True
+        
+    def open (self, *args, **kwargs):
+        """Adds a check to see if the object is unlocked before running the
+           .open() function of the parent class"""
+        if self.locked:
+            raise ValueError ("Open failed: object locked")
+        super (Lockable, self).open(*args, **kwargs)
+        
+class Carryable (DynamicObject):
+    """Allows objects to be stored in containers"""
+    def __init__ (self, weight=0.0, bulk=0.0, **kwargs):
+        DynamicObject.__init__(self, **kwargs)
+        self.attributes.append("carryable")
+        self.in_container = None
+        self.on_map = None
+        self.agent = None
+        self.weight = weight
+        self.bulk = bulk
+
+    def getInventoryThumbnail(self):
+        """Returns the inventory thumbnail of the object"""
+        # TODO: Implement properly after the objects database is in place
+        if self.image == None:
+            return "gui/inv_images/inv_litem.png"
+        else:
+            return self.image
+    
+class Container (DynamicObject, Scriptable):
+    """Gives objects the capability to hold other objects"""
+    class TooBig(Exception):
+        """Exception to be raised when the object is too big
+        to fit into container"""
+        pass
+    
+    class SlotBusy(Exception):
+        """Exception to be raised when the requested slot is occupied"""
+        pass
+    
+    class ItemSelf(Exception):
+        """Exception to be raised when trying to add the container as an item"""
+        pass
+  
+    def __init__ (self, capacity = 0, items = None, **kwargs):
+        DynamicObject.__init__(self, **kwargs)
+        Scriptable.__init__(self, **kwargs)
+        self.attributes.append("container")
+        self.items = {}
+        self.capacity = capacity
+        if items:
+            for item in items:
+                self.placeItem(item)
+        
+    def placeItem (self, item, index=None):
+        """Adds the provided carryable item to the inventory. 
+           Runs an 'onStoreItem' script, if present""" 
+        if item is self:
+            raise self.ItemSelf("Paradox: Can't contain myself")    
+        if not item.trueAttr ('carryable'):
+            raise TypeError ('%s is not carryable!' % item)
+        if self.capacity and self.getContentsBulk()+item.bulk > self.capacity:
+            raise self.TooBig ('%s is too big to fit into %s' % (item, self))
+        item.in_container = self
+        if index == None:
+            self._placeAtVacant(item)
+        else:
+            if index in self.items :
+                raise self.SlotBusy('Slot %d is busy in %s' % (index, 
+                                                               self.name))
+            self.items[index] = item
+
+        # Run any parpg associated with storing an item in the container
+        try:
+            if self.trueAttr ('scriptable'):
+                self.runScript('onPlaceItem')
+        except AttributeError :
+            pass
+
+    def _placeAtVacant(self, item):
+        """Places an item at a vacant slot"""
+        vacant = None
+        for i in range(len(self.items)):
+            if i not in self.items :
+                vacant = i
+        if vacant == None :
+            vacant = len(self.items)
+        self.items[vacant] = item
+    
+    def takeItem (self, item):
+        """Takes the listed item out of the inventory. 
+           Runs an 'onTakeItem' script"""        
+        if not item in self.items.values():
+            raise ValueError ('I do not contain this item: %s' % item)
+        del self.items[self.items.keys()[self.items.values().index(item)]]
+
+        # Run any parpg associated with popping an item out of the container
+        try:
+            if self.trueAttr ('scriptable'):
+                self.runScript('onTakeItem')
+        except AttributeError :
+            pass
+    
+    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
+        """
+        old_index = self.indexOf(old_item.ID)
+        self.removeItem(old_item)
+        self.placeItem(new_item, old_index)
+        
+    def removeItem(self, item):
+        """Removes an item from the container, basically the same as 'takeItem'
+        but does run a different script. This should be used when an item is
+        destroyed rather than moved out.
+        Runs 'onRemoveItem' script
+        """
+        if not item in self.items.values():
+            raise ValueError ('I do not contain this item: %s' % item)
+        del self.items[self.items.keys()[self.items.values().index(item)]]
+
+        # Run any parpg associated with popping an item out of the container
+        try:
+            if self.trueAttr ('scriptable'):
+                self.runScript('onRemoveItem')
+        except AttributeError :
+            pass
+
+    def count (self, item_type = ""):
+        """Returns the number of items"""
+        if item_type:
+            ret_count = 0
+            for index in self.items :
+                if self.items[index].item_type == item_type:
+                    ret_count += 1
+            return ret_count
+        return len(self.items)   
+    
+    def getContentsBulk(self):
+        """Bulk of the container contents"""
+        return sum((item.bulk for item in self.items.values()))
+
+    def getItemAt(self, index):
+        return self.items[index]
+    
+    def indexOf(self, ID):
+        """Returns the index of the item with the passed ID"""
+        for index in self.items :
+            if self.items[index].ID == ID:
+                return index
+        return None
+
+    def findItemByID(self, ID):
+        """Returns the item with the passed ID"""
+        for i in self.items :
+            if self.items[i].ID == ID:
+                return self.items[i]
+        return None
+
+    def findItemByItemType(self, item_type):
+        """Returns the item with the passed item_type"""
+        for index in self.items :
+            if self.items[index].item_type == item_type:
+                return self.items[index]
+        return None
+
+    def findItem(self, **kwargs):
+        """Find an item in container by attributes. All params are optional.
+           @type name: String
+           @param name: If the name is non-unique, return first matching object
+           @type kind: String
+           @param kind: One of the possible object types
+           @return: The item matching criteria or None if none was found"""
+        for index in self.items :
+            if "name" in kwargs and self.items[index].name != kwargs["name"]:
+                continue
+            if "ID" in kwargs and self.items[index].ID != kwargs["ID"]:
+                continue
+            if "kind" in kwargs and not self.items[index].trueAttr(kwargs["kind"]):
+                continue
+            if "item_type" in kwargs and self.items[index].item_type != kwargs["item_type"]:
+                continue
+            return self.items[index]
+        return None    
+    
+    def serializeItems(self):
+        """Returns the items as a list"""
+        items = []
+        for index, item in self.items.iteritems():
+            item_dict = item.getStateForSaving()
+            item_dict["index"] = index
+            item_dict["type"] = item.item_type
+            items.append(item_dict)
+        return items
+    
+    def getStateForSaving(self):
+        """Returns state for saving
+        """
+        ret_state = DynamicObject.getStateForSaving(self)
+        ret_state["Items"] = self.serializeItems()
+        return ret_state
+        
+class Living (BaseObject):
+    """Objects that 'live'"""
+    def __init__ (self, **kwargs):
+        BaseObject.__init__(self)
+        self.attributes.append("living")
+        self.lives = True
+
+    def die(self):
+        """Kills the object"""
+        self.lives = False   
+
+class CharStats (BaseObject):
+    """Provides the object with character statistics"""
+    def __init__ (self, **kwargs):
+        BaseObject.__init__(self)
+        self.attributes.append("charstats")
+        
+class Wearable (BaseObject):
+    """Objects than can be weared"""
+    def __init__ (self, slots, **kwargs):
+        """Allows the object to be worn somewhere on the body (e.g. pants)"""
+        BaseObject.__init__(self)
+        self.attributes.append("wearable")
+        if isinstance(slots, tuple) :
+            self.slots = slots
+        else :
+            self.slots = (slots,)
+    
+class Usable (BaseObject):
+    """Allows the object to be used in some way (e.g. a Zippo lighter 
+       to make a fire)"""
+    def __init__ (self, actions = None, **kwargs):
+        BaseObject.__init__(self)
+        self.attributes.append("usable")
+        self.actions = actions or {}   
+        
+class Weapon (BaseObject):
+    """Allows the object to be used as a weapon"""
+    def __init__ (self, **kwargs):
+        BaseObject.__init__(self)
+        self.attributes.append("weapon")
+        
+class Destructable (BaseObject):
+    """Allows the object to be destroyed"""
+    def __init__ (self, **kwargs):
+        BaseObject.__init__(self)
+        self.attributes.append("destructable")
+        
+class Trapable (BaseObject):
+    """Provides trap slots to the object"""
+    def __init__ (self, **kwargs):
+        BaseObject.__init__(self)
+        self.attributes.append("trapable")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/objects/composed.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,145 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Composite game object classes are kept here"""
+
+from base import GameObject, Container, Lockable, \
+                Scriptable, Trapable, Destructable, Carryable, \
+                Usable
+
+class ImmovableContainer(GameObject, Container, Lockable, Scriptable, 
+                         Trapable, Destructable):
+    """Composite class that can be used for crates, chests, etc."""
+    def __init__ (self, **kwargs):
+        GameObject   .__init__(self, **kwargs)
+        Container    .__init__(self, **kwargs)
+        Lockable     .__init__(self, **kwargs)
+        Scriptable   .__init__(self, **kwargs)
+        Trapable    .__init__(self, **kwargs)
+        Destructable .__init__(self, **kwargs)
+        self.blocking = True
+
+class SingleItemContainer (Container) :
+    """Container that can only store a single item.
+       This class can represent single-item inventory slots"""
+    def __init__ (self, **kwargs):
+        Container.__init__(self, **kwargs)
+
+    def placeItem(self,item, index=None):
+        if len(self.items) > 0 :
+            raise self.SlotBusy ('%s is already busy' % self)
+        Container.placeItem(self, item)
+    
+class CarryableItem (GameObject, Carryable, Usable):
+    """Composite class that will be used for all carryable items"""
+    def __init__(self, item_type, **kwargs):
+        GameObject.__init__(self, **kwargs)
+        Carryable.__init__(self, **kwargs)
+        Usable.__init__(self, **kwargs)
+        self.item_type = item_type
+
+    def prepareStateForSaving(self, state):
+        """Prepares state for saving
+        @type state: dictionary
+        @param state: State of the object  
+        """
+        super(CarryableItem, self).prepareStateForSaving(state)
+        if state.has_key("in_container"):
+            del state["in_container"]
+        if state.has_key("on_map"):
+            del state["on_map"]
+        if state.has_key("agent"):
+            del state["agent"]
+
+    def getStateForSaving(self):
+        """Returns state for saving
+        @type state: dictionary
+        @param state: State of the object  
+        """
+        ret_dict = self.__dict__.copy()
+        self.prepareStateForSaving(ret_dict)
+        return ret_dict
+
+class CarryableContainer(Container, CarryableItem):
+    """Composite class that will be used for backpack, pouches, etc."""
+    def __init__ (self, item_type, **kwargs):
+        Container.__init__(self, **kwargs)
+        CarryableItem.__init__(self, item_type, **kwargs)
+        self.own_bulk = 0
+        self.own_weight = 0
+
+    def getWeight(self):
+        """Resulting weight of a container"""
+        return sum((item.weight for item in self.items.values()), 
+                   self.own_weight)
+
+    def setWeight(self, weight):
+        """Set container's own weight. 
+        For compatibility with inherited methods"""
+        self.own_weight = weight
+
+    weight = property(getWeight, setWeight, "Total weight of container")
+
+    def getBulk(self):
+        """Resulting bulk of container"""
+        return self.getContentsBulk()+self.own_bulk
+
+    def setBulk(self, bulk):
+        """Set container's own bulk. For compatibility with inherited methods"""
+        self.own_bulk = bulk
+
+    bulk = property(getBulk, setBulk, "Total bulk of container")
+    
+    def __repr__(self):
+        return "[%s" % self.name + str(reduce((lambda a, b: a + ', ' + \
+                                    str(self.items[b])), self.items, "")) + " ]"
+
+    def getStateForSaving(self):
+        """Returns state for saving
+        @type state: dictionary
+        @param state: State of the object  
+        """
+        state = Container.getStateForSaving(self)
+        if not state.has_key("attributes"):
+            state["attributes"] = []
+        state["attributes"].append("Container")
+        state.update(CarryableItem.getStateForSaving(self))
+        return state
+
+class CarryableSingleItemContainer (SingleItemContainer, CarryableContainer) :
+    """Container that can only store a single item.
+       This class can represent single-item inventory slots"""
+    def __init__ (self, item_type, **kwargs):
+        SingleItemContainer.__init__(self, **kwargs)
+        CarryableContainer.__init__(self, item_type, **kwargs)
+        
+class Door(GameObject, Lockable, Scriptable, Trapable):
+    """Composite class that can be used to create doors on a map."""
+    def __init__ (self, target_map_name = 'my-map',
+                  target_x = 0.0, target_y = 0.0, **kwargs):
+        GameObject.__init__(self, **kwargs)
+        Lockable.__init__(self, **kwargs)
+        Scriptable.__init__(self, **kwargs)
+        Trapable.__init__(self, **kwargs)
+        self.attributes.append("door")
+        self.target_map_name = target_map_name
+        self.target_pos = (target_x, target_y)
+        self.blocking = True
+
+    def getStateForSaving(self):
+        """Returns state for saving
+        """
+        ret_dict = super(Door, self).getStateForSaving()
+        return ret_dict
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/objects/containers.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,110 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Containes classes defining concrete container game objects like crates,
+   barrels, chests, etc."""
+
+__all__ = ["WoodenCrate", "Footlocker"]
+
+_AGENT_STATE_NONE, _AGENT_STATE_OPENED, _AGENT_STATE_CLOSED, \
+_AGENT_STATE_OPENING, _AGENT_STATE_CLOSING = xrange(5)
+
+from composed import ImmovableContainer
+from fife import fife
+
+class WoodenCrate (ImmovableContainer):
+    def __init__(self, object_id, name = 'Wooden Crate',
+                 text = 'A battered crate', gfx = 'crate', **kwargs):
+        ImmovableContainer.__init__(self, ID = object_id, name = name, 
+                                    gfx = gfx, text = text, **kwargs)
+        
+class ContainerBehaviour(fife.InstanceActionListener):
+    def __init__(self, parent = None, agent_layer = None):
+        fife.InstanceActionListener.__init__(self)
+        self.parent = parent
+        self.layer = agent_layer
+        self.state = _AGENT_STATE_CLOSED
+        self.agent = None
+
+    def attachToLayer(self, agent_id):
+        """ Attaches to a certain layer
+            @type agent_id: String
+            @param agent_id: ID of the layer to attach to.
+            @return: None"""
+        self.agent = self.layer.getInstance(agent_id)
+        self.agent.addActionListener(self)
+        self.state = _AGENT_STATE_CLOSED
+        self.agent.act('closed', self.agent.getLocation())
+        
+    def onInstanceActionFinished(self, instance, action):
+        """What the Actor does when it has finished an action.
+           Called by the engine and required for InstanceActionListeners.
+           @type instance: fife.Instance
+           @param instance: self.agent (the Actor listener is listening for this
+            instance)
+           @type action: ???
+           @param action: ???
+           @return: None"""
+        if self.state == _AGENT_STATE_OPENING:
+            self.agent.act('opened', self.agent.getFacingLocation(), True)
+            self.state = _AGENT_STATE_OPENED
+        if self.state == _AGENT_STATE_CLOSING:
+            self.agent.act('closed', self.agent.getFacingLocation(), True)
+            self.state = _AGENT_STATE_CLOSED
+        
+    def open (self):
+        if self.state != _AGENT_STATE_OPENED and self.state != \
+                                                _AGENT_STATE_OPENING:
+            self.agent.act('open', self.agent.getLocation())
+            self.state = _AGENT_STATE_OPENING
+
+    def close(self):
+        if self.state != _AGENT_STATE_CLOSED and self.state != \
+                                                _AGENT_STATE_CLOSING:
+            self.agent.act('close', self.agent.getLocation())
+            self.state = _AGENT_STATE_CLOSING  
+    
+class Footlocker(ImmovableContainer):
+    def __init__ (self, object_id, agent_layer=None, name = 'Footlocker',
+                  text = 'A Footlocker', gfx = 'lock_box_metal01', **kwargs):
+        ImmovableContainer.__init__(self, ID = object_id, name = name, 
+                                    gfx = gfx, text = text, **kwargs)
+        self.behaviour = None
+
+        self.attributes.append("AnimatedContainer")
+        self.createBehaviour(agent_layer)        
+        
+    def prepareStateForSaving(self, state):
+        """Prepares state for saving
+        @type state: dictionary
+        @param state: State of the object  
+        """
+        ImmovableContainer.prepareStateForSaving(self, state)
+        del state["behaviour"]
+    
+    def createBehaviour(self, layer):
+        self.behaviour = ContainerBehaviour(self, layer)
+
+    def setup(self):
+        """@return: None"""
+        self.behaviour.attachToLayer(self.ID)
+
+    def open (self):
+        super (Footlocker, self).open()
+        self.behaviour.open()
+
+    def close(self):
+        super (Footlocker, self).close()
+        self.behaviour.close()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/objects/doors.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,29 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+"""Containes classes defining concrete door game objects."""
+
+__all__ = ["ShantyDoor", ]
+
+from composed import Door
+
+class ShantyDoor(Door):
+    def __init__ (self, ID, name = 'Shanty Door', text = 'A door',
+                   gfx = 'shanty-door', target_map_name = 'my-map',
+                   target_x = 0.0, target_y = 0.0, **kwargs):
+        Door.__init__(self, ID = ID, name = name, text = text, gfx = gfx,
+                      target_map_name = target_map_name,
+                      target_x = target_x,
+                      target_y = target_y, **kwargs)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/objects/items.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,26 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+__all__ = ["MapItem", ]
+
+from composed import CarryableItem
+
+class MapItem(CarryableItem):
+    """Item that is lying on a map"""
+    def __init__(self, ID, item_type, item, name = 'Item', text = 'An item',
+                   gfx = 'item', **kwargs):
+        CarryableItem.__init__(self, ID = ID, item_type = item_type, name = name, 
+                               text = text, gfx = gfx, **kwargs)
+        self.item = item
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/quest_engine.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,259 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+import yaml
+from parpg.common.utils import locateFiles
+import os.path
+
+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"""
+        files = locateFiles("*.yaml", self.quest_dir)
+        self.quests = {}
+        self.active_quests = []
+        self.finished_quests = []
+        self.failed_quests = []
+        for quest_file in files:
+            quest_file = os.path.relpath(quest_file).replace("\\", "/")
+            tree = yaml.load(open(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"]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/serializers.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,156 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+"""
+Provides classes used to serialize and deserialize Python classes.
+"""
+
+from abc import ABCMeta, abstractmethod
+try:
+    from xml.etree import cElementTree as ElementTree
+except ImportError:
+    from xml.etree import ElementTree
+try:
+    from collections import OrderedDict
+except ImportError:
+    from .common.ordereddict import OrderedDict
+
+from .common.utils import dedent_chomp
+
+import logging
+
+logger = logging.getLogger('serializers')
+
+class Serializable(object):
+    def __init__(self, class_, init_args=None, attributes=None):
+        self.class_ = class_
+        if init_args is not None:
+            self.init_args = OrderedDict(init_args)
+        else:
+            self.init_args = OrderedDict()
+        if attributes is not None:
+            self.attributes = OrderedDict(attributes)
+        else:
+            self.attributes = OrderedDict()
+
+
+class SerializableRegistry(object):
+    """
+    Class holding the data used to serialize and deserialize a particular
+    Python object.
+    """
+    registered_classes = {}
+    
+    @classmethod
+    def registerClass(cls, name, class_, init_args=None, attributes=None):
+        serializable = Serializable(class_, init_args, attributes)
+        cls.registered_classes[name] = serializable
+
+
+class AbstractSerializer(object):
+    __metaclass__ = ABCMeta
+    
+    @abstractmethod
+    def serialize(self, object_, stream):
+        pass
+    
+    @abstractmethod
+    def deserialize(self, stream):
+        pass
+
+
+class XmlSerializer(AbstractSerializer):
+    def serialize(self, statistic, stream):
+        pass
+    
+    @classmethod
+    def deserialize(cls, stream):
+        element_tree = ElementTree.parse(stream)
+        root_element = element_tree.getroot()
+        object_ = cls.construct_object(root_element)
+        return object_
+    
+    @classmethod
+    def construct_object(cls, element):
+        element_name = element.tag
+        if element_name in SerializableRegistry.registered_classes.keys():
+            object_ = cls.construct_registered_class(element)
+        elif len(element) > 0:
+            # Element contains subelements, so we'll treat it as an
+            # OrderedDict.
+            if element_name == 'list':
+                object_ = cls.construct_list(element)
+            else:
+                object_ = cls.construct_ordered_dict(element)
+        else:
+            object_ = cls.construct_primitive(element)
+        return object_
+    
+    @classmethod
+    def construct_registered_class(cls, element):
+        element_name = element.tag
+        serializable = SerializableRegistry.registered_classes[element_name]
+        class_ = serializable.class_
+        init_args = OrderedDict()
+        for subelement in element:
+            arg = cls.construct_object(subelement)
+            subelement_name = subelement.tag
+            init_args[subelement_name] = arg
+        try:
+            object_ = class_(**init_args)
+        except (TypeError, ValueError) as exception:
+            logger.error(init_args)
+            error_message = \
+                'unable to deserialize tag {0}: {1}'.format(element_name,
+                                                            exception)
+            raise ValueError(error_message)
+        return object_
+    
+    @classmethod
+    def construct_ordered_dict(cls, element):
+        object_ = OrderedDict()
+        for subelement in element:
+            child = cls.construct_object(subelement)
+            name = subelement.tag
+            object_[name] = child
+        return object_
+    
+    @classmethod
+    def construct_list(cls, element):
+        object_ = []
+        for subelement in element:
+            child = cls.construct_object(subelement)
+            object_.append(child)
+        return object_
+    
+    @classmethod
+    def construct_primitive(cls, element):
+        text = element.text
+        # Interpret the element's text as unicode by default.
+        element_type = element.attrib.get('type', 'unicode')
+        if element_type == 'unicode':
+            formatted_text = dedent_chomp(text)
+            object_ = unicode(formatted_text)
+        elif element_type == 'str':
+            formatted_text = dedent_chomp(text)
+            object_ = str(formatted_text)
+        elif element_type == 'int':
+            object_ = int(text)
+        elif element_type == 'float':
+            object_ = float(text)
+        else:
+            error_message = '{0!r} is not a recognized primitive type'
+            error_message.format(element_type)
+            raise ValueError(error_message)
+        return object_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/settings.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,477 @@
+#!/usr/bin/env python2
+
+#  Copyright (C) 2011  Edwin Marshall <emarshall85@gmail.com>
+
+#   This file is part of PARPG.
+#
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+""" Provides a class used for reading and writing various configurable options
+    throughout the game
+
+    This class produces an INI formated settings file as opposed to an XML
+    formatted one. The reason that python's built-in ConfigurationParser isn't 
+    sufficient is because comments aren't preserved when writing a settings
+    file, the order in which the options are written isn't preserved, and the 
+    interface used with this class is arguably more convenient that
+    ConfigParser's.
+
+    Default Settings may be generated by envoking this module from the
+    command line:
+        python -m settings.py [system] [data_directory]
+
+    where [system] is one of local, windows, or linux (mac coming soon),
+    and data_directory is the base path for the data files to be loaded.
+
+    Both [system] and [data_directory] are option. If omitted, both
+    default to whichever what is reasonable based on the system settings.py
+    is run on
+"""
+
+import os
+import sys
+import platform
+
+#TODO: add logging to replace print statements
+class Section(object):
+    """ An object that represents a section in a settings file.
+
+        Options can be added to a section by simply assigning a value to an 
+        attribute:
+            section.foo = baz
+        would produce:
+            [section]
+            foo = baz
+        in the settings file. Options that do not exist on assignment
+        are created dynamcially.
+
+        Values are automatically converted to the appropriate python type. 
+        Options that begin and end with brackets([, ]) are converted to lists,
+        and options that are double-quoted (") are converted to strings. 
+        Section also recognizes booleans regardless of case, in addition to the
+        literals 'yes' and 'no' of any case. Except in the case of 
+        double-quoted strings, extra white-space is trimmed, so you need not 
+        worry. For example:
+            foo = bar
+        is equivalent to :
+            foo    =         baz
+    """
+    def __init__(self, name):
+        """ Initialize a new section.
+
+            @param name: name of the section. In the INI file, sections are surrounded
+                         by brackets ([name])
+            @type name: string
+        """
+        self.name = name
+
+    def __setattr__(self, option, value):
+        """ Assign a value to an option, converting types when appropriate.
+
+            @param option: name of the option to assign a value to.
+            @type option: string @param value: value to be assigned to the option.
+            @type value: int, float, string, boolean, or list
+        """
+        value = str(value)
+        if value.startswith('[') and value.endswith(']'):
+            value = [item.strip() for item in value[1:-1].split(',')]
+        elif value.lower() == 'true' or value.lower() == 'yes':
+            value = True
+        elif value.lower() == 'false' or value.lower() == 'no':
+            value = False
+        elif value.isdigit():
+            value = int(value)
+        else:
+            try:
+                value = float(value)
+            except ValueError:
+                # leave as string
+                pass
+
+        self.__dict__[option] = value
+
+    def __getattribute__(self, option):
+        """ Returns the option's value"""
+        # Remove leading and trailing quotes from strings that have them
+        return_value = object.__getattribute__(self, option)
+        try:
+            for key, value in return_value.iteritems():
+                if (hasattr(value, 'split') and 
+                    value.startswith("\"") and value.endswith("\"")):
+                    return_value[key] = value[1:-1]
+        except AttributeError:
+            pass
+
+        return return_value
+
+    @property
+    def options(self):
+        """ Returns a dictionary of existing options """
+        options = self.__dict__
+        # get rid of properties that aren't actually options
+        if options.has_key('name'):
+            options.pop('name')
+
+        return options
+
+class Settings(object):
+    """ An object that represents a settings file, its sectons,
+        and the options defined within those sections.
+    """
+    def __init__(self, settings_path='', system_path='', user_path='', suffix='.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
+        """
+        if not suffix.startswith('.'):
+            suffix = '.' + suffix
+
+        self.suffix = suffix
+        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'], 
+                                      'system{0}'.format(self.suffix)),
+                         os.path.join(self.paths['user'],
+                                      'user{0}'.format(self.suffix))]
+        elif hasattr(filenames, 'split'):
+            filenames = [filenames]
+
+        for filename in filenames:
+            section = None
+
+            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'))
+        
+        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|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
+
+# 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
+
+# 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)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sounds.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,67 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+# sounds.py holds the object code to play sounds and sound effects
+
+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.getSoundClipPool().addResourceFromFile(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)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/viewbase.py	Sat May 14 01:12:35 2011 -0700
@@ -0,0 +1,26 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+class ViewBase(object):
+    """Base class for views"""
+    def __init__(self, engine, model):
+        """Constructor for engine
+           @param engine: A fife.Engine instance
+           @type engine: fife.Engine
+           @param model: a script.GameModel instance
+           @type model: script.GameModel 
+           """
+        self.engine = engine
+        self.model = model