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

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

When you press the open button on a crate, it now will move you to the crate and display a dialog with
9 slots to show the contents of the crate. The dialog will interface with the inventory.

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