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

Revision 498, 12.0 KB checked in by b0rland_parpg, 10 years ago (diff)

Patch by b0rland

  • Fixed the saving/loading mechanism. All NPCs and objects work correctly after game load
  • Eventually the game still segfaults. Couldn't catch that yet

[t:200]

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