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

Revision 253, 10.9 KB checked in by tZee_parpg, 10 years ago (diff)

#65 Exported map data in a separate class. Next is to sort out the loading strategy (right now the call goes through the engine, then to the world and then to the map class) and the ownership of objects like the cameras.

  • 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 loadObjects(self, filename):
105        """Load objects from the XML file
106           Returns True if it worked, False otherwise.
107           @type filename: string
108           @param filename: The XML file to read.
109           @rtype: boolean
110           @return: Status of result (True/False)"""
111        try:
112            objects_file = open(filename, 'rt')
113        except(IOError):
114            sys.stderr.write("Error: Can't find objects file\n")
115            return False
116        # now open and read the XML file
117        other_handler = ObjectXMLParser()
118        other_handler.getObjects(objects_file)
119        objects_file.close()
120           
121        # now add to the map and the engine
122        self.createGameObjects(other_handler.local_info)
123        return True
124
125    def createGameObjects(self, objList):
126        """Add all found game objects to the world
127           @type objList: list
128           @param objList: a list of the objects found in the xml file
129           @return: None"""
130       
131        # create the extra data
132        extra = {}
133        extra['agent_layer'] = self.view.activeMap.agent_layer
134        extra['engine'] = self
135       
136        # create the objects
137        for info in objList:
138            obj = createObject(info, extra)
139            if obj.trueAttr("PC"):
140                self.addPC(obj)
141            else:
142                self.addObject(obj)
143
144    def addPC(self,pc):
145        """Add the PC to the world
146           @type pc: list
147           @param pc: List of data for PC attributes
148           @return: None"""
149        # add to view data   
150        self.view.activeMap.addObject(pc.X, pc.X, pc.gfx, pc.ID)
151       
152        # sync with game data
153        if not self.gameState.PC:
154            self.gameState.PC = pc
155           
156        self.gameState.PC.setup()
157        self.view.activeMap.addPC(self.gameState.PC.behaviour.agent)
158           
159        # create the PC agent
160        self.gameState.PC.start()
161
162    def addObject(self,obj):
163        """Adds an object to the game state.
164           @type npcs: list
165           @param npcs: List of NPC's to add
166           @return: None"""
167       
168        ref = self.gameState.getObjectById(obj.ID) 
169        if ref is None:
170            # no, add it to the game state
171            obj.map_id = self.gameState.currentMap
172            self.gameState.objects[obj.ID] = obj
173        else:
174            # yes, use the current game state data
175            obj.X = ref.X
176            obj.Y = ref.Y
177            obj.gfx = ref.gfx 
178           
179        # add it to the view
180        self.view.activeMap.addObject(obj.X, obj.Y, obj.gfx, obj.ID)         
181       
182        if obj.trueAttr("NPC"):
183            # create the agent
184            obj.setup()
185           
186            # create the PC agent
187            obj.start()
188
189    def addDoors(self, doors):
190        """Add all the doors to the map as well.
191           As an object they will have already been added.
192           @type doors: list
193           @param doors: List of doors
194           @return: None"""
195        for i in doors:
196            self.doors[str(i.id)] = MapDoor(i.id, i.destmap, (i.destx, i.desty))
197
198    def objectActive(self, ident):
199        """Given the objects ID, pass back the object if it is active,
200           False if it doesn't exist or not displayed
201           @type ident: string
202           @param ident: ID of object
203           @rtype: boolean
204           @return: Status of result (True/False)"""
205        for i in self.gameState.getObjectsFromMap(self.gameState.currentMap):
206            if (i.ID == ident):
207                # we found a match
208                return i         
209        # no match
210        return False
211
212    def getItemActions(self, obj_id):
213        """Given the objects ID, return the text strings and callbacks.
214           @type obj_id: string
215           @param obj_id: ID of object
216           @rtype: list
217           @return: List of text and callbacks"""
218        actions=[]
219        # note: ALWAYS check NPC's first!
220        obj = self.gameState.getObjectById(obj_id)
221       
222        if obj:
223            if obj.trueAttr("NPC"):
224                # keep it simple for now, None to be replaced by callbacks
225                actions.append(["Talk", "Talk", self.initTalk, obj])
226                actions.append(["Attack", "Attack", self.nullFunc, obj]) 
227            elif obj.trueAttr("Door"):
228                actions.append(["Change Map", "Change Map", \
229                       self.gameState.PC.approach, [obj.X, obj.Y], \
230                        ChangeMapAction(self, self.doors[str(i.ID)].map, [i.destx, i.desty])])
231                pass
232            else:
233                actions.append(["Examine", "Examine", self.gameState.PC.approach, 
234                                [obj.X, obj.Y], ExamineBoxAction(self, obj.name, obj.text)])
235                # is it a container?
236                if obj.trueAttr("container"):
237                    actions.append(["Open", "Open", self.gameState.PC.approach, [obj.X, obj.Y], OpenBoxAction(self, "Box")])
238                # can you pick it up?
239                if obj.trueAttr("carryable"):
240                    actions.append(["Pick Up", "Pick Up", self.nullFunc, obj])       
241                   
242        return actions
243   
244    def nullFunc(self, userdata):
245        """Sample callback for the context menus."""
246        print userdata
247   
248    def initTalk(self, npcInfo):
249        """ Starts the PC talking to an NPC. """
250        # TODO: work more on this when we get NPCData and HeroData straightened
251        # out
252        npc = self.gameState.getObjectById(npcInfo.ID)
253        if npc:
254            npc.talk()
255        self.gameState.PC.approach([npc.getLocation().getLayerCoordinates().x, npc.getLocation().getLayerCoordinates().y])
256
257    def loadMap(self, map_name, map_file):
258        """Load a new map. TODO: needs some error checking
259           @type map_file: string
260           @param map_file: Name of map file to load
261           @return: None"""
262        # then we let FIFE load the rest of the map
263        self.view.loadMap(map_name, str(map_file))
264        self.view.setActiveMap(map_name)
265        # then we update FIFE with the PC, NPC and object details
266        self.reset()
267        self.gameState.currentMap = map_file
268        self.loadObjects(map_file[:-4] + "_objects.xml")
269
270    def handleMouseClick(self,position):
271        """Code called when user left clicks the screen.
272           @type position: fife.ScreenPoint
273           @param position: Screen position of click
274           @return: None"""
275        self.gameState.PC.run(position)
276       
277    def changeMap(self, map, targetPosition):
278        """Registers for a mapchange on the next pump().
279           @type map: ???
280           @param map: Name of the target map.
281           @type targetPosition: ???
282           @param targetPosition: Position of PC on target map.
283           @return: None"""
284        # save the postions
285        self.updateGameState()
286        # set the PC position
287        self.gameState.PC.posx = targetPosition[0]
288        self.gameState.PC.posy = targetPosition[1]
289        # set the parameters for the mapchange
290        self.targetMap = map
291        # issue the mapchange
292        self.mapchange = True
293
294    def handleCommands(self):
295        if self.mapchange:
296            self.loadMap(self.targetMap)
297            self.mapchange = False
298
299    def pump(self):
300        """Main loop in the engine."""
301        self.handleCommands()
302
Note: See TracBrowser for help on using the repository browser.