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

Revision 565, 25.6 KB checked in by beliar, 9 years ago (diff)

Ticket #260 Patch by Beliar

  • When creating inventory objects there will now e a consecutively numbered number added to the ID. The new ID will have the form <ID>_<number>

fixes[s:trac, t:260

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