source: trunk/game/scripts/gamemodel.py @ 566

Revision 566, 25.8 KB checked in by beliar, 9 years ago (diff)

Patch by Beliar.

  • Fixed inventory/container contents not saved/restored correctly
  • Changed numbering of inventory-item id's to only add a number if the original id is in use. (Id's are being saved and we don't want to add another number to one already present)
  • 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
21import os.path
22from gamestate import GameState
23from objects import createObject
24from objects.composed import CarryableItem
25from gamemap import GameMap
26from fife import fife
27from common.utils import locateFiles
28from common.utils import parseBool
29from inventory import Inventory
30try:
31    import xml.etree.cElementTree as ElementTree
32except ImportError:
33    import xml.etree.ElementTree as ElementTree
34
35import yaml
36
37class GameModel(object):
38    """GameModel holds the logic for the game.
39       Since some data (object position and so forth) is held in the
40       fife, and would be pointless to replicate, we hold a instance of
41       the fife view here. This also prevents us from just having a
42       function heavy controller."""
43    ALL_AGENTS_KEY = "All"
44    MAX_ID_NUMBER = 1000
45   
46    def __init__(self, engine, settings):
47        """Initialize the instance.
48        @param engine: A fife.Engine object
49        @type emgome: fife.Engine
50        @param setting: The applications settigns
51        @type setting: fife_settings.Setting
52        @return: None"""
53        self.map_change = False
54        self.load_saver = False
55        self.savegame = None
56        self.game_state = GameState()
57        self.pc_run = 1
58        self.target_position = None
59        self.target_map_name = None
60        self.object_db = {}
61        self.active_map = None
62        self.maps = {}
63        self.map_files = {}
64        self.agents = {}
65        self.agents[self.ALL_AGENTS_KEY] = {}
66        self.engine = engine
67        self.fife_model = engine.getModel()
68        self.maps_file = "maps/maps.yaml"
69        self.all_agents_file = "maps/all_agents.yaml"
70        self.object_db_file = "objects/ObjectDatabase.yaml"
71        self.agents_directory = "objects/"
72        self.dialogues_directory = "dialogue"
73        self.dialogues = {}
74        self.agent_import_files = {}
75        self.settings = settings
76
77    def checkAttributes(self, attributes):
78        """Checks for attributes that where not given in the map file
79        and fills them with values from the object database
80        @param attributes: attributes to check
81        @type attributes: Dictionary
82        @return: The modified attributes"""       
83        if attributes.has_key("object_type"):
84            class_name = attributes.pop("object_type")
85        else:
86            class_name = attributes["type"]
87        if self.object_db.has_key(class_name):
88            db_attributes = self.object_db[class_name].copy()
89            for key in db_attributes.keys():
90                if attributes.has_key(key):
91                    attributes[key] = attributes[key] or db_attributes[key]
92                else:
93                    attributes[key] = db_attributes[key]
94        return attributes
95   
96    def createContainerObject(self, attributes):
97        """Create an object that can be stored in
98        an container and return it
99        @param attributes: Dictionary of all object attributes
100        @type attributes: Dictionary
101        @return: The created object
102        """
103        # create the extra data
104        extra = {}
105        extra['controller'] = self
106        attributes = self.checkAttributes(attributes)
107       
108        """TODO: Change when we use other and or more classes
109        for inventory objects"""
110        info = {}
111        info.update(attributes)
112        info.update(extra)
113        ID = info.pop("id") if info.has_key("id") else info.pop("ID")
114        if self.game_state.hasObject(ID):
115            id_number = 1
116            while self.game_state.hasObject(ID + "_" + str(id_number)):
117                id_number += 1
118                if id_number > self.MAX_ID_NUMBER:
119                    raise ValueError("Number exceeds MAX_ID_NUMBER:" + 
120                                     str(self.MAX_ID_NUMBER))
121            ID = ID + "_" + str(id_number)
122        new_item = CarryableItem(ID = ID, **info) 
123        self.game_state.addObject(None, new_item)
124        return new_item
125     
126    def createInventoryObject(self, container, attributes):
127        """Create an inventory object and place it into a container
128           @type container: base.Container
129           @param container: Container where the item is on
130           @type attributes: Dictionary
131           @param attributes: Dictionary of all object attributes
132           @return: None"""
133        index = attributes.pop("index") if attributes.has_key("index") else None
134        slot = attributes.pop("slot") if attributes.has_key("slot") else None
135        obj = self.createContainerObject(attributes)       
136        #obj = createObject(attributes, extra)
137        if slot:
138            container.moveItemToSlot(obj, slot)
139        else:
140            container.placeItem(obj, index)
141       
142    def save(self, path, filename):
143        """Writes the saver to a file.
144           @type filename: string
145           @param filename: the name of the file to write to
146           @return: None"""
147        fname = '/'.join([path, filename])
148        try:
149            save_file = open(fname, 'w')
150        except(IOError):
151            sys.stderr.write("Error: Can't create save game: " + fname + "\n")
152            return
153        save_state = {}
154        save_state["Agents"] = {}
155        for map_name in self.agents:
156            if map_name == self.ALL_AGENTS_KEY:
157                continue
158            agents_dict = {}
159            for agent in self.agents[map_name]:
160                agent_obj = self.game_state.getObjectById(agent, map_name)
161                agent_inst = self.maps[map_name].agent_layer.getInstance(agent)
162                agent_dict = self.agents[map_name][agent]
163                agent_dict.update(agent_obj.getStateForSaving())
164                agent_dict["Rotation"] = agent_inst.getRotation()
165                agents_dict[agent] = agent_dict
166            save_state["Agents"][map_name] = agents_dict
167        agents_dict = {}
168        for agent in self.agents["All"]:
169            map_name = self.agents["All"][agent]["Map"]
170            agent_dict = self.agents["All"][agent]
171            agent_obj = None
172            if agent == "PlayerCharacter":
173                agent_obj = self.game_state.PlayerCharacter
174            else:
175                agent_obj = self.game_state.getObjectById(agent, map_name)
176            if agent_obj:
177                agent_inst = self.maps[map_name].agent_layer.getInstance(agent)
178                agent_dict.update(agent_obj.getStateForSaving())
179                agent_dict["Rotation"] = agent_inst.getRotation()
180                agent_dict["MapName"] = map_name
181            agents_dict[agent] = agent_dict
182        save_state["Agents"]["All"] = agents_dict
183        save_state["GameState"] = self.game_state.getStateForSaving()
184        yaml.dump(save_state, save_file)
185       
186        save_file.close()       
187
188    def load(self, path, filename):
189        """Loads a saver from a file.
190           @type filename: string
191           @param filename: the name of the file (including path) to load from
192           @return: None"""
193        fname = '/'.join([path, filename])
194
195        try:
196            load_file = open(fname, 'r')
197        except(IOError):
198            sys.stderr.write("Error: Can't find save game file\n")
199            return       
200        self.deleteMaps()
201        self.clearAgents()
202       
203        save_state = yaml.load(load_file)
204        self.game_state.restoreFromState(save_state["GameState"])
205        maps = save_state["Agents"]
206        for map_name in maps:
207            for agent_name in maps[map_name]:
208                agent = {agent_name:maps[map_name][agent_name]}
209                self.addAgent(map_name, agent)
210               
211        # Load the current map
212        if self.game_state.current_map_name:
213            self.loadMap(self.game_state.current_map_name)         
214        load_file.close()
215       
216
217        # Recreate all the behaviours. These can't be saved because FIFE
218        # objects cannot be pickled
219       
220        self.placeAgents()
221        self.placePC()
222     
223        # In most maps we'll create the PlayerCharacter Instance internally.
224        # In these cases we need a target position
225         
226    def teleport(self, agent, position):
227        """Called when a an agent is moved instantly to a new position.
228        The setting of position may wan to be created as its own method down the road.
229        @type position: String Tuple
230        @param position: X,Y coordinates passed from engine.changeMap
231        @return: fife.Location"""
232        print position
233        coord = fife.DoublePoint3D(float(position[0]), float(position[1]), 0)
234        location = fife.Location(self.active_map.agent_layer)
235        location.setMapCoordinates(coord)
236        agent.teleport(location)         
237               
238    def getObjectAtCoords(self, coords):
239        """Get the object which is at the given coords
240        @type coords: fife.Screenpoint
241        @param coords: Coordinates where to check for an object
242        @rtype: fife.Object
243        @return: An object or None"""
244        instances = self.active_map.cameras[
245                                            self.active_map.my_cam_id].\
246            getMatchingInstances(coords, self.active_map.agent_layer)
247        # no object returns an empty tuple
248        if(instances != ()):
249            front_y = 0
250           
251
252            for obj in instances:
253                # check to see if this in our list at all
254                if(self.objectActive(obj.getId())):
255                    # check if the object is on the foreground
256                    obj_map_coords = \
257                                      obj.getLocation().getMapCoordinates()
258                    obj_screen_coords = self.active_map.\
259                        cameras[self.active_map.my_cam_id]\
260                        .toScreenCoordinates(obj_map_coords)
261
262                    if obj_screen_coords.y > front_y:
263                        #Object on the foreground
264                        front_y = obj_screen_coords.y
265                        return obj
266                    else:
267                        return None
268        else:
269            return None
270
271    def getCoords(self, click):
272        """Get the map location x, y coordinates from the screen coordinates
273           @type click: fife.ScreenPoint
274           @param click: Screen coordinates
275           @rtype: fife.Location
276           @return: The map coordinates"""
277        coord = self.active_map.cameras[self.active_map.my_cam_id].\
278                    toMapCoordinates(click, False)
279        coord.z = 0
280        location = fife.Location(self.active_map.agent_layer)
281        location.setMapCoordinates(coord)
282        return location
283
284    def togglePause(self):
285        """ Pause/Unpause the game.
286            @return: nothing"""
287        self.active_map.togglePause()
288   
289    def readMapFiles(self):
290        """Read all a available map-files and store them"""
291        maps_data = file(self.maps_file)
292        self.map_files = yaml.load(maps_data)["Maps"]
293   
294    def addAgent(self, namespace, agent):
295        """Adds an agent to the agents dictionary
296        @param namespace: the namespace where the agent is to be added to
297        @type namespace: str
298        @param agent: The agent to be added
299        @type agent: dict
300        """
301        from local_loaders.loaders import loadImportFile
302        if not self.agents.has_key(namespace):
303            self.agents[namespace] = {}
304           
305        self.agents[namespace].update(agent)
306        object_model = ""
307        agent_values = agent.values()[0]
308        if agent_values.has_key("ObjectModel"): 
309            object_model =  agent_values["ObjectModel"]
310        else:
311            object_model = self.object_db[agent_values["ObjectType"]]["gfx"]
312        import_file = self.agent_import_files[object_model]
313        loadImportFile(import_file, self.engine)
314       
315    def readAgentsOfMap(self, map_name):
316        """Read the agents of the map
317        @param map_name: Name of the map
318        @type map_name: str
319        """
320        #Get the agents of the map       
321        map_agents_file = self.map_files[map_name].\
322                            replace(".xml", "_agents.yaml")   
323        agents_data = file(map_agents_file)
324        agents = yaml.load_all(agents_data)
325        for agent in agents:
326            if not agent == None:
327                self.addAgent(map_name, agent) 
328   
329    def readAllAgents(self):
330        """Read the agents of the all_agents_file and store them"""
331        agents_data = file(self.all_agents_file)
332        agents = yaml.load_all(agents_data)
333        for agent in agents:
334            if not agent == None:
335                self.addAgent(self.ALL_AGENTS_KEY, agent) 
336               
337    def getAgentsOfMap(self, map_name):
338        """Returns the agents that are on the given map
339        @param map_name: Name of the map
340        @type map_name: str
341        @return: A dictionary with the agents of the map
342        """
343        if not self.agents.has_key(map_name):
344            return {}
345        ret_dict = self.agents[map_name].copy()
346        for agent_name, agent_value in self.agents[self.ALL_AGENTS_KEY].iteritems():
347            if agent_value["Map"] == map_name:
348                ret_dict[agent_name] = agent_value
349        return ret_dict
350               
351    def getAgentsOfActiveMap(self):
352        """Returns the agents that are on active map
353        @return: A dictionary with the agents of the map
354        """
355        return self.getAgentsOfMap(self.active_map.map.getId())
356
357    def clearAgents(self):
358        """Resets the agents dictionary"""
359        self.agents = {}
360        self.agents[self.ALL_AGENTS_KEY] = {}
361   
362    def loadMap(self, map_name):
363        """Load a new map.
364           @type map_name: string
365           @param map_name: Name of the map to load
366           @return: None"""
367        if not map_name in self.maps: 
368            map_file = self.map_files[map_name]
369            new_map = GameMap(self.engine, self)
370            self.maps[map_name] = new_map
371            new_map.load(map_file)
372   
373    def createAgent(self, agent, inst_id):
374        object_type = agent["ObjectType"]
375        object_id = agent["ObjectModel"] \
376                                if agent.has_key("ObjectModel") \
377                                else None
378        if object_id == None:
379            object_id = self.object_db[object_type]["gfx"]
380        map_obj = self.fife_model.getObject(str(object_id), "PARPG")
381        if not map_obj:
382            print ''.join(['Object with inst_id=', str(object_id), 
383                           ' ns=PARPG', \
384                           ' could not be found. Omitting...'])
385
386        x_pos = agent["Position"][0]
387        y_pos = agent["Position"][1]
388        z_pos = agent["Position"][2] if len(agent["Position"]) == 3 else 0.0
389        stack_pos = agent["Stackposition"] if \
390                        agent.has_key("StackPosition") \
391                        else None
392        inst = self.active_map.agent_layer.\
393                        createInstance(map_obj,
394                                       fife.ExactModelCoordinate(x_pos, 
395                                                                 y_pos, 
396                                                                 z_pos),
397                                       inst_id)
398
399        inst.setId(inst_id)
400        rotation = agent["Rotation"]
401        inst.setRotation(rotation)
402
403        fife.InstanceVisual.create(inst)
404        if (stack_pos):
405            inst.get2dGfxVisual().setStackPosition(int(stack_pos))
406
407        if (map_obj.getAction('default')):
408            target = fife.Location(self.active_map.agent_layer)
409            inst.act('default', target, True)
410           
411        inst_dict = {}
412        inst_dict["type"] = object_type
413        inst_dict["id"] = inst_id
414        inst_dict["xpos"] = x_pos
415        inst_dict["ypos"] = y_pos
416        inst_dict["gfx"] = object_id
417        inst_dict["is_open"] = parseBool(agent["Open"]) \
418                                if agent.has_key("Open") \
419                                else False
420        inst_dict["locked"] = parseBool(agent["Locked"]) \
421                                if agent.has_key("Locked") \
422                                else False
423        inst_dict["name"] = agent["ViewName"]
424        inst_dict["real_name"] = agent["RealName"] \
425                                    if agent.has_key("RealName") \
426                                    else agent["ViewName"]
427        inst_dict["text"] = agent["Text"] \
428                                    if agent.has_key("Text") \
429                                    else None
430        if self.dialogues.has_key(inst_id):
431            inst_dict["dialogue"] = self.dialogues[inst_id]
432        inst_dict["target_map_name"] = agent["TargetMap"] \
433                                        if agent.\
434                                            has_key("TargetMap") \
435                                        else None
436        inst_dict["target_x"] = agent["TargetPosition"][0] \
437                                    if agent.\
438                                        has_key("TargetPosition") \
439                                    else None
440        inst_dict["target_y"] = agent["TargetPosition"][1] \
441                                    if agent.\
442                                        has_key("TargetPosition") \
443                                    else None
444        if agent.has_key("Inventory"):
445            inventory = Inventory()
446            inventory_objs = agent["Inventory"]
447            for inventory_obj in inventory_objs:
448                self.createInventoryObject(inventory,
449                                           inventory_obj
450                                           )
451            inst_dict["inventory"] = inventory
452
453        if agent.has_key("Items"):
454            items = []
455            inventory_objs = agent["Items"]
456            for inventory_obj in inventory_objs:
457                items.append(self.createContainerObject(inventory_obj))
458            inst_dict["items"] = items
459
460                   
461        self.createMapObject(self.active_map.agent_layer, inst_dict, inst)
462   
463    def placeAgents(self):
464        """Places the current maps agents
465        """
466        if not self.active_map:
467            return
468        agents = self.getAgentsOfMap(self.game_state.current_map_name)
469        for agent in agents:
470            if agent == "PlayerCharacter":
471                continue
472            if self.active_map.agent_layer.getInstances(agent):
473                continue
474            self.createAgent(agents[agent], agent)
475
476    def placePC(self):
477        """Places the PlayerCharacter on the map"""
478        agent = self.agents[self.ALL_AGENTS_KEY]["PlayerCharacter"]
479        inst_id = "PlayerCharacter"
480        self.createAgent(agent, inst_id)
481       
482        # create the PlayerCharacter agent
483        self.active_map.addPC()
484        self.game_state.PlayerCharacter.start()
485                     
486    def changeMap(self, map_name, target_position = None):
487        """Registers for a map change on the next pump().
488           @type map_name: String
489           @param map_name: Id of the map to teleport to
490           @type map_file: String
491           @param map_file: Filename of the map to teleport to
492           @type target_position: Tuple
493           @param target_position: Position of PlayerCharacter on target map.
494           @return None"""
495        # set the parameters for the map change if moving to a new map
496        if map_name != self.game_state.current_map_name:
497            self.target_map_name = map_name
498            self.target_position = target_position
499            # issue the map change
500            self.map_change = True
501        else:
502            #set the player position on the current map
503            self.teleport(target_position)
504
505    def deleteMaps(self):
506        """Clear all currently loaded maps from FIFE as well as clear our
507            local map cache
508            @return: nothing"""
509        self.engine.getModel().deleteMaps()
510        self.engine.getModel().deleteObjects()
511        self.game_state.clearObjects()
512        self.maps = {}
513       
514    def setActiveMap(self, map_name):
515        """Sets the active map that is to be rendered.
516           @type map_name: String
517           @param map_name: The name of the map to load
518           @return: None"""
519        # Turn off the camera on the old map before we turn on the camera
520        # on the new map.
521        self.active_map.cameras[self.active_map.my_cam_id].setEnabled(False)
522        # Make the new map active.
523        self.active_map = self.maps[map_name]
524        self.active_map.makeActive()
525        self.game_state.current_map_name = map_name
526
527    def createMapObject (self, layer, attributes, instance):
528        """Create an object and add it to the current map.
529           @type layer: fife.Layer
530           @param layer: FIFE layer object exists in
531           @type attributes: Dictionary
532           @param attributes: Dictionary of all object attributes
533           @type instance: fife.Instance
534           @param instance: FIFE instance corresponding to the object
535           @return: None"""
536        # create the extra data
537        extra = {}
538        if layer is not None:
539            extra['agent_layer'] = layer
540        attributes = self.checkAttributes(attributes)
541       
542        obj = createObject(attributes, extra)
543       
544        if obj.trueAttr("PC"):
545            self.addPC(layer, obj, instance)
546        else:
547            self.addObject(layer, obj, instance) 
548
549    def addPC(self, layer, player_char, instance):
550        """Add the PlayerCharacter to the map
551           @type layer: fife.Layer
552           @param layer: FIFE layer object exists in
553           @type player_char: PlayerCharacter
554           @param player_char: PlayerCharacter object
555           @type instance: fife.Instance
556           @param instance: FIFE instance of PlayerCharacter
557           @return: None"""
558        # For now we copy the PlayerCharacter,
559        # in the future we will need to copy
560        # PlayerCharacter specifics between the different PlayerCharacter's
561        self.game_state.PlayerCharacter = player_char
562        self.game_state.PlayerCharacter.setup()       
563
564    def addObject(self, layer, obj, instance):
565        """Adds an object to the map.
566           @type layer: fife.Layer
567           @param layer: FIFE layer object exists in
568           @type obj: GameObject
569           @param obj: corresponding object class
570           @type instance: fife.Instance
571           @param instance: FIFE instance of object
572           @return: None"""
573        ref = self.game_state.getObjectById(obj.ID, \
574                                            self.game_state.current_map_name) 
575        if ref is None:
576            # no, add it to the game state
577            self.game_state.addObject(self.game_state.current_map_name, obj)
578        else:
579            # yes, use the current game state data
580            obj.X = ref.X
581            obj.Y = ref.Y
582            obj.gfx = ref.gfx 
583             
584        if obj.trueAttr("NPC"):
585            # create the agent
586            obj.setup()
587            # create the PlayerCharacter agent
588            obj.start()
589        if obj.trueAttr("AnimatedContainer"):
590            # create the agent
591            obj.setup()
592
593    def objectActive(self, ident):
594        """Given the objects ID, pass back the object if it is active,
595           False if it doesn't exist or not displayed
596           @type ident: string
597           @param ident: ID of object
598           @rtype: boolean
599           @return: Status of result (True/False)"""
600        for i in \
601           self.game_state.getObjectsFromMap(self.game_state.current_map_name):
602            if (i.ID == ident):
603                # we found a match
604                return i
605        # no match
606        return False   
607
608    def movePlayer(self, position):
609        """Code called when the player should move to another location
610           @type position: fife.ScreenPoint
611           @param position: Screen position to move to
612           @return: None"""
613        if(self.pc_run == 1):
614            self.game_state.PlayerCharacter.run(position)
615        else:
616            self.game_state.PlayerCharacter.walk(position)
617       
618    def teleportAgent(self, agent, position):
619        """Code called when an agent should teleport to another location
620           @type position: fife.ScreenPoint
621           @param position: Screen position to teleport to
622           @return: None"""
623        agent.teleport(position)
624        self.agents[agent.ID]["Position"] = position
625
626    def readObjectDB(self):
627        """Reads the Object Information Database from a file.
628        """
629        database_file = file(self.object_db_file, "r")
630        database = yaml.load_all(database_file)
631        for object_info in database:
632            self.object_db.update(object_info)
633
634    def getAgentImportFiles(self):
635        """Searches the agents directory for import files
636        """
637        files = locateFiles("*.xml", self.agents_directory)
638        for xml_file in files:
639            xml_file = os.path.relpath(xml_file).replace("\\", "/")
640            root = ElementTree.parse(xml_file).getroot()
641            if root.tag == "object":
642                self.agent_import_files[root.attrib["id"]] = xml_file
643   
644    def getDialogues(self):
645        """Searches the dialogue directory for dialogues
646        """
647        files = locateFiles("*.yaml", self.dialogues_directory)
648        for dialogue_file in files:
649            dialogue_file = os.path.relpath(dialogue_file).replace("\\", "/")
650            dialogues = yaml.load_all(file(dialogue_file, "r"))
651            for dialogue in dialogues:
652                self.dialogues[dialogue["NPC"]] = dialogue
Note: See TracBrowser for help on using the repository browser.