# NPC merchants ``` *** Welcome to ye Old Sword shop! *** Things for sale (choose 1-3 to inspect, quit to exit): _________________________________________________________ 1. A rusty sword (5 gold) 2. A sword with a leather handle (10 gold) 3. Excalibur (100 gold) ``` This will introduce an NPC able to sell things. In practice this means that when you interact with them you'll get shown a _menu_ of choices. Evennia provides the [EvMenu](../Components/EvMenu.md) utility to easily create in-game menus. We will store all the merchant's wares in their inventory. This means that they may stand in an actual shop room, at a market or wander the road. We will also use 'gold' as an example currency. To enter the shop, you'll just need to stand in the same room and use the `buy/shop` command. ## Making the merchant class The merchant will respond to you giving the `shop` or `buy` command in their presence. ```python # in for example mygame/typeclasses/merchants.py from typeclasses.objects import Object from evennia import Command, CmdSet, EvMenu class CmdOpenShop(Command): """ Open the shop! Usage: shop/buy """ key = "shop" aliases = ["buy"] def func(self): # this will sit on the Merchant, which is self.obj. # the self.caller is the player wanting to buy stuff. self.obj.open_shop(self.caller) class MerchantCmdSet(CmdSet): def at_cmdset_creation(self): self.add(CmdOpenShop()) class NPCMerchant(Object): def at_object_creation(self): self.cmdset.add_default(MerchantCmdSet) def open_shop(self, shopper): menunodes = {} # TODO! shopname = self.db.shopname or "The shop" EvMenu(shopper, menunodes, startnode="shopfront", shopname=shopname, shopkeeper=self, wares=self.contents) ``` We could also have put the commands in a separate module, but for compactness, we put it all with the merchant typeclass. Note that we make the merchant an `Object`! Since we don't give them any other commands, it makes little sense to let them be a `Character`. We make a very simple `shop`/`buy` Command and make sure to add it on the merchant in its own cmdset. We initialize `EvMenu` on the `shopper` but we haven't created any `menunodes` yet, so this will not actually do much at this point. It's important that we we pass `shopname`, `shopkeeper` and `wares` into the menu, it means they will be made available as properties on the EvMenu instance - we will be able to access them from inside the menu. ## Coding the shopping menu [EvMenu](../Components/EvMenu.md) splits the menu into _nodes_ represented by Python functions. Each node represents a stop in the menu where the user has to make a choice. For simplicity, we'll code the shop interface above the `NPCMerchant` class in the same module. The start node of the shop named "ye Old Sword shop!" will look like this if there are only 3 wares to sell: ``` *** Welcome to ye Old Sword shop! *** Things for sale (choose 1-3 to inspect, quit to exit): _________________________________________________________ 1. A rusty sword (5 gold) 2. A sword with a leather handle (10 gold) 3. Excalibur (100 gold) ``` ```python # in mygame/typeclasses/merchants.py # top of module, above NPCMerchant class. def node_shopfront(caller, raw_string, **kwargs): "This is the top-menu screen." # made available since we passed them to EvMenu on start menu = caller.ndb._evmenu shopname = menu.shopname shopkeeper = menu.shopkeeper wares = menu.wares text = f"*** Welcome to {shopname}! ***\n" if wares: text += f" Things for sale (choose 1-{len(wares)} to inspect); quit to exit:" else: text += " There is nothing for sale; quit to exit." options = [] for ware in wares: # add an option for every ware in store gold_val = ware.db.gold_value or 1 options.append({"desc": f"{ware.key} ({gold_val} gold)", "goto": ("inspect_and_buy", {"selected_ware": ware}) }) return text, options ``` Inside the node we can access the menu on the caller as `caller.ndb._evmenu`. The extra keywords we passed into `EvMenu` are available on this menu instance. Armed with this we can easily present a shop interface. Each option will become a numbered choice on this screen. Note how we pass the `ware` with each option and label it `selected_ware`. This will be accessible in the next node's `**kwargs` argument If a player choose one of the wares, they should be able to inspect it. Here's how it should look if they selected `1` in ye Old Sword shop: ``` You inspect A rusty sword: This is an old weapon maybe once used by soldiers in some long forgotten army. It is rusty and in bad condition. __________________________________________________________ 1. Buy A rusty sword (5 gold) 2. Look for something else. ``` If you buy, you'll see ``` You pay 5 gold and purchase A rusty sword! ``` or ``` You cannot afford 5 gold for A rusty sword! ``` Either way you should end up back at the top level of the shopping menu again and can continue browsing or quit the menu with `quit`. Here's how it looks in code: ```python # in mygame/typeclasses/merchants.py # right after the other node def _buy_item(caller, raw_string, **kwargs): "Called if buyer chooses to buy" selected_ware = kwargs["selected_ware"] value = selected_ware.db.gold_value or 1 wealth = caller.db.gold or 0 if wealth >= value: rtext = f"You pay {value} gold and purchase {ware.key}!" caller.db.gold -= value move_to(caller, quiet=True, move_type="buy") else: rtext = f"You cannot afford {value} gold for {ware.key}!" caller.msg(rtext) # no matter what, we return to the top level of the shop return "shopfront" def node_inspect_and_buy(caller, raw_string, **kwargs): "Sets up the buy menu screen." # passed from the option we chose selected_ware = kwargs["selected_ware"] value = selected_ware.db.gold_value or 1 text = f"You inspect {ware.key}:\n\n{ware.db.desc}" gold_val = ware.db.gold_value or 1 options = ({ "desc": f"Buy {ware.key} for {gold_val} gold", "goto": (_buy_item, kwargs) }, { "desc": "Look for something else", "goto": "shopfront", }) return text, options ``` In this node we grab the `selected_ware` from `kwargs` - this we pased along from the option on the previous node. We display its description and value. If the user buys, we reroute through the `_buy_item` helper function (this is not a node, it's just a callable that must return the name of the next node to go to.). In `_buy_item` we check if the buyer can affort the ware, and if it can we move it to their inventory. Either way, this method returns `shop_front` as the next node. We have been referring to two nodes here: `"shopfront"` and `"inspect_and_buy"` , we should map them to the code in the menu. Scroll down to the `NPCMerchant` class in the same module and find that unfinished `open_shop` method again: ```python # in /mygame/typeclasses/merchants.py def node_shopfront(caller, raw_string, **kwargs): # ... def _buy_item(caller, raw_string, **kwargs): # ... def node_inspect_and_buy(caller, raw_string, **kwargs): # ... class NPCMerchant(Object): # ... def open_shop(self, shopper): menunodes = { "shopfront": node_shopfront, "inspect_and_buy": node_inspect_and_buy } shopname = self.db.shopname or "The shop" EvMenu(shopper, menunodes, startnode="shopfront", shopname=shopname, shopkeeper=self, wares=self.contents) ``` We now added the nodes to the Evmenu under their right labels. The merchant is now ready! ## The shop is open for business! Make sure to `reload`. Let's try it out by creating the merchant and a few wares in-game. Remember that we also must create some gold get this economy going. ``` > set self/gold = 8 > create/drop Stan S. Stanman;stan:typeclasses.merchants.NPCMerchant > set stan/shopname = Stan's previously owned vessles > create/drop A proud vessel;ship > set ship/desc = The thing has holes in it. > set ship/gold_value = 5 > create/drop A classic speedster;rowboat > set rowboat/gold_value = 2 > set rowboat/desc = It's not going anywhere fast. ``` Note that a builder without any access to Python code can now set up a personalized merchant with just in-game commands. With the shop all set up, we just need to be in the same room to start consuming! ``` > buy *** Welcome to Stan's previously owned vessels! *** Things for sale (choose 1-3 to inspect, quit to exit): _________________________________________________________ 1. A proud vessel (5 gold) 2. A classic speedster (2 gold) > 1 You inspect A proud vessel: The thing has holes in it. __________________________________________________________ 1. Buy A proud vessel (5 gold) 2. Look for something else. > 1 You pay 5 gold and purchase A proud vessel! *** Welcome to Stan's previously owned vessels! *** Things for sale (choose 1-3 to inspect, quit to exit): _________________________________________________________ 1. A classic speedster (2 gold) ```