''' consumable.py Consumable item type. By default, implements edible and drinkable. Allows players to add new consumable types, and subtypes (liquor, water, juice, ...?). Comes with OLC object interface. This module has been designed to allow for easy addition of a potion/pill system. When an item is consumed, consume hooks are run (in addition to eat and drink hooks). Magical affects can piggyback on to this, in addition to other special things like extracting depleted flasks. ''' import olc, mudsys, auxiliary, mudsock, storage, mud, utils, hooks ################################################################################ # item type definition ################################################################################ # a map from our type to possible subtypes. # Also the default subtype when we create an item of the specified type. ctype_map = { } ctype_dflt = { } class ConsumableData: '''Data for Consumables. Contains a type (food, drink), a subtype (liquor, water, juice), a maximum number of consumptions, and a current number of consumptions.''' # this line is needed for registering the item type, and using OLC __item_type__ = "consumable" def __init__(self, set = None): # set our default values self.type = "edible" self.subtype = get_consume_dflt(self.type) self.max_consumes = 1 self.consumes = 1 # do we have any other values to read in? if set != None: self.type = set.readString("type") self.subtype = get_consume_dflt(self.type) if set.contains("subtype"): self.subtype = set.readString("subtype") if set.contains("max_consumes"): self.max_consumes = set.readInt("max_consumes") if set.contains("consumes"): self.consumes = set.readInt("consumes") def copyTo(self, to): to.type = self.type to.subtype = self.subtype to.max_consumes = self.max_consumes to.consumes = self.consumes def copy(self): newdata = ConsumableData() self.copyTo(newdata) return newdata def store(self): set = storage.StorageSet() set.storeString("type", self.type) if self.subtype != get_consume_dflt(self.type): set.storeString("subtype", self.subtype) if self.max_consumes != 1: set.storeInt("max_consumes", self.max_consumes) if self.consumes != 1: set.storeInt("consumes", self.consumes) return set def __obj_isConsumeType__(obj, type): '''Return whether the object is consumable, and of the specified type of consumable.''' if not obj.istype("consumable"): return False if obj.get_type_data("consumable").type != type: return False return True ################################################################################ # creating and interacting with consumable types ################################################################################ def register_consume_type(type, dflt_subtype = "unknown"): '''create a new consumable type, and add a default subtype''' subtypes = [dflt_subtype] ctype_map[type] = subtypes ctype_dflt[type] = dflt_subtype def register_consume_subtype(type, subtype): '''create a new subtype for the specified consumable type''' if ctype_map.has_key(type): subtype = subtype.lower() subtypes = ctype_map[type] if not subtype in subtypes: subtypes.append(subtype) def get_consume_dflt(type): '''get the default subtype for a consumable''' if ctype_dflt.has_key(type): return ctype_dflt[type] return None def get_consume_types(): '''list all consumable types''' return ctype_map.keys() def get_consume_subtypes(type): '''list all subtypes for a consumable''' if ctype_map.has_key(type): return [t for t in ctype_map[type]] return None def is_consume_type(type): '''return whether type is a registered consumable type''' return ctype_map.has_key(type) def is_consume_subtype(type, subtype): '''return whether subtype is a registered subtype for type''' if ctype_map.has_key(type): return subtype in ctype_map[type] return False ################################################################################ # Consumable OLC ################################################################################ __CONSOLC_TYPE__ = 1 __CONSOLC_SUBTYPE__ = 2 __CONSOLC_MAX_USES__ = 3 __CONSOLC_USES__ = 4 consolc_opt_map = { '1' : (__CONSOLC_TYPE__, "Choose a type: "), '2' : (__CONSOLC_SUBTYPE__, "Choose a subtype: "), '3' : (__CONSOLC_MAX_USES__, "Enter max number of uses: "), '4' : (__CONSOLC_USES__, "Enter default number of uses: ") } def consolc_menu(sock, data): '''display our menu interface''' sock.send("{g1) Type : {c" + data.type + "\n" + \ "{g2) Subtype: : {c" + data.subtype + "\n" + \ "{g3) Max uses : {c" + str(data.max_consumes) + "\n" + \ "{g4) Default uses: {c" + str(data.consumes)) def consolc_chooser(sock, data, option): '''figure out what field we want to edit, and display a prompt''' if not consolc_opt_map.has_key(option): return olc.MENU_CHOICE_INVALID ret, mssg = consolc_opt_map[option] # list our type and subtype options opts = None if ret == __CONSOLC_TYPE__: opts = get_consume_types() elif ret == __CONSOLC_SUBTYPE__: opts = get_consume_subtypes(data.type) # do we have to list subtype options? if opts != None: utils.olc_display_table(sock, opts, 4) sock.send_raw(mssg) return ret def consolc_parser(sock, data, choice, arg): '''parse out what we want to change a specified field to''' try: arg = int(arg) except: return False if choice == __CONSOLC_TYPE__: opts = get_consume_types() arg = int(arg) if arg < 0 or arg >= len(opts): return False newtype = opts[arg] if newtype != data.type: data.consumes = 1 data.max_consumes = 1 data.subtype = get_consume_dflt(newtype) data.type = newtype elif choice == __CONSOLC_SUBTYPE__: opts = get_consume_subtypes(data.type) arg = int(arg) if arg < 0 or arg >= len(opts): return False data.subtype = opts[arg] elif choice == __CONSOLC_MAX_USES__: data.max_consumes = max(int(arg), 1) elif choice == __CONSOLC_USES__: data.consumes = max(int(arg), 0) data.max_consumes = max(data.max_consumes, data.consumes) else: return False return True def consolc_to_proto(data): '''return a script that will generate relevant info to create this item''' buf = 'me.get_type_data("consumable").type = "' + data.type + '"\n' buf += 'me.get_type_data("consumable").subtype = "' + data.subtype + '"\n' if data.max_consumes != 1: buf += 'me.get_type_data("consumable").max_consumes = ' + \ str(data.max_consumes)+'\n' if data.consumes != 1: buf += 'me.get_type_data("consumable").consumes = ' + \ str(data.consumes)+'\n' return buf ################################################################################ # game hooks ################################################################################ def consumable_desc_info(info): '''appends consumable information about an object to the description buffer when a person looks at it.''' obj, ch = hooks.parse_info(info) # no point in continuing if not obj.istype("consumable"): return # get our consumable info, and make a description data = obj.get_type_data("consumable") # what word do we describe its state usable, unusable = "is", "finished" try: usable, unusable = { "drinkable" : ("contains", "empty"), "edible" : ("is", "finished") }[data.type] except: pass # is it usable or unusable? if data.consumes == 0: desc = " It is " + unusable + "." else: desc = (" It %s %s %s and can be consumed %d more times." % (usable, data.type, data.subtype, data.consumes)) # append our description ch.look_buf += desc ################################################################################ # game commands ################################################################################ def cmd_eat(ch, cmd, arg): '''Allows players to eat food they are carrying.''' try: obj, = mud.parse_args(ch, True, cmd, arg, "[the] obj.inv") except: return if not obj.isConsumeType("edible"): mud.message(ch, None, obj, None, True, "to_char", "$o is not edible!") else: # take a bite and send our message data = obj.get_type_data("consumable") if data.max_consumes > 0: data.consumes -= 1 mud.message(ch, None, obj, None, True, "to_char", "You take a bite out of $o.") # do we want to make edible effects? Here's where to hook onto hooks.run("consume", hooks.build_info("ch obj", (ch, obj))) hooks.run("eat", hooks.build_info("ch obj", (ch, obj))) # have we eaten the entire object? if data.max_consumes > 0 and data.consumes <= 0: mud.message(ch, None, obj, None, True, "to_char", "You finish eating $o.") mud.extract(obj) def cmd_drink(ch, cmd, arg): '''Allows a player to sip a drinkable object they are carrying, or are around.''' try: obj, = mud.parse_args(ch, True, cmd, arg, "[from] obj.room.inv") except: return if not obj.isConsumeType("drinkable"): mud.message(ch, None, obj, None, True, "to_char", "You cannot drink from $o!") elif obj.get_type_data("consumable").consumes <= 0: mud.message(ch, None, obj, None, True, "to_char", "You cannot drink from $o, it is empty!") else: # take a sip and send our message data = obj.get_type_data("consumable") data.consumes -= 1 mud.message(ch, None, obj, None, True, "to_char", "You drink %s from $o." % data.subtype) # Do we want to make potions? Here's where to hook on to hooks.run("consume", hooks.build_info("ch obj", (ch, obj))) hooks.run("drink", hooks.build_info("ch obj", (ch, obj))) def cmd_fill(ch, cmd, arg): '''Attempt to fill one drinkable source with liquid from another.''' try: dest, src = mud.parse_args(ch, True, cmd, arg, "obj.inv [from] obj.room.inv") except: return # make sure we can drink from both, and they are different items if dest == src: mud.message(ch, None, None, None, True, "to_char", "That would not accomplish much!") elif not dest.isConsumeType("drinkable"): mud.message(ch, None, dest, None, True, "to_char", "$o cannot contain liquids.") elif (not src.isConsumeType("drinkable") or src.get_type_data("consumable").consumes == 0): mud.message(ch, None, dest, None, True, "to_char", "$o does not contain liquids.") elif (dest.get_type_data("consumable").consumes > 0 and dest.get_type_data("consumable").subtype != src.get_type_data("consumable").subtype): mud.message(ch, None, dest, src, True, "to_char", "$o and $O contain different liquids; you must empty $o first.") elif (dest.get_type_data("consumable").max_consumes == dest.get_type_data("consumable").consumes): mud.message(ch, None, dest, None, True, "to_char", "$o is as full as it will ever be.") # ok, we're good! Let's fill 'er up else: mud.message(ch, None, dest, src, True, "to_char", "You fill $o from $O.") mud.message(ch, None, dest, src, True, "to_room", "$n fills $o from $O.") # get our data for a little easier working sdata = src.get_type_data("consumable") ddata = dest.get_type_data("consumable") # figure out what our limit is, and change our type if neccessary amnt = min((ddata.max_consumes - ddata.consumes), sdata.consumes) ddata.consumes += amnt sdata.consumes -= amnt ddata.subtype = sdata.subtype # run our hooks hooks.run("fill", hooks.build_info("ch obj obj", (ch, dest, src))) def cmd_empty(ch, cmd, arg): '''Empty out the liquids in a drink consumable.''' try: obj, = mud.parse_args(ch, True, cmd, arg, "obj.inv") except: return if not obj.isConsumeType("drinkable"): mud.message(ch, None, obj, None, True, "to_char", "You can only empty drinking containers.") elif obj.get_type_data("consumable").consumes == 0: mud.message(ch, None, obj, None, True, "to_char", "$o is already empty.") else: data = obj.get_type_data("consumable") mud.message(ch, None, obj, None, True, "to_char", "You empty out the " + data.subtype + " in $o.") data.consumes = 0 # run our hooks hooks.run("empty", hooks.build_info("ch obj", (ch, obj))) ################################################################################ # initialization ################################################################################ # initialize our item data, and its OLC mudsys.item_add_type(ConsumableData.__item_type__, ConsumableData) olc.item_add_olc(ConsumableData.__item_type__, consolc_menu, consolc_chooser, consolc_parser, None, consolc_to_proto) # add methods to Objs mudsys.add_obj_method("isConsumeType", __obj_isConsumeType__) # add commands mudsys.add_cmd("eat", None, cmd_eat, "player", True) mudsys.add_cmd("drink", None, cmd_drink, "player", True) mudsys.add_cmd("fill", None, cmd_fill, "player", True) mudsys.add_cmd("empty", None, cmd_empty, "player", True) # display hooks for food and drink hooks.add("append_obj_desc", consumable_desc_info) # initialize some default consumable types and subtypes register_consume_type("edible", "bread") register_consume_subtype("edible", "potato") register_consume_subtype("edible", "steak") register_consume_type("drinkable", "water") register_consume_subtype("drinkable", "liquor") register_consume_subtype("drinkable", "wine") register_consume_subtype("drinkable", "beer")