Note

You are reading an old version of the Evennia documentation. The latest version is here

.

# Source code for evennia.contrib.tutorials.evadventure.commands

    """
    EvAdventure commands and cmdsets. We don't need that many stand-alone new
    commands since a lot of functionality is managed in menus. These commands
    are in additional to normal Evennia commands and should be added
    to the CharacterCmdSet

    New commands:
        inventory
        wield/wear <item>
        unwield/remove <item>
        give <item or coin> to <character>
        talk <npc>

    To install, add the `EvAdventureCmdSet` from this module to the default character cmdset:

    ```python
        # in mygame/commands/default_cmds.py

        from evennia.contrib.tutorials.evadventure.commands import EvAdventureCmdSet  # <---

        # ...

        class CharacterCmdSet(CmdSet):
            def at_cmdset_creation(self):
                # ...
                self.add(EvAdventureCmdSet)   # <-----

    ```
    """

    from evennia import CmdSet, Command, InterruptCommand
    from evennia.utils.evmenu import EvMenu
    from evennia.utils.utils import inherits_from

    from .enums import WieldLocation
    from .equipment import EquipmentError
    from .npcs import EvAdventureTalkativeNPC
    from .utils import get_obj_stats

    class EvAdventureCommand(Command):
        """
        Base EvAdventure command. This is on the form

            command <args>

        where whitespace around the argument(s) are stripped.

        """

        def parse(self):
            self.args = self.args.strip()

    class CmdInventory(EvAdventureCommand):
        """
        View your inventory

        Usage:
          inventory

        """

        key = "inventory"
        aliases = ("i", "inv")

        def func(self):
            loadout = self.caller.equipment.display_loadout()
            backpack = self.caller.equipment.display_backpack()
            slot_usage = self.caller.equipment.display_slot_usage()

            self.caller.msg(f"{loadout}\n{backpack}\nYou use {slot_usage} equipment slots.")

    class CmdWieldOrWear(EvAdventureCommand):
        """
        Wield a weapon/shield, or wear a piece of armor or a helmet.

        Usage:
          wield <item>
          wear <item>

        The item will automatically end up in the suitable spot, replacing whatever
        was there previously.

        """

        key = "wield"
        aliases = ("wear",)

        out_txts = {
            WieldLocation.BACKPACK: "You shuffle the position of {key} around in your backpack.",
            WieldLocation.TWO_HANDS: "You hold {key} with both hands.",
            WieldLocation.WEAPON_HAND: "You hold {key} in your strongest hand, ready for action.",
            WieldLocation.SHIELD_HAND: "You hold {key} in your off hand, ready to protect you.",
            WieldLocation.BODY: "You strap {key} on yourself.",
            WieldLocation.HEAD: "You put {key} on your head.",
        }

        def func(self):
            # find the item among those in equipment
            item = self.caller.search(self.args, candidates=self.caller.equipment.all(only_objs=True))
            if not item:
                # An 'item not found' error will already have been reported; we add another line
                # here for clarity.
                self.caller.msg("You must carry the item you want to wield or wear.")
                return

            use_slot = getattr(item, "inventory_use_slot", WieldLocation.BACKPACK)

            # check what is currently in this slot
            current = self.caller.equipment.slots[use_slot]

            if current == item:
                self.caller.msg(f"You are already using {item.key}.")
                return

            # move it to the right slot based on the type of object
            self.caller.equipment.move(item)

            # inform the user of the change (and potential swap)
            if current:
                self.caller.msg(f"Returning {current.key} to the backpack.")
            self.caller.msg(self.out_txts[use_slot].format(key=item.key))

    class CmdRemove(EvAdventureCommand):
        """
        Remove a remove a weapon/shield, armor or helmet.

        Usage:
          remove <item>
          unwield <item>
          unwear <item>

        To remove an item from the backpack, use |wdrop|n instead.

        """

        key = "remove"
        aliases = ("unwield", "unwear")

        def func(self):
            caller = self.caller

            # find the item among those in equipment
            item = caller.search(self.args, candidates=caller.equipment.all(only_objs=True))
            if not item:
                # An 'item not found' error will already have been reported
                return

            current_slot = caller.equipment.get_current_slot(item)

            if current_slot is WieldLocation.BACKPACK:
                # we don't allow dropping this way since it may be unexepected by users who forgot just
                # where their item currently is.
                caller.msg(
                    f"You already stashed away {item.key} in your backpack. Use 'drop' if "
                    "you want to get rid of it."
                )
                return

            caller.equipment.remove(item)
            caller.equipment.add(item)
            caller.msg(f"You stash {item.key} in your backpack.")

    # give / accept menu

    def _rescind_gift(caller, raw_string, **kwargs):
        """
        Called when giver rescinds their gift in `node_give` below.
        It means they entered 'cancel' on the gift screen.

        """
        # kill the gift menu for the receiver immediately
        receiver = kwargs["receiver"]
        receiver.ndb._evmenu.close_menu()
        receiver.msg("The offer was rescinded.")
        return "node_end"

    def node_give(caller, raw_string, **kwargs):
        """
        This will show to the giver until receiver accepts/declines. It allows them
        to rescind their offer.

        The `caller` here is the one giving the item. We also make sure to feed
        the 'item' and 'receiver' into the Evmenu.

        """
        item = kwargs["item"]
        receiver = kwargs["receiver"]
        text = f"""
    You are offering {item.key} to {receiver.get_display_name(looker=caller)}.
    |wWaiting for them to accept or reject the offer ...|n
    """.strip()

        options = {
            "key": ("cancel", "abort"),
            "desc": "Rescind your offer.",
            "goto": (_rescind_gift, kwargs),
        }
        return text, options

    def _accept_or_reject_gift(caller, raw_string, **kwargs):
        """
        Called when receiver enters yes/no in `node_receive` below. We first need to
        figure out which.

        """
        item = kwargs["item"]
        giver = kwargs["giver"]
        if raw_string.lower() in ("yes", "y"):
            # they accepted - move the item!
            item = giver.equipment.remove(item)
            if item:
                try:
                    # this will also add them to the equipment backpack, if possible
                    item.move_to(caller, quiet=True, move_type="give")
                except EquipmentError:
                    caller.location.msg_contents(
                        (
                            f"$You({giver.key.key}) $conj(try) to give "
                            f"{item.key} to $You({caller.key}), but they can't accept it since their "
                            "inventory is full."
                        ),
                        mapping={giver.key: giver, caller.key: caller},
                    )
                else:
                    caller.location.msg_contents(
                        (
                            f"$You({giver.key}) $conj(give) {item.key} to $You({caller.key}), "
                            "and they accepted the offer."
                        ),
                        mapping={giver.key: giver, caller.key: caller},
                    )
            giver.ndb._evmenu.close_menu()
            return "node_end"

    def node_receive(caller, raw_string, **kwargs):
        """
        Will show to the receiver and allow them to accept/decline the offer for
        as long as the giver didn't rescind it.

        The `caller` here is the one receiving the item. We also make sure to feed
        the 'item' and 'giver' into the EvMenu.

        """
        item = kwargs["item"]
        giver = kwargs["giver"]
        text = f"""
    {giver.get_display_name()} is offering you {item.key}:

    {get_obj_stats(item)}

    [Your inventory usage: {caller.equipment.display_slot_usage()}]
    |wDo you want to accept the given item? Y/[N]
        """
        options = ({"key": "_default", "goto": (_accept_or_reject_gift, kwargs)},)
        return text, options

    def node_end(caller, raw_string, **kwargs):
        return "", None

    class CmdGive(EvAdventureCommand):
        """
        Give item or money to another person. Items need to be accepted before
        they change hands. Money changes hands immediately with no wait.

        Usage:
          give <item> to <receiver>
          give <number of coins> [coins] to receiver

        If item name includes ' to ', surround it in quotes.

        Examples:
          give apple to ranger
          give "road to happiness" to sad ranger
          give 10 coins to ranger
          give 12 to ranger

        """

        key = "give"

        def parse(self):
            """
            Parsing is a little more complex for this command.

            """
            super().parse()
            args = self.args
            if " to " not in args:
                self.caller.msg(
                    "Usage: give <item> to <recevier>. Specify e.g. '10 coins' to pay money. "
                    "Use quotes around the item name it if includes the substring ' to '. "
                )
                raise InterruptCommand

            self.item_name = ""
            self.coins = 0

            # make sure we can use '...' to include items with ' to ' in the name
            if args.startswith('"') and args.count('"') > 1:
                end_ind = args[1:].index('"') + 1
                item_name = args[:end_ind]
                _, receiver_name = args.split(" to ", 1)
            elif args.startswith("'") and args.count("'") > 1:
                end_ind = args[1:].index("'") + 1
                item_name = args[:end_ind]
                _, receiver_name = args.split(" to ", 1)
            else:
                item_name, receiver_name = args.split(" to ", 1)

            # a coin count rather than a normal name
            if " coins" in item_name:
                item_name = item_name[:-6]
            if item_name.isnumeric():
                self.coins = max(0, int(item_name))

            self.item_name = item_name
            self.receiver_name = receiver_name

        def func(self):
            caller = self.caller

            receiver = caller.search(self.receiver_name)
            if not receiver:
                return

            # giving of coins is always accepted

            if self.coins:
                current_coins = caller.coins
                if self.coins > current_coins:
                    caller.msg(f"You only have |y{current_coins}|n coins to give.")
                    return
                # do transaction
                caller.coins -= self.coins
                receiver.coins += self.coins
                caller.location.msg_contents(
                    f"$You() $conj(give) $You({receiver.key}) {self.coins} coins.",
                    from_obj=caller,
                    mapping={receiver.key: receiver},
                )
                return

            # giving of items require acceptance before it happens

            item = caller.search(self.item_name, candidates=caller.equipment.all(only_objs=True))
            if not item:
                return

            # testing hook
            if not item.at_pre_give(caller, receiver):
                return

            # before we start menus, we must check so either part is not already in a menu,
            # that would be annoying otherwise
            if receiver.ndb._evmenu:
                caller.msg(
                    f"{receiver.get_display_name(looker=caller)} seems busy talking to someone else."
                )
                return
            if caller.ndb._evmenu:
                caller.msg("Close the current menu first.")
                return

            # this starts evmenus for both parties
            EvMenu(
                receiver, {"node_receive": node_receive, "node_end": node_end}, item=item, giver=caller
            )
            EvMenu(caller, {"node_give": node_give, "node_end": node_end}, item=item, receiver=receiver)

    class CmdTalk(EvAdventureCommand):
        """
        Start a conversations with shop keepers and other NPCs in the world.

        Args:
          talk <npc>

        """

        key = "talk"

        def func(self):
            target = self.caller.search(self.args)
            if not target:
                return

            if not inherits_from(target, EvAdventureTalkativeNPC):
                self.caller.msg(
                    f"{target.get_display_name(looker=self.caller)} does not seem very talkative."
                )
                return
            target.at_talk(self.caller)

    class EvAdventureCmdSet(CmdSet):
        """
        Groups all commands in one cmdset which can be added in one go to the DefaultCharacter cmdset.

        """

        key = "evadventure"

        def at_cmdset_creation(self):
            self.add(CmdInventory())
            self.add(CmdWieldOrWear())
            self.add(CmdRemove())
            self.add(CmdGive())
            self.add(CmdTalk())

Note

You are reading an old version of the Evennia documentation. The latest version is here

.