source: trunk/game/scripts/objects/base.py @ 541

Revision 541, 13.0 KB checked in by beliar, 10 years ago (diff)
  • Change to make PARPG run with the latest FIFE revision (3373)
    • Removed argument to addCamera that FIFE no longer needs
  • Changed settings code to use the FIFE extension fife_settings
  • Additions to pylintrc
  • 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"""Containes classes defining the base properties of all interactable in-game
19   objects (such as Carryable, Openable, etc. These are generally independent
20   classes, which can be combined in almost any way and order.
21
22   Some rules that should be followed when CREATING base property classes:
23   
24   1. If you want to support some custom initialization arguments, always define
25      them as keyword ones. Only GameObject would use positional arguments.
26   2. In __init__() **ALWAYS** call the parent's __init__(**kwargs), preferably
27      *at the end* of your __init__() (makes it easier to follow)
28   3. There should always be an is_x class member set to True on __init__
29      (where X is the name of the class)
30
31   EXAMPLE:
32
33   class Openable(object):
34       def __init__ (self, is_open = True, **kwargs):
35           self.is_openable = True
36           self.is_open = is_open
37           super(Openable,self).__init__ (**kwargs)
38       
39
40   Some rules are to be followed when USING the base classes to make composed ones:
41
42   1. The first parent should always be the base GameObject class
43   2. Base classes other than GameObject can be inherited in any order
44   3. The __init__ functoin of the composed class should always invoke the
45      parent's __init__() *before* it starts customizing any variables.
46
47   EXAMPLE:
48
49   class TinCan (GameObject, Container, Scriptable, Destructable, Carryable):
50       def __init__ (self, *args, **kwargs):
51           super(TinCan,self).__init__ (*args, **kwargs)
52           self.name = 'Tin Can'"""
53           
54from fife import fife
55from fife.extensions.fife_settings import Setting
56from random import randrange
57from scripts.gui.popups import ExaminePopup, ContainerGUI
58
59class DynamicObject (object):
60    """A base class that only supports dynamic attributes functionality"""
61    def __init__ (self, name="Dynamic object", image=None, **kwargs):
62        """Initialise minimalistic set of data
63           @type name: String
64           @param name: Object display name
65           @type image: String or None
66           @param name: Filename of image to use in inventory"""
67        self.name = name
68        self.image = image
69
70    def trueAttr(self, attr):
71        """Shortcut function to check if the current object has a member named
72           is_%attr and if that attribute evaluates to True"""
73        return hasattr(self,'is_%s' % attr) and getattr(self, 'is_%s' % attr)
74
75
76class GameObject (DynamicObject):
77    """A base class to be inherited by all game objects. This must be the
78       first class (left to right) inherited by any game object."""
79    def __init__ (self, ID, gfx = {}, xpos = 0.0, ypos = 0.0, map_id = None, 
80                  blocking=True, name="Generic object", text="Item description",
81                  desc="Detailed description", **kwargs):
82        """Set the basic values that are shared by all game objects.
83           @type ID: String
84           @param ID: Unique object identifier. Must be present.
85           @type gfx: Dictionary
86           @param gfx: Dictionary with graphics for the different contexts       
87           @type coords 2-item tuple
88           @param coords: Initial coordinates of the object.
89           @type map_id: String
90           @param map_id: Identifier of the map where the object is located
91           @type blocking: Boolean
92           @param blocking: Whether the object blocks character movement
93           @type name: String
94           @param name: The display name of this object (e.g. 'Dirty crate')
95           @type text: String
96           @param text: A longer description of the item
97           @type desc: String
98           @param desc: A long description of the item that is displayed when it is examined
99           """
100        DynamicObject.__init__(self, name, **kwargs)
101        self.ID = ID
102        self.gfx = gfx
103        self.X = xpos
104        self.Y = ypos
105        self.map_id = map_id
106        self.blocking = True
107        self.text = text
108        self.desc = desc
109       
110    def _getCoords(self):
111        """Get-er property function"""
112        return (self.X, self.Y)
113   
114    def _setCoords(self, coords):
115        """Set-er property function"""
116        self.X, self.Y = float(coords[0]), float (coords[1])
117       
118    coords = property (_getCoords, _setCoords, 
119        doc = "Property allowing you to get and set the obejct's coordinates via tuples")
120   
121    def __repr__(self):
122        """A debugging string representation of the object"""
123        return "<%s:%s>" % (self.name, self.ID)
124
125class Openable(object):
126    """Adds open() and .close() capabilities to game objects
127       The current state is tracked by the .is_open variable"""
128    def __init__(self, is_open = True, **kwargs):
129        """Init operation for openable objects
130           @type is_open: Boolean
131           @param is_open: Keyword boolean argument sets the initial state."""
132        self.is_openable = True
133        self.is_open = is_open
134   
135    def open(self):
136        """Opens the object, and runs an 'onOpen' script, if present"""
137        self.is_open = True
138        try:
139            if self.trueAttr ('scriptable'):
140                self.runScript('onOpen')
141        except AttributeError :
142            pass
143           
144    def close(self):
145        """Opens the object, and runs an 'onClose' script, if present"""
146        self.is_open = False
147        try:
148            if self.trueAttr ('scriptable'):
149                self.runScript('onClose')
150        except AttributeError :
151            pass
152       
153class Lockable (Openable):
154    """Allows objects to be locked"""
155    def __init__ (self, locked = False, is_open = True, **kwargs):
156        """Init operation for lockable objects
157           @type locked: Boolean
158           @param locked: Keyword boolen argument sets the initial locked state.
159           @type is_open: Boolean
160           @param is_open: Keyword boolean argument sets the initial open state.
161                           It is ignored if locked is True -- locked objects
162                           are always closed."""
163        self.is_lockable = True
164        self.locked = locked
165        if locked :
166            is_open=False
167        Openable.__init__( self, is_open, **kwargs )
168       
169    def unlock (self):
170        """Handles unlocking functionality"""
171        self.locked = False     
172       
173    def lock (self):
174        """Handles  locking functionality"""
175        self.close()
176        self.locked = True
177       
178    def open (self, *args, **kwargs):
179        """Adds a check to see if the object is unlocked before running the
180           .open() function of the parent class"""
181        if self.locked:
182            raise ValueError ("Open failed: object locked")
183        super (Lockable,self).open(*args,**kwargs)
184       
185class Carryable (object):
186    """Allows objects to be stored in containers"""
187    def __init__ (self, weight=0.0, bulk=0.0, **kwargs):
188        self.is_carryable = True
189        self.in_container = None
190        self.weight = weight
191        self.bulk=bulk
192
193    def getInventoryThumbnail(self):
194        # TODO: Implement properly after the objects database is in place
195        if self.image == None:
196            return "gui/inv_images/inv_litem.png"
197        else:
198            return self.image
199   
200class Container (object):
201    """Gives objects the capability to hold other objects"""
202    class TooBig(Exception):
203        """Exception to be raised when the object is too big
204        to fit into container"""
205        pass
206   
207    class SlotBusy(Exception):
208        """Exception to be raised when the requested slot is occupied"""
209        pass
210
211    def __init__ (self, capacity = 0, **kwargs):
212        self.is_container = True
213        self.items = {}
214        self.capacity=capacity
215       
216    def placeItem (self, item, index=None):
217        """Adds the provided carriable item to the inventory.
218           Runs an 'onStoreItem' script, if present"""   
219        if not item.trueAttr ('carryable'):
220            raise TypeError ('%s is not carriable!' % item)
221        if self.capacity and self.getContentsBulk()+item.bulk > self.capacity:
222            raise self.TooBig ('%s is too big to fit into %s' % (item,self))
223        item.in_container = self
224        if index == None:
225            self.placeAtVacant(item)
226        else:
227            if index in self.items :
228                raise self.SlotBusy('Slot %d is busy in %s' % (index, self.name))
229            self.items[index]=item
230
231        # Run any scripts associated with storing an item in the container
232        try:
233            if self.trueAttr ('scriptable'):
234                self.runScript('onPlaceItem')
235        except AttributeError :
236            pass
237
238    def placeAtVacant(self, item):
239        vacant = None
240        for i in range(len(self.items)):
241            if i not in self.items :
242                vacant = i
243        if vacant == None :
244            vacant = len(self.items)
245        self.items[vacant] = item
246
247
248    def takeItem (self, item):
249        """Takes the listed item out of the inventory.
250           Runs an 'ontakeItem' script"""       
251        if not item in self.items.values():
252            raise ValueError ('I do not contain this item: %s' % item)
253        del self.items[self.items.keys()[self.items.values().index(item)]]
254
255        # Run any scripts associated with popping an item out of the container
256        try:
257            if self.trueAttr ('scriptable'):
258                self.runScript('onTakeItem')
259        except AttributeError :
260            pass
261
262    def count (self):
263        return len(self.items)
264
265    def getContentsBulk(self):
266        """Bulk of the container contents"""
267        return sum((item.bulk for item in self.items.values()))
268
269    def findItemByID(self, ID):
270        for i in self.items :
271            if self.items[i].ID == ID:
272                return self.items[i]
273        return None
274
275    def findItem(self, **kwargs):
276        """Find an item in container by attributes. All params are optional.
277           @type name: String
278           @param name: If the name is non-unique, return first matching object
279           @type kind: String
280           @param kind: One of the possible object types
281           @return: The item matching criteria or None if none was found"""
282        for i in self.items :
283            if "name" in kwargs and self.items[i].name != kwargs["name"]:
284                continue
285            if "kind" in kwargs and not self.items[i].trueAttr(kwargs["kind"]):
286                continue
287            return self.items[i]
288        return None
289       
290class Living (object):
291    def __init__ (self, **kwargs):
292        self.is_living = True
293
294    def die(self):
295        self.is_living = False
296       
297class Scriptable (object):
298    """Allows objects to have predefined scripts executed on certain events"""
299    def __init__ (self, scripts = {}, **kwargs):
300        """Init operation for scriptable objects
301           @type scripts: Dictionary
302           @param scripts: Dictionary where the event strings are keys. The
303           values are 3-item tuples (function, positional_args, keyword_args)"""
304        self.is_scriptable = True
305        self.scripts = scripts
306       
307    def runScript (self, event):
308        """Runs the script for the given event"""
309        if event in self.scripts and self.scripts[event]:
310            func, args, kwargs = self.scripts[event]
311            func (*args, **kwargs)
312           
313    def setScript (self, event, func, args = [] , kwargs={}):
314        """Sets a script to be executed for the given event."""
315        self.scripts[event] = (func, args, kwargs)
316
317class CharStats (object):
318    """Provides the object with character statistics"""
319    def __init__ (self, **kwargs):
320        self.is_charstats = True
321       
322class Wearable (object):
323    def __init__ (self, slots, **kwargs):
324        """Allows the object to be worn somewhere on the body (e.g. pants)"""
325        self.is_wearable = True
326        if isinstance(slots,tuple) :
327            self.slots = slots
328        else :
329            self.slots = (slots,)
330   
331class Usable (object):
332    """Allows the object to be used in some way (e.g. a Zippo lighter
333       to make a fire)"""
334    def __init__ (self, **kwargs):
335        self.is_usable = True
336       
337class Weapon (object):
338    """Allows the object to be used as a weapon"""
339    def __init__ (self, **kwargs):
340        self.is_weapon = True
341       
342class Destructable (object):
343    """Allows the object to be destroyed"""
344    def __init__ (self, **kwargs):
345        self.is_destructable = True
346       
347class Trappable (object):
348    """Provides trap slots to the object"""
349    def __init__ (self, **kwargs):
350        self.is_trappable = True
Note: See TracBrowser for help on using the repository browser.