source: trunk/PARPG/scripts/engine.py @ 212

Revision 212, 10.8 KB checked in by tZee_parpg, 10 years ago (diff)

#70 Merged the approach functions and added a tiny action queue for the PC.

  • 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.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.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.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.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.desc)])
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_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.load(str(map_file))
264        # then we update FIFE with the PC, NPC and object details
265        self.reset()
266        self.gameState.currentMap = map_file
267        self.loadObjects(map_file[:-4] + "_objects.xml")
268
269    def handleMouseClick(self,position):
270        """Code called when user left clicks the screen.
271           @type position: fife.ScreenPoint
272           @param position: Screen position of click
273           @return: None"""
274        self.gameState.PC.run(position)
275       
276    def changeMap(self, map, targetPosition):
277        """Registers for a mapchange on the next pump().
278           @type map: ???
279           @param map: Name of the target map.
280           @type targetPosition: ???
281           @param targetPosition: Position of PC on target map.
282           @return: None"""
283        # save the postions
284        self.updateGameState()
285        # set the PC position
286        self.gameState.PC.posx = targetPosition[0]
287        self.gameState.PC.posy = targetPosition[1]
288        # set the parameters for the mapchange
289        self.targetMap = map
290        # issue the mapchange
291        self.mapchange = True
292
293    def handleCommands(self):
294        if self.mapchange:
295            self.loadMap(self.targetMap)
296            self.mapchange = False
297
298    def pump(self):
299        """Main loop in the engine."""
300        self.handleCommands()
301
Note: See TracBrowser for help on using the repository browser.