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

Revision 563, 15.4 KB checked in by beliar, 10 years ago (diff)

Ticket #200: Patch by Beliar.

  • Merging map_loading_change branch back into trunk

fixes[s:trac, t:200]

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