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

Revision 187, 11.5 KB checked in by tZee_parpg, 10 years ago (diff)

Moved code to the new object classes and the new objectLoader.py.
(N)PC and (N)PCBehaviour coupling is still a bit clumsy: Too many dereferences between those two. Needs to be improved.
Doors are missing for now and need to be added. Except that all functionality should have been preserved.

  • 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 objects 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        other_handler = ObjectXMLParser()
123        other_handler.getObjects(objects_file, self.view.agent_layer)
124        objects_file.close()
125           
126        # now add to the map and the engine
127        self.addGameObjs(other_handler.local_info)
128        return True
129
130    def addGameObjs(self, objList):
131        """Add all found game objects to the world
132           @type objList: list
133           @param objList: a list of the objects found in the xml file
134           @return: None"""
135
136        self.addPC(*[obj for obj in objList if obj.trueAttr("PC")])
137        self.addNPCs([obj for obj in objList if obj.trueAttr("NPC")])
138        self.addObjects([obj for obj in objList if (not obj.trueAttr("PC") and not obj.trueAttr("NPC"))])
139
140    def addPC(self,pc):
141        """Add the PC to the world
142           @type pc: list
143           @param pc: List of data for PC attributes
144           @return: None"""
145        # add to view data   
146        self.view.addObject(pc.X, pc.X, pc.gfx, pc.ID)
147       
148        # sync with game data
149        if not self.gameState.PC:
150            self.gameState.PC = pc
151           
152        self.gameState.PC.setup()
153        self.view.addPC(self.gameState.PC.behaviour.agent)
154           
155        # create the PC agent
156        self.gameState.PC.start()
157
158    def addObjects(self,objects):
159        """Add all of the objects we found into the fife map
160           and into our class. An NPC is just an object to FIFE
161           @type objects: list
162           @param objects: List of objects to add
163           @return: None"""
164        for i in objects:
165            # already in game data?
166            ref = self.gameState.getObjectById(i.ID)
167            if ref is None:
168                # no, add it to the game state
169                i.map_id = self.gameState.currentMap
170                self.gameState.objects[i.ID] = i
171            else:
172                # yes, use the current game state data
173                i.X = ref.X
174                i.Y = ref.Y
175                i.gfx = ref.gfx       
176           
177            self.view.addObject(i.X, i.Y, i.gfx, i.ID)
178
179    def addNPCs(self,npcs):
180        """Add all of the NPCs we found into the fife map to FIFE.
181           @type npcs: list
182           @param npcs: List of NPC's to add
183           @return: None"""
184        for i in npcs:
185            # already in the game data?
186            ref = self.gameState.getObjectById(i.ID) 
187            if ref is None:
188                # no, add it to the game state
189                i.map_id = self.gameState.currentMap
190                self.gameState.objects[i.ID] = i
191            else:
192                # yes, use the current game state data
193                i.X = ref.X
194                i.Y = ref.Y
195                i.gfx = ref.gfx 
196               
197            # add it to the view
198            self.view.addObject(i.X, i.Y, i.gfx, i.ID)         
199           
200            # create the agent
201            i.setup()
202           
203            # create the PC agent
204            i.start()
205
206    def addDoors(self, doors):
207        """Add all the doors to the map as well.
208           As an object they will have already been added.
209           @type doors: list
210           @param doors: List of doors
211           @return: None"""
212        for i in doors:
213            self.doors[str(i.id)] = MapDoor(i.id, i.destmap, (i.destx, i.desty))
214
215    def objectActive(self, ident):
216        """Given the objects ID, pass back the object if it is active,
217           False if it doesn't exist or not displayed
218           @type ident: string
219           @param ident: ID of object
220           @rtype: boolean
221           @return: Status of result (True/False)"""
222        for i in self.gameState.getObjectsFromMap(self.gameState.currentMap):
223            if (i.ID == ident):
224                # we found a match
225                return i         
226        # no match
227        return False
228
229    def getItemActions(self, obj_id):
230        """Given the objects ID, return the text strings and callbacks.
231           @type obj_id: string
232           @param obj_id: ID of object
233           @rtype: list
234           @return: List of text and callbacks"""
235        actions=[]
236        # note: ALWAYS check NPC's first!
237        obj = self.gameState.getObjectById(obj_id)
238       
239        if obj:
240            if obj.trueAttr("NPC"):
241                # keep it simple for now, None to be replaced by callbacks
242                actions.append(["Talk", "Talk", self.initTalk, obj])
243                actions.append(["Attack", "Attack", self.nullFunc, obj]) 
244            elif obj.trueAttr("Door"):
245                #actions.append(["Change Map", "Change Map", \
246                #       self.gameState.PC.approachDoor, [obj.X, obj.Y], \
247                #        self.doors[str(i.ID)].map, [i.destx, i.desty]])
248                pass
249            else:
250                actions.append(["Examine", "Examine", self.nullFunc, obj])
251                # is it a container?
252                if obj.trueAttr("container"):
253                    actions.append(["Open", "Open", self.gameState.PC.approachBox, [obj.X, obj.Y]])
254                # can you pick it up?
255                if obj.trueAttr("carryable"):
256                    actions.append(["Pick Up", "Pick Up", self.nullFunc, obj])       
257                   
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        npc = self.gameState.getObjectById(npcInfo.ID)
269        if npc:
270            npc.talk()
271        self.gameState.PC.approachNPC(npc.getLocation())
272
273    def loadMap(self, map_file):
274        """Load a new map. TODO: needs some error checking
275           @type map_file: string
276           @param map_file: Name of map file to load
277           @return: None"""
278        # then we let FIFE load the rest of the map
279        self.view.load(str(map_file))
280        # then we update FIFE with the PC, NPC and object details
281        self.reset()
282        self.gameState.currentMap = map_file
283        self.loadObjects(map_file[:-4] + "_objects.xml")
284
285    def handleMouseClick(self,position):
286        """Code called when user left clicks the screen.
287           @type position: fife.ScreenPoint
288           @param position: Screen position of click
289           @return: None"""
290        self.gameState.PC.run(position)
291       
292    def changeMap(self, map, targetPosition):
293        """Registers for a mapchange on the next pump().
294           @type map: ???
295           @param map: Name of the target map.
296           @type targetPosition: ???
297           @param targetPosition: Position of PC on target map.
298           @return: None"""
299        # save the postions
300        self.updateGameState()
301        # set the PC position
302        self.gameState.PC.posx = targetPosition[0]
303        self.gameState.PC.posy = targetPosition[1]
304        # set the parameters for the mapchange
305        self.targetMap = map
306        # issue the mapchange
307        self.mapchange = True
308
309    def handleCommands(self):
310        if self.mapchange:
311            self.loadMap(self.targetMap)
312            self.mapchange = False
313
314    def pump(self):
315        """Main loop in the engine."""
316        self.handleCommands()
317
Note: See TracBrowser for help on using the repository browser.