source: trunk/game/scripts/engine.py @ 316

Revision 314, 11.1 KB checked in by eliedebrauwer, 10 years ago (diff)

Ticket #66: Patch by saritor, eliedebrauwer & kaydeth cross map teleporting is now possible, we extended the objects within gamestate to make use of double hashing (f(obj_id,map_id)->object). Going back to a previously loaded map is still broken (camera issue). Also extended map.xml and map2.xml in order to make it possible to go back and forth between these two maps. comment[s:trac, t:66]

  • Property svn:eol-style set to native
RevLine 
[54]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!
[312]19import pickle
20import sys
[177]21from gamestate import GameState
[187]22from objects import *
[212]23from objects.action import *
[54]24
25
26class Engine:
[142]27    """Engine holds the logic for the game.
[54]28       Since some data (object position and so forth) is held in the
29       fife, and would be pointless to replicate, we hold a instance of
30       the fife view here. This also prevents us from just having a
[142]31       function heavy controller."""
[150]32   
[66]33    def __init__(self, view):
[314]34        """Initialize the instance.
[144]35           @type view: world
36           @param view: A world instance
[142]37           @return: None"""
[151]38        # a World object (the fife stuff, essentially)
[66]39        self.view = view
[312]40        self.map_change = False
41        self.game_state = GameState()
[295]42        self.pc_run = 1
[312]43        self.target_position = None
[314]44        self.target_map_name = None
45        self.target_map_file = None
[128]46    def reset(self):
47        """Clears the data on a map reload so we don't have objects/npcs from
[142]48           other maps hanging around.
49           @return: None"""
[128]50
[162]51    def save(self, path, filename):
[150]52        """Writes the saver to a file.
53           @type filename: string
54           @param filename: the name of the file to write to
55           @return: None"""
[162]56        fname = '/'.join([path,filename])
[151]57        try:
[162]58            f = open(fname, 'w')
[151]59        except(IOError):
[177]60            sys.stderr.write("Error: Can't find save game: " + fname + "\n")
[151]61            return
[198]62       
63        # can't pickle SwigPyObjects
64        behaviours = {}
[312]65        behaviours[self.game_state.PC.ID] = self.game_state.PC.behaviour;
66        self.game_state.PC.behaviour = None;
[198]67       
[312]68        npcs = [npc for npc in self.game_state.objects.values() \
69                if npc.trueAttr("NPC")]
[198]70        for npc in npcs:
71            behaviours[npc.ID] = npc.behaviour;
72            npc.behaviour = None;
73       
[312]74        pickle.dump(self.game_state, f)
[148]75        f.close()
[198]76       
77        # restore behaviours
78        for npc in npcs:
79            npc.behaviour = behaviours[npc.ID];
[312]80        self.game_state.PC.behaviour = behaviours[self.game_state.PC.ID]
[148]81
[162]82    def load(self, path, filename):
[150]83        """Loads a saver from a file.
[310]84           @type path: string
[313]85           @param path: the path where the save file is located
[150]86           @type filename: string
87           @param filename: the name of the file to load from
88           @return: None"""
[162]89        fname = '/'.join([path, filename])
[151]90        try:
[162]91            f = open(fname, 'r')
[151]92        except(IOError):
93            sys.stderr.write("Error: Can't find save game file\n")
94            return
[312]95        self.game_state = pickle.load(f)
[148]96        f.close()
[313]97        if self.game_state.current_map:
98            self.loadMap(self.game_state.current_map_name, \
[314]99                         self.game_state.current_map_file) 
[148]100
[264]101    def createObject (self, layer, attributes, instance):
102        """Create an object and add it to the current map.
[310]103           @type layer: fife.Layer
104           @param layer: FIFE layer object exists in
105           @type attributes: Dictionary
106           @param attributes: Dictionary of all object attributes
107           @type instance: fife.Instance
108           @param instance: FIFE instance corresponding to the object
109           @return: None
[264]110        """
[198]111        # create the extra data
112        extra = {}
[264]113        extra['agent_layer'] = layer
[198]114        extra['engine'] = self
115       
[264]116        obj = createObject(attributes, extra)
117       
118        if obj.trueAttr("PC"):
[312]119            self.addPC(layer, obj, instance)
[264]120        else:
[312]121            self.addObject(layer, obj, instance)
[178]122
[187]123       
[264]124
125    def addPC(self, layer, pc, instance):
126        """Add the PC to the map
[310]127           @type layer: fife.Layer
128           @param layer: FIFE layer object exists in
129           @type pc: PlayerCharacter
130           @param pc: PlayerCharacter object
131           @type instance: fife.Instance
132           @param instance: FIFE instance of PC
133           @return: None
[264]134        """
[314]135        # If this map has already a PC
[313]136        self.view.active_map.addObject(pc.ID, instance)         
[264]137       
[314]138        # For now we copy the PC, in the future we will need to copy
139        # PC specifics between the different PC's
140        self.game_state.PC = pc
[187]141           
[312]142        self.game_state.PC.setup()
[62]143
[264]144    def addObject(self, layer, obj, instance):
145        """Adds an object to the map.
[310]146           @type layer: fife.Layer
147           @param layer: FIFE layer object exists in
148           @type obj: GameObject
149           @param obj: corresponding object class
150           @type instance: fife.Instance
151           @param instance: FIFE instance of object
[314]152           @return: None
[264]153        """
[314]154
155        ref = self.game_state.getObjectById(obj.ID, \
156                                            self.game_state.current_map_name) 
[198]157        if ref is None:
158            # no, add it to the game state
[314]159            self.game_state.objects[self.game_state.current_map_name][obj.ID] = obj
[198]160        else:
161            # yes, use the current game state data
162            obj.X = ref.X
163            obj.Y = ref.Y
164            obj.gfx = ref.gfx 
[187]165           
[198]166        # add it to the view
[313]167        self.view.active_map.addObject(obj.ID, instance)         
[310]168
[198]169        if obj.trueAttr("NPC"):
[150]170            # create the agent
[198]171            obj.setup()
[187]172           
173            # create the PC agent
[198]174            obj.start()
[58]175
[104]176    def objectActive(self, ident):
177        """Given the objects ID, pass back the object if it is active,
[142]178           False if it doesn't exist or not displayed
179           @type ident: string
180           @param ident: ID of object
181           @rtype: boolean
182           @return: Status of result (True/False)"""
[314]183        for i in self.game_state.getObjectsFromMap(self.game_state.current_map_name):
[187]184            if (i.ID == ident):
[104]185                # we found a match
[310]186                return i
[104]187        # no match
188        return False
189
[121]190    def getItemActions(self, obj_id):
[142]191        """Given the objects ID, return the text strings and callbacks.
192           @type obj_id: string
193           @param obj_id: ID of object
194           @rtype: list
195           @return: List of text and callbacks"""
[121]196        actions=[]
[122]197        # note: ALWAYS check NPC's first!
[314]198        obj = self.game_state.getObjectById(obj_id, \
199                                            self.game_state.current_map_name)
[187]200       
[312]201        if obj is not None:
[187]202            if obj.trueAttr("NPC"):
203                # keep it simple for now, None to be replaced by callbacks
204                actions.append(["Talk", "Talk", self.initTalk, obj])
[310]205                actions.append(["Attack", "Attack", self.nullFunc, obj])
[187]206            else:
[312]207                actions.append(["Examine", "Examine", \
208                                self.game_state.PC.approach, [obj.X, obj.Y], \
209                                ExamineBoxAction(self, obj.name, obj.text)])
[310]210                # is it a Door?
211                if obj.trueAttr("door"):
212                    actions.append(["Change Map", "Change Map", \
[312]213                       self.game_state.PC.approach, [obj.X, obj.Y], \
[310]214                            ChangeMapAction(self, obj.target_map_name, \
215                                obj.target_map, obj.target_pos)])
[187]216                # is it a container?
217                if obj.trueAttr("container"):
[312]218                    actions.append(["Open", "Open", 
219                                    self.game_state.PC.approach, \
220                                    [obj.X, obj.Y], \
221                                    OpenBoxAction(self, "Box")])
[187]222                # can you pick it up?
223                if obj.trueAttr("carryable"):
[310]224                    actions.append(["Pick Up", "Pick Up", self.nullFunc, obj])
225
[150]226        return actions
227   
228    def nullFunc(self, userdata):
229        """Sample callback for the context menus."""
230        print userdata
[153]231   
[156]232    def initTalk(self, npcInfo):
[153]233        """ Starts the PC talking to an NPC. """
234        # TODO: work more on this when we get NPCData and HeroData straightened
235        # out
[314]236        npc = self.game_state.getObjectById(npcInfo.ID, \
237                                            self.game_state.current_map_name)
[312]238        self.game_state.PC.approach([npc.getLocation().\
239                                     getLayerCoordinates().x, \
240                                     npc.getLocation().\
241                                     getLayerCoordinates().y], \
242                                     TalkAction(self, npc))
[121]243
[253]244    def loadMap(self, map_name, map_file):
[314]245        """Load a new map.
[302]246           @type map_name: string
247           @param map_name: Name of the map to load
[142]248           @type map_file: string
[302]249           @param map_file: Filename of map file to load
[142]250           @return: None"""
[314]251        self.game_state.current_map_file = map_file
252        self.game_state.current_map_name = map_name
[253]253        self.view.loadMap(map_name, str(map_file))
254        self.view.setActiveMap(map_name)
[310]255        self.reset()
256
[264]257        # create the PC agent
[313]258        self.view.active_map.addPC(self.game_state.PC.behaviour.agent)
[312]259        self.game_state.PC.start()
[264]260
[62]261    def handleMouseClick(self,position):
[142]262        """Code called when user left clicks the screen.
263           @type position: fife.ScreenPoint
264           @param position: Screen position of click
265           @return: None"""
[312]266        if(self.pc_run == 1):
267            self.game_state.PC.run(position)
[295]268        else:
[312]269            self.game_state.PC.walk(position)
[310]270
[314]271    def changeMap(self, map_name, map_file, target_position):
[312]272        """Registers for a map change on the next pump().
[314]273           @type name_name: String
[313]274           @param map_name: Id of the map to teleport to
[314]275           @type map_file: String
276           @param map_file: Filename of the map to teleport to
[312]277           @type target_position: Tuple
278           @param target_position: Position of PC on target map.
[310]279           @return None"""
[312]280        # set the parameters for the map change if moving to a new map
[313]281        if map_name != self.game_state.current_map_name:
[314]282            self.target_map_name = map_name
283            self.target_map_file = map_file
[312]284            self.target_position = target_position
285            # issue the map change
286            self.map_change = True
[310]287        else:
288            #set the player position on the current map
[312]289            self.view.teleport(target_position)
[150]290
291    def handleCommands(self):
[312]292        if self.map_change:
[314]293            self.loadMap(self.target_map_name, self.target_map_file)
[312]294            self.view.teleport(self.target_position)
295            self.map_change = False
[150]296
297    def pump(self):
298        """Main loop in the engine."""
299        self.handleCommands()
Note: See TracBrowser for help on using the repository browser.