mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Expand on shop management
This commit is contained in:
parent
19bd7ce0b7
commit
e6e632c13a
2 changed files with 194 additions and 2 deletions
|
|
@ -220,6 +220,7 @@ class EvAdventureShopKeeper(EvAdventureTalkativeNPC):
|
|||
upsell_factor = AttributePropert(1.0, autocreate=False)
|
||||
# how much of the raw cost the shopkeep is willing to pay when buying from character
|
||||
miser_factor = Attribute(0.5, autocreate=False)
|
||||
# prototypes of common wares
|
||||
common_ware_prototypes = AttributeProperty([], autocreate=False)
|
||||
|
||||
def at_damage(self, damage, attacker=None):
|
||||
|
|
|
|||
|
|
@ -36,22 +36,213 @@ node name will be the name of the option capitalized, with underscores replaced
|
|||
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from random import choice
|
||||
|
||||
from evennia.utils.evmenu import EvMenu
|
||||
from evennia.prototypes.prototypes import search_prototype
|
||||
from evennia.prototypes.spawner import flatten_prototype
|
||||
from evennia.utils.evmenu import EvMenu, list_node
|
||||
from evennia.utils.logger import log_err, log_trace
|
||||
from evennia.utils.utils import make_iter
|
||||
|
||||
from .enums import Ability, ObjType, WieldLocation
|
||||
from .npcs import EvAdventureShopKeeper
|
||||
|
||||
|
||||
@dataclass
|
||||
class BuyItem:
|
||||
"""
|
||||
Storage container for storing generic info about an item for sale. This means it can be used
|
||||
both for real objects and for prototypes without constantly having to track which is which.
|
||||
|
||||
"""
|
||||
|
||||
# skipping typehints here since we are not using them anywhere else
|
||||
|
||||
# available for all buyable items
|
||||
key = ""
|
||||
desc = ""
|
||||
obj_type = ObjType.GEAR
|
||||
size = 1
|
||||
value = 0
|
||||
use_slot = WieldLocation.BACKPACK
|
||||
|
||||
uses = None
|
||||
quality = None
|
||||
attack_type = None
|
||||
defense_type = None
|
||||
damage_roll = None
|
||||
|
||||
# references the original (always only one of the two)
|
||||
obj = None
|
||||
prototype = None
|
||||
|
||||
@staticmethod
|
||||
def create_from_obj(obj, shopkeeper):
|
||||
"""
|
||||
Build a new BuyItem container from a real db obj.
|
||||
|
||||
Args:
|
||||
obj (EvAdventureObject): An object to analyze.
|
||||
shopkeeper (EvAdventureShopKeeper): The shopkeeper.
|
||||
|
||||
Returns:
|
||||
BuyItem: A general representation of the original data.
|
||||
|
||||
"""
|
||||
try:
|
||||
# mandatory
|
||||
key = obj.key
|
||||
desc = obj.db.desc
|
||||
obj_type = obj.obj_type
|
||||
size = obj.size
|
||||
use_slot = obj.use_slot
|
||||
value = obj.value * shopkeeper.upsell_factor
|
||||
except AttributeError:
|
||||
# not a buyable item
|
||||
log_trace("Not a buyable item")
|
||||
return None
|
||||
|
||||
# getting optional properties
|
||||
|
||||
return BuyItem(
|
||||
key=key,
|
||||
desc=desc,
|
||||
obj_type=obj_type,
|
||||
size=size,
|
||||
use_slot=use_slot,
|
||||
value=value,
|
||||
# optional fields
|
||||
uses=getattr(obj, "uses", None),
|
||||
quality=getattr(obj, "quality", None),
|
||||
attack_type=getattr(obj, "attack_type", None),
|
||||
defense_type=getattr(obj, "defense_type", None),
|
||||
damage_roll=getattr(obj, "damage_roll", None),
|
||||
# back-reference (don't set prototype)
|
||||
obj=obj,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create_from_prototype(self, prototype_or_key, shopkeeper):
|
||||
"""
|
||||
Build a new BuyItem container from a prototype.
|
||||
|
||||
Args:
|
||||
prototype (dict or key): An Evennia prototype dict or the key of one
|
||||
registered with the system. This is assumed to be a full prototype,
|
||||
including having parsed and included parentage.
|
||||
|
||||
Returns:
|
||||
BuyItem: A general representation of the original data.
|
||||
|
||||
"""
|
||||
|
||||
def _get_attr_value(key, prot, optional=True):
|
||||
"""
|
||||
We want the attribute's value, which is always in the `attrs` field of
|
||||
the prototype.
|
||||
|
||||
"""
|
||||
attr = [tup for tup in prot.get("attrs", ()) if tup[0] == key]
|
||||
try:
|
||||
return attr[0][1]
|
||||
except IndexError:
|
||||
if optional:
|
||||
return None
|
||||
raise
|
||||
|
||||
if isinstance(prototype_or_key, dict):
|
||||
prototype = prototype_or_key
|
||||
else:
|
||||
# make sure to generate a 'full' prototype with all inheritance applied ('flattened'),
|
||||
# otherwise we will not get inherited data when we analyze it.
|
||||
prototype = flatten_prototype(search_prototype(key=prototype_or_key))
|
||||
|
||||
if not prototype:
|
||||
log_err(f"No valid prototype '{prototype_or_key}' found")
|
||||
return None
|
||||
|
||||
try:
|
||||
# at this point we should have a full, flattened prototype ready to spawn. It must
|
||||
# contain all fields needed for buying
|
||||
key = prototype["key"]
|
||||
desc = _get_attr_value("desc", prototype, optional=False)
|
||||
obj_type = _get_attr_value("obj_type", prototype, optional=False)
|
||||
size = _get_attr_value("size", prototype, optional=False)
|
||||
use_slot = _get_attr_value("use_slot", prototype, optional=False)
|
||||
value = int(_get_attr_value("value", prototype, optional=False)
|
||||
* shopkeeper.upsell_factor)
|
||||
except (KeyError, IndexError):
|
||||
# not a buyable item
|
||||
log_trace("Not a buyable item")
|
||||
return None
|
||||
|
||||
return BuyItem(
|
||||
key=key,
|
||||
desc=desc,
|
||||
obj_type=obj_type,
|
||||
size=size,
|
||||
use_slot=use_slot,
|
||||
value=value,
|
||||
# optional fields
|
||||
uses=_get_attr_value("uses", prototype),
|
||||
quality=_get_attr_value("quality", prototype),
|
||||
attack_type=_get_attr_value("attack_type", prototype),
|
||||
defense_type=_get_attr_value("defense_type", prototype),
|
||||
damage_roll=_get_attr_value("damage_roll", prototype),
|
||||
# back-reference (don't set obj)
|
||||
prototype=prototype,
|
||||
)
|
||||
|
||||
def get_sdesc(self):
|
||||
"""
|
||||
Get the short description to show in buy list.
|
||||
|
||||
"""
|
||||
return self.key
|
||||
|
||||
def get_detail(self):
|
||||
"""
|
||||
Get more info when looking at the item.
|
||||
|
||||
"""
|
||||
return f"""
|
||||
|c{self.key}|n
|
||||
{self.desc}
|
||||
|
||||
Slots: {self.size} Used from: {self.use_slot.value}
|
||||
|
||||
|
||||
|
||||
# Helper functions for building the shop listings and select a ware to buy
|
||||
def _get_all_wares_to_buy(caller, raw_string, **kwargs):
|
||||
"""
|
||||
This helper is used by `EvMenu.list_node` to build the list of items to buy.
|
||||
|
||||
We rely on `**kwargs` being forwarded from `node_start_buy`, which in turns contains
|
||||
the `npc` kwarg pointing to the shopkeeper (`caller` is the one doing the buying).
|
||||
|
||||
"""
|
||||
shopkeep = kwargs["npc"]
|
||||
# items carried by the shopkeep are sellable (these are items already created, such as
|
||||
# things sold to the shopkeep earlier). We
|
||||
wares = [BuyItem.create_from_obj(obj) for obj in list(shopkeep.contents)] + [
|
||||
BuyItem.create_from_prototype(prototype) for prototype in shopkeep.common_ware_prototypes
|
||||
]
|
||||
# clean out any ByItems that failed to create for some reason
|
||||
wares = [ware for ware in wares if ware]
|
||||
|
||||
|
||||
# shop menu nodes to use for building a Shopkeeper npc
|
||||
|
||||
|
||||
@list_node(_get_all_wares_to_buy, select=_select_ware_to_buy, pagesize=10)
|
||||
def node_start_buy(caller, raw_string, **kwargs):
|
||||
"""
|
||||
Menu node for the caller to buy items from the shopkeep. This assumes `**kwargs` contains
|
||||
a kwarg `npc` referencing the npc/shopkeep being talked to.
|
||||
|
||||
Items available to sell are a combination of items in the shopkeep's inventory and prototypes
|
||||
Items available to sell are a combination of items in the shopkeep's inventory and
|
||||
the list of `prototypes` stored in the Shopkeep's "common_ware_prototypes` Attribute. In the
|
||||
latter case, the properties will be extracted from the prototype when inspecting it (object will
|
||||
only spawn when bought).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue