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

Revision 563, 25.2 KB checked in by beliar, 9 years ago (diff)

Ticket #200: Patch by Beliar.

  • Merging map_loading_change branch back into trunk

fixes[s:trac, t:200]

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