immortal:creationlaw:shops

Creation Law — Shops

You know classic Diku shop tables / sedit. Neo has no sedit. A shop is a workspace prototype keyed by an existing mobile VID, edited with @shop …, reviewed with @examine shop, then committed with @save shop.

Hub: Creation Law. Build the keeper mobile first: Mobiles. Place it: Zones. Player-facing buy/list/auction flow: Shopkeepers.

The mobile is the body players talk to. The shop row (H2 table shopkeeper) is the trade config: home room, produce list, item types traded, hours, and keeper messages. When a mobile has Acts.SHOPKEEPER, runtime looks up ShopkeeperDB.at(mobile.vid()).

So the build order is: mobile (with SHOPKEEPER)shop workspace on that same VIDzone placezone reset. Creating @new shop before the mobile exists fails. Forgetting the Acts flag leaves a shop row that never handles list / buy.

  • An npc prototype already @saved (same VID you will use for the shop)
  • On npc_stats: @npc_stats flags acts shopkeeper (and usually sentinel so the keeper stays put)
  • A room that already exists for @shop room
  • Item prototypes you intend to produces (optional slots — up to 10)

1. Confirm the mobile (build it if needed — Mobiles):

> @load npc_stats 1210
Okay.
> @npc_stats flags acts shopkeeper
Okay.
> @npc_stats flags acts sentinel
Okay.
> @examine npc_stats
> @save npc_stats
Okay.
>

2. Open the shop draft — VID is the mobile VID (must be in your mobile range; mobile must exist; no second shop draft while one is open):

> @new shop 1210
Okay.
>

Echoes you will see on failure (exact Immortality strings):

  • A new shop entry already exists in your buffer.@examine shop then @save shop or @clear shop
  • A shop already exists for that <mobile-vid>.
  • No mobile with vid <n> exists.
  • That vid is not in your range.
  • Bad usage line in code prints @new show <mobile-vid> (typo for @new shop).

3. Home room (why: Shopkeeper.check refuses @save when room is 0 — the room VID must already exist in RoomDB):

> @shop room 1200
Okay.
>

4. What the keeper trades (ItemType flags via @shop trades; prefix ! to clear when the modifier supports it). At least one type is required to save:

> @shop trades weapon
Okay.
> @shop trades armor
Okay.
>

Bare @shop trades reprints @shop trades (!)<flag> plus the ItemType list from code.

5. Produce list (optional but usual — stock the keeper lists / sells at a fixed cost). Positions 1–10 (Shopkeeper.MAX_PRODUCES). Cost must be ≥ 1 gold for any filled slot or @save returns producing vid:… for 0gc:

> @shop produces 1 3100 500
Okay.
> @shop produces 2 3101 50
Okay.
>

Syntax: @shop produces <pos> <item-vid> <cost>. The produce setter does not verify the item prototype exists at edit time — @examine shows ItemDB.proper when the item is known.

6. Hourshours1 is required to save (open1 must be non-zero). Both start and finish for hours1 must be positive integers, and start must be strictly less than finish. hours2 is optional and may only be set after hours1:

> @shop hours1 8 12
Okay.
> @shop hours2 14 20
Okay.
>

7. Keeper messages (one-line writers — all six are required by Shopkeeper.check before @save):

> @shop keeper_no_item_msg
I do not have that item.
> @shop customer_no_item_msg
You do not seem to have that.
> @shop customer_no_cash_msg
You cannot afford that.
> @shop keeper_no_buy_msg
I do not buy that sort of thing.
> @shop keeper_buy_msg
That will be %d coins.
> @shop keeper_sell_msg
Sold.
>

keeper_buy_msg is passed through String.format(…, price) on several buy paths — include a %d where you want the gold amount echoed (same idea as the default string in S.keeperBuyMsg).

8. Review and commit:

> @examine shop
--- Shop ---
MOBILE  : 1210
ROOM    : 1200
HOURS1  : 8 to 12
HOURS2  : 14 to 20
--- produces ---
...
--- trades ---
...
--- keeper has no item ---
...
> @save shop
Okay.
>

If @save returns a short error from Shopkeeper.check, fix the field and save again — the draft stays open until save succeeds or you @clear shop. Exact check strings include: no room is set; no item types are set; no customerNoItemMsg set (also returned when keeper_no_item_msg is missing — code quirk); no customerNoCashMsg set; no keeperNoBuyMsg set; no keeperBuyMsg set; no keeperSellMsg set; open time must be set; producing vid:<n> for 0gc.

Why: the shop row alone does not spawn the mobile. Zone reset loads the npc into a room; players then use list / buy / auction / … against that keeper (Shopkeepers).

> @load zone 1299
Okay.
> @zone npc 1210 1200
Okay.
> @examine zone
> @save zone
Okay.
> zone reset
Okay.
>

From ModifySocial.cmdCrShop (real subcommands only):

  • room <room-vid> — home / shop room (room must exist)
  • produces <pos> <item-vid> <cost> — produce slot 1–10
  • trades (!)<ItemType> — types the keeper will handle
  • keeper_no_item_msg — one-line writer
  • customer_no_item_msg — one-line writer
  • customer_no_cash_msg — one-line writer
  • keeper_no_buy_msg — one-line writer
  • keeper_buy_msg — one-line writer (often with %d)
  • keeper_sell_msg — one-line writer
  • hours1 <start> <finish> — required window
  • hours2 <start> <finish> — optional second window

Workspace companions (same pattern as rooms/items): @new shop, @examine shop, @clear shop, @save shop, @load shop <vid>, @destroy shop <vid>.

> @load shop 1210
Okay.
> @shop produces 1 3100 450
Okay.
> @examine shop
> @save shop
Okay.
>

You never enter a numbered shop editor. There is no sedit. You open a shop workspace on a mobile VID, set fields with @shop, @examine, @save. Trade behavior also needs Acts.SHOPKEEPER on the mobile and a zone placement.

  • @new shop before the mobile prototype exists
  • Mobile without flags acts shopkeeper
  • Second @new shop while a shop draft is still open
  • @save shop without room, trades, hours1, or all six messages
  • Produce cost 0 on a filled slot
  • Building the shop but never @zone npc + zone reset
  • Expecting redit-style menus — workspace only

Documented as facts from the Java tree (not guesses):

  • Help-string typos: createNewShop usage prints @new show <mobile-vid>; bad produces args print @new shop produces …; deleteShop usage prints @delete shop while the verb is @destroy; @destroy's usage line omits shop even though the handler accepts it.
  • Shopkeeper.check: when keeper_no_item_msg is missing it still returns no customerNoItemMsg set (copy/paste quirk).
  • hours2 validation message says “ahead of the open1 finish-hour” but the comparison uses open1() (the start).
  • Runtime (quick scan of MobileScript): trades, produces/cost, keeper_no_item_msg, keeper_no_buy_msg, and keeper_buy_msg are used. room, hours1/hours2, customer_no_item_msg, customer_no_cash_msg, and keeper_sell_msg are required to save and stored, but no call sites were found that gate trading on hours/room or speak those three messages — treat as save requirements / future hooks unless you prove otherwise in a later code pass.
  • immortal/creationlaw/shops.txt
  • Last modified: 2026/09/23 22:22
  • by 127.0.0.1