source: trunk/game/scripts/engine.py @ 284

Revision 264, 10.4 KB checked in by Kaydeth_parpg, 10 years ago (diff)

Ticket #72: Patch by Kaydeth. The separate objects file was merged back into the main map file that FIFE parses to load each Map. The Map, Engine, and World classes were all updated to allow game object creation to happen as the map file is loaded. loaders.py and xmlmap.py were copied from the FIFE code base and made local so that we can now customize how we pass the parsed map and object information to FIFE.

  • Property svn:eol-style set to native
Line 
1#!/usr/bin/python
2
3#   This file is part of PARPG.
4
5#   PARPG is free software: you can redistribute it and/or modify
6#   it under the terms of the GNU General Public License as published by
7#   the Free Software Foundation, either version 3 of the License, or
8#   (at your option) any later version.
9
10#   PARPG is distributed in the hope that it will be useful,
11#   but WITHOUT ANY WARRANTY; without even the implied warranty of
12#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13#   GNU General Public License for more details.
14
15#   You should have received a copy of the GNU General Public License
16#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
17
18# there should be NO references to FIFE here!
19import pickle, sys
20from gamestate import GameState
21from objects import *
22from objectLoader import ObjectXMLParser
23from objects.action import *
24
25# design note:
26# there is a map file that FIFE reads. We use that file for half the map
27# format because the map editor in FIFE uses it, and secondly because it
28# save us writing a bunch of new code.
29# However, the objects and characters on a map are liable to change
30# whilst the game is being run, so when we change the map, we need to
31# to grab the objects and npc data EITHER from the engine state, or grab
32# from another file if in their initial state
33# This other file has the name AAA_objects.xml where AAA.xml is the name
34# of the original mapfile.
35
36class Engine:
37    """Engine holds the logic for the game.
38       Since some data (object position and so forth) is held in the
39       fife, and would be pointless to replicate, we hold a instance of
40       the fife view here. This also prevents us from just having a
41       function heavy controller."""
42   
43    def __init__(self, view):
44        """Initialise the instance.
45           @type view: world
46           @param view: A world instance
47           @return: None"""
48        # a World object (the fife stuff, essentially)
49        self.view = view
50        self.mapchange = False
51        self.gameState = GameState()
52
53    def reset(self):
54        """Clears the data on a map reload so we don't have objects/npcs from
55           other maps hanging around.
56           @return: None"""
57
58    def save(self, path, filename):
59        """Writes the saver to a file.
60           @type filename: string
61           @param filename: the name of the file to write to
62           @return: None"""
63        fname = '/'.join([path,filename])
64        try:
65            f = open(fname, 'w')
66        except(IOError):
67            sys.stderr.write("Error: Can't find save game: " + fname + "\n")
68            return
69       
70        # can't pickle SwigPyObjects
71        behaviours = {}
72        behaviours[self.gameState.PC.ID] = self.gameState.PC.behaviour;
73        self.gameState.PC.behaviour = None;
74       
75        npcs = [npc for npc in self.gameState.objects.values() if npc.trueAttr("NPC")]
76        for npc in npcs:
77            behaviours[npc.ID] = npc.behaviour;
78            npc.behaviour = None;
79       
80        pickle.dump(self.gameState, f)
81        f.close()
82       
83        # restore behaviours
84        for npc in npcs:
85            npc.behaviour = behaviours[npc.ID];
86        self.gameState.PC.behaviour = behaviours[self.gameState.PC.ID]
87
88    def load(self, path, filename):
89        """Loads a saver from a file.
90           @type filename: string
91           @param filename: the name of the file to load from
92           @return: None"""
93        fname = '/'.join([path, filename])
94        try:
95            f = open(fname, 'r')
96        except(IOError):
97            sys.stderr.write("Error: Can't find save game file\n")
98            return
99        self.gameState = pickle.load(f)
100        f.close()
101        if self.gameState.currentMap:
102            self.loadMap(self.gameState.currentMap) 
103
104    def createObject (self, layer, attributes, instance):
105        """Create an object and add it to the current map.
106            Inputs:
107                layer = FIFE layer object exists in
108                attributes = dictionary of all object attributes
109                instance = FIFE instance corresponding to the object
110            Return:
111                Nothing
112        """
113        # create the extra data
114        extra = {}
115        extra['agent_layer'] = layer
116        extra['engine'] = self
117       
118        obj = createObject(attributes, extra)
119       
120        if obj.trueAttr("PC"):
121            self.addPC( layer, obj, instance)
122        else:
123            self.addObject( layer, obj, instance)
124
125       
126
127    def addPC(self, layer, pc, instance):
128        """Add the PC to the map
129            Inputs:
130                layer = FIFE layer object exists in
131                pc = PlayerCharacter object
132                instance = FIFE instance of PC
133            Returns:
134                Nothing
135        """
136        # add to view data
137        self.view.activeMap.addObject(pc.ID, instance)         
138       
139        # sync with game data
140        if not self.gameState.PC:
141            self.gameState.PC = pc
142           
143        self.gameState.PC.setup()
144
145
146    def addObject(self, layer, obj, instance):
147        """Adds an object to the map.
148            Inputs:
149                layer = FIFE layer object exists in
150                obj = corresponding object class
151                instance = FIFE instance of object
152            Returns:
153                Nothing
154        """
155       
156        ref = self.gameState.getObjectById(obj.ID) 
157        if ref is None:
158            # no, add it to the game state
159            obj.map_id = self.gameState.currentMap
160            self.gameState.objects[obj.ID] = obj
161        else:
162            # yes, use the current game state data
163            obj.X = ref.X
164            obj.Y = ref.Y
165            obj.gfx = ref.gfx 
166           
167        # add it to the view
168        self.view.activeMap.addObject(obj.ID, instance)         
169       
170        if obj.trueAttr("NPC"):
171            # create the agent
172            obj.setup()
173           
174            # create the PC agent
175            obj.start()
176
177    def addDoors(self, doors):
178        """Add all the doors to the map as well.
179           As an object they will have already been added.
180           @type doors: list
181           @param doors: List of doors
182           @return: None"""
183        for i in doors:
184            self.doors[str(i.id)] = MapDoor(i.id, i.destmap, (i.destx, i.desty))
185
186    def objectActive(self, ident):
187        """Given the objects ID, pass back the object if it is active,
188           False if it doesn't exist or not displayed
189           @type ident: string
190           @param ident: ID of object
191           @rtype: boolean
192           @return: Status of result (True/False)"""
193        for i in self.gameState.getObjectsFromMap(self.gameState.currentMap):
194            if (i.ID == ident):
195                # we found a match
196                return i         
197        # no match
198        return False
199
200    def getItemActions(self, obj_id):
201        """Given the objects ID, return the text strings and callbacks.
202           @type obj_id: string
203           @param obj_id: ID of object
204           @rtype: list
205           @return: List of text and callbacks"""
206        actions=[]
207        # note: ALWAYS check NPC's first!
208        obj = self.gameState.getObjectById(obj_id)
209       
210        if obj:
211            if obj.trueAttr("NPC"):
212                # keep it simple for now, None to be replaced by callbacks
213                actions.append(["Talk", "Talk", self.initTalk, obj])
214                actions.append(["Attack", "Attack", self.nullFunc, obj]) 
215            elif obj.trueAttr("Door"):
216                actions.append(["Change Map", "Change Map", \
217                       self.gameState.PC.approach, [obj.X, obj.Y], \
218                        ChangeMapAction(self, self.doors[str(i.ID)].map, [i.destx, i.desty])])
219                pass
220            else:
221                actions.append(["Examine", "Examine", self.gameState.PC.approach, 
222                                [obj.X, obj.Y], ExamineBoxAction(self, obj.name, obj.text)])
223                # is it a container?
224                if obj.trueAttr("container"):
225                    actions.append(["Open", "Open", self.gameState.PC.approach, [obj.X, obj.Y], OpenBoxAction(self, "Box")])
226                # can you pick it up?
227                if obj.trueAttr("carryable"):
228                    actions.append(["Pick Up", "Pick Up", self.nullFunc, obj])       
229                   
230        return actions
231   
232    def nullFunc(self, userdata):
233        """Sample callback for the context menus."""
234        print userdata
235   
236    def initTalk(self, npcInfo):
237        """ Starts the PC talking to an NPC. """
238        # TODO: work more on this when we get NPCData and HeroData straightened
239        # out
240        npc = self.gameState.getObjectById(npcInfo.ID)
241        if npc:
242            npc.talk()
243        self.gameState.PC.approach([npc.getLocation().getLayerCoordinates().x, npc.getLocation().getLayerCoordinates().y])
244
245    def loadMap(self, map_name, map_file):
246        """Load a new map. TODO: needs some error checking
247           @type map_file: string
248           @param map_file: Name of map file to load
249           @return: None"""
250        self.gameState.currentMap = map_file
251        self.view.loadMap(map_name, str(map_file))
252        self.view.setActiveMap(map_name)
253
254        self.reset()       
255       
256        # create the PC agent
257        self.view.activeMap.addPC(self.gameState.PC.behaviour.agent)
258        self.gameState.PC.start()
259
260    def handleMouseClick(self,position):
261        """Code called when user left clicks the screen.
262           @type position: fife.ScreenPoint
263           @param position: Screen position of click
264           @return: None"""
265        self.gameState.PC.run(position)
266       
267    def changeMap(self, map, targetPosition):
268        """Registers for a mapchange on the next pump().
269           @type map: ???
270           @param map: Name of the target map.
271           @type targetPosition: ???
272           @param targetPosition: Position of PC on target map.
273           @return: None"""
274        # save the postions
275        self.updateGameState()
276        # set the PC position
277        self.gameState.PC.posx = targetPosition[0]
278        self.gameState.PC.posy = targetPosition[1]
279        # set the parameters for the mapchange
280        self.targetMap = map
281        # issue the mapchange
282        self.mapchange = True
283
284    def handleCommands(self):
285        if self.mapchange:
286            self.loadMap(self.targetMap)
287            self.mapchange = False
288
289    def pump(self):
290        """Main loop in the engine."""
291        self.handleCommands()
292
Note: See TracBrowser for help on using the repository browser.