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

Revision 757, 15.3 KB checked in by aspidites, 8 years ago (diff)

Patch by Aspidites:

+ fixed walk speed regression, though in the future, it may be more elegant

to pass the model's settings instance to a PlayableCharacter? constructor

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