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

Revision 477, 13.0 KB checked in by maximinus_parpg, 10 years ago (diff)

More clean up of the code.
Finshed first pass.
So should be at least stands compliant.

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