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