comparison src/parpg/objects/actors.py @ 0:1fd2201f5c36

Initial commit of parpg-core.
author M. George Hansen <technopolitica@gmail.com>
date Sat, 14 May 2011 01:12:35 -0700
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:1fd2201f5c36
1 # This file is part of PARPG.
2
3 # PARPG is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation, either version 3 of the License, or
6 # (at your option) any later version.
7
8 # PARPG is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12
13 # You should have received a copy of the GNU General Public License
14 # along with PARPG. If not, see <http://www.gnu.org/licenses/>.
15
16 from random import randrange
17
18
19 from fife import fife
20
21 from base import GameObject, Living, Scriptable, CharStats
22 from composed import CarryableItem
23 from parpg.inventory import Inventory
24
25 """All actors go here. Concrete classes only."""
26
27 __all__ = ["PlayerCharacter", "NonPlayerCharacter", ]
28
29 _AGENT_STATE_NONE, _AGENT_STATE_IDLE, _AGENT_STATE_APPROACH, _AGENT_STATE_RUN, _AGENT_STATE_WANDER, _AGENT_STATE_TALK = xrange(6)
30
31 class ActorBehaviour (fife.InstanceActionListener):
32 """Fife agent listener"""
33 def __init__(self, layer):
34 fife.InstanceActionListener.__init__(self)
35 self.layer = layer
36 self.agent = None
37 self.state = None
38 self.speed = 0
39 self.idle_counter = 1
40
41 def attachToLayer(self, agent_ID):
42 """Attaches to a certain layer
43 @type agent_ID: String
44 @param agent_ID: ID of the layer to attach to.
45 @return: None"""
46 self.agent = self.layer.getInstance(agent_ID)
47 self.agent.addActionListener(self)
48 self.state = _AGENT_STATE_NONE
49
50 def getX(self):
51 """Get the NPC's x position on the map.
52 @rtype: integer"
53 @return: the x coordinate of the NPC's location"""
54 return self.agent.getLocation().getLayerCoordinates().x
55
56 def getY(self):
57 """Get the NPC's y position on the map.
58 @rtype: integer
59 @return: the y coordinate of the NPC's location"""
60 return self.agent.getLocation().getLayerCoordinates().y
61
62 def onNewMap(self, layer):
63 """Sets the agent onto the new layer."""
64 if self.agent is not None:
65 self.agent.removeActionListener(self)
66
67 self.agent = layer.getInstance(self.parent.ID)
68 self.agent.addActionListener(self)
69 self.state = _AGENT_STATE_NONE
70 self.idle_counter = 1
71
72 def idle(self):
73 """@return: None"""
74 self.state = _AGENT_STATE_IDLE
75 self.agent.act('stand', self.agent.getFacingLocation())
76
77 def onInstanceActionFinished(self, instance, action):
78 pass
79
80 class PCBehaviour (ActorBehaviour):
81 def __init__(self, parent=None, layer=None):
82 super(PCBehaviour, self).__init__(layer)
83 self.parent = parent
84 self.idle_counter = 1
85 self.speed = 0
86 self.nextAction = None
87 self.agent = None
88
89 def onInstanceActionFinished(self, instance, action):
90 """@type instance: ???
91 @param instance: ???
92 @type action: ???
93 @param action: ???
94 @return: None"""
95 # First we reset the next behavior
96 act = self.nextAction
97 self.nextAction = None
98 self.idle()
99
100 if act:
101 act.execute()
102
103 if(action.getId() != 'stand'):
104 self.idle_counter = 1
105 else:
106 self.idle_counter += 1
107
108
109 class NPCBehaviour(ActorBehaviour):
110 def __init__(self, Parent=None, Layer=None):
111 super(NPCBehaviour, self).__init__(Layer)
112
113 self.parent = Parent
114 self.state = _AGENT_STATE_NONE
115 self.pc = None
116 self.target_loc = None
117 self.nextAction = None
118
119 # hard code these for now
120 self.distRange = (2, 4)
121 # these are parameters to lower the rate of wandering
122 # wander rate is the number of "IDLEs" before a wander step
123 # this could be set for individual NPCs at load time
124 # or thrown out altogether.
125 self.wanderCounter = 0
126 self.wanderRate = 9
127
128 def getTargetLocation(self):
129 """@rtype: fife.Location
130 @return: NPC's position"""
131 x = self.getX()
132 y = self.getY()
133 if self.state == _AGENT_STATE_WANDER:
134 """ Random Target Location """
135 l = [0, 0]
136 for i in range(len(l)):
137 sign = randrange(0, 2)
138 dist = randrange(self.distRange[0], self.distRange[1])
139 if sign == 0:
140 dist *= -1
141 l[i] = dist
142 x += l[0]
143 y += l[1]
144 # Random walk is
145 # rl = randint(-1, 1);ud = randint(-1, 1);x += rl;y += ud
146 l = fife.Location(self.agent.getLocation())
147 l.setLayerCoordinates(fife.ModelCoordinate(x, y))
148 return l
149
150 def onInstanceActionFinished(self, instance, action):
151 """What the NPC does when it has finished an action.
152 Called by the engine and required for InstanceActionListeners.
153 @type instance: fife.Instance
154 @param instance: self.agent (the NPC listener is listening for this
155 instance)
156 @type action: ???
157 @param action: ???
158 @return: None"""
159 if self.state == _AGENT_STATE_WANDER:
160 self.target_loc = self.getTargetLocation()
161 self.idle()
162
163
164 def idle(self):
165 """Controls the NPC when it is idling. Different actions
166 based on the NPC's state.
167 @return: None"""
168 if self.state == _AGENT_STATE_NONE:
169 self.state = _AGENT_STATE_IDLE
170 self.agent.act('stand', self.agent.getFacingLocation())
171 elif self.state == _AGENT_STATE_IDLE:
172 if self.wanderCounter > self.wanderRate:
173 self.wanderCounter = 0
174 self.state = _AGENT_STATE_WANDER
175 else:
176 self.wanderCounter += 1
177 self.state = _AGENT_STATE_NONE
178
179 self.target_loc = self.getTargetLocation()
180 self.agent.act('stand', self.agent.getFacingLocation())
181 elif self.state == _AGENT_STATE_WANDER:
182 self.parent.wander(self.target_loc)
183 self.state = _AGENT_STATE_NONE
184 elif self.state == _AGENT_STATE_TALK:
185 self.agent.act('stand', self.pc.getLocation())
186
187 class CharacterBase(GameObject, CharStats, Living):
188 """Base class for Characters"""
189 def __init__(self, ID, agent_layer=None, inventory=None, text="",
190 primary_stats=None, secondary_stats=None, **kwargs):
191 GameObject.__init__(self, ID, text=text, **kwargs)
192 CharStats.__init__(self, **kwargs)
193 Living.__init__(self, **kwargs)
194 self.statistics = {}
195 if primary_stats is not None:
196 for primary_stat in primary_stats:
197 name = primary_stat.name
198 self.statistics[name] = primary_stat
199 if secondary_stats is not None:
200 for secondary_stat in primary_stats:
201 long_name = secondary_stat.long_name
202 self.statistics[long_name] = secondary_stat
203 short_name = secondary_stat.short_name
204 self.statistics[short_name] = secondary_stat
205 secondary_stat.attach(self)
206 self.behaviour = None
207 if inventory == None:
208 self.inventory = Inventory()
209 else:
210 self.inventory = inventory
211 self.state = _AGENT_STATE_NONE
212 self.layer_id = agent_layer.getId()
213 self.createBehaviour(agent_layer)
214
215 def createBehaviour(self, layer):
216 """Creates the behaviour for this actor.
217 @return: None"""
218 pass
219
220 def setup(self):
221 """@return: None"""
222 self.behaviour.attachToLayer(self.ID)
223
224 def start(self):
225 """@return: None"""
226 self.behaviour.idle()
227
228 def teleport(self, location):
229 """Teleports a Character instantly to the given location.
230 @type location: fife.Location
231 @param location: Target coordinates for Character.
232 @return: None"""
233 self.state = _AGENT_STATE_IDLE
234 self.behaviour.nextAction = None
235 self.behaviour.agent.setLocation(location)
236
237 def give (self, item, actor):
238 """Gives the specified item to the different actor. Raises an exception if the item was invalid or not found
239 @type item: Carryable
240 @param item: The item object to give
241 @param actor: Person to give item to"""
242 if item == None:
243 raise ValueError("I don't have %s" % item.name)
244 self.inventory.takeItem(item)
245 actor.inventory.placeItem(item)
246
247 def hasItem(self, item_type):
248 """Returns wether an item is present in the players inventory or not
249 @param item_type: ID of the item
250 @type item_type: str
251 @return: True when the item is present, False when not"""
252 return self.inventory.findItem(item_type=item_type)
253
254 def itemCount(self, item_type=""):
255 """Returns number of all items or items specified by item_type
256 the player has.
257 @param item_type: ID of the item, can be empty
258 @type item_type: str
259 @return: Number of items"""
260 return self.inventory.count(item_type)
261
262 def getLocation(self):
263 """Get the NPC's position as a fife.Location object. Basically a
264 wrapper.
265 @rtype: fife.Location
266 @return: the location of the NPC"""
267 return self.behaviour.agent.getLocation()
268
269 def run(self, location):
270 """Makes the PC run to a certain location
271 @type location: fife.ScreenPoint
272 @param location: Screen position to run to.
273 @return: None"""
274 self.state = _AGENT_STATE_RUN
275 self.behaviour.nextAction = None
276 self.behaviour.agent.move('run', location, self.behaviour.speed + 1)
277
278 def walk(self, location):
279 """Makes the PC walk to a certain location.
280 @type location: fife.ScreenPoint
281 @param location: Screen position to walk to.
282 @return: None"""
283 self.state = _AGENT_STATE_RUN
284 self.behaviour.nextAction = None
285 self.behaviour.agent.move('walk', location, self.behaviour.speed - 1)
286
287 def getStateForSaving(self):
288 """Returns state for saving
289 """
290 ret_dict = GameObject.getStateForSaving(self)
291 ret_dict["Inventory"] = self.inventory.serializeInventory()
292 return ret_dict
293
294 def _getCoords(self):
295 """Get-er property function"""
296 return (self.getLocation().getMapCoordinates().x,
297 self.getLocation().getMapCoordinates().y)
298
299 def _setCoords(self, coords):
300 """Set-er property function"""
301 map_coords = self.getLocation().getMapCoordinates()
302 map_coords.X, map_coords.Y = float(coords[0]), float (coords[1])
303 self.teleport(map_coords)
304
305 coords = property (_getCoords, _setCoords,
306 doc="Property allowing you to get and set the object's \
307 coordinates via tuples")
308
309 class PlayerCharacter (CharacterBase):
310 """PC class"""
311 def __init__ (self, ID, agent_layer=None, inventory=None,
312 text="Its you. Who would've thought that?", **kwargs):
313 if inventory == None:
314 inventory = Inventory()
315 inventory.placeItem(CarryableItem(ID=456, name="Dagger123"))
316 inventory.placeItem(CarryableItem(ID=555, name="Beer"))
317 inventory.placeItem(CarryableItem(ID=616,
318 name="Pamphlet",
319 image="/gui/inv_images/inv_pamphlet.png"))
320 CharacterBase.__init__(self, ID, agent_layer, inventory, text, **kwargs)
321 self.people_i_know = set()
322 self.attributes.append("PC")
323
324 def getStateForSaving(self):
325 """Returns state for saving
326 """
327 ret_dict = super(PlayerCharacter, self).getStateForSaving()
328 ret_dict["PeopleKnown"] = self.people_i_know
329 return ret_dict
330
331 def meet(self, npc):
332 """Record that the PC has met a certain NPC
333 @type npc: str
334 @param npc: The NPC's name or id"""
335 if npc in self.people_i_know:
336 # we could raise an error here, but should probably be a warn
337 # raise RuntimeError("I already know %s" % npc)
338 return
339 self.people_i_know.add(npc)
340
341 def met(self, npc):
342 """Indicate whether the PC has met this npc before
343 @type npc: str
344 @param npc: The NPC's name or id
345 @return: None"""
346 return npc in self.people_i_know
347
348 def createBehaviour(self, layer):
349 """Creates the behaviour for this actor.
350 @return: None"""
351 self.behaviour = PCBehaviour(self, layer)
352
353 def approach(self, location, action=None):
354 """Approaches a location and then perform an action (if set).
355 @type loc: fife.Location
356 @param loc: the location to approach
357 @type action: Action
358 @param action: The action to schedule for execution after the approach.
359 @return: None"""
360 self.state = _AGENT_STATE_APPROACH
361 self.behaviour.nextAction = action
362 boxLocation = tuple([int(float(i)) for i in location])
363 l = fife.Location(self.behaviour.agent.getLocation())
364 l.setLayerCoordinates(fife.ModelCoordinate(*boxLocation))
365 self.behaviour.agent.move('run', l, self.behaviour.speed + 1)
366
367 class NonPlayerCharacter(CharacterBase, Scriptable):
368 """NPC class"""
369 def __init__(self, ID, agent_layer=None, name='NPC', \
370 text='A nonplayer character', inventory=None,
371 real_name='NPC', dialogue=None, **kwargs):
372 # init game object
373 CharacterBase.__init__(self, ID, agent_layer=agent_layer,
374 inventory=inventory, name=name,
375 real_name=real_name, text=text, **kwargs)
376 Scriptable.__init__(self, **kwargs)
377
378 self.attributes.append("NPC")
379 self.dialogue = dialogue
380
381 def prepareStateForSaving(self, state):
382 """Prepares state for saving
383 @type state: dictionary
384 @param state: State of the object
385 """
386 CharacterBase.prepareStateForSaving(self, state)
387 del state["behaviour"]
388
389 def getStateForSaving(self):
390 """Returns state for saving
391 """
392 ret_dict = CharacterBase.getStateForSaving(self)
393 ret_dict["Lives"] = self.lives
394 ret_dict["State"] = self.behaviour.state
395 return ret_dict
396
397 def createBehaviour(self, layer):
398 """Creates the behaviour for this actor.
399 @return None """
400 self.behaviour = NPCBehaviour(self, layer)
401
402 def wander(self, location):
403 """Nice slow movement for random walking.
404 @type location: fife.Location
405 @param location: Where the NPC will walk to.
406 @return: None"""
407 self.behaviour.agent.move('walk', location, self.behaviour.speed - 1)
408
409 def talk(self, pc):
410 """Makes the NPC ready to talk to the PC
411 @return: None"""
412 self.behaviour.state = _AGENT_STATE_TALK
413 self.behaviour.pc = pc.behaviour.agent
414 self.behaviour.idle()