Package scripts :: Package objects :: Script base
[hide private]
[frames] | no frames]

Source Code for Script scripts.objects.base

  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   
 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 attributes.append(x) call 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.attribbutes.append("openable") 
 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            
56 -class BaseObject(object):
57 """A base class that supports dynamic attributes functionality"""
58 - def __init__ (self):
59 if not self.__dict__.has_key("attributes"): 60 self.attributes = []
61
62 - def trueAttr(self, attr):
63 """Method that checks if the instance has an attribute""" 64 return attr in self.attributes
65
66 - def getStateForSaving(self):
67 """Returns state for saving 68 """ 69 state = {} 70 state["attributes"] = self.attributes 71 return state
72
73 -class DynamicObject (BaseObject):
74 """Class with basic attributes"""
75 - def __init__ (self, name="Dynamic object", real_name=None, image=None, **kwargs):
76 """Initialise minimalistic set of data 77 @type name: String 78 @param name: Object display name 79 @type image: String or None 80 @param name: Filename of image to use in inventory""" 81 BaseObject.__init__(self) 82 self.name = name 83 self.real_name = real_name or name 84 self.image = image
85
86 - def prepareStateForSaving(self, state):
87 """Prepares state for saving 88 @type state: dictionary 89 @param state: State of the object 90 """ 91 pass
92
93 - def restoreState(self, state):
94 """Restores a state from a saved state 95 @type state: dictionary 96 @param state: Saved state 97 """ 98 self.__dict__.update(state)
99
100 - def __getstate__(self):
101 odict = self.__dict__.copy() 102 self.prepareStateForSaving(odict) 103 return odict
104
105 - def __setstate__(self, state):
106 self.restoreState(state)
107
108 - def getStateForSaving(self):
109 """Returns state for saving 110 """ 111 state = BaseObject.getStateForSaving(self) 112 state["Name"] = self.name 113 state["RealName"] = self.real_name 114 state["Image"] = self.image 115 return state
116
117 -class GameObject (DynamicObject):
118 """A base class to be inherited by all game objects. This must be the 119 first class (left to right) inherited by any game object."""
120 - def __init__ (self, ID, gfx = None, xpos = 0.0, ypos = 0.0, map_id = None, 121 blocking=True, name="Generic object", real_name="Generic object", text="Item description", 122 desc="Detailed description", **kwargs):
123 """Set the basic values that are shared by all game objects. 124 @type ID: String 125 @param ID: Unique object identifier. Must be present. 126 @type gfx: Dictionary 127 @param gfx: Dictionary with graphics for the different contexts 128 @type coords 2-item tuple 129 @param coords: Initial coordinates of the object. 130 @type map_id: String 131 @param map_id: Identifier of the map where the object is located 132 @type blocking: Boolean 133 @param blocking: Whether the object blocks character movement 134 @type name: String 135 @param name: The display name of this object (e.g. 'Dirty crate') 136 @type text: String 137 @param text: A longer description of the item 138 @type desc: String 139 @param desc: A long description of the item that is displayed when it is examined 140 """ 141 DynamicObject.__init__(self, name, real_name, **kwargs) 142 self.ID = ID 143 self.gfx = gfx or {} 144 self.X = xpos 145 self.Y = ypos 146 self.map_id = map_id 147 self.blocking = True 148 self.text = text 149 self.desc = desc
150
151 - def _getCoords(self):
152 """Get-er property function""" 153 return (self.X, self.Y)
154
155 - def _setCoords(self, coords):
156 """Set-er property function""" 157 self.X, self.Y = float(coords[0]), float (coords[1])
158 159 coords = property (_getCoords, _setCoords, 160 doc = "Property allowing you to get and set the object's \ 161 coordinates via tuples") 162
163 - def __repr__(self):
164 """A debugging string representation of the object""" 165 return "<%s:%s>" % (self.name, self.ID)
166
167 - def getStateForSaving(self):
168 """Returns state for saving 169 """ 170 state = super(GameObject, self).getStateForSaving() 171 state["ObjectModel"] = self.gfx 172 state["Text"] = self.text 173 state["Desc"] = self.desc 174 state["Position"] = list(self.coords) 175 return state
176 177
178 -class Scriptable (BaseObject):
179 """Allows objects to have predefined scripts executed on certain events"""
180 - def __init__ (self, scripts = None, **kwargs):
181 """Init operation for scriptable objects 182 @type scripts: Dictionary 183 @param scripts: Dictionary where the event strings are keys. The 184 values are 3-item tuples (function, positional_args, keyword_args)""" 185 BaseObject.__init__(self) 186 self.attributes.append("scriptable") 187 self.scripts = scripts or {}
188
189 - def runScript (self, event):
190 """Runs the script for the given event""" 191 if event in self.scripts and self.scripts[event]: 192 func, args, kwargs = self.scripts[event] 193 func (*args, **kwargs)
194
195 - def setScript (self, event, func, args = None , kwargs = None):
196 """Sets a script to be executed for the given event.""" 197 args = args or {} 198 kwargs = kwargs or {} 199 self.scripts[event] = (func, args, kwargs)
200
201 -class Openable(DynamicObject, Scriptable):
202 """Adds open() and .close() capabilities to game objects 203 The current state is tracked by the .is_open variable"""
204 - def __init__(self, is_open = True, **kwargs):
205 """Init operation for openable objects 206 @type is_open: Boolean 207 @param is_open: Keyword boolean argument sets the initial state.""" 208 DynamicObject.__init__(self, **kwargs) 209 Scriptable.__init__(self, **kwargs) 210 self.attributes.append("openable") 211 self.is_open = is_open
212
213 - def open(self):
214 """Opens the object, and runs an 'onOpen' script, if present""" 215 self.is_open = True 216 try: 217 if self.trueAttr ('scriptable'): 218 self.runScript('onOpen') 219 except AttributeError : 220 pass
221
222 - def close(self):
223 """Opens the object, and runs an 'onClose' script, if present""" 224 self.is_open = False 225 try: 226 if self.trueAttr ('scriptable'): 227 self.runScript('onClose') 228 except AttributeError : 229 pass
230
231 -class Lockable (Openable):
232 """Allows objects to be locked"""
233 - def __init__ (self, locked = False, is_open = True, **kwargs):
234 """Init operation for lockable objects 235 @type locked: Boolean 236 @param locked: Keyword boolen argument sets the initial locked state. 237 @type is_open: Boolean 238 @param is_open: Keyword boolean argument sets the initial open state. 239 It is ignored if locked is True -- locked objects 240 are always closed.""" 241 self.attributes.append("lockable") 242 self.locked = locked 243 if locked : 244 is_open = False 245 Openable.__init__( self, is_open, **kwargs )
246
247 - def unlock (self):
248 """Handles unlocking functionality""" 249 self.locked = False
250
251 - def lock (self):
252 """Handles locking functionality""" 253 self.close() 254 self.locked = True
255
256 - def open (self, *args, **kwargs):
257 """Adds a check to see if the object is unlocked before running the 258 .open() function of the parent class""" 259 if self.locked: 260 raise ValueError ("Open failed: object locked") 261 super (Lockable, self).open(*args, **kwargs)
262
263 -class Carryable (DynamicObject):
264 """Allows objects to be stored in containers"""
265 - def __init__ (self, weight=0.0, bulk=0.0, **kwargs):
266 DynamicObject.__init__(self, **kwargs) 267 self.attributes.append("carryable") 268 self.in_container = None 269 self.on_map = None 270 self.agent = None 271 self.weight = weight 272 self.bulk = bulk
273
274 - def getInventoryThumbnail(self):
275 """Returns the inventory thumbnail of the object""" 276 # TODO: Implement properly after the objects database is in place 277 if self.image == None: 278 return "gui/inv_images/inv_litem.png" 279 else: 280 return self.image
281
282 -class Container (DynamicObject, Scriptable):
283 """Gives objects the capability to hold other objects"""
284 - class TooBig(Exception):
285 """Exception to be raised when the object is too big 286 to fit into container""" 287 pass
288
289 - class SlotBusy(Exception):
290 """Exception to be raised when the requested slot is occupied""" 291 pass
292
293 - class ItemSelf(Exception):
294 """Exception to be raised when trying to add the container as an item""" 295 pass
296
297 - def __init__ (self, capacity = 0, items = None, **kwargs):
298 DynamicObject.__init__(self, **kwargs) 299 Scriptable.__init__(self, **kwargs) 300 self.attributes.append("container") 301 self.items = {} 302 self.capacity = capacity 303 if items: 304 for item in items: 305 self.placeItem(item)
306
307 - def placeItem (self, item, index=None):
308 """Adds the provided carryable item to the inventory. 309 Runs an 'onStoreItem' script, if present""" 310 if item is self: 311 raise self.ItemSelf("Paradox: Can't contain myself") 312 if not item.trueAttr ('carryable'): 313 raise TypeError ('%s is not carryable!' % item) 314 if self.capacity and self.getContentsBulk()+item.bulk > self.capacity: 315 raise self.TooBig ('%s is too big to fit into %s' % (item, self)) 316 item.in_container = self 317 if index == None: 318 self._placeAtVacant(item) 319 else: 320 if index in self.items : 321 raise self.SlotBusy('Slot %d is busy in %s' % (index, 322 self.name)) 323 self.items[index] = item 324 325 # Run any scripts associated with storing an item in the container 326 try: 327 if self.trueAttr ('scriptable'): 328 self.runScript('onPlaceItem') 329 except AttributeError : 330 pass
331
332 - def _placeAtVacant(self, item):
333 """Places an item at a vacant slot""" 334 vacant = None 335 for i in range(len(self.items)): 336 if i not in self.items : 337 vacant = i 338 if vacant == None : 339 vacant = len(self.items) 340 self.items[vacant] = item
341
342 - def takeItem (self, item):
343 """Takes the listed item out of the inventory. 344 Runs an 'onTakeItem' script""" 345 if not item in self.items.values(): 346 raise ValueError ('I do not contain this item: %s' % item) 347 del self.items[self.items.keys()[self.items.values().index(item)]] 348 349 # Run any scripts associated with popping an item out of the container 350 try: 351 if self.trueAttr ('scriptable'): 352 self.runScript('onTakeItem') 353 except AttributeError : 354 pass
355
356 - def replaceItem(self, old_item, new_item):
357 """Replaces the old item with the new one 358 @param old_item: Old item which is removed 359 @type old_item: Carryable 360 @param new_item: New item which is added 361 @type new_item: Carryable 362 """ 363 old_index = self.indexOf(old_item.ID) 364 self.removeItem(old_item) 365 self.placeItem(new_item, old_index)
366
367 - def removeItem(self, item):
368 """Removes an item from the container, basically the same as 'takeItem' 369 but does run a different script. This should be used when an item is 370 destroyed rather than moved out. 371 Runs 'onRemoveItem' script 372 """ 373 if not item in self.items.values(): 374 raise ValueError ('I do not contain this item: %s' % item) 375 del self.items[self.items.keys()[self.items.values().index(item)]] 376 377 # Run any scripts associated with popping an item out of the container 378 try: 379 if self.trueAttr ('scriptable'): 380 self.runScript('onRemoveItem') 381 except AttributeError : 382 pass
383
384 - def count (self, item_type = ""):
385 """Returns the number of items""" 386 if item_type: 387 ret_count = 0 388 for index in self.items : 389 if self.items[index].item_type == item_type: 390 ret_count += 1 391 return ret_count 392 return len(self.items)
393
394 - def getContentsBulk(self):
395 """Bulk of the container contents""" 396 return sum((item.bulk for item in self.items.values()))
397
398 - def getItemAt(self, index):
399 return self.items[index]
400
401 - def indexOf(self, ID):
402 """Returns the index of the item with the passed ID""" 403 for index in self.items : 404 if self.items[index].ID == ID: 405 return index 406 return None
407
408 - def findItemByID(self, ID):
409 """Returns the item with the passed ID""" 410 for i in self.items : 411 if self.items[i].ID == ID: 412 return self.items[i] 413 return None
414
415 - def findItemByItemType(self, item_type):
416 """Returns the item with the passed item_type""" 417 for index in self.items : 418 if self.items[index].item_type == item_type: 419 return self.items[index] 420 return None
421
422 - def findItem(self, **kwargs):
423 """Find an item in container by attributes. All params are optional. 424 @type name: String 425 @param name: If the name is non-unique, return first matching object 426 @type kind: String 427 @param kind: One of the possible object types 428 @return: The item matching criteria or None if none was found""" 429 for index in self.items : 430 if "name" in kwargs and self.items[index].name != kwargs["name"]: 431 continue 432 if "ID" in kwargs and self.items[index].ID != kwargs["ID"]: 433 continue 434 if "kind" in kwargs and not self.items[index].trueAttr(kwargs["kind"]): 435 continue 436 if "item_type" in kwargs and self.items[index].item_type != kwargs["item_type"]: 437 continue 438 return self.items[index] 439 return None
440
441 - def serializeItems(self):
442 """Returns the items as a list""" 443 items = [] 444 for index, item in self.items.iteritems(): 445 item_dict = item.getStateForSaving() 446 item_dict["index"] = index 447 item_dict["type"] = item.item_type 448 items.append(item_dict) 449 return items
450
451 - def getStateForSaving(self):
452 """Returns state for saving 453 """ 454 ret_state = DynamicObject.getStateForSaving(self) 455 ret_state["Items"] = self.serializeItems() 456 return ret_state
457
458 -class Living (BaseObject):
459 """Objects that 'live'"""
460 - def __init__ (self, **kwargs):
461 BaseObject.__init__(self) 462 self.attributes.append("living") 463 self.lives = True
464
465 - def die(self):
466 """Kills the object""" 467 self.lives = False
468
469 -class CharStats (BaseObject):
470 """Provides the object with character statistics"""
471 - def __init__ (self, **kwargs):
472 BaseObject.__init__(self) 473 self.attributes.append("charstats")
474
475 -class Wearable (BaseObject):
476 """Objects than can be weared"""
477 - def __init__ (self, slots, **kwargs):
478 """Allows the object to be worn somewhere on the body (e.g. pants)""" 479 BaseObject.__init__(self) 480 self.attributes.append("wearable") 481 if isinstance(slots, tuple) : 482 self.slots = slots 483 else : 484 self.slots = (slots,)
485
486 -class Usable (BaseObject):
487 """Allows the object to be used in some way (e.g. a Zippo lighter 488 to make a fire)"""
489 - def __init__ (self, actions = None, **kwargs):
490 BaseObject.__init__(self) 491 self.attributes.append("usable") 492 self.actions = actions or {}
493
494 -class Weapon (BaseObject):
495 """Allows the object to be used as a weapon"""
496 - def __init__ (self, **kwargs):
497 BaseObject.__init__(self) 498 self.attributes.append("weapon")
499
500 -class Destructable (BaseObject):
501 """Allows the object to be destroyed"""
502 - def __init__ (self, **kwargs):
503 BaseObject.__init__(self) 504 self.attributes.append("destructable")
505
506 -class Trapable (BaseObject):
507 """Provides trap slots to the object"""
508 - def __init__ (self, **kwargs):
509 BaseObject.__init__(self) 510 self.attributes.append("trapable")
511