source: trunk/game/scripts/engine.py @ 357

Revision 356, 12.2 KB checked in by Kaydeth_parpg, 10 years ago (diff)

Ticket #73. Patch by Kaydeth. Most of the changes were made to the map loading logic in the Engine, World, and Map classes to make sure it could handle loading maps after the game state has been restored by a load game. Also added a floating door back to both maps so map changing can still be tested in game. fixes[s:trac, t:73]

  • 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
20import sys
21from gamestate import GameState
22from objects import *
23from objects.action import *
24
25
26class Engine:
27    """Engine holds the logic for the game.
28       Since some data (object position and so forth) is held in the
29       fife, and would be pointless to replicate, we hold a instance of
30       the fife view here. This also prevents us from just having a
31       function heavy controller."""
32   
33    def __init__(self, view):
34        """Initialize the instance.
35           @type view: world
36           @param view: A world instance
37           @return: None"""
38        # a World object (the fife stuff, essentially)
39        self.view = view
40        self.map_change = False
41        self.load_saver = False
42        self.savegame = None
43        self.game_state = GameState()
44        self.pc_run = 1
45        self.target_position = None
46        self.target_map_name = None
47        self.target_map_file = None
48
49    def save(self, path, filename):
50        """Writes the saver to a file.
51           @type filename: string
52           @param filename: the name of the file to write to
53           @return: None"""
54        fname = '/'.join([path, filename])
55        try:
56            f = open(fname, 'wb')
57        except(IOError):
58            sys.stderr.write("Error: Can't find save game: " + fname + "\n")
59            return
60       
61        # save the PC coordinates before we destroy the behaviour
62        coords = self.game_state.PC.behaviour.agent.getLocation().\
63                    getMapCoordinates()
64        self.game_state.saved_pc_coordinates = (coords.x, coords.y)
65       
66        # can't pickle SwigPyObjects
67        behaviours = {}
68        behaviour_player = self.game_state.PC.behaviour
69        self.game_state.PC.behaviour = None
70       
71        # Backup the behaviours
72        for map_id in self.game_state.objects:
73            behaviours[map_id] = {}
74            for (object_id, npc) in self.game_state.objects[map_id].items():
75                if npc.trueAttr("NPC"):
76                    behaviours[map_id][object_id] = npc.behaviour
77                    npc.behaviour = None
78       
79        # Pickle it
80        pickle.dump(self.game_state, f)
81        f.close()
82       
83        # Restore behaviours
84        for map_id in behaviours:
85            for (object_id, behaviour) in behaviours[map_id].items():
86                self.game_state.objects[map_id][object_id].behaviour = \
87                    behaviours[map_id][object_id]
88               
89        self.game_state.PC.behaviour = behaviour_player
90
91    def load(self, path, filename):
92        """Loads a saver from a file.
93           @type filename: string
94           @param filename: the name of the file (including path) to load from
95           @return: None"""
96           
97        fname = '/'.join([path, filename])
98
99        try:
100            f = open(fname, 'rb')
101        except(IOError):
102            sys.stderr.write("Error: Can't find save game file\n")
103            return
104       
105        #Remove all currently loaded maps so we can start fresh
106        self.view.deleteMaps();
107       
108        self.game_state = pickle.load(f)
109        f.close()
110       
111        # Recreate all the behaviours. These can't be saved because FIFE
112        # objects cannot be pickled
113        for map_id in self.game_state.objects:
114            for (object_id, npc) in self.game_state.objects[map_id].items():
115                if npc.trueAttr("NPC"):
116                    npc.createBehaviour(self.view.active_map.agent_layer)
117
118        # Fix the player behaviour
119        self.game_state.PC.createBehaviour(self.view.active_map.agent_layer)
120       
121        #In most maps we'll create the PC Instance internally. In these
122        #cases we need a target position
123        self.target_position = self.game_state.saved_pc_coordinates
124       
125        #Load the current map
126        if self.game_state.current_map_file:
127            self.loadMap(self.game_state.current_map_name, \
128                         self.game_state.current_map_file) 
129           
130    def createObject (self, layer, attributes, instance):
131        """Create an object and add it to the current map.
132           @type layer: fife.Layer
133           @param layer: FIFE layer object exists in
134           @type attributes: Dictionary
135           @param attributes: Dictionary of all object attributes
136           @type instance: fife.Instance
137           @param instance: FIFE instance corresponding to the object
138           @return: None
139        """
140        # create the extra data
141        extra = {}
142        extra['agent_layer'] = layer
143        extra['engine'] = self
144       
145        obj = createObject(attributes, extra)
146       
147        if obj.trueAttr("PC"):
148            self.addPC(layer, obj, instance)
149        else:
150            self.addObject(layer, obj, instance)
151
152       
153
154    def addPC(self, layer, pc, instance):
155        """Add the PC to the map
156           @type layer: fife.Layer
157           @param layer: FIFE layer object exists in
158           @type pc: PlayerCharacter
159           @param pc: PlayerCharacter object
160           @type instance: fife.Instance
161           @param instance: FIFE instance of PC
162           @return: None
163        """
164        # For now we copy the PC, in the future we will need to copy
165        # PC specifics between the different PC's
166        self.game_state.PC = pc
167        self.game_state.PC.setup()
168
169    def addObject(self, layer, obj, instance):
170        """Adds an object to the map.
171           @type layer: fife.Layer
172           @param layer: FIFE layer object exists in
173           @type obj: GameObject
174           @param obj: corresponding object class
175           @type instance: fife.Instance
176           @param instance: FIFE instance of object
177           @return: None
178        """
179
180        ref = self.game_state.getObjectById(obj.ID, \
181                                            self.game_state.current_map_name) 
182        if ref is None:
183            # no, add it to the game state
184            self.game_state.objects[self.game_state.current_map_name][obj.ID] \
185                                                                          = obj
186        else:
187            # yes, use the current game state data
188            obj.X = ref.X
189            obj.Y = ref.Y
190            obj.gfx = ref.gfx 
191             
192        if obj.trueAttr("NPC"):
193            # create the agent
194            obj.setup()
195           
196            # create the PC agent
197            obj.start()
198
199    def objectActive(self, ident):
200        """Given the objects ID, pass back the object if it is active,
201           False if it doesn't exist or not displayed
202           @type ident: string
203           @param ident: ID of object
204           @rtype: boolean
205           @return: Status of result (True/False)"""
206        for i in \
207           self.game_state.getObjectsFromMap(self.game_state.current_map_name):
208            if (i.ID == ident):
209                # we found a match
210                return i
211        # no match
212        return False
213
214    def getItemActions(self, obj_id):
215        """Given the objects ID, return the text strings and callbacks.
216           @type obj_id: string
217           @param obj_id: ID of object
218           @rtype: list
219           @return: List of text and callbacks"""
220        actions = []
221        # note: ALWAYS check NPC's first!
222        obj = self.game_state.getObjectById(obj_id, \
223                                            self.game_state.current_map_name)
224       
225        if obj is not None:
226            if obj.trueAttr("NPC"):
227                # keep it simple for now, None to be replaced by callbacks
228                actions.append(["Talk", "Talk", self.initTalk, obj])
229                actions.append(["Attack", "Attack", self.nullFunc, obj])
230            else:
231                actions.append(["Examine", "Examine", \
232                                self.game_state.PC.approach, [obj.X, obj.Y], \
233                                ExamineBoxAction(self, obj.name, obj.text)])
234                # is it a Door?
235                if obj.trueAttr("door"):
236                    actions.append(["Change Map", "Change Map", \
237                       self.game_state.PC.approach, [obj.X, obj.Y], \
238                            ChangeMapAction(self, obj.target_map_name, \
239                                obj.target_map, obj.target_pos)])
240                # is it a container?
241                if obj.trueAttr("container"):
242                    actions.append(["Open", "Open", 
243                                    self.game_state.PC.approach, \
244                                    [obj.X, obj.Y], \
245                                    OpenBoxAction(self, "Box")])
246                # can you pick it up?
247                if obj.trueAttr("carryable"):
248                    actions.append(["Pick Up", "Pick Up", self.nullFunc, obj])
249
250        return actions
251   
252    def nullFunc(self, userdata):
253        """Sample callback for the context menus."""
254        print userdata
255   
256    def initTalk(self, npcInfo):
257        """ Starts the PC talking to an NPC. """
258        # TODO: work more on this when we get NPCData and HeroData straightened
259        # out
260        npc = self.game_state.getObjectById(npcInfo.ID, \
261                                            self.game_state.current_map_name)
262        self.game_state.PC.approach([npc.getLocation().\
263                                     getLayerCoordinates().x, \
264                                     npc.getLocation().\
265                                     getLayerCoordinates().y], \
266                                     TalkAction(self, npc))
267
268    def loadMap(self, map_name, map_file):
269        """Load a new map.
270           @type map_name: string
271           @param map_name: Name of the map to load
272           @type map_file: string
273           @param map_file: Filename of map file to load
274           @return: None"""
275        self.game_state.current_map_file = map_file
276        self.game_state.current_map_name = map_name
277        self.view.loadMap(map_name, str(map_file))
278
279        # create the PC agent
280        self.view.active_map.addPC()
281        self.game_state.PC.start()
282
283    def handleMouseClick(self, position):
284        """Code called when user left clicks the screen.
285           @type position: fife.ScreenPoint
286           @param position: Screen position of click
287           @return: None"""
288        if(self.pc_run == 1):
289            self.game_state.PC.run(position)
290        else:
291            self.game_state.PC.walk(position)
292
293    def changeMap(self, map_name, map_file, target_position):
294        """Registers for a map change on the next pump().
295           @type name_name: String
296           @param map_name: Id of the map to teleport to
297           @type map_file: String
298           @param map_file: Filename of the map to teleport to
299           @type target_position: Tuple
300           @param target_position: Position of PC on target map.
301           @return None"""
302        # set the parameters for the map change if moving to a new map
303        if map_name != self.game_state.current_map_name:
304            self.target_map_name = map_name
305            self.target_map_file = map_file
306            self.target_position = target_position
307            # issue the map change
308            self.map_change = True
309        else:
310            #set the player position on the current map
311            self.view.teleport(target_position)
312
313    def handleCommands(self):
314        if self.map_change:
315            self.loadMap(self.target_map_name, self.target_map_file)
316            self.view.teleport(self.target_position)
317            self.map_change = False
318       
319        if self.load_saver:
320            self.load(self.savegame)
321            self.load_saver = False
322
323    def pump(self):
324        """Main loop in the engine."""
325        self.handleCommands()
Note: See TracBrowser for help on using the repository browser.