Prep for shopkeepers

This commit is contained in:
Griatch 2022-08-02 11:48:06 +02:00
parent d22b11c723
commit d97106948b
5 changed files with 157 additions and 12 deletions

View file

@ -62,7 +62,7 @@ class LivingMixin:
Called when attacked and taking damage.
"""
pass
self.hp -= damage
def at_defeat(self):
"""

View file

@ -288,8 +288,6 @@ class CombatActionAttack(CombatAction):
message = f" $You() $conj(hit) $You({defender.key}) for |r{dmg}|n damage!"
self.msg(message)
defender.hp -= dmg
# call hook
defender.at_damage(dmg, attacker=attacker)

View file

@ -6,6 +6,8 @@ from random import choice
from evennia import DefaultCharacter
from evennia.typeclasses.attributes import AttributeProperty
from evennia.utils.evmenu import EvMenu
from evennia.utils.utils import make_iter
from .characters import LivingMixin
from .enums import Ability, WieldLocation
@ -95,18 +97,140 @@ class EvAdventureNPC(LivingMixin, DefaultCharacter):
pass
class EvAdventureShopKeeper(EvAdventureNPC):
class EvAdventureTalkativeNPC(EvAdventureNPC):
"""
Talkative NPCs can be addressed by `talk [to] <npc>`. This opens a chat menu with
communication options. The menu is created with the npc and we override the .create
to allow passing in the menu nodes.
"""
menudata = AttributeProperty(None, autocreate=False)
menu_kwargs = AttributeProperty(None, autocreate=False)
# text shown when greeting at the start of a conversation. If this is an
# iterable, a random reply will be chosen by the menu
hi_text = AttributeProperty("Hi!", autocreate=False)
def at_damage(self, damage, attacker=None):
"""
Talkative NPCs are generally immortal (we don't deduct HP here by default)."
"""
attacker.msg(f'{self.key} dodges the damage and shouts "|wHey! What are you doing?|n"')
@classmethod
def create(cls, key, account=None, **kwargs):
"""
Overriding the creation of the NPC, allowing some extra `**kwargs`.
Args:
key (str): Name of the new object.
account (Account, optional): Account to attribute this object to.
Keyword Args:
description (str): Brief description for this object (same as default Evennia)
ip (str): IP address of creator (for object auditing) (same as default Evennia).
menudata (dict or str): The `menudata` argument to `EvMenu`. This is either a dict of
`{"nodename": <node_callable>,...}` or the python-path to a module containing
such nodes (see EvMenu docs). This will be used to generate the chat menu
chat menu for the character that talks to the NPC (which means the `at_talk` hook
is called (by our custom `talk` command).
menu_kwargs (dict): This will be passed as `**kwargs` into `EvMenu` when it
is created. Make sure this dict can be pickled to an Attribute.
Returns:
tuple: `(new_character, errors)`. On error, the `new_character` is `None` and
`errors` is a `list` of error strings (an empty list otherwise).
"""
menudata = kwargs.pop("menudata", None)
menu_kwargs = kwargs.pop("menu_kwargs", {})
# since this is a @classmethod we can't use super() here
new_object, errors = EvAdventureNPC.create(
key, account=account, attributes=(("menudata", menudata), ("menu_kwargs", menu_kwargs))
)
return new_object, errors
def at_talk(self, talker, startnode="node_start", session=None, **kwargs):
"""
Called by the `talk` command when another entity addresses us.
Args:
talker (Object): The one talking to us.
startnode (str, optional): Allows to start in a different location in the menu tree.
The given node must exist in the tree.
session (Session, optional): The talker's current session, allows for routing
correctly in multi-session modes.
**kwargs: This will be passed into the `EvMenu` creation and appended and `menu_kwargs`
given to the NPC at creation.
Notes:
We pass `npc=self` into the EvMenu for easy back-reference. This will appear in the
`**kwargs` of the start node.
"""
menu_kwargs = {**self.menu_kwargs, **kwargs}
EvMenu(talker, self.menudata, startnode=startnode, session=session, npc=self, **menu_kwargs)
def node_start(caller, raw_string, **kwargs):
"""
This is the intended start menu node for the Talkative NPC interface. It will
use on-npc Attributes to build its message and will also pick its options
based on nodes named `node_start_*` are available in the node tree.
"""
# we presume a back-reference to the npc this is added when the menu is created
npc = kwargs["npc"]
# grab a (possibly random) welcome text
text = choice(make_iter(npc.hi_text))
# determine options based on `node_start_*` nodes available
toplevel_node_keys = [
node_key for node_key in caller.ndb._evmenu._menutree if node_key.startswith("node_start_")
]
options = []
for node_key in toplevel_node_keys:
option_name = node_key[11:].replace("_", " ").capitalized()
# we let the menu number the choices, so we don't use key here
options.append({"desc": option_name, "goto": node_key})
return text, options
class EvAdventureQuestGiver(EvAdventureTalkativeNPC):
"""
An NPC that acts as a dispenser of quests.
"""
class EvAdventureShopKeeper(EvAdventureTalkativeNPC):
"""
ShopKeeper NPC.
"""
# how much extra the shopkeeper adds on top of the item cost
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)
common_ware_prototypes = AttributeProperty([], autocreate=False)
class EvAdventureQuestGiver(EvAdventureNPC):
"""
An NPC that acts as a dispenser of quests.
def at_damage(self, damage, attacker=None):
"""
Immortal - we don't deduct any damage here.
"""
"""
attacker.msg(
f"{self.key} brushes off the hit and shouts "
'"|wHey! This is not the way to get a discount!|n"'
)
class EvAdventureMob(EvAdventureNPC):

View file

@ -23,14 +23,37 @@ A shop is run by an NPC. It can provide one or more of several possible services
All shops are menu-driven. One starts talking to the npc and will then end up in their shop
interface.
This is a series of menu nodes meant to be added as a mapping via
`EvAdventureShopKeeper.create(menudata={},...)`.
To make this pluggable, the shopkeeper start page will analyze the available nodes
and auto-add options to all nodes in the three named `node_start_*`. The last part of the
node name will be the name of the option capitalized, with underscores replaced by spaces, so
`node_start_sell_items` will become a top-level option `Sell items`.
"""
from random import choice
from evennia.utils.evmenu import EvMenu
from evennia.utils.utils import make_iter
from .npcs import EvAdventureShopKeeper
# shop menu nodes to use for building a Shopkeeper npc
def start_npc_menu(caller, shopkeeper, **kwargs):
def node_start_buy(caller, raw_string, **kwargs):
"""
Access function - start the NPC interaction/shop interface.
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
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).
"""

View file

@ -2419,8 +2419,8 @@ class DefaultCharacter(DefaultObject):
All other kwargs will be passed into the create_object call.
Returns:
character (Object): A newly created Character of the given typeclass.
errors (list): A list of errors in string form, if any.
tuple: `(new_character, errors)`. On error, the `new_character` is `None` and
`errors` is a `list` of error strings (an empty list otherwise).
"""
errors = []