source: branches/active/character_customization/game/parpg/objects/actors.py @ 755

Revision 755, 15.5 KB checked in by aspidites, 8 years ago (diff)

Patch by Aspidites:

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