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

Revision 360, 12.2 KB checked in by eliedebrauwer, 10 years ago (diff)

Ticket #140: patch by eliedebrauwer. Modified the PCBehaviour which allows actions to trigger actions, this way we can keep following an NPC until we get close enough to start a conversation. This is not an optimal path (yet), but at least we got close now. fixes[s:trac, t:140]

  • 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.