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

Revision 162, 12.3 KB checked in by bretzel_parpg, 10 years ago (diff)

You can now select the file you want to save to or load from. filebrowser.py is actually
built into FIFE, but I copied it to the scripts folder because I had to make some
modifications to it.

  • 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 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, path, 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        fname = '/'.join([path,filename])
76        try:
77            f = open(fname, 'w')
78        except(IOError):
79            sys.stderr.write("Error: Can't find save game\n")
80            return
81        pickle.dump(self.PC_old, f)
82        pickle.dump(self.objects, f)
83        pickle.dump(self.currentMap, f)
84        f.close()
85
86    def load(self, path, filename):
87        """Loads a saver from a file.
88           @type filename: string
89           @param filename: the name of the file to load from
90           @return: None"""
91        fname = '/'.join([path, filename])
92        try:
93            f = open(fname, 'r')
94        except(IOError):
95            sys.stderr.write("Error: Can't find save game file\n")
96            return
97        self.PC_old = pickle.load(f)
98        self.objects = pickle.load(f)
99        self.currentMap = pickle.load(f)
100        f.close()
101        if self.currentMap:
102            self.loadMap(self.currentMap)
103           
104    def updateGameState(self):
105        """Stores the current pc and npcs positons in the game state."""
106        # save the PC position
107        self.PC_old.posx = self.PC.getX()
108        self.PC_old.posy = self.PC.getY()
109        #save npc positions
110        for i in self.npcs:
111            if str(i.id) in self.objects:
112                self.getObjectById(str(i.id)).posx = i.getX()
113                self.getObjectById(str(i.id)).posy = i.getY()
114
115    def loadObjects(self, filename):
116        """Load objects from the XML file
117           Returns True if it worked, False otherwise.
118           @type filename: string
119           @param filename: The XML file to read.
120           @rtype: boolean
121           @return: Status of result (True/False)"""
122        try:
123            objects_file = open(filename, 'rt')
124        except(IOError):
125            sys.stderr.write("Error: Can't find objects file\n")
126            return False
127        # now open and read the XML file
128        cur_handler = LocalXMLParser()
129        parser = cur_handler.getParser()
130        parser.setContentHandler(cur_handler)
131        parser.parse(objects_file)
132        objects_file.close()
133        # must have at least 1 PC
134        if(cur_handler.pc == None):
135            sys.stderr.write("Error: No PC defined\n")
136            sys.exit(False)
137        # now add to the map and the engine
138        self.addPC(cur_handler.pc)
139        self.addNPCs(cur_handler.npcs)
140        self.addObjects(cur_handler.objects)
141        self.addDoors(cur_handler.doors)
142        return True
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        # sync with game data
150        if self.PC_old:
151            # use existing position
152            posx = self.PC_old.posx
153            posy = self.PC_old.posy
154        else:
155            posx = pc[0]
156            posy = pc[1]
157            # save the new PC to the game data
158            self.PC_old = HeroData("PC", posx, posy, "PC", self.currentMap)
159        # add to game data   
160        self.view.addObject(float(posx), float(posy),"PC","PC")
161         # create the PC agent
162        self.PC = Hero("PC", self.view.agent_layer, self)
163        self.PC.start()
164        self.view.addPC(self.PC.agent)
165
166    def addObjects(self,objects):
167        """Add all of the objects we found into the fife map
168           and into our class. An NPC is just an object to FIFE
169           @type objects: list
170           @param objects: List of objects to add
171           @return: None"""
172        for i in objects:
173            # already in game data?
174            ref = self.getObjectById(i.id)
175            if ref is None:
176                # no, add it to the game state
177                i.map = self.currentMap
178                self.objects[i.id] = i
179            else:
180                # yes, use the current game state data
181                i.posx = ref.posx
182                i.posy = ref.posy
183                i.gfx = ref.gfx       
184            # is it visible?
185            if(i.display == True):
186                self.view.addObject(i.posx, i.posy, i.gfx, i.id)
187
188    def addNPCs(self,npcs):
189        """Add all of the NPCs we found into the fife map to FIFE.
190           @type npcs: list
191           @param npcs: List of NPC's to add
192           @return: None"""
193        for i in npcs:
194            # already in the game data?
195            ref = self.getObjectById(i.id) 
196            if ref is None:
197                # no, add it to the game state
198                i.map = self.currentMap
199                self.objects[i.id] = i
200            else:
201                # yes, use the current game state data
202                i.posx = ref.posx
203                i.posy = ref.posy
204                i.gfx = ref.gfx             
205            # add it to the view
206            self.view.addObject(i.posx, i.posy, i.gfx, i.id)         
207            # create the agent
208            self.npcs.append(NPC(i.text, str(i.id), self.view.agent_layer))
209            self.npcs[-1].start()
210
211    def addDoors(self, doors):
212        """Add all the doors to the map as well.
213           As an object they will have already been added.
214           @type doors: list
215           @param doors: List of doors
216           @return: None"""
217        for i in doors:
218            self.doors[str(i.id)] = MapDoor(i.id, i.destmap, (i.destx, i.desty))
219
220    def objectActive(self, ident):
221        """Given the objects ID, pass back the object if it is active,
222           False if it doesn't exist or not displayed
223           @type ident: string
224           @param ident: ID of object
225           @rtype: boolean
226           @return: Status of result (True/False)"""
227        for i in self.getObjectsFromMap(self.currentMap):
228            if i.display and (i.id == ident):
229                # we found a match
230                return i         
231        # no match
232        return False
233
234    def getItemActions(self, obj_id):
235        """Given the objects ID, return the text strings and callbacks.
236           @type obj_id: string
237           @param obj_id: ID of object
238           @rtype: list
239           @return: List of text and callbacks"""
240        actions=[]
241        # note: ALWAYS check NPC's first!
242        for i in self.getObjectsFromMap(self.currentMap):
243            if(obj_id == i.id):
244                if isinstance(i, NpcData):
245                    # keep it simple for now, None to be replaced by callbacks
246                    actions.append(["Talk", "Talk", self.initTalk, i])
247                    actions.append(["Attack", "Attack", self.nullFunc, i]) 
248                elif isinstance(i, DoorData):
249                    actions.append(["Change Map", "Change Map", \
250                            self.PC.approachDoor, [i.posx, i.posy], \
251                            self.doors[str(i.id)].map, [i.destx, i.desty]])
252                elif isinstance(i, NonLivingObjectData):
253                    actions.append(["Examine", "Examine", self.nullFunc, i])
254                    # is it a container?
255                    if(i.container == True):
256                        actions.append(["Open", "Open", self.nullFunc, i])
257                    # can you pick it up?
258                    if(i.carryable == True):
259                        actions.append(["Pick Up", "Pick Up", self.nullFunc, i])       
260        return actions
261   
262    def nullFunc(self, userdata):
263        """Sample callback for the context menus."""
264        print userdata
265   
266    def initTalk(self, npcInfo):
267        """ Starts the PC talking to an NPC. """
268        # TODO: work more on this when we get NPCData and HeroData straightened
269        # out
270        for npc in self.npcs:
271            if str(npcInfo.id) == npc.id:
272                npc.talk()
273                break
274        self.PC.approachNPC(npc.getLocation())
275
276    def loadMap(self, map_file):
277        """Load a new map. TODO: needs some error checking
278           @type map_file: string
279           @param map_file: Name of map file to load
280           @return: None"""
281        # then we let FIFE load the rest of the map
282        self.view.load(str(map_file))
283        # then we update FIFE with the PC, NPC and object details
284        self.reset()
285        self.currentMap = map_file
286        self.loadObjects(map_file[:-4] + "_objects.xml")
287
288    def handleMouseClick(self,position):
289        """Code called when user left clicks the screen.
290           @type position: fife.ScreenPoint
291           @param position: Screen position of click
292           @return: None"""
293        self.PC.run(position)
294       
295    def changeMap(self, map, targetPosition):
296        """Registers for a mapchange on the next pump().
297           @type map: ???
298           @param map: Name of the target map.
299           @type targetPosition: ???
300           @param targetPosition: Position of PC on target map.
301           @return: None"""
302        # save the postions
303        self.updateGameState()
304        # set the PC position
305        self.PC_old.posx = targetPosition[0]
306        self.PC_old.posy = targetPosition[1]
307        # set the parameters for the mapchange
308        self.targetMap = map
309        # issue the mapchange
310        self.mapchange = True
311
312    def getObjectsFromMap(self, cmap):
313        """Gets all objects that are currently on the given map.
314           @type cmap: String
315           @param cmap: The map name.
316           @returns: The list of objects on this map."""
317        return [i for i in self.objects.values() if i.map == cmap]
318   
319    def getObjectById(self, ident):
320        """Gets an object by it's id
321           @type ident: String
322           @param ident: The id of the object.
323           @returns: The object or None."""
324        if ident in self.objects:
325            return self.objects[ident]
326
327    def handleCommands(self):
328        if self.mapchange:
329            self.loadMap(self.targetMap)
330            self.mapchange = False
331
332    def pump(self):
333        """Main loop in the engine."""
334        self.handleCommands()
335
Note: See TracBrowser for help on using the repository browser.