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

Revision 150, 11.5 KB checked in by maximinus_parpg, 10 years ago (diff)

Added tZee's patch.
Some other minor editing.

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