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

Revision 287, 10.4 KB checked in by orlandov, 10 years ago (diff)

Ticket #74: Patch by or1andov, Integrate dialogue GUI and engine into game.

It should now be possible to engage in a conversation with NPC characters by
selecting talk from their context menu. fixes[s:trac, t:74]

Known Issues:


  • the GUI is terrible. instead of a dialog window, we could use a better looking background image and fixed position widgets
  • requires PyYAML to be installed sudo apt-get install python-yaml easy_install PyYAML
  • we have to create a simple state model for the PC can remember tasks
  • need to make NPC's be able to remember things - need a model in NPCs for this
  • no way currently to give or take items from npc's; again, more model work. this could be done by adding another callback similar to start_quest. may be necessary for npc's to have an inventory for this
  • The dialogue engine is not hooked up to specific NPC's... it currently always loads dialogue/sample.yaml - need to have the dialogue looked up from some XML attribute
  • dialogue is not broken off if the PC moves away from the NPC
  • npc image is hardcoded, it should use the image specified in the yaml file
  • 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, sys
20from gamestate import GameState
21from objects import *
22from objectLoader import ObjectXMLParser
23from objects.action import *
24
25# design note:
26# there is a map file that FIFE reads. We use that file for half the map
27# format because the map editor in FIFE uses it, and secondly because it
28# save us writing a bunch of new code.
29# However, the objects and characters on a map are liable to change
30# whilst the game is being run, so when we change the map, we need to
31# to grab the objects and npc data EITHER from the engine state, or grab
32# from another file if in their initial state
33# This other file has the name AAA_objects.xml where AAA.xml is the name
34# of the original mapfile.
35
36class Engine:
37    """Engine holds the logic for the game.
38       Since some data (object position and so forth) is held in the
39       fife, and would be pointless to replicate, we hold a instance of
40       the fife view here. This also prevents us from just having a
41       function heavy controller."""
42   
43    def __init__(self, view):
44        """Initialise the instance.
45           @type view: world
46           @param view: A world instance
47           @return: None"""
48        # a World object (the fife stuff, essentially)
49        self.view = view
50        self.mapchange = False
51        self.gameState = GameState()
52
53    def reset(self):
54        """Clears the data on a map reload so we don't have objects/npcs from
55           other maps hanging around.
56           @return: None"""
57
58    def save(self, path, filename):
59        """Writes the saver to a file.
60           @type filename: string
61           @param filename: the name of the file to write to
62           @return: None"""
63        fname = '/'.join([path,filename])
64        try:
65            f = open(fname, 'w')
66        except(IOError):
67            sys.stderr.write("Error: Can't find save game: " + fname + "\n")
68            return
69       
70        # can't pickle SwigPyObjects
71        behaviours = {}
72        behaviours[self.gameState.PC.ID] = self.gameState.PC.behaviour;
73        self.gameState.PC.behaviour = None;
74       
75        npcs = [npc for npc in self.gameState.objects.values() if npc.trueAttr("NPC")]
76        for npc in npcs:
77            behaviours[npc.ID] = npc.behaviour;
78            npc.behaviour = None;
79       
80        pickle.dump(self.gameState, f)
81        f.close()
82       
83        # restore behaviours
84        for npc in npcs:
85            npc.behaviour = behaviours[npc.ID];
86        self.gameState.PC.behaviour = behaviours[self.gameState.PC.ID]
87
88    def load(self, path, filename):
89        """Loads a saver from a file.
90           @type filename: string
91           @param filename: the name of the file to load from
92           @return: None"""
93        fname = '/'.join([path, filename])
94        try:
95            f = open(fname, 'r')
96        except(IOError):
97            sys.stderr.write("Error: Can't find save game file\n")
98            return
99        self.gameState = pickle.load(f)
100        f.close()
101        if self.gameState.currentMap:
102            self.loadMap(self.gameState.currentMap) 
103
104    def createObject (self, layer, attributes, instance):
105        """Create an object and add it to the current map.
106            Inputs:
107                layer = FIFE layer object exists in
108                attributes = dictionary of all object attributes
109                instance = FIFE instance corresponding to the object
110            Return:
111                Nothing
112        """
113        # create the extra data
114        extra = {}
115        extra['agent_layer'] = layer
116        extra['engine'] = self
117       
118        obj = createObject(attributes, extra)
119       
120        if obj.trueAttr("PC"):
121            self.addPC( layer, obj, instance)
122        else:
123            self.addObject( layer, obj, instance)
124
125       
126
127    def addPC(self, layer, pc, instance):
128        """Add the PC to the map
129            Inputs:
130                layer = FIFE layer object exists in
131                pc = PlayerCharacter object
132                instance = FIFE instance of PC
133            Returns:
134                Nothing
135        """
136        # add to view data
137        self.view.activeMap.addObject(pc.ID, instance)         
138       
139        # sync with game data
140        if not self.gameState.PC:
141            self.gameState.PC = pc
142           
143        self.gameState.PC.setup()
144
145
146    def addObject(self, layer, obj, instance):
147        """Adds an object to the map.
148            Inputs:
149                layer = FIFE layer object exists in
150                obj = corresponding object class
151                instance = FIFE instance of object
152            Returns:
153                Nothing
154        """
155       
156        ref = self.gameState.getObjectById(obj.ID) 
157        if ref is None:
158            # no, add it to the game state
159            obj.map_id = self.gameState.currentMap
160            self.gameState.objects[obj.ID] = obj
161        else:
162            # yes, use the current game state data
163            obj.X = ref.X
164            obj.Y = ref.Y
165            obj.gfx = ref.gfx 
166           
167        # add it to the view
168        self.view.activeMap.addObject(obj.ID, instance)         
169       
170        if obj.trueAttr("NPC"):
171            # create the agent
172            obj.setup()
173           
174            # create the PC agent
175            obj.start()
176
177    def addDoors(self, doors):
178        """Add all the doors to the map as well.
179           As an object they will have already been added.
180           @type doors: list
181           @param doors: List of doors
182           @return: None"""
183        for i in doors:
184            self.doors[str(i.id)] = MapDoor(i.id, i.destmap, (i.destx, i.desty))
185
186    def objectActive(self, ident):
187        """Given the objects ID, pass back the object if it is active,
188           False if it doesn't exist or not displayed
189           @type ident: string
190           @param ident: ID of object
191           @rtype: boolean
192           @return: Status of result (True/False)"""
193        for i in self.gameState.getObjectsFromMap(self.gameState.currentMap):
194            if (i.ID == ident):
195                # we found a match
196                return i         
197        # no match
198        return False
199
200    def getItemActions(self, obj_id):
201        """Given the objects ID, return the text strings and callbacks.
202           @type obj_id: string
203           @param obj_id: ID of object
204           @rtype: list
205           @return: List of text and callbacks"""
206        actions=[]
207        # note: ALWAYS check NPC's first!
208        obj = self.gameState.getObjectById(obj_id)
209       
210        if obj:
211            if obj.trueAttr("NPC"):
212                # keep it simple for now, None to be replaced by callbacks
213                actions.append(["Talk", "Talk", self.initTalk, obj])
214                actions.append(["Attack", "Attack", self.nullFunc, obj]) 
215            elif obj.trueAttr("Door"):
216                actions.append(["Change Map", "Change Map", \
217                       self.gameState.PC.approach, [obj.X, obj.Y], \
218                        ChangeMapAction(self, self.doors[str(i.ID)].map, [i.destx, i.desty])])
219                pass
220            else:
221                actions.append(["Examine", "Examine", self.gameState.PC.approach, 
222                                [obj.X, obj.Y], ExamineBoxAction(self, obj.name, obj.text)])
223                # is it a container?
224                if obj.trueAttr("container"):
225                    actions.append(["Open", "Open", self.gameState.PC.approach, [obj.X, obj.Y], OpenBoxAction(self, "Box")])
226                # can you pick it up?
227                if obj.trueAttr("carryable"):
228                    actions.append(["Pick Up", "Pick Up", self.nullFunc, obj])       
229                   
230        return actions
231   
232    def nullFunc(self, userdata):
233        """Sample callback for the context menus."""
234        print userdata
235   
236    def initTalk(self, npcInfo):
237        """ Starts the PC talking to an NPC. """
238        # TODO: work more on this when we get NPCData and HeroData straightened
239        # out
240        npc = self.gameState.getObjectById(npcInfo.ID)
241        self.gameState.PC.approach([npc.getLocation().getLayerCoordinates().x, npc.getLocation().getLayerCoordinates().y], TalkAction(self, npc))
242
243    def loadMap(self, map_name, map_file):
244        """Load a new map. TODO: needs some error checking
245           @type map_file: string
246           @param map_file: Name of map file to load
247           @return: None"""
248        self.gameState.currentMap = map_file
249        self.view.loadMap(map_name, str(map_file))
250        self.view.setActiveMap(map_name)
251
252        self.reset()       
253       
254        # create the PC agent
255        self.view.activeMap.addPC(self.gameState.PC.behaviour.agent)
256        self.gameState.PC.start()
257
258    def handleMouseClick(self,position):
259        """Code called when user left clicks the screen.
260           @type position: fife.ScreenPoint
261           @param position: Screen position of click
262           @return: None"""
263        self.gameState.PC.run(position)
264       
265    def changeMap(self, map, targetPosition):
266        """Registers for a mapchange on the next pump().
267           @type map: ???
268           @param map: Name of the target map.
269           @type targetPosition: ???
270           @param targetPosition: Position of PC on target map.
271           @return: None"""
272        # save the postions
273        self.updateGameState()
274        # set the PC position
275        self.gameState.PC.posx = targetPosition[0]
276        self.gameState.PC.posy = targetPosition[1]
277        # set the parameters for the mapchange
278        self.targetMap = map
279        # issue the mapchange
280        self.mapchange = True
281
282    def handleCommands(self):
283        if self.mapchange:
284            self.loadMap(self.targetMap)
285            self.mapchange = False
286
287    def pump(self):
288        """Main loop in the engine."""
289        self.handleCommands()
290
Note: See TracBrowser for help on using the repository browser.