'''
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")