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

Revision 449, 13.5 KB checked in by b0rland_parpg, 10 years ago (diff)

Ticket #107: patch by b0rland

  • Added bulk and capacity into Carryable and Containers
  • Slightly improved weight property
  • Replaced ValueError? with domain-specific exceptions
  • Added bulk limits processing into InventoryGUI
  • 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'"""
53import fife
54from settings import Setting
55from random import randrange
56from scripts.gui.popups import ExaminePopup, ContainerGUI
57
58class DynamicObject (object):
59    """A base class that only supports dynamic attributes functionality"""
60    def __init__ (self, name="Dynamic object", **kwargs):
61        """Initialise minimalistic set of data
62        @type name: String
63        @param name: Object display name
64        """
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 to set the initial locked state.
156        @type is_open: Boolean
157        @param is_open: Keyword boolean argument sets the initial open state. It is ignored if locked is True -- locked objects are always closed.
158        """
159        self.is_lockable = True
160        if locked == True: #HACK: For some reason locked appears to NOT be a bool. Needs investigation
161            locked = True
162        else: locked = False
163        self.locked = locked
164        if locked :
165            is_open=False
166        Openable.__init__( self, is_open, **kwargs )
167       
168    def unlock (self):
169        """Handles unlocking functionality"""
170        self.locked = False     
171       
172    def lock (self):
173        """Handles  locking functionality"""
174        self.close()
175        self.locked = True
176       
177    def open (self, *args, **kwargs):
178        """Adds a check to see if the object is unlocked before running the
179           .open() function of the parent class"""
180        if self.locked:
181            raise ValueError ("Open failed: object locked")
182        super (Lockable,self).open(*args,**kwargs)
183       
184class Carryable (object):
185    """Allows objects to be stored in containers"""
186    def __init__ (self, weight=0.0, bulk=0.0, **kwargs):
187        self.is_carryable = True
188        self.in_container = None
189        self.weight = weight
190        self.bulk=bulk
191
192    def getInventoryThumbnail(self):
193        #TODO: Implement properly after the objects database is in place
194        return "gui/inv_images/inv_litem.png"
195   
196class Container (object):
197    """Gives objects the capability to hold other objects"""
198    class TooBig(Exception):
199        """Exception to be raised when the object is too big
200        to fit into container"""
201        pass
202   
203    class SlotBusy(Exception):
204        """Exception to be raised when the requested slot is occupied"""
205        pass
206
207    def __init__ (self,
208                  events = {},
209                  engine = None, capacity = 0, **kwargs):
210        self.is_container = True
211        self.items = {}
212        self.events = events
213        self.containergui = None
214        self.capacity=capacity
215
216        if not self.events:
217            self.events = {'takeAllButton':self.hideContainer,
218                      'closeButton':self.hideContainer}
219        if engine :
220            self.containergui = engine.view.hud.createBoxGUI(self.name, self, self.events)
221       
222    def placeItem (self, item, index=None):
223        """Adds the provided carriable item to the inventory.
224           Runs an 'onStoreItem' script, if present"""   
225        if not item.trueAttr ('carryable'):
226            raise TypeError ('%s is not carriable!' % item)
227        if self.capacity and self.getContentsBulk()+item.bulk > self.capacity:
228            raise self.TooBig ('%s is too big to fit into %s' % (item,self))
229        item.in_container = self
230        if index == None:
231            self.placeAtVacant(item)
232        else:
233            if index in self.items :
234                raise self.SlotBusy('Slot %d is busy in %s' % (index, self.name))
235            self.items[index]=item
236
237        # Run any scripts associated with storing an item in the container
238        try:
239            if self.trueAttr ('scriptable'):
240                self.runScript('onPlaceItem')
241        except AttributeError :
242            pass
243
244    def placeAtVacant(self, item):
245        vacant = None
246        for i in range(len(self.items)):
247            if i not in self.items :
248                vacant = i
249        if vacant == None :
250            vacant = len(self.items)
251        self.items[vacant] = item
252
253
254    def takeItem (self, item):
255        """Takes the listed item out of the inventory.
256           Runs an 'ontakeItem' script"""       
257        if not item in self.items.values():
258            raise ValueError ('I do not contain this item: %s' % item)
259        del self.items[self.items.keys()[self.items.values().index(item)]]
260
261        # Run any scripts associated with popping an item out of the container
262        try:
263            if self.trueAttr ('scriptable'):
264                self.runScript('onTakeItem')
265        except AttributeError :
266            pass
267
268    def count (self):
269        return len(self.items)
270
271    def getContentsBulk(self):
272        """Bulk of the container contents"""
273        return sum((item.bulk for item in self.items.values()))
274
275    def showContainer (self):
276        if self.containergui:
277            self.containergui.showContainer()
278
279    def hideContainer(self):
280        if self.containergui:
281            self.containergui.hideContainer()
282
283    def findItemByID(self, ID):
284        for i in self.items :
285            if self.items[i].ID == ID:
286                return self.items[i]
287        return None
288
289    def findItem(self, **kwargs):
290        """Find an item in container by various attributes. All parameters are optional.
291           @type name: String
292           @param name: Unique or non-unique object name. If the name is non-unique, first matching object is returned
293           @type kind: String
294           @param kind: One of the possible object kinds like "openable" or "weapon"
295           @return: The item matching criteria or None if none was found
296        """
297        for i in self.items :
298            if "name" in kwargs and self.items[i].name != kwargs["name"]:
299                continue
300            if "kind" in kwargs and not self.items[i].trueAttr(kwargs["kind"]):
301                continue
302            return self.items[i]
303        return None
304       
305class Living (object):
306    def __init__ (self, **kwargs):
307        self.is_living = True
308    def die(self):
309        self.is_living = False
310       
311class Scriptable (object):
312    """Allows objects to have predefined scripts executed on certain events"""
313    def __init__ (self, scripts = {}, **kwargs):
314        """Init operation for scriptable objects
315           @type scripts: Dictionary
316           @param scripts: Dictionary where the event strings are keys. The
317           values are 3-item tuples (function, positional_args, keyword_args)"""
318        self.is_scriptable = True
319        self.scripts = scripts
320       
321    def runScript (self, event):
322        """Runs the script for the given event"""
323        if event in self.scripts and self.scripts[event]:
324            func, args, kwargs = self.scripts[event]
325            func (*args, **kwargs)
326           
327    def setScript (self, event, func, args = [] , kwargs={}):
328        """Sets a script to be executed for the given event."""
329        self.scripts[event] = (func, args, kwargs)
330
331class CharStats (object):
332    """Provides the object with character statistics"""
333    def __init__ (self, **kwargs):
334        self.is_charstats = True
335       
336class Wearable (object):
337    def __init__ (self, slots, **kwargs):
338        """Allows the object to be worn somewhere on the body (e.g. pants)"""
339        self.is_wearable = True
340        if isinstance(slots,tuple) :
341            self.slots = slots
342        else :
343            self.slots = (slots,)
344   
345class Usable (object):
346    """Allows the object to be used in some way (e.g. a Zippo lighter
347       to make a fire)"""
348    def __init__ (self, **kwargs):
349        self.is_usable = True
350       
351class Weapon (object):
352    """Allows the object to be used as a weapon"""
353    def __init__ (self, **kwargs):
354        self.is_weapon = True
355       
356class Destructable (object):
357    """Allows the object to be destroyed"""
358    def __init__ (self, **kwargs):
359        self.is_destructable = True
360       
361class Trappable (object):
362    """Provides trap slots to the object"""
363    def __init__ (self, **kwargs):
364        self.is_trappable = True
Note: See TracBrowser for help on using the repository browser.