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

Revision 594, 26.9 KB checked in by beliar, 9 years ago (diff)

Patch by Beliar.

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