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

Revision 590, 26.4 KB checked in by beliar, 9 years ago (diff)

Patch by Beliar.

  • 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        else:
320            object_model = self.object_db[agent_values["ObjectType"]]["gfx"]
321        import_file = self.agent_import_files[object_model]
322        loadImportFile(import_file, self.engine)
323       
324    def readAgentsOfMap(self, map_name):
325        """Read the agents of the map
326        @param map_name: Name of the map
327        @type map_name: str """
328        #Get the agents of the map       
329        map_agents_file = self.map_files[map_name].\
330                            replace(".xml", "_agents.yaml")   
331        agents_data = file(map_agents_file)
332        agents = yaml.load_all(agents_data)
333        for agent in agents:
334            if not agent == None:
335                self.addAgent(map_name, agent) 
336   
337    def readAllAgents(self):
338        """Read the agents of the all_agents_file and store them"""
339        agents_data = file(self.all_agents_file)
340        agents = yaml.load_all(agents_data)
341        for agent in agents:
342            if not agent == None:
343                self.addAgent(self.ALL_AGENTS_KEY, agent) 
344               
345    def getAgentsOfMap(self, map_name):
346        """Returns the agents that are on the given map
347        @param map_name: Name of the map
348        @type map_name: str
349        @return: A dictionary with the agents of the map"""
350        if not self.agents.has_key(map_name):
351            return {}
352        ret_dict = self.agents[map_name].copy()
353        for agent_name, agent_value in self.agents[self.ALL_AGENTS_KEY]\
354                                                .iteritems():
355            if agent_value["Map"] == map_name:
356                ret_dict[agent_name] = agent_value
357        return ret_dict
358               
359    def getAgentsOfActiveMap(self):
360        """Returns the agents that are on active map
361        @return: A dictionary with the agents of the map """
362        return self.getAgentsOfMap(self.active_map.map.getId())
363
364    def clearAgents(self):
365        """Resets the agents dictionary"""
366        self.agents = {}
367        self.agents[self.ALL_AGENTS_KEY] = {}
368   
369    def loadMap(self, map_name):
370        """Load a new map.
371           @type map_name: string
372           @param map_name: Name of the map to load
373           @return: None"""
374        if not map_name in self.game_state.maps: 
375            map_file = self.map_files[map_name]
376            new_map = GameMap(self.engine, self)
377            self.game_state.maps[map_name] = new_map
378            new_map.load(map_file)
379   
380    def createAgent(self, agent, inst_id):
381        object_type = agent["ObjectType"]
382        object_id = agent["ObjectModel"] \
383                                if agent.has_key("ObjectModel") \
384                                else None
385        if object_id == None:
386            object_id = self.object_db[object_type]["gfx"]
387        map_obj = self.fife_model.getObject(str(object_id), "PARPG")
388        if not map_obj:
389            print ''.join(['Object with inst_id=', str(object_id), 
390                           ' ns=PARPG', \
391                           ' could not be found. Omitting...'])
392
393        x_pos = agent["Position"][0]
394        y_pos = agent["Position"][1]
395        z_pos = agent["Position"][2] if len(agent["Position"]) == 3 else 0.0
396        stack_pos = agent["Stackposition"] if \
397                        agent.has_key("StackPosition") \
398                        else None
399        inst = self.active_map.agent_layer.\
400                        createInstance(map_obj,
401                                       fife.ExactModelCoordinate(x_pos, 
402                                                                 y_pos, 
403                                                                 z_pos),
404                                       inst_id)
405
406        inst.setId(inst_id)
407        rotation = agent["Rotation"]
408        inst.setRotation(rotation)
409
410        fife.InstanceVisual.create(inst)
411        if (stack_pos):
412            inst.get2dGfxVisual().setStackPosition(int(stack_pos))
413
414        if (map_obj.getAction('default')):
415            target = fife.Location(self.active_map.agent_layer)
416            inst.act('default', target, True)
417           
418        inst_dict = {}
419        inst_dict["type"] = object_type
420        inst_dict["id"] = inst_id
421        inst_dict["xpos"] = x_pos
422        inst_dict["ypos"] = y_pos
423        inst_dict["gfx"] = object_id
424        inst_dict["is_open"] = parseBool(agent["Open"]) \
425                                if agent.has_key("Open") \
426                                else False
427        inst_dict["locked"] = parseBool(agent["Locked"]) \
428                                if agent.has_key("Locked") \
429                                else False
430        inst_dict["name"] = agent["ViewName"]
431        inst_dict["real_name"] = agent["RealName"] \
432                                    if agent.has_key("RealName") \
433                                    else agent["ViewName"]
434        inst_dict["text"] = agent["Text"] \
435                                    if agent.has_key("Text") \
436                                    else None
437        if self.dialogues.has_key(inst_id):
438            inst_dict["dialogue"] = self.dialogues[inst_id]
439        inst_dict["target_map_name"] = agent["TargetMap"] \
440                                        if agent.\
441                                            has_key("TargetMap") \
442                                        else None
443        inst_dict["target_x"] = agent["TargetPosition"][0] \
444                                    if agent.\
445                                        has_key("TargetPosition") \
446                                    else None
447        inst_dict["target_y"] = agent["TargetPosition"][1] \
448                                    if agent.\
449                                        has_key("TargetPosition") \
450                                    else None
451        if agent.has_key("Inventory"):
452            inventory = Inventory()
453            inventory_objs = agent["Inventory"]
454            for inventory_obj in inventory_objs:
455                self.createInventoryObject(inventory,
456                                           inventory_obj
457                                           )
458            inst_dict["inventory"] = inventory
459
460        if agent.has_key("Items"):
461            items = []
462            inventory_objs = agent["Items"]
463            for inventory_obj in inventory_objs:
464                items.append(self.createContainerObject(inventory_obj))
465            inst_dict["items"] = items
466
467                   
468        self.createMapObject(self.active_map.agent_layer, inst_dict, inst)
469   
470    def placeAgents(self):
471        """Places the current maps agents """
472        if not self.active_map:
473            return
474        agents = self.getAgentsOfMap(self.game_state.current_map_name)
475        for agent in agents:
476            if agent == "PlayerCharacter":
477                continue
478            if self.active_map.agent_layer.getInstances(agent):
479                continue
480            self.createAgent(agents[agent], agent)
481
482    def placePC(self):
483        """Places the PlayerCharacter on the map"""
484        agent = self.agents[self.ALL_AGENTS_KEY]["PlayerCharacter"]
485        inst_id = "PlayerCharacter"
486        self.createAgent(agent, inst_id)
487       
488        # create the PlayerCharacter agent
489        self.active_map.addPC()
490        self.game_state.player_character.start()
491        if agent.has_key("PeopleKnown"):
492            self.game_state.player_character.people_i_know = agent["PeopleKnown"]
493                     
494    def changeMap(self, map_name, target_position = None):
495        """Registers for a map change on the next pump().
496           @type map_name: String
497           @param map_name: Id of the map to teleport to
498           @type map_file: String
499           @param map_file: Filename of the map to teleport to
500           @type target_position: Tuple
501           @param target_position: Position of PlayerCharacter on target map.
502           @return None"""
503        # set the parameters for the map change if moving to a new map
504        if map_name != self.game_state.current_map_name:
505            self.target_map_name = map_name
506            self.target_position = target_position
507            # issue the map change
508            self.map_change = True
509
510    def deleteMaps(self):
511        """Clear all currently loaded maps from FIFE as well as clear our
512            local map cache
513            @return: nothing"""
514        self.engine.getModel().deleteMaps()
515        self.engine.getModel().deleteObjects()
516        self.game_state.clearObjects()
517        self.game_state.maps = {}
518       
519    def setActiveMap(self, map_name):
520        """Sets the active map that is to be rendered.
521           @type map_name: String
522           @param map_name: The name of the map to load
523           @return: None"""
524        # Turn off the camera on the old map before we turn on the camera
525        # on the new map.
526        self.active_map.cameras[self.active_map.my_cam_id].setEnabled(False)
527        # Make the new map active.
528        self.active_map = self.game_state.maps[map_name]
529        self.active_map.makeActive()
530        self.game_state.current_map_name = map_name
531
532    def createMapObject (self, layer, attributes, instance):
533        """Create an object and add it to the current map.
534           @type layer: fife.Layer
535           @param layer: FIFE layer object exists in
536           @type attributes: Dictionary
537           @param attributes: Dictionary of all object attributes
538           @type instance: fife.Instance
539           @param instance: FIFE instance corresponding to the object
540           @return: None"""
541        # create the extra data
542        extra = {}
543        if layer is not None:
544            extra['agent_layer'] = layer
545        attributes = self.checkAttributes(attributes)
546       
547        obj = createObject(attributes, extra)
548       
549        if obj.trueAttr("PC"):
550            self.addPC(layer, obj, instance)
551        else:
552            self.addObject(layer, obj, instance) 
553
554    def addPC(self, layer, player_char, instance):
555        """Add the PlayerCharacter to the map
556           @type layer: fife.Layer
557           @param layer: FIFE layer object exists in
558           @type player_char: PlayerCharacter
559           @param player_char: PlayerCharacter object
560           @type instance: fife.Instance
561           @param instance: FIFE instance of PlayerCharacter
562           @return: None"""
563        # For now we copy the PlayerCharacter,
564        # in the future we will need to copy
565        # PlayerCharacter specifics between the different PlayerCharacter's
566        self.game_state.player_character = player_char
567        self.game_state.player_character.setup()       
568
569    def addObject(self, layer, obj, instance):
570        """Adds an object to the map.
571           @type layer: fife.Layer
572           @param layer: FIFE layer object exists in
573           @type obj: GameObject
574           @param obj: corresponding object class
575           @type instance: fife.Instance
576           @param instance: FIFE instance of object
577           @return: None"""
578        ref = self.game_state.getObjectById(obj.ID, \
579                                            self.game_state.current_map_name) 
580        if ref is None:
581            # no, add it to the game state
582            self.game_state.addObject(self.game_state.current_map_name, obj)
583        else:
584            # yes, use the current game state data
585            obj.X = ref.X
586            obj.Y = ref.Y
587            obj.gfx = ref.gfx 
588             
589        if obj.trueAttr("NPC"):
590            # create the agent
591            obj.setup()
592            # create the PlayerCharacter agent
593            obj.start()
594        if obj.trueAttr("AnimatedContainer"):
595            # create the agent
596            obj.setup()
597
598    def objectActive(self, ident):
599        """Given the objects ID, pass back the object if it is active,
600           False if it doesn't exist or not displayed
601           @type ident: string
602           @param ident: ID of object
603           @rtype: boolean
604           @return: Status of result (True/False)"""
605        for game_object in \
606           self.game_state.getObjectsFromMap(self.game_state.current_map_name):
607            if (game_object.ID == ident):
608                # we found a match
609                return game_object
610        # no match
611        return False   
612
613    def movePlayer(self, position):
614        """Code called when the player should move to another location
615           @type position: fife.ScreenPoint
616           @param position: Screen position to move to
617           @return: None"""
618        if(self.pc_run == 1):
619            self.game_state.player_character.run(position)
620        else:
621            self.game_state.player_character.walk(position)
622       
623    def teleportAgent(self, agent, position):
624        """Code called when an agent should teleport to another location
625           @type position: fife.ScreenPoint
626           @param position: Screen position to teleport to
627           @return: None"""
628        agent.teleport(position)
629        self.agents[agent.ID]["Position"] = position
630
631    def readObjectDB(self):
632        """Reads the Object Information Database from a file. """
633        database_file = file(self.object_db_file, "r")
634        database = yaml.load_all(database_file)
635        for object_info in database:
636            self.object_db.update(object_info)
637
638    def getAgentImportFiles(self):
639        """Searches the agents directory for import files """
640        files = locateFiles("*.xml", self.agents_directory)
641        for xml_file in files:
642            xml_file = os.path.relpath(xml_file).replace("\\", "/")
643            root = ElementTree.parse(xml_file).getroot()
644            if root.tag == "object":
645                self.agent_import_files[root.attrib["id"]] = xml_file
646   
647    def getDialogues(self):
648        """Searches the dialogue directory for dialogues """
649        files = locateFiles("*.yaml", self.dialogues_directory)
650        for dialogue_file in files:
651            dialogue_file = os.path.relpath(dialogue_file).replace("\\", "/")
652            dialogues = yaml.load_all(file(dialogue_file, "r"))
653            for dialogue in dialogues:
654                self.dialogues[dialogue["NPC"]] = dialogue
Note: See TracBrowser for help on using the repository browser.