@rpgm-tools/neo-angband-core 0.26.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/agent/act.d.ts.map +1 -1
- package/dist/agent/act.js +2 -1
- package/dist/agent/act.js.map +1 -1
- package/dist/agent/types.d.ts +11 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/game/context.d.ts +21 -4
- package/dist/game/context.d.ts.map +1 -1
- package/dist/game/context.js +13 -1
- package/dist/game/context.js.map +1 -1
- package/dist/game/loop.d.ts +4 -4
- package/dist/game/loop.d.ts.map +1 -1
- package/dist/game/loop.js +17 -8
- package/dist/game/loop.js.map +1 -1
- package/dist/game/player-turn.d.ts +26 -0
- package/dist/game/player-turn.d.ts.map +1 -1
- package/dist/game/player-turn.js +97 -2
- package/dist/game/player-turn.js.map +1 -1
- package/dist/game/world.d.ts +15 -0
- package/dist/game/world.d.ts.map +1 -1
- package/dist/game/world.js +48 -1
- package/dist/game/world.js.map +1 -1
- package/dist/mod/save-blocks.d.ts +1 -1
- package/dist/session/game.d.ts.map +1 -1
- package/dist/session/game.js +11 -4
- package/dist/session/game.js.map +1 -1
- package/dist/store/store-cmd.d.ts +58 -0
- package/dist/store/store-cmd.d.ts.map +1 -1
- package/dist/store/store-cmd.js +162 -0
- package/dist/store/store-cmd.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/src/agent/act.ts +2 -1
- package/src/agent/types.ts +11 -1
- package/src/game/context.ts +28 -5
- package/src/game/loop.ts +17 -7
- package/src/game/player-turn.ts +95 -1
- package/src/game/world.ts +53 -1
- package/src/session/game.ts +11 -3
- package/src/store/store-cmd.ts +186 -0
- package/src/version.ts +1 -1
package/src/store/store-cmd.ts
CHANGED
|
@@ -16,10 +16,26 @@
|
|
|
16
16
|
* - or anything that moves the player mid-transaction - reaches these. They are
|
|
17
17
|
* therefore real guards on the command path, returning upstream's message or
|
|
18
18
|
* null to proceed, rather than assertions.
|
|
19
|
+
*
|
|
20
|
+
* Also here: `installStoreCommands`, which registers the `shop-buy` /
|
|
21
|
+
* `shop-sell` / `shop-exit` action-registry handlers `createAgentActions`
|
|
22
|
+
* (agent/act.ts) builds commands for. Those three codes had no handler at
|
|
23
|
+
* all until this was added (docs/PLANNED.md, "An agent cannot trade") - a
|
|
24
|
+
* controller's shopping decisions reached the queue and were silently
|
|
25
|
+
* dropped. The handlers use the guards above plus `storeAtGrid` to resolve
|
|
26
|
+
* "which store" fresh from the player's grid, then commit through the same
|
|
27
|
+
* `StartedGame.buy` / `.sell` closures the interactive shop screen calls.
|
|
19
28
|
*/
|
|
20
29
|
|
|
21
30
|
import { FEAT } from "../generated/index.js";
|
|
31
|
+
import { describeObject } from "../game/describe.js";
|
|
32
|
+
import type { GameState } from "../game/context.js";
|
|
33
|
+
import { gearGet } from "../game/gear.js";
|
|
34
|
+
import type { ActionRegistry } from "../game/player-turn.js";
|
|
35
|
+
import { ODESC } from "../obj/desc.js";
|
|
36
|
+
import type { GameObject } from "../obj/object.js";
|
|
22
37
|
import type { Store } from "./store.js";
|
|
38
|
+
import type { BuyResult, SellResult } from "./transact.js";
|
|
23
39
|
|
|
24
40
|
/**
|
|
25
41
|
* do_cmd_buy (store.c:1667-1670): purchasing requires standing in a store.
|
|
@@ -71,3 +87,173 @@ export function enterStoreGuard(store: Store | null): string | null {
|
|
|
71
87
|
if (!store) return "You see no store here.";
|
|
72
88
|
return null;
|
|
73
89
|
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* store_at (store.c, read fresh from `square_shopnum`): the live store whose
|
|
93
|
+
* entrance feature the player is standing on right now, or null. Every store
|
|
94
|
+
* command below re-resolves this rather than trust a Store handed to it, for
|
|
95
|
+
* the same reason the guards above exist - do_cmd_buy / do_cmd_sell /
|
|
96
|
+
* do_cmd_retrieve / do_cmd_stash all call store_at(cave, player->grid) fresh,
|
|
97
|
+
* every time (store.c:1665, :1795, :1872, :2014). The shell's own
|
|
98
|
+
* `storeAtPlayer` (packages/web/src/main.ts) does the same lookup for the
|
|
99
|
+
* interactive shop screen; this is the engine-side twin an agent's direct
|
|
100
|
+
* commands need, since they carry no Store reference at all - only a stock
|
|
101
|
+
* index or a gear handle.
|
|
102
|
+
*/
|
|
103
|
+
export function storeAtGrid(state: GameState): Store | null {
|
|
104
|
+
const feat = state.chunk.feat(state.actor.grid);
|
|
105
|
+
return state.stores?.find((s) => s.feat === feat) ?? null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The store transaction closures a `shop-buy` / `shop-sell` handler commits
|
|
110
|
+
* through - the same `StartedGame.buy` / `.sell` a human's shop screen calls
|
|
111
|
+
* (session/game.ts's makeStoreApi), so an agent's direct command and a
|
|
112
|
+
* keystroke reach identical pack/gold/knowledge effects, artifact reveal and
|
|
113
|
+
* quiver refresh included. Kept as an interface (not a StartedGame import)
|
|
114
|
+
* so this module does not depend on session/game.ts.
|
|
115
|
+
*/
|
|
116
|
+
export interface StoreCommandDeps {
|
|
117
|
+
buy: (store: Store, obj: GameObject, amt: number) => BuyResult;
|
|
118
|
+
sell: (store: Store, handle: number, amt: number) => SellResult;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const BUY_FAILURE_SHOP: Record<string, string> = {
|
|
122
|
+
"not-in-stock": "You cannot buy that item because it's not in the store.",
|
|
123
|
+
"no-room": "You cannot carry that many items.",
|
|
124
|
+
"cannot-afford": "You cannot afford that purchase.",
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const BUY_FAILURE_HOME: Record<string, string> = {
|
|
128
|
+
"not-in-stock": "You cannot retrieve that item because it's not in the home.",
|
|
129
|
+
"no-room": "You cannot carry that many items.",
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Register `shop-buy`, `shop-sell` and `shop-exit` on the action registry -
|
|
134
|
+
* the three command codes `createAgentActions` (agent/act.ts) builds and
|
|
135
|
+
* nothing in the engine handled (docs/PLANNED.md, "An agent cannot trade").
|
|
136
|
+
* On the same terms as every other verb: a plain `registry.register` entry
|
|
137
|
+
* that runs whether the command arrived from a keystroke or an agent's
|
|
138
|
+
* controller, and spends no energy - do_cmd_buy/_sell/_retrieve/_stash pass
|
|
139
|
+
* no game turn (a shop visit does not let monsters act), and there is no
|
|
140
|
+
* upstream leave-store command to spend one either.
|
|
141
|
+
*
|
|
142
|
+
* `shop-buy`/`shop-sell` carry no Store reference (an agent addresses a ware
|
|
143
|
+
* by its stock index, an item by its gear handle - AgentView.stores()[n]
|
|
144
|
+
* and view.inventory()[n], never a live object), so the handler resolves
|
|
145
|
+
* "which store" itself via storeAtGrid and re-runs the entry guard
|
|
146
|
+
* do_cmd_buy/_sell/_retrieve/_stash each run first, exactly as those C
|
|
147
|
+
* functions do. `shop-exit` has no upstream engine counterpart at all - the
|
|
148
|
+
* C command is a UI-only "close the shop screen" with no game-state effect -
|
|
149
|
+
* so it is registered as a legitimate no-op rather than left unhandled.
|
|
150
|
+
*/
|
|
151
|
+
export function installStoreCommands(
|
|
152
|
+
registry: ActionRegistry,
|
|
153
|
+
deps: StoreCommandDeps,
|
|
154
|
+
): void {
|
|
155
|
+
registry.register("shop-buy", (state, cmd) => {
|
|
156
|
+
const args = cmd.args ?? {};
|
|
157
|
+
const index = typeof args["index"] === "number" ? args["index"] : null;
|
|
158
|
+
if (index === null) return 0;
|
|
159
|
+
|
|
160
|
+
const store = storeAtGrid(state);
|
|
161
|
+
if (!store) {
|
|
162
|
+
state.msg?.(storeBuyGuard(null) ?? "You cannot purchase items when not in a store.");
|
|
163
|
+
return 0;
|
|
164
|
+
}
|
|
165
|
+
const isHome = store.feat === FEAT.HOME;
|
|
166
|
+
const refusal = isHome ? storeRetrieveGuard(store) : storeBuyGuard(store);
|
|
167
|
+
if (refusal) {
|
|
168
|
+
state.msg?.(refusal);
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const obj = store.stock[index];
|
|
173
|
+
if (!obj) {
|
|
174
|
+
state.msg?.(
|
|
175
|
+
isHome
|
|
176
|
+
? "You cannot retrieve that item because it's not in the home."
|
|
177
|
+
: "You cannot buy that item because it's not in the store.",
|
|
178
|
+
);
|
|
179
|
+
return 0;
|
|
180
|
+
}
|
|
181
|
+
const rawQty = typeof args["quantity"] === "number" ? args["quantity"] : 1;
|
|
182
|
+
const amt = Math.min(Math.max(1, rawQty), obj.number);
|
|
183
|
+
|
|
184
|
+
const result = deps.buy(store, obj, amt);
|
|
185
|
+
if (!result.ok) {
|
|
186
|
+
const table = isHome ? BUY_FAILURE_HOME : BUY_FAILURE_SHOP;
|
|
187
|
+
state.msg?.(table[result.failure ?? ""] ?? "The purchase failed.");
|
|
188
|
+
return 0;
|
|
189
|
+
}
|
|
190
|
+
const bought = result.bought
|
|
191
|
+
? describeObject(state, result.bought, ODESC.PREFIX | ODESC.FULL)
|
|
192
|
+
: "the item";
|
|
193
|
+
/* comment_accept (do_cmd_buy L1717) prints before the sale line, exactly
|
|
194
|
+
* as the interactive shop screen orders it. */
|
|
195
|
+
if (result.acceptComment) state.msg?.(result.acceptComment);
|
|
196
|
+
if (isHome) state.msg?.(`You have ${bought}.`);
|
|
197
|
+
else state.msg?.(`You bought ${bought} for ${result.price} gold.`);
|
|
198
|
+
if (result.emptied === "retired") state.msg?.("The shopkeeper retires.");
|
|
199
|
+
else if (result.emptied === "restocked") {
|
|
200
|
+
state.msg?.("The shopkeeper brings out some new stock.");
|
|
201
|
+
}
|
|
202
|
+
return 0;
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
registry.register("shop-sell", (state, cmd) => {
|
|
206
|
+
const args = cmd.args ?? {};
|
|
207
|
+
const handle = typeof args["handle"] === "number" ? args["handle"] : null;
|
|
208
|
+
if (handle === null) return 0;
|
|
209
|
+
|
|
210
|
+
const store = storeAtGrid(state);
|
|
211
|
+
if (!store) {
|
|
212
|
+
state.msg?.(storeSellGuard(null) ?? "You cannot sell items when not in a store.");
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
const isHome = store.feat === FEAT.HOME;
|
|
216
|
+
const refusal = isHome ? storeStashGuard(store) : storeSellGuard(store);
|
|
217
|
+
if (refusal) {
|
|
218
|
+
state.msg?.(refusal);
|
|
219
|
+
return 0;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const obj = gearGet(state.gear, handle);
|
|
223
|
+
if (!obj) {
|
|
224
|
+
state.msg?.("You do not have that item.");
|
|
225
|
+
return 0;
|
|
226
|
+
}
|
|
227
|
+
const rawQty = typeof args["quantity"] === "number" ? args["quantity"] : obj.number;
|
|
228
|
+
const amt = Math.min(Math.max(1, rawQty), obj.number);
|
|
229
|
+
|
|
230
|
+
const result = deps.sell(store, handle, amt);
|
|
231
|
+
if (!result.ok) {
|
|
232
|
+
const why: Record<string, string> = {
|
|
233
|
+
"no-item": "You do not have that item.",
|
|
234
|
+
stuck: "Hmmm, it seems to be stuck.",
|
|
235
|
+
refused: "I do not wish to purchase this item.",
|
|
236
|
+
"no-room": isHome
|
|
237
|
+
? "Your home is full."
|
|
238
|
+
: "I have not the room in my store to keep it.",
|
|
239
|
+
};
|
|
240
|
+
state.msg?.(why[result.failure ?? ""] ?? "The sale failed.");
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
const sold = result.sold
|
|
244
|
+
? describeObject(state, result.sold, ODESC.PREFIX | ODESC.FULL)
|
|
245
|
+
: "the item";
|
|
246
|
+
if (isHome) state.msg?.(`You have stashed ${sold}.`);
|
|
247
|
+
else if (result.price) state.msg?.(`You sold ${sold} for ${result.price} gold.`);
|
|
248
|
+
else state.msg?.(`You had ${sold}.`);
|
|
249
|
+
/* purchase_analyze's reaction comment (do_cmd_sell L1972), same ordering
|
|
250
|
+
* as the interactive shop screen. */
|
|
251
|
+
if (result.reactionComment) state.msg?.(result.reactionComment);
|
|
252
|
+
return 0;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
/* leave_store (ui-store.c): a UI-only close with no upstream game-state
|
|
256
|
+
* effect. Registered so the code has a real handler rather than falling
|
|
257
|
+
* through to the base registry's no-op stub silently. */
|
|
258
|
+
registry.register("shop-exit", () => 0);
|
|
259
|
+
}
|
package/src/version.ts
CHANGED
|
@@ -23,4 +23,4 @@ export const PARITY_BASELINE = "4.2.6";
|
|
|
23
23
|
* released tag is iterated takes a MINOR bump, because a published tag is pinned
|
|
24
24
|
* by digest in a catalogue and must never be moved.
|
|
25
25
|
*/
|
|
26
|
-
export const ENGINE_VERSION = "0.
|
|
26
|
+
export const ENGINE_VERSION = "0.27.0";
|