changeset 2:06145a6ee387

Fixed resource path dependencies issue that caused PARPG to crash on start. * PARPG should now run without issue (system installation not tested). * Utilized FIFE's VFS module to remove path dependencies from most PARPG modules. * The new parpg.vfs module is a singleton with a single global variable, VFS, which is a reference to the global VFS instance. Although a singleton is not ideal it should be replaced once PARPG's core code is refactored. * The parpg.vfs singleton is initialized in the parpg.applicaiton.PARPGApplication class with the absolute path to the data directory via the parpg.settings module and corresponding configuration file. * A new DataPath entry was added to the default system configuration file template under the [parpg] section to support the new parpg.vfs module. * Updated the parpg-assets subrepo to revision 3 to fix some dialog file format issues (for details see commit message for parpg-assets). * Fixed a few bugs in the parpg.dialogueparsers.YAMLDialogueParser class related to exception handling.
author M. George Hansen <technopolitica@gmail.com>
date Mon, 06 Jun 2011 15:56:14 -1000
parents 4912a6f97c52
children 06be71be07f1
files application.py charactercreationcontroller.py charactercreationview.py common/utils.py controllerbase.py dialogueparsers.py font.py gamemap.py gamemodel.py gamescenecontroller.py gui/__init__.py gui/charactercreationview.py gui/containergui_base.py gui/dialogs.py gui/dialoguegui.py gui/filebrowser.py gui/hud.py gui/inventorygui.py gui/menus.py main.py mainmenuview.py objects/action.py quest_engine.py vfs.py
diffstat 24 files changed, 235 insertions(+), 208 deletions(-) [+]
line wrap: on
line diff
--- a/application.py	Tue May 31 02:46:20 2011 -0700
+++ b/application.py	Mon Jun 06 15:56:14 2011 -1000
@@ -21,7 +21,7 @@
 from fife.extensions.serializers.xmlanimation import XMLAnimationLoader
 from fife.extensions.basicapplication import ApplicationBase
 
-from parpg import console
+from parpg import console, vfs
 from parpg.font import PARPGFont
 from parpg.gamemodel import GameModel
 from parpg.mainmenuview import MainMenuView
@@ -116,6 +116,9 @@
         self.engine = fife.Engine()
         self.loadSettings()
         self.engine.init()
+        # KLUDGE M. George Hansen 2011-06-04: See parpg/vfs.py.
+        vfs.VFS = self.engine.getVFS()
+        vfs.VFS.addNewSource(setting.parpg.DataPath)
         self._animationloader = XMLAnimationLoader(self.engine.getImagePool(),
                                                    self.engine.getVFS())
         self.engine.getAnimationPool().addResourceLoader(self._animationloader)
@@ -133,6 +136,9 @@
         self.model.getAgentImportFiles()
         self.model.readAllAgents()
         self.model.getDialogues()
+        # KLUDGE M. George Hansen 2011-06-04: Hack to allow loaded PyChan XML
+        #     scripts to locate their resources.
+        os.chdir(setting.parpg.DataPath)
         self.view = MainMenuView(self.engine, self.model)
         self.loadFonts()
         self.event_listener = EventListener(self.engine)
@@ -150,8 +156,7 @@
 
     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))
+        sys.path.insert(0, os.path.join(self._setting.parpg.DataPath, 'fonts'))
         from oldtypewriter import fontdefs
 
         for fontdef in fontdefs:
@@ -167,9 +172,10 @@
 
         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.setDefaultFontPath(
+            '{0}/fonts/{1}'.format(self._setting.parpg.DataPath,
+                                   self._setting.fife.Font)
+        )
         engineSetting.setDefaultFontSize(self._setting.fife.DefaultFontSize)
         engineSetting.setBitsPerPixel(self._setting.fife.BitsPerPixel)
         engineSetting.setInitialVolume(self._setting.fife.InitialVolume)
@@ -186,9 +192,9 @@
                                     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))
+        engineSetting.setWindowIcon(
+            '/'.join(['gui/icons', self._setting.fife.WindowIcon])
+        )
 
     def createListener(self):
         """ __init__ takes care of creating an event listener, so
--- a/charactercreationcontroller.py	Tue May 31 02:46:20 2011 -0700
+++ b/charactercreationcontroller.py	Mon Jun 06 15:56:14 2011 -1000
@@ -15,6 +15,7 @@
 """Provides the controller that defines the behaviour of the character creation
    screen."""
 
+from parpg import vfs
 import characterstatistics as char_stats
 from serializers import XmlSerializer
 from controllerbase import ControllerBase
@@ -27,8 +28,10 @@
 
 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:
@@ -106,23 +109,31 @@
            @type application: 
                L{fife.extensions.basicapplication.ApplicationBase}"""
         ControllerBase.__init__(self, engine, view, model, application)
+        self.settings = self.model.settings
         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())
+        # FIXME M. George Hansen 2011-06-06: character stats scripts aren't
+        #     finished, unfortunately.
+#        primary_stats_file = \
+#            vfs.VFS.open('character_scripts/primary_stats.xml')
+#        primary_stats = XmlSerializer.deserialize(primary_stats_file)
+#        secondary_stats_file = \
+#            vfs.VFS.open('character_scripts/secondary_stats.xml')
+#        secondary_stats = XmlSerializer.deserialize(secondary_stats_file)
+        primary_stats = []
+        secondary_stats = []
+        self.char_data = SimpleCharacter(
+            "",
+            self.GENDERS[0],
+            self.ORIGINS.keys()[0],
+            20,
+            self.PICTURES[self.GENDERS[0]][0],
+            [],
+            primary_stats,
+            secondary_stats,
+            Inventory()
+        )
         self._stat_points = 200
   
        
@@ -134,7 +145,7 @@
                                          self.application)
         self.application.view = view
         self.application.switchController(controller)
-        start_map = self.model.settings.parpg.Map
+        start_map = self.settings.parpg.Map
         self.model.changeMap(start_map)
     
     def cancelNewGame(self):
--- a/charactercreationview.py	Tue May 31 02:46:20 2011 -0700
+++ b/charactercreationview.py	Mon Jun 06 15:56:14 2011 -1000
@@ -14,10 +14,9 @@
 #   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 parpg import vfs
 from viewbase import ViewBase
 
 class CharacterCreationView(ViewBase):
@@ -40,17 +39,17 @@
            @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'))
+        xml_file = vfs.VFS.open('gui/main_menu_background.xml')
+        self.background = pychan.loadXML(xml_file)
         screen_mode = self.engine.getRenderBackend().getCurrentScreenMode()
         self.background.width = screen_mode.getWidth()
         self.background.height = screen_mode.getHeight()
         self.start_new_game_callback = None
         self.cancel_new_game_callback = None
-        self.character_screen = pychan.loadXML(os.path.join(gui_path,
-                                                       'character_screen.xml'))
+
+        xml_file = vfs.VFS.open('gui/character_screen.xml')
+        self.character_screen = pychan.loadXML(xml_file)
+
         self.character_screen.adaptLayout()
         character_screen_events = {}
         character_screen_events['startButton'] = self.startNewGame
--- a/common/utils.py	Tue May 31 02:46:20 2011 -0700
+++ b/common/utils.py	Mon Jun 06 15:56:14 2011 -1000
@@ -13,8 +13,13 @@
 
 # Miscellaneous game functions
 
-import os, sys, fnmatch
+import sys
+import os
+import fnmatch
+
 from textwrap import dedent
+from contextlib import contextmanager
+from parpg import vfs
 
 def addPaths (*paths):
     """Adds a list of paths to sys.path. Paths are expected to use forward
@@ -38,9 +43,16 @@
 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)
+    filepaths = []
+    filenames = vfs.VFS.listFiles(root)
+    for filename in fnmatch.filter(filenames, pattern):
+        vfs_file_path = '/'.join([root, filename])
+        filepaths.append(vfs_file_path)
+    dirnames = vfs.VFS.listDirectories(root)
+    for dirname in dirnames:
+        subdir_filepaths = locateFiles(pattern, '/'.join([root, dirname]))
+        filepaths.extend(subdir_filepaths)
+    return filepaths
 
 def dedent_chomp(string):
     """Remove common leading whitespace and chomp each non-blank line."""
@@ -59,3 +71,13 @@
         formatted_lines.append(line)
     result = ''.join(formatted_lines)
     return result
+
+@contextmanager
+def cwd(dirname):
+    cwd = os.getcwd()
+
+    try:
+        os.chdir(dirname)
+        yield
+    finally:
+        os.chdir(cwd)
--- a/controllerbase.py	Tue May 31 02:46:20 2011 -0700
+++ b/controllerbase.py	Mon Jun 06 15:56:14 2011 -1000
@@ -12,7 +12,6 @@
 
 #   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
@@ -83,10 +82,8 @@
     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)
+        image =  '/'.join(['gui/cursors/',
+                           self.model.settings.parpg.CursorDefault])
         self.setMouseCursor(image, image)
         
     def onStop(self):
--- a/dialogueparsers.py	Tue May 31 02:46:20 2011 -0700
+++ b/dialogueparsers.py	Mon Jun 06 15:56:14 2011 -1000
@@ -114,11 +114,7 @@
         @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))
+        dialogue = self._constructDialogue(loader, loader.get_single_node())
         return dialogue
     
     def dump(self, dialogue, output_stream, dumper_class=yaml.Dumper):
@@ -368,8 +364,9 @@
                             section_node
                         )
                         sections.append(dialogue_section)
-        except (AttributeError, TypeError, ValueError) as e:
-            raise DialogueFormatError(e)
+        except (AttributeError, TypeError, ValueError,
+                yaml.scanner.ScannerError) as error:
+            raise DialogueFormatError(error)
         
         dialogue = Dialogue(npc_name=npc_name, avatar_path=avatar_path,
                             default_greeting=default_greeting,
--- a/font.py	Tue May 31 02:46:20 2011 -0700
+++ b/font.py	Mon Jun 06 15:56:14 2011 -1000
@@ -27,9 +27,7 @@
         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.source = '/'.join(['fonts', self.filename])
         self.row_spacing = fontdef.get('row_spacing', 0)
         self.glyph_spacing = fontdef.get('glyph_spacing', 0)
 
--- a/gamemap.py	Tue May 31 02:46:20 2011 -0700
+++ b/gamemap.py	Mon Jun 06 15:56:14 2011 -1000
@@ -14,7 +14,6 @@
 #   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):
@@ -25,6 +24,7 @@
         self.map = None
         self.engine = engine
         self.model = model
+        self.settings = self.model.settings
 
         # init map attributes
         self.my_cam_id = None
@@ -46,6 +46,7 @@
         if self.map:
             self.model.deleteObjects()
             self.model.deleteMap(self.map)
+
         self.transitions = []
         self.map = None
         self.agent_layer = None        
@@ -71,7 +72,9 @@
            @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')      
             
@@ -88,8 +91,8 @@
         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
+            width = self.settings.fife.ScreenWidth
+            height = self.settings.fife.ScreenHeight
             viewport = fife.Rect(0, 0, width, height)
             cam.setViewPort(viewport)
             self.my_cam_id = cam.getId()
@@ -108,9 +111,10 @@
         rend = fife.FloatingTextRenderer.getInstance(self.cameras[
                                                                   self.my_cam_id
                                                                   ])
-        text = self.engine.getGuiManager().\
-                        createFont('fonts/rpgfont.png', 0, \
-                                   self.model.settings.fife.FontGlyphs)
+        text = self.engine.getGuiManager().createFont('fonts/rpgfont.png',
+                                                      0,
+                                                 self.settings.fife.FontGlyphs)
+
         rend.changeDefaultFont(text)
         rend.activateAllLayers(self.map)
         rend.setEnabled(True)
--- a/gamemodel.py	Tue May 31 02:46:20 2011 -0700
+++ b/gamemodel.py	Mon Jun 06 15:56:14 2011 -1000
@@ -22,6 +22,7 @@
 from fife import fife
 from fife.extensions.serializers.xmlobject import XMLObjectLoader 
 
+from parpg import vfs
 from gamestate import GameState
 from objects import createObject
 from objects.composed import CarryableItem, CarryableContainer
@@ -61,8 +62,7 @@
         self.map_change = False
         self.load_saver = False
         self.savegame = None
-        quests_directory = os.path.join(self.settings.system_path,
-                                        self.settings.parpg.QuestsPath)
+        quests_directory = settings.parpg.QuestsPath
         self.game_state = GameState(quests_dir=quests_directory)
         #self.game_state.quest_engine = 
         #self.game_state.quest_engine.readQuests()
@@ -78,23 +78,16 @@
         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
+        maps_directory = settings.parpg.MapsPath
+        self.game_state.maps_file = '/'.join([maps_directory,
+                                              settings.parpg.MapsFile])
+        self.all_agents_file = '/'.join([maps_directory,
+                                         settings.parpg.AllAgentsFile])
+        objects_directory = self.settings.parpg.ObjectsPath
+        self.objects_directory = objects_directory
+        self.object_db_file = '/'.join([objects_directory,
+                                        settings.parpg.ObjectDatabaseFile])
+        self.dialogue_directory = settings.parpg.DialoguesPath
         self.dialogues = {}
         self.agent_import_files = {}
         self.obj_loader = XMLObjectLoader(
@@ -373,8 +366,8 @@
     
     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"]
+        maps_file = vfs.VFS.open(self.game_state.maps_file)
+        self.map_files = yaml.load(maps_file)["Maps"]
     
     def addAgent(self, namespace, agent):
         """Adds an agent to the agents dictionary
@@ -410,7 +403,7 @@
         #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_data = vfs.VFS.open(map_agents_file)
         agents = yaml.load_all(agents_data)
         for agent in agents:
             if not agent == None:
@@ -418,10 +411,10 @@
     
     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)
+        agents_file = vfs.VFS.open(self.all_agents_file)
+        agents = yaml.load_all(agents_file)
         for agent in agents:
-            if not agent == None:
+            if agent is not None:
                 self.addAgent(self.ALL_AGENTS_KEY, agent)  
                 
     def getAgentsOfMap(self, map_name):
@@ -730,44 +723,40 @@
 
     def readObjectDB(self):
         """Reads the Object Information Database from a file. """
-        database_file = file(self.object_db_file, "r")
+        database_file = vfs.VFS.open(self.object_db_file)
         database = yaml.load_all(database_file)
         for object_info in database:
             self.object_db.update(object_info)
 
     def 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("\\", "/")
+        filepaths = locateFiles("*.xml", self.objects_directory)
+        for filepath in filepaths:
             try:
+                xml_file = vfs.VFS.open(filepath)
                 root = ElementTree.parse(xml_file).getroot()
                 if root.tag == "object":
-                    self.agent_import_files[root.attrib["id"]] = xml_file
+                    self.agent_import_files[root.attrib["id"]] = filepath
             except SyntaxError as error:
-                assert(isinstance(error, SyntaxError))
-                logging.critical("Error parsing file {0}: "
-                                       "{1}".format(xml_file, error.msg))
-                sys.exit(1)
+                logging.error("Error parsing file {0}: {1}".format(filepath,
+                                                                   error))
     
     def getDialogues(self):
         """Searches the dialogue directory for dialogues """
-        files = locateFiles("*.yaml", self.dialogues_directory)
+        files = locateFiles("*.yaml", self.dialogue_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
+            dialogue_file = vfs.VFS.open(dialogue_filepath)
+            try:
+                dialogue = dialogue_parser.load(dialogue_file)
+            except DialogueFormatError as error:
+                logging.error('unable to load dialogue file {0}: {1}'
+                              .format(dialogue_filepath, error))
+            else:
+                self.dialogues[dialogue.npc_name] = dialogue
             # Note Technomage 2010-11-13: the below code is used to load
             #     multiple dialogues from a single file. Is this functionality
             #     used/necessary?
--- a/gamescenecontroller.py	Tue May 31 02:46:20 2011 -0700
+++ b/gamescenecontroller.py	Mon Jun 06 15:56:14 2011 -1000
@@ -94,9 +94,10 @@
 
         if model.settings.fife.EnableSound:
             if not self.view.sounds.music_init:
-                music_file = random.choice(glob.glob(os.path.join(
-                                                                  "music", 
-                                                                  "*.ogg")))
+                music_path = 'music'
+                music_file = random.choice(
+                    glob.glob('/'.join([music_path, '*.ogg']))
+                )
                 self.view.sounds.playMusic(music_file) 
         self.initHud()
                 
@@ -135,8 +136,10 @@
             # F7 saves a screenshot to screenshots directory
 
             settings = self.model.settings
+            # FIXME M. George Hansen 2011-06-06: Not sure that user_path is set
+            #     correctly atm.
             screenshot_directory = os.path.join(settings.user_path,
-                                           settings.parpg.ScreenshotsPath)
+                                                'screenshots')
             # try to create the screenshots directory
             try:
                 os.mkdir(screenshot_directory)
@@ -256,37 +259,25 @@
             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)
+                image = '/'.join(['gui/cursors', 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)
+                image = '/'.join(['gui/cursors', 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)
+                image = '/'.join(['gui/cursors', 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)
+                image = '/'.join(['gui/cursors', settings.parpg.CursorLeft])
             
             if image is not None and not data_drag.dragging:
                 self.setMouseCursor(image, image)
@@ -310,9 +301,12 @@
                 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
--- a/gui/__init__.py	Tue May 31 02:46:20 2011 -0700
+++ b/gui/__init__.py	Mon Jun 06 15:56:14 2011 -1000
@@ -1,4 +1,5 @@
 from fife.extensions import pychan
+
 from .inventorygui import EquipmentSlot, InventoryGrid
 from .spinners import Spinner, IntSpinner
 from .tabwidget import TabWidget
--- a/gui/charactercreationview.py	Tue May 31 02:46:20 2011 -0700
+++ b/gui/charactercreationview.py	Mon Jun 06 15:56:14 2011 -1000
@@ -1,11 +1,13 @@
 from fife.extensions import pychan
 from fife.extensions.pychan.widgets import Label, HBox
 
+from parpg import vfs
 from parpg.gui.spinner import IntSpinner
 
 class CharacterCreationView(object):
     def __init__(self, xml_script_path='gui/character_creation.xml'):
-        self.gui = pychan.loadXML(xml_script_path)
+        xml_file = vfs.VFS.open(xml_script_path)
+        self.gui = pychan.loadXML(xml_file)
     
     def createStatisticList(self, statistics):
         statistics_list = self.gui.findChild(name='statisticsList')
--- a/gui/containergui_base.py	Tue May 31 02:46:20 2011 -0700
+++ b/gui/containergui_base.py	Mon Jun 06 15:56:14 2011 -1000
@@ -10,13 +10,15 @@
 
 #   You should have received a copy of the GNU General Public License
 #   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+from copy import deepcopy
+from types import StringTypes
 
 from fife import fife
 from fife.extensions import pychan
 
+from parpg import vfs
 from parpg.gui import drag_drop_data as data_drag
 from parpg.objects.action import ACTIONS
-from copy import deepcopy
 
 class ContainerGUIBase(object):
     """
@@ -26,7 +28,11 @@
 
     def __init__(self, controller, gui_file):
         self.controller = controller
-        self.gui = pychan.loadXML(gui_file)
+        if isinstance(gui_file, StringTypes):
+            xml_file = vfs.VFS.open(gui_file)
+            self.gui = pychan.loadXML(xml_file)
+        else:
+            self.gui = pychan.loadXML(gui_file)
 
     def dragDrop(self, obj):
         """Decide whether to drag or drop the image.
--- a/gui/dialogs.py	Tue May 31 02:46:20 2011 -0700
+++ b/gui/dialogs.py	Mon Jun 06 15:56:14 2011 -1000
@@ -12,17 +12,15 @@
 
 #   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
 
-import os
-
-from fife.extensions import pychan
+from parpg import vfs
 
 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'))
+        xml_file = vfs.VFS.open('gui/restart_dialog.xml')
+        self.window = pychan.loadXML(xml_file)
         self.window.mapEvents({'closeButton': self.hide})
 
     def hide(self):
--- a/gui/dialoguegui.py	Tue May 31 02:46:20 2011 -0700
+++ b/gui/dialoguegui.py	Mon Jun 06 15:56:14 2011 -1000
@@ -18,6 +18,7 @@
 from fife.extensions import pychan
 from fife.extensions.pychan import widgets
 
+from parpg import vfs
 from parpg.dialogueprocessor import DialogueProcessor
 
 logger = logging.getLogger('dialoguegui')
@@ -29,7 +30,8 @@
     def __init__(self, controller, npc, quest_engine, player_character):
         self.active = False
         self.controller = controller
-        self.dialogue_gui = pychan.loadXML("gui/dialogue.xml")
+        xml_file = vfs.VFS.open('gui/dialogue.xml')
+        self.dialogue_gui = pychan.loadXML(xml_file)
         self.npc = npc
         # TODO Technomage 2010-11-10: the QuestEngine should probably be
         #     a singleton-like object, which would avoid all of this instance
--- a/gui/filebrowser.py	Tue May 31 02:46:20 2011 -0700
+++ b/gui/filebrowser.py	Mon Jun 06 15:56:14 2011 -1000
@@ -10,13 +10,14 @@
 
 #   You should have received a copy of the GNU General Public License
 #   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+import sys
+import os
+import logging
 
 from fife.extensions import pychan
 from fife.extensions.pychan import widgets
 
-import sys
-import os
-import logging
+from parpg import vfs
 
 logger = logging.getLogger('filebrowser')
 
@@ -37,7 +38,6 @@
                  extensions=('.dat',)):
         self.engine = engine
         self.settings = settings
-        print self.settings.parpg.SavesPath
         self.file_selected = file_selected
 
         self._widget = None
@@ -47,8 +47,10 @@
         self.gui_xml_path = gui_xml_path 
         
         self.extensions = extensions
-        self.path = os.path.join(self.settings.user_path,
-                                 self.settings.parpg.SavesPath)
+        # FIXME M. George Hansen 2011-06-06: Not sure that user_path is set
+        #     correctly atm. Plus, I don't think that this should be
+        #     hard-coded.
+        self.path = os.path.join(self.settings.user_path, 'saves')
         self.dir_list = []
         self.file_list = []
         
@@ -63,7 +65,8 @@
         if self._widget:
             self._widget.show()
             return
-        self._widget = pychan.loadXML(self.gui_xml_path)
+        xml_file = vfs.VFS.open(self.gui_xml_path)
+        self._widget = pychan.loadXML(xml_file)
         self._widget.mapEvents({
             'dirList'       : self._setPath,
             'selectButton'  : self._selectFile,
--- a/gui/hud.py	Tue May 31 02:46:20 2011 -0700
+++ b/gui/hud.py	Mon Jun 06 15:56:14 2011 -1000
@@ -19,13 +19,14 @@
 from fife.extensions import pychan
 from fife.extensions.pychan.tools import callbackWithArguments as cbwa
 
+from parpg import vfs
 from parpg.gui.filebrowser import FileBrowser
 from parpg.gui.menus import ContextMenu, SettingsMenu
-from parpg.gui 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 parpg.gui.inventorygui import InventoryGUI
 from actionsbox import ActionsBox
 
 logger = logging.getLogger('hud')
@@ -46,12 +47,15 @@
            @return: None"""
 
         # TODO: perhaps this should not be hard-coded here
+        self.settings = settings
         pychan.registerWidget(ActionsBox)
-        self.hud = pychan.loadXML("gui/hud.xml")
+        
+        xml_file = vfs.VFS.open('gui/hud.xml')
+        self.hud = pychan.loadXML(xml_file)
+
         self.controller = controller
         self.engine = controller.engine
         self.model = controller.model
-        self.settings = settings
         self.inventory = None
         self.character_screen = None
 
@@ -152,9 +156,8 @@
     def initializeInventory(self):
         """Initialize the inventory"""
         if not self.inventory:
-            self.inventory = inventorygui.InventoryGUI(self.controller,
-                                                       None,
-                                                       None)
+            xml_file = vfs.VFS.open('gui/inventory.xml')
+            self.inventory = InventoryGUI(self.controller, xml_file, None)
 #        inv_callbacks = {
 #            'refreshReadyImages': self.refreshReadyImages,
 #            'toggleInventoryButton': self.toggleInventoryButton,
@@ -173,13 +176,13 @@
         """Initialize the character screen."""
         # TODO Technomage 2010-12-24: 
         if not self.character_screen:
-            self.character_screen = pychan.loadXML('gui/character_screen.xml')
-        
+            xml_file = vfs.VFS.open('gui/character_screen.xml')
+            self.character_screen = pychan.loadXML(xml_file)
     
     def initializeContextMenu(self):
         """Initialize the Context Menu
            @return: None"""
-        self.context_menu = ContextMenu (self.engine, [], (0, 0))
+        self.context_menu = ContextMenu(self.engine, [], (0, 0))
 
     def showContextMenu(self, data, pos):
         """Display the Context Menu with model at pos
@@ -199,7 +202,10 @@
     def initializeMainMenu(self):
         """Initalize the main menu.
            @return: None"""
-        self.main_menu = pychan.loadXML("gui/hud_pause_menu.xml")
+        
+        xml_file = vfs.VFS.open('gui/hud_pause_menu.xml')
+        self.main_menu = pychan.loadXML(xml_file)
+
         #TODO: find more suitalbe place for onOptilonsPress implementation
         self.menu_events = {"resumeButton": self.hideMenu, 
                             "settingsButton": self.displaySettings,
@@ -237,7 +243,10 @@
     def initializeHelpMenu(self):
         """Initialize the help menu
            @return: None"""
-        self.help_dialog = pychan.loadXML("gui/help.xml")
+
+        xml_file = vfs.VFS.open('gui/help.xml')
+        self.help_dialog = pychan.loadXML(xml_file)
+
         help_events = {"closeButton":self.help_dialog.hide}
         self.help_dialog.mapEvents(help_events)
         main_help_text = u"Welcome to Post-Apocalyptic RPG or PARPG![br][br]"\
@@ -273,9 +282,7 @@
         """ 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')
+        xml_path = 'gui/savebrowser.xml'
         save_browser = FileBrowser(self.engine,
                                    self.settings,
                                    self.save_game_callback,
@@ -318,9 +325,7 @@
         """ 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')
+        xml_path = 'gui/loadbrowser.xml'
         load_browser = FileBrowser(self.engine,
                                    self.settings,
                                    self.load_game_callback,
@@ -380,13 +385,15 @@
     def toggleInventory(self, toggle_image=True):
         """Displays the inventory screen
            @return: None"""
-        if self.inventory == None:
+        if self.inventory is None:
             self.initializeInventory()
+
         self.inventory.toggleInventory(toggle_image)
     
     def toggleCharacterScreen(self):
-        if not self.character_screen:
+        if self.characcter_screen is None:
             self.initializeCharacterScreen()
+
         if not self.character_screen.isVisible():
             self.character_screen.show()
         else:
--- a/gui/inventorygui.py	Tue May 31 02:46:20 2011 -0700
+++ b/gui/inventorygui.py	Mon Jun 06 15:56:14 2011 -1000
@@ -1,5 +1,3 @@
-#!/usr/bin/env python
-
 #   This file is part of PARPG.
 
 #   PARPG is free software: you can redistribute it and/or modify
@@ -48,11 +46,10 @@
     
     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
+        self.background_image = 'gui/inv_images/inv_background.png'
         label = pychan.Label(text=unicode(label_text))
         self.addChild(label)
         self.label_text = label_text
@@ -114,7 +111,7 @@
 
 class InventoryGUI(ContainerGUIBase):
     def __init__(self, controller, inventory, callbacks):
-        super(InventoryGUI, self).__init__(controller, "gui/inventory.xml")
+        super(InventoryGUI, self).__init__(controller, inventory)
         self.engine = controller.engine
         self.inventory_shown = False
         render_backend = self.engine.getRenderBackend()
--- a/gui/menus.py	Tue May 31 02:46:20 2011 -0700
+++ b/gui/menus.py	Mon Jun 06 15:56:14 2011 -1000
@@ -13,10 +13,11 @@
 #   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 parpg import vfs
 from dialogs import RestartDialog
 
 logger = logging.getLogger('menus')
@@ -88,17 +89,16 @@
 
         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'))
+        
+        xml_file = vfs.VFS.open('gui/settings_menu.xml')
+        self.window = pychan.loadXML(xml_file)
         self.restart_dialog = RestartDialog(self.settings)
         self.window.mapEvents({'okButton': self.save,
                                'cancelButton': self.hide,
--- a/main.py	Tue May 31 02:46:20 2011 -0700
+++ b/main.py	Mon Jun 06 15:56:14 2011 -1000
@@ -18,6 +18,8 @@
 
 from optparse import OptionParser
 
+from parpg.settings import Settings
+
 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'
@@ -30,8 +32,6 @@
                   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:
@@ -39,12 +39,6 @@
     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)
 
--- a/mainmenuview.py	Tue May 31 02:46:20 2011 -0700
+++ b/mainmenuview.py	Mon Jun 06 15:56:14 2011 -1000
@@ -13,10 +13,9 @@
 #   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 parpg import vfs
 from viewbase import ViewBase
 from parpg.gui.filebrowser import FileBrowser
 from parpg.gui.menus import SettingsMenu
@@ -38,9 +37,7 @@
         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()
@@ -54,29 +51,16 @@
     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'))
+        xml_file = vfs.VFS.open('gui/main_menu_background.xml')
+        self.main_menu_background = pychan.loadXML(xml_file)
         
         # Initialize the main menu screen.
         screen_mode = self.engine.getRenderBackend().getCurrentScreenMode()
         self.main_menu_background.width = screen_mode.getWidth()
         self.main_menu_background.height = screen_mode.getHeight()
-        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')
+        xml_file = vfs.VFS.open('gui/main_menu.xml')
+        self.main_menu = pychan.loadXML(xml_file)
 
         self.main_menu.adaptLayout()
         self.new_game_callback = new_game
@@ -103,8 +87,7 @@
         load_browser = FileBrowser(self.engine,
                                    self.model.settings,
                                    self.load_game_callback,
-                                   gui_xml_path=os.path.join(self.gui_path,
-                                                             'loadbrowser.xml'),
+                                   gui_xml_path='gui/loadbrowser.xml',
                                    save_file=False,
                                    extensions=('.dat'))
         load_browser.showBrowser()
--- a/objects/action.py	Tue May 31 02:46:20 2011 -0700
+++ b/objects/action.py	Mon Jun 06 15:56:14 2011 -1000
@@ -20,6 +20,7 @@
 logger = logging.getLogger('action')
 
 from parpg.gui import drag_drop_data as data_drag
+from parpg.dialoguecontroller import DialogueController
 
 class NoSuchQuestException(Exception):
     """NoQuestException is used when there is no active quest with the id"""
@@ -288,8 +289,6 @@
     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.\
--- a/quest_engine.py	Tue May 31 02:46:20 2011 -0700
+++ b/quest_engine.py	Mon Jun 06 15:56:14 2011 -1000
@@ -14,8 +14,9 @@
 #   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
 
 import yaml
+
 from parpg.common.utils import locateFiles
-import os.path
+from parpg import vfs
 
 class Quest(object):
     """Class that holds the information for a quest"""
@@ -142,14 +143,14 @@
     
     def readQuests(self):
         """Reads in the quests in the quest directory"""
-        files = locateFiles("*.yaml", self.quest_dir)
+        filepaths = 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))
+        for filepath in filepaths:
+            quest_file = vfs.VFS.open(filepath)
+            tree = yaml.load(quest_file)
             quest_properties = tree["QUEST_PROPERTIES"]
             variable_defines = tree["DEFINES"]
     
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vfs.py	Mon Jun 06 15:56:14 2011 -1000
@@ -0,0 +1,17 @@
+#   This file is part of PARPG.
+
+#   PARPG is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+
+#   PARPG is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+
+#   You should have received a copy of the GNU General Public License
+#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
+
+# KLUDGE M. George Hansen 2011-06-04: Die, global variable, die!
+VFS = None
\ No newline at end of file