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

Revision 156, 12.2 KB checked in by meggie_parpg, 10 years ago (diff)

Believe that mouseMoved 'bug' is fixed by making mousePressed mouseReleased instead (see forums and chat logs for discussion).

  • 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
20from agents.hero import Hero
21from agents.npc import NPC
22from objLoader import LocalXMLParser
23from saver import Saver
24from gamedata import *
25from gamedata import BaseObjectData
26
27# design note:
28# there is a map file that FIFE reads. We use that file for half the map
29# format because the map editor in FIFE uses it, and secondly because it
30# save us writing a bunch of new code.
31# However, the objects and characters on a map are liable to change
32# whilst the game is being run, so when we change the map, we need to
33# to grab the objects and npc data EITHER from the engine state, or grab
34# from another file if in their initial state
35# This other file has the name AAA_objects.xml where AAA.xml is the name
36# of the original mapfile.
37
38class Engine:
39    """Engine holds the logic for the game.
40       Since some data (object position and so forth) is held in the
41       fife, and would be pointless to replicate, we hold a instance of
42       the fife view here. This also prevents us from just having a
43       function heavy controller."""
44   
45    def __init__(self, view):
46        """Initialise the instance.
47           @type view: world
48           @param view: A world instance
49           @return: None"""
50        # a World object (the fife stuff, essentially)
51        self.view = view
52        self.doors = {}
53        self.npcs = []
54        self.PC = None
55        self.mapchange = False
56        # below this needs to be pickled
57        self.PC_old = None
58        self.objects = {}
59        self.currentMap = None
60
61    def reset(self):
62        """Clears the data on a map reload so we don't have objects/npcs from
63           other maps hanging around.
64           @return: None"""
65        self.PC = None
66        self.npcs = []
67        self.doors = {}
68
69    def save(self, filename):
70        """Writes the saver to a file.
71           @type filename: string
72           @param filename: the name of the file to write to
73           @return: None"""
74        self.updateGameState()
75        try:
76            f = open(filename, 'r')
77        except(IOError):
78            sys.stderr.write("Error: Can't find save game\n")
79            return
80        pickle.dump(self.PC_old, f)
81        pickle.dump(self.objects, f)
82        pickle.dump(self.currentMap, f)
83        f.close()
84
85    def load(self, filename):
86        """Loads a saver from a file.
87           @type filename: string
88           @param filename: the name of the file to load from
89           @return: None"""
90        try:
91            f = open(filename, 'r')
92        except(IOError):
93            sys.stderr.write("Error: Can't find save game file\n")
94            return
95        self.PC_old = pickle.load(f)
96        self.objects = pickle.load(f)
97        self.currentMap = pickle.load(f)
98        f.close()
99        if self.gameState.currentMap:
100            self.loadMap(self.gameState.currentMap)
101           
102    def updateGameState(self):
103        """Stores the current pc and npcs positons in the game state."""
104        # save the PC position
105        self.PC_old.posx = self.PC.getX()
106        self.PC_old.posy = self.PC.getY()
107        #save npc positions
108        for i in self.npcs:
109            if str(i.id) in self.objects:
110                self.getObjectById(str(i.id)).posx = i.getX()
111                self.getObjectById(str(i.id)).posy = i.getY()
112
113    def loadObjects(self, filename):
114        """Load objects from the XML file
115           Returns True if it worked, False otherwise.
116           @type filename: string
117           @param filename: The XML file to read.
118           @rtype: boolean
119           @return: Status of result (True/False)"""
120        try:
121            objects_file = open(filename, 'rt')
122        except(IOError):
123            sys.stderr.write("Error: Can't find objects file\n")
124            return False
125        # now open and read the XML file
126        cur_handler = LocalXMLParser()
127        parser = cur_handler.getParser()
128        parser.setContentHandler(cur_handler)
129        parser.parse(objects_file)
130        objects_file.close()
131        # must have at least 1 PC
132        if(cur_handler.pc == None):
133            sys.stderr.write("Error: No PC defined\n")
134            sys.exit(False)
135        # now add to the map and the engine
136        self.addPC(cur_handler.pc)
137        self.addNPCs(cur_handler.npcs)
138        self.addObjects(cur_handler.objects)
139        self.addDoors(cur_handler.doors)
140        return True
141
142    def addPC(self,pc):
143        """Add the PC to the world
144           @type pc: list
145           @param pc: List of data for PC attributes
146           @return: None"""
147        # sync with game data
148        if self.PC_old:
149            # use existing position
150            posx = self.PC_old.posx
151            posy = self.PC_old.posy
152        else:
153            posx = pc[0]
154            posy = pc[1]
155            # save the new PC to the game data
156            self.PC_old = HeroData("PC", posx, posy, "PC", self.currentMap)
157        # add to game data   
158        self.view.addObject(float(posx), float(posy),"PC","PC")
159         # create the PC agent
160        self.PC = Hero("PC", self.view.agent_layer, self)
161        self.PC.start()
162        self.view.addPC(self.PC.agent)
163
164    def addObjects(self,objects):
165        """Add all of the objects we found into the fife map
166           and into our class. An NPC is just an object to FIFE
167           @type objects: list
168           @param objects: List of objects to add
169           @return: None"""
170        for i in objects:
171            # already in game data?
172            ref = self.getObjectById(i.id)
173            if ref is None:
174                # no, add it to the game state
175                i.map = self.currentMap
176                self.objects[i.id] = i
177            else:
178                # yes, use the current game state data
179                i.posx = ref.posx
180                i.posy = ref.posy
181                i.gfx = ref.gfx       
182            # is it visible?
183            if(i.display == True):
184                self.view.addObject(i.posx, i.posy, i.gfx, i.id)
185
186    def addNPCs(self,npcs):
187        """Add all of the NPCs we found into the fife map to FIFE.
188           @type npcs: list
189           @param npcs: List of NPC's to add
190           @return: None"""
191        for i in npcs:
192            # already in the game data?
193            ref = self.getObjectById(i.id) 
194            if ref is None:
195                # no, add it to the game state
196                i.map = self.currentMap
197                self.objects[i.id] = i
198            else:
199                # yes, use the current game state data
200                i.posx = ref.posx
201                i.posy = ref.posy
202                i.gfx = ref.gfx             
203            # add it to the view
204            self.view.addObject(i.posx, i.posy, i.gfx, i.id)         
205            # create the agent
206            self.npcs.append(NPC(i.text, str(i.id), self.view.agent_layer))
207            self.npcs[-1].start()
208
209    def addDoors(self, doors):
210        """Add all the doors to the map as well.
211           As an object they will have already been added.
212           @type doors: list
213           @param doors: List of doors
214           @return: None"""
215        for i in doors:
216            self.doors[str(i.id)] = MapDoor(i.id, i.destmap, (i.destx, i.desty))
217
218    def objectActive(self, ident):
219        """Given the objects ID, pass back the object if it is active,
220           False if it doesn't exist or not displayed
221           @type ident: string
222           @param ident: ID of object
223           @rtype: boolean
224           @return: Status of result (True/False)"""
225        for i in self.getObjectsFromMap(self.currentMap):
226            if i.display and (i.id == ident):
227                # we found a match
228                return i         
229        # no match
230        return False
231
232    def getItemActions(self, obj_id):
233        """Given the objects ID, return the text strings and callbacks.
234           @type obj_id: string
235           @param obj_id: ID of object
236           @rtype: list
237           @return: List of text and callbacks"""
238        actions=[]
239        # note: ALWAYS check NPC's first!
240        for i in self.getObjectsFromMap(self.currentMap):
241            if(obj_id == i.id):
242                if isinstance(i, NpcData):
243                    # keep it simple for now, None to be replaced by callbacks
244                    actions.append(["Talk", "Talk", self.initTalk, i])
245                    actions.append(["Attack", "Attack", self.nullFunc, i]) 
246                elif isinstance(i, DoorData):
247                    actions.append(["Change Map", "Change Map", \
248                            self.PC.approachDoor, [i.posx, i.posy], \
249                            self.doors[str(i.id)].map, [i.destx, i.desty]])
250                elif isinstance(i, NonLivingObjectData):
251                    actions.append(["Examine", "Examine", self.nullFunc, i])
252                    # is it a container?
253                    if(i.container == True):
254                        actions.append(["Open", "Open", self.nullFunc, i])
255                    # can you pick it up?
256                    if(i.carryable == True):
257                        actions.append(["Pick Up", "Pick Up", self.nullFunc, i])       
258        return actions
259   
260    def nullFunc(self, userdata):
261        """Sample callback for the context menus."""
262        print userdata
263   
264    def initTalk(self, npcInfo):
265        """ Starts the PC talking to an NPC. """
266        # TODO: work more on this when we get NPCData and HeroData straightened
267        # out
268        for npc in self.npcs:
269            if str(npcInfo.id) == npc.id:
270                npc.talk()
271                break
272        self.PC.approachNPC(npc.getLocation())
273
274    def loadMap(self, map_file):
275        """Load a new map. TODO: needs some error checking
276           @type map_file: string
277           @param map_file: Name of map file to load
278           @return: None"""
279        # then we let FIFE load the rest of the map
280        self.view.load(str(map_file))
281        # then we update FIFE with the PC, NPC and object details
282        self.reset()
283        self.currentMap = map_file
284        self.loadObjects(map_file[:-4] + "_objects.xml")
285
286    def handleMouseClick(self,position):
287        """Code called when user left clicks the screen.
288           @type position: fife.ScreenPoint
289           @param position: Screen position of click
290           @return: None"""
291        self.PC.run(position)
292       
293    def changeMap(self, map, targetPosition):
294        """Registers for a mapchange on the next pump().
295           @type map: ???
296           @param map: Name of the target map.
297           @type targetPosition: ???
298           @param targetPosition: Position of PC on target map.
299           @return: None"""
300        # save the postions
301        self.updateGameState()
302        # set the PC position
303        self.PC_old.posx = targetPosition[0]
304        self.PC_old.posy = targetPosition[1]
305        # set the parameters for the mapchange
306        self.targetMap = map
307        # issue the mapchange
308        self.mapchange = True
309
310    def getObjectsFromMap(self, cmap):
311        """Gets all objects that are currently on the given map.
312           @type cmap: String
313           @param cmap: The map name.
314           @returns: The list of objects on this map."""
315        return [i for i in self.objects.values() if i.map == cmap]
316   
317    def getObjectById(self, ident):
318        """Gets an object by it's id
319           @type ident: String
320           @param ident: The id of the object.
321           @returns: The object or None."""
322        if ident in self.objects:
323            return self.objects[ident]
324
325    def handleCommands(self):
326        if self.mapchange:
327            self.loadMap(self.targetMap)
328            self.mapchange = False
329
330    def pump(self):
331        """Main loop in the engine."""
332        self.handleCommands()
333
Note: See TracBrowser for help on using the repository browser.