@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/dist/store/store-cmd.js
CHANGED
|
@@ -16,8 +16,20 @@
|
|
|
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
|
import { FEAT } from "../generated/index.js";
|
|
30
|
+
import { describeObject } from "../game/describe.js";
|
|
31
|
+
import { gearGet } from "../game/gear.js";
|
|
32
|
+
import { ODESC } from "../obj/desc.js";
|
|
21
33
|
/**
|
|
22
34
|
* do_cmd_buy (store.c:1667-1670): purchasing requires standing in a store.
|
|
23
35
|
* Returns the refusal message, or null when the command may proceed.
|
|
@@ -70,4 +82,154 @@ export function enterStoreGuard(store) {
|
|
|
70
82
|
return "You see no store here.";
|
|
71
83
|
return null;
|
|
72
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* store_at (store.c, read fresh from `square_shopnum`): the live store whose
|
|
87
|
+
* entrance feature the player is standing on right now, or null. Every store
|
|
88
|
+
* command below re-resolves this rather than trust a Store handed to it, for
|
|
89
|
+
* the same reason the guards above exist - do_cmd_buy / do_cmd_sell /
|
|
90
|
+
* do_cmd_retrieve / do_cmd_stash all call store_at(cave, player->grid) fresh,
|
|
91
|
+
* every time (store.c:1665, :1795, :1872, :2014). The shell's own
|
|
92
|
+
* `storeAtPlayer` (packages/web/src/main.ts) does the same lookup for the
|
|
93
|
+
* interactive shop screen; this is the engine-side twin an agent's direct
|
|
94
|
+
* commands need, since they carry no Store reference at all - only a stock
|
|
95
|
+
* index or a gear handle.
|
|
96
|
+
*/
|
|
97
|
+
export function storeAtGrid(state) {
|
|
98
|
+
const feat = state.chunk.feat(state.actor.grid);
|
|
99
|
+
return state.stores?.find((s) => s.feat === feat) ?? null;
|
|
100
|
+
}
|
|
101
|
+
const BUY_FAILURE_SHOP = {
|
|
102
|
+
"not-in-stock": "You cannot buy that item because it's not in the store.",
|
|
103
|
+
"no-room": "You cannot carry that many items.",
|
|
104
|
+
"cannot-afford": "You cannot afford that purchase.",
|
|
105
|
+
};
|
|
106
|
+
const BUY_FAILURE_HOME = {
|
|
107
|
+
"not-in-stock": "You cannot retrieve that item because it's not in the home.",
|
|
108
|
+
"no-room": "You cannot carry that many items.",
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Register `shop-buy`, `shop-sell` and `shop-exit` on the action registry -
|
|
112
|
+
* the three command codes `createAgentActions` (agent/act.ts) builds and
|
|
113
|
+
* nothing in the engine handled (docs/PLANNED.md, "An agent cannot trade").
|
|
114
|
+
* On the same terms as every other verb: a plain `registry.register` entry
|
|
115
|
+
* that runs whether the command arrived from a keystroke or an agent's
|
|
116
|
+
* controller, and spends no energy - do_cmd_buy/_sell/_retrieve/_stash pass
|
|
117
|
+
* no game turn (a shop visit does not let monsters act), and there is no
|
|
118
|
+
* upstream leave-store command to spend one either.
|
|
119
|
+
*
|
|
120
|
+
* `shop-buy`/`shop-sell` carry no Store reference (an agent addresses a ware
|
|
121
|
+
* by its stock index, an item by its gear handle - AgentView.stores()[n]
|
|
122
|
+
* and view.inventory()[n], never a live object), so the handler resolves
|
|
123
|
+
* "which store" itself via storeAtGrid and re-runs the entry guard
|
|
124
|
+
* do_cmd_buy/_sell/_retrieve/_stash each run first, exactly as those C
|
|
125
|
+
* functions do. `shop-exit` has no upstream engine counterpart at all - the
|
|
126
|
+
* C command is a UI-only "close the shop screen" with no game-state effect -
|
|
127
|
+
* so it is registered as a legitimate no-op rather than left unhandled.
|
|
128
|
+
*/
|
|
129
|
+
export function installStoreCommands(registry, deps) {
|
|
130
|
+
registry.register("shop-buy", (state, cmd) => {
|
|
131
|
+
const args = cmd.args ?? {};
|
|
132
|
+
const index = typeof args["index"] === "number" ? args["index"] : null;
|
|
133
|
+
if (index === null)
|
|
134
|
+
return 0;
|
|
135
|
+
const store = storeAtGrid(state);
|
|
136
|
+
if (!store) {
|
|
137
|
+
state.msg?.(storeBuyGuard(null) ?? "You cannot purchase items when not in a store.");
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
const isHome = store.feat === FEAT.HOME;
|
|
141
|
+
const refusal = isHome ? storeRetrieveGuard(store) : storeBuyGuard(store);
|
|
142
|
+
if (refusal) {
|
|
143
|
+
state.msg?.(refusal);
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
const obj = store.stock[index];
|
|
147
|
+
if (!obj) {
|
|
148
|
+
state.msg?.(isHome
|
|
149
|
+
? "You cannot retrieve that item because it's not in the home."
|
|
150
|
+
: "You cannot buy that item because it's not in the store.");
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
const rawQty = typeof args["quantity"] === "number" ? args["quantity"] : 1;
|
|
154
|
+
const amt = Math.min(Math.max(1, rawQty), obj.number);
|
|
155
|
+
const result = deps.buy(store, obj, amt);
|
|
156
|
+
if (!result.ok) {
|
|
157
|
+
const table = isHome ? BUY_FAILURE_HOME : BUY_FAILURE_SHOP;
|
|
158
|
+
state.msg?.(table[result.failure ?? ""] ?? "The purchase failed.");
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
const bought = result.bought
|
|
162
|
+
? describeObject(state, result.bought, ODESC.PREFIX | ODESC.FULL)
|
|
163
|
+
: "the item";
|
|
164
|
+
/* comment_accept (do_cmd_buy L1717) prints before the sale line, exactly
|
|
165
|
+
* as the interactive shop screen orders it. */
|
|
166
|
+
if (result.acceptComment)
|
|
167
|
+
state.msg?.(result.acceptComment);
|
|
168
|
+
if (isHome)
|
|
169
|
+
state.msg?.(`You have ${bought}.`);
|
|
170
|
+
else
|
|
171
|
+
state.msg?.(`You bought ${bought} for ${result.price} gold.`);
|
|
172
|
+
if (result.emptied === "retired")
|
|
173
|
+
state.msg?.("The shopkeeper retires.");
|
|
174
|
+
else if (result.emptied === "restocked") {
|
|
175
|
+
state.msg?.("The shopkeeper brings out some new stock.");
|
|
176
|
+
}
|
|
177
|
+
return 0;
|
|
178
|
+
});
|
|
179
|
+
registry.register("shop-sell", (state, cmd) => {
|
|
180
|
+
const args = cmd.args ?? {};
|
|
181
|
+
const handle = typeof args["handle"] === "number" ? args["handle"] : null;
|
|
182
|
+
if (handle === null)
|
|
183
|
+
return 0;
|
|
184
|
+
const store = storeAtGrid(state);
|
|
185
|
+
if (!store) {
|
|
186
|
+
state.msg?.(storeSellGuard(null) ?? "You cannot sell items when not in a store.");
|
|
187
|
+
return 0;
|
|
188
|
+
}
|
|
189
|
+
const isHome = store.feat === FEAT.HOME;
|
|
190
|
+
const refusal = isHome ? storeStashGuard(store) : storeSellGuard(store);
|
|
191
|
+
if (refusal) {
|
|
192
|
+
state.msg?.(refusal);
|
|
193
|
+
return 0;
|
|
194
|
+
}
|
|
195
|
+
const obj = gearGet(state.gear, handle);
|
|
196
|
+
if (!obj) {
|
|
197
|
+
state.msg?.("You do not have that item.");
|
|
198
|
+
return 0;
|
|
199
|
+
}
|
|
200
|
+
const rawQty = typeof args["quantity"] === "number" ? args["quantity"] : obj.number;
|
|
201
|
+
const amt = Math.min(Math.max(1, rawQty), obj.number);
|
|
202
|
+
const result = deps.sell(store, handle, amt);
|
|
203
|
+
if (!result.ok) {
|
|
204
|
+
const why = {
|
|
205
|
+
"no-item": "You do not have that item.",
|
|
206
|
+
stuck: "Hmmm, it seems to be stuck.",
|
|
207
|
+
refused: "I do not wish to purchase this item.",
|
|
208
|
+
"no-room": isHome
|
|
209
|
+
? "Your home is full."
|
|
210
|
+
: "I have not the room in my store to keep it.",
|
|
211
|
+
};
|
|
212
|
+
state.msg?.(why[result.failure ?? ""] ?? "The sale failed.");
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
215
|
+
const sold = result.sold
|
|
216
|
+
? describeObject(state, result.sold, ODESC.PREFIX | ODESC.FULL)
|
|
217
|
+
: "the item";
|
|
218
|
+
if (isHome)
|
|
219
|
+
state.msg?.(`You have stashed ${sold}.`);
|
|
220
|
+
else if (result.price)
|
|
221
|
+
state.msg?.(`You sold ${sold} for ${result.price} gold.`);
|
|
222
|
+
else
|
|
223
|
+
state.msg?.(`You had ${sold}.`);
|
|
224
|
+
/* purchase_analyze's reaction comment (do_cmd_sell L1972), same ordering
|
|
225
|
+
* as the interactive shop screen. */
|
|
226
|
+
if (result.reactionComment)
|
|
227
|
+
state.msg?.(result.reactionComment);
|
|
228
|
+
return 0;
|
|
229
|
+
});
|
|
230
|
+
/* leave_store (ui-store.c): a UI-only close with no upstream game-state
|
|
231
|
+
* effect. Registered so the code has a real handler rather than falling
|
|
232
|
+
* through to the base registry's no-op stub silently. */
|
|
233
|
+
registry.register("shop-exit", () => 0);
|
|
234
|
+
}
|
|
73
235
|
//# sourceMappingURL=store-cmd.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store-cmd.js","sourceRoot":"","sources":["../../src/store/store-cmd.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"store-cmd.js","sourceRoot":"","sources":["../../src/store/store-cmd.ts"],"names":[],"mappings":"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"}
|
package/dist/version.d.ts
CHANGED
|
@@ -21,5 +21,5 @@ export declare const PARITY_BASELINE = "4.2.6";
|
|
|
21
21
|
* released tag is iterated takes a MINOR bump, because a published tag is pinned
|
|
22
22
|
* by digest in a catalogue and must never be moved.
|
|
23
23
|
*/
|
|
24
|
-
export declare const ENGINE_VERSION = "0.
|
|
24
|
+
export declare const ENGINE_VERSION = "0.27.0";
|
|
25
25
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/version.js
CHANGED
|
@@ -21,5 +21,5 @@ export const PARITY_BASELINE = "4.2.6";
|
|
|
21
21
|
* released tag is iterated takes a MINOR bump, because a published tag is pinned
|
|
22
22
|
* by digest in a catalogue and must never be moved.
|
|
23
23
|
*/
|
|
24
|
-
export const ENGINE_VERSION = "0.
|
|
24
|
+
export const ENGINE_VERSION = "0.27.0";
|
|
25
25
|
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
package/src/agent/act.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import type { GameState } from "../game/context.js";
|
|
20
|
+
import { REST_COMPLETE } from "../game/context.js";
|
|
20
21
|
import { targetSetLocation, targetSetMonster } from "../game/target.js";
|
|
21
22
|
import { loc } from "../loc.js";
|
|
22
23
|
import { AgentCapabilityError } from "./types.js";
|
|
@@ -67,7 +68,7 @@ export function createAgentActions(
|
|
|
67
68
|
move: (dir) => dirCmd("walk", dir),
|
|
68
69
|
melee: (dir) => dirCmd("walk", dir),
|
|
69
70
|
hold: () => cmd("hold"),
|
|
70
|
-
rest: () => cmd("rest"),
|
|
71
|
+
rest: (count) => cmd("rest", { count: count ?? REST_COMPLETE }),
|
|
71
72
|
descend: () => cmd("descend"),
|
|
72
73
|
ascend: () => cmd("ascend"),
|
|
73
74
|
tunnel: (dir) => dirCmd("tunnel", dir),
|
package/src/agent/types.ts
CHANGED
|
@@ -634,7 +634,17 @@ export interface AgentActions {
|
|
|
634
634
|
/** Melee an adjacent monster (a walk into its grid). */
|
|
635
635
|
melee(dir: number): AgentCommand;
|
|
636
636
|
hold(): AgentCommand;
|
|
637
|
-
|
|
637
|
+
/**
|
|
638
|
+
* do_cmd_rest (cmd-cave.c:1619): rest for `count` game turns, or one of the
|
|
639
|
+
* REST_ special modes (REST_COMPLETE / REST_ALL_POINTS / REST_SOME_POINTS,
|
|
640
|
+
* player-util.h:53-55, exported from game/context.js). One command turn is
|
|
641
|
+
* many game turns: the engine self-continues on its internal queue exactly
|
|
642
|
+
* like a run, so a caller normally issues this ONCE and is not asked for
|
|
643
|
+
* another command until the rest ends (disturbed, or its own condition met).
|
|
644
|
+
* Omitting `count` rests as needed (REST_COMPLETE), matching the human UI's
|
|
645
|
+
* own default prompt answer ("&").
|
|
646
|
+
*/
|
|
647
|
+
rest(count?: number): AgentCommand;
|
|
638
648
|
descend(): AgentCommand;
|
|
639
649
|
ascend(): AgentCommand;
|
|
640
650
|
tunnel(dir: number): AgentCommand;
|
package/src/game/context.ts
CHANGED
|
@@ -458,8 +458,7 @@ export interface GameState {
|
|
|
458
458
|
* (the per-session file-static player_turns_rested that gates the x2 regen):
|
|
459
459
|
* resting_turn accumulates for the character's whole life. Bumped once per rested
|
|
460
460
|
* turn (player-util.c:1487). Persists in the save; absent is treated as 0.
|
|
461
|
-
* The live producer
|
|
462
|
-
* WIRING-NEEDED note in session/save.ts.
|
|
461
|
+
* The live producer is the rest command's restAction (game/player-turn.ts).
|
|
463
462
|
*/
|
|
464
463
|
restingTurn?: number;
|
|
465
464
|
/**
|
|
@@ -604,11 +603,20 @@ export interface GameState {
|
|
|
604
603
|
* player->upkeep->resting + the file-static player_turns_rested
|
|
605
604
|
* (player-util.c:1417,1472). count mirrors upkeep->resting (>0 = timed turns
|
|
606
605
|
* left, or a REST_ special: COMPLETE=-2, ALL_POINTS=-1, SOME_POINTS=-3);
|
|
607
|
-
* turnsRested gates the x2 regen. Set/tracked by the
|
|
608
|
-
* (
|
|
606
|
+
* turnsRested gates the x2 regen. Set/tracked by the rest command
|
|
607
|
+
* (restAction, game/player-turn.ts); absent when not resting.
|
|
609
608
|
*/
|
|
610
609
|
resting?: { count: number; turnsRested: number };
|
|
611
610
|
|
|
611
|
+
/**
|
|
612
|
+
* player_resting_repeat_count (player-util.c:1523): the file-static count
|
|
613
|
+
* remembered from the last TIMED rest, so re-issuing rest with count === 1
|
|
614
|
+
* repeats it (cmd-cave.c:1638-1643). Session-transient like the file-static
|
|
615
|
+
* it mirrors - never persisted, reset only by a fresh rest that supplies a
|
|
616
|
+
* new count > 1. Absent is the upstream static's zero-initialized state.
|
|
617
|
+
*/
|
|
618
|
+
restRepeatCount?: number;
|
|
619
|
+
|
|
612
620
|
/* --- injected hooks --- */
|
|
613
621
|
/**
|
|
614
622
|
* The EffectBuilder injections this session was wired with: the resolvers for
|
|
@@ -1168,6 +1176,17 @@ export interface RunState {
|
|
|
1168
1176
|
* reach for. The HOST still resolves the choice map (it is save state, and the
|
|
1169
1177
|
* Fixes & tweaks menu is built from it) - that reading lives in the host. */
|
|
1170
1178
|
|
|
1179
|
+
/**
|
|
1180
|
+
* The REST_ constants (player-util.h:53-55): conditional rest modes, which
|
|
1181
|
+
* rest until a condition is met rather than for a fixed turn count. Exported
|
|
1182
|
+
* so callers outside the engine (the agent facade, a mod, a host UI) can build
|
|
1183
|
+
* a rest command's `args.count` without re-declaring the magic numbers - the
|
|
1184
|
+
* web shell used to carry its own private copy of exactly these three.
|
|
1185
|
+
*/
|
|
1186
|
+
export const REST_COMPLETE = -2;
|
|
1187
|
+
export const REST_ALL_POINTS = -1;
|
|
1188
|
+
export const REST_SOME_POINTS = -3;
|
|
1189
|
+
|
|
1171
1190
|
/**
|
|
1172
1191
|
* player_resting_is_special (player-util.c:1382): the conditional REST_ modes,
|
|
1173
1192
|
* which rest until a condition is met rather than for a fixed count.
|
|
@@ -1180,7 +1199,11 @@ export interface RunState {
|
|
|
1180
1199
|
* three bodies only agrees by luck.
|
|
1181
1200
|
*/
|
|
1182
1201
|
export function playerRestingIsSpecial(count: number): boolean {
|
|
1183
|
-
return
|
|
1202
|
+
return (
|
|
1203
|
+
count === REST_ALL_POINTS ||
|
|
1204
|
+
count === REST_COMPLETE ||
|
|
1205
|
+
count === REST_SOME_POINTS
|
|
1206
|
+
);
|
|
1184
1207
|
}
|
|
1185
1208
|
|
|
1186
1209
|
/**
|
package/src/game/loop.ts
CHANGED
|
@@ -49,6 +49,7 @@ import {
|
|
|
49
49
|
playAmbientSound,
|
|
50
50
|
playerHasWorld,
|
|
51
51
|
playerOfHasWorld,
|
|
52
|
+
playerRestingCompleteSpecial,
|
|
52
53
|
playerUpdateLight,
|
|
53
54
|
processDamageOverTime,
|
|
54
55
|
processExpDrain,
|
|
@@ -177,7 +178,8 @@ const REST_REQUIRED_FOR_REGEN = 5;
|
|
|
177
178
|
* player_resting_can_regenerate (player-util.c:1461): the player earns the x2
|
|
178
179
|
* regeneration bonus once REST_REQUIRED_FOR_REGEN turns of the current rest
|
|
179
180
|
* have elapsed, or immediately for the conditional REST_ modes. The rest
|
|
180
|
-
* command (
|
|
181
|
+
* command's restAction (game/player-turn.ts) sets state.resting; absent when
|
|
182
|
+
* not resting. No RNG.
|
|
181
183
|
*/
|
|
182
184
|
function playerRestingCanRegenerate(state: GameState): boolean {
|
|
183
185
|
const r = state.resting;
|
|
@@ -551,10 +553,10 @@ function loopStop(state: GameState): LoopStatus | null {
|
|
|
551
553
|
* keeps the headless harnesses, the borg and the tests driving a whole run
|
|
552
554
|
* inside one call.
|
|
553
555
|
*
|
|
554
|
-
* The upstream call sits at the top of process_player,
|
|
555
|
-
*
|
|
556
|
-
*
|
|
557
|
-
*
|
|
556
|
+
* The upstream call sits at the top of process_player, right after
|
|
557
|
+
* player_resting_complete_special (game-world.c:936-937); processPlayerChecked
|
|
558
|
+
* calls playerRestingCompleteSpecial immediately before this, which is the
|
|
559
|
+
* same point in the order.
|
|
558
560
|
*/
|
|
559
561
|
export function checkForPlayerInterrupt(state: GameState): InterruptResponse {
|
|
560
562
|
if (!state.checkInterrupt) return "go";
|
|
@@ -582,8 +584,15 @@ export function checkForPlayerInterrupt(state: GameState): InterruptResponse {
|
|
|
582
584
|
}
|
|
583
585
|
|
|
584
586
|
/**
|
|
585
|
-
* process_player (game-world.c:933):
|
|
586
|
-
* is the host asking for its event
|
|
587
|
+
* process_player (game-world.c:933-937): player_resting_complete_special, the
|
|
588
|
+
* interrupt check, then the turn. "pause" is the host asking for its event
|
|
589
|
+
* loop back before the turn is taken.
|
|
590
|
+
*
|
|
591
|
+
* playerRestingCompleteSpecial runs first, exactly as upstream orders it -
|
|
592
|
+
* a conditional rest (REST_ALL_POINTS / REST_COMPLETE / REST_SOME_POINTS) that
|
|
593
|
+
* has already met its own stopping condition (HP/SP full, no bad statuses) is
|
|
594
|
+
* disturbed before the interrupt check or the turn, so it does not spend one
|
|
595
|
+
* extra turn beyond upstream's.
|
|
587
596
|
*
|
|
588
597
|
* mayPause is false for the FIRST turn of a runGameLoop call. A call resumed
|
|
589
598
|
* after LOOP_STATUS.PAUSE re-enters at that same point, so pausing there would
|
|
@@ -596,6 +605,7 @@ function processPlayerChecked(
|
|
|
596
605
|
registry: ActionRegistry,
|
|
597
606
|
mayPause: boolean,
|
|
598
607
|
): PlayerTurnResult | "pause" {
|
|
608
|
+
playerRestingCompleteSpecial(state);
|
|
599
609
|
if (checkForPlayerInterrupt(state) === "pause" && mayPause) return "pause";
|
|
600
610
|
return processPlayer(state, registry);
|
|
601
611
|
}
|
package/src/game/player-turn.ts
CHANGED
|
@@ -59,6 +59,8 @@ import {
|
|
|
59
59
|
deleteMonster,
|
|
60
60
|
gameTakeHitHooks,
|
|
61
61
|
movePlayer,
|
|
62
|
+
playerIsResting,
|
|
63
|
+
playerRestingIsSpecial,
|
|
62
64
|
squareMonster,
|
|
63
65
|
} from "./context.js";
|
|
64
66
|
import { gearGet } from "./gear.js";
|
|
@@ -800,6 +802,98 @@ export function sleepAction(state: GameState, _cmd: PlayerCommand): number {
|
|
|
800
802
|
return state.z.moveEnergy;
|
|
801
803
|
}
|
|
802
804
|
|
|
805
|
+
/**
|
|
806
|
+
* rest (do_cmd_rest, cmd-cave.c:1619-1669): rest for `cmd.args.count` turns, or
|
|
807
|
+
* until one of the REST_ special conditions (REST_COMPLETE / REST_ALL_POINTS /
|
|
808
|
+
* REST_SOME_POINTS, player-util.h:53-55). A DIFFERENT upstream function from
|
|
809
|
+
* hold/sleep - it calls neither search nor autopickup nor the shop-entry
|
|
810
|
+
* disturb (see holdAction's doc for the contrast).
|
|
811
|
+
*
|
|
812
|
+
* ONE CALL IS ONE GAME TURN, exactly like upstream's own do_cmd_rest: the C
|
|
813
|
+
* takes a single player_resting_step_turn and then re-queues itself with
|
|
814
|
+
* cmdq_push(CMD_REST) so process_player's do-while keeps draining it without a
|
|
815
|
+
* fresh keypress. This is the same shape as runAction's self-continuation
|
|
816
|
+
* (player-path.ts) - the command pushes its own follow-up onto
|
|
817
|
+
* state.cmdQueue, which processPlayer drains before asking the host's
|
|
818
|
+
* nextCommand for anything. A host that never pauses (a headless harness, an
|
|
819
|
+
* agent, the tests) therefore gets every turn of a whole rest inside one
|
|
820
|
+
* runGameLoop call, same as it already does for a run.
|
|
821
|
+
*
|
|
822
|
+
* disturb() (player-path.ts) is what stops the self-continuation: it deletes
|
|
823
|
+
* state.resting AND empties state.cmdQueue, so a rest interrupted mid-flight
|
|
824
|
+
* (a monster becoming visible, damage taken, the HP/SP-full check in
|
|
825
|
+
* playerRestingCompleteSpecial) simply has nothing left to pop next time
|
|
826
|
+
* process_player asks - upstream's player_rest_disturb flag achieves the same
|
|
827
|
+
* "the stale queued continuation must not fire" result with a lagging flag;
|
|
828
|
+
* this port's synchronous queue-clear needs no flag at all.
|
|
829
|
+
*/
|
|
830
|
+
export function restAction(state: GameState, cmd: PlayerCommand): number {
|
|
831
|
+
/* cmd_get_arg_choice returning != CMD_OK (cmd-cave.c:1624-1625): no count at
|
|
832
|
+
* all is a silent no-op, taking no turn. */
|
|
833
|
+
const arg = cmd.args?.count;
|
|
834
|
+
let n = typeof arg === "number" ? arg : NaN;
|
|
835
|
+
if (!Number.isFinite(n)) return 0;
|
|
836
|
+
|
|
837
|
+
/* "A little sanity checking on the input" (cmd-cave.c:1627-1632): only the
|
|
838
|
+
* three REST_ codes are valid negative choices. */
|
|
839
|
+
if (n < 0 && !playerRestingIsSpecial(n)) return 0;
|
|
840
|
+
|
|
841
|
+
/* Do some upkeep on the first turn of rest (cmd-cave.c:1634-1644). Checked
|
|
842
|
+
* BEFORE this call's count takes effect, exactly as upstream checks
|
|
843
|
+
* player_is_resting() before player_resting_set_count() runs. */
|
|
844
|
+
if (!playerIsResting(state)) {
|
|
845
|
+
/* player->upkeep->update |= PU_BONUS (cmd-cave.c:1636): applied eagerly
|
|
846
|
+
* here rather than deferred, matching how the rest of this port recomputes
|
|
847
|
+
* derived state through direct hook calls instead of dirty bits. */
|
|
848
|
+
state.updateBonuses?.();
|
|
849
|
+
|
|
850
|
+
if (n > 1) state.restRepeatCount = n;
|
|
851
|
+
else if (n === 1) n = state.restRepeatCount ?? 0;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/* player_resting_set_count (player-util.c:1425-1445): store the count (or
|
|
855
|
+
* cancel to the not-resting sentinel), clamped at 9999. This port represents
|
|
856
|
+
* "not resting" as state.resting being absent rather than count === 0, so a
|
|
857
|
+
* count of 0 (an explicit "0 turns", or a repeat with nothing remembered)
|
|
858
|
+
* deletes the field instead of storing a zero. */
|
|
859
|
+
if (n === 0 || (n < 0 && !playerRestingIsSpecial(n))) {
|
|
860
|
+
delete state.resting;
|
|
861
|
+
} else if (!state.resting) {
|
|
862
|
+
state.resting = { count: Math.min(n, 9999), turnsRested: 0 };
|
|
863
|
+
} else {
|
|
864
|
+
state.resting.count = Math.min(n, 9999);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/* "Set the counter, and stop if told to" (cmd-cave.c:1646-1649). */
|
|
868
|
+
if (!playerIsResting(state)) return 0;
|
|
869
|
+
const r = state.resting!;
|
|
870
|
+
|
|
871
|
+
/* player_resting_step_turn (player-util.c:1472-1489): spend the turn,
|
|
872
|
+
* decrement a timed count, bump both rested counters. */
|
|
873
|
+
if (r.count > 0) r.count--;
|
|
874
|
+
r.turnsRested++;
|
|
875
|
+
state.restingTurn = (state.restingTurn ?? 0) + 1;
|
|
876
|
+
|
|
877
|
+
/* "Prepare to continue, or cancel and clean up" (cmd-cave.c:1657-1667). `n`
|
|
878
|
+
* here is upstream's own local n: unchanged since the upkeep block above for
|
|
879
|
+
* a special rest, or the count this call was asked to do, decremented by
|
|
880
|
+
* step_turn's already-applied r.count-- for a timed one - so r.count IS
|
|
881
|
+
* upstream's `n - 1` at this point, and pushing it as the next choice is
|
|
882
|
+
* pushing exactly that. */
|
|
883
|
+
if (r.count > 0) {
|
|
884
|
+
if (!state.cmdQueue) state.cmdQueue = [];
|
|
885
|
+
state.cmdQueue.push({ code: "rest", args: { count: r.count } });
|
|
886
|
+
} else if (playerRestingIsSpecial(n)) {
|
|
887
|
+
if (!state.cmdQueue) state.cmdQueue = [];
|
|
888
|
+
state.cmdQueue.push({ code: "rest", args: { count: n } });
|
|
889
|
+
state.restRepeatCount = 0;
|
|
890
|
+
} else {
|
|
891
|
+
delete state.resting;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
return state.z.moveEnergy;
|
|
895
|
+
}
|
|
896
|
+
|
|
803
897
|
/**
|
|
804
898
|
* descend / ascend: signal a level change (player->upkeep->generate_level).
|
|
805
899
|
* The generation and depth change belong to the world integration, which does
|
|
@@ -854,7 +948,7 @@ export function createDefaultRegistry(): ActionRegistry {
|
|
|
854
948
|
reg.register("walk", walkAction);
|
|
855
949
|
reg.register("jump", jumpAction);
|
|
856
950
|
reg.register("hold", holdAction);
|
|
857
|
-
reg.register("rest",
|
|
951
|
+
reg.register("rest", restAction);
|
|
858
952
|
reg.register("sleep", sleepAction);
|
|
859
953
|
reg.register("descend", descendAction);
|
|
860
954
|
reg.register("ascend", ascendAction);
|
package/src/game/world.ts
CHANGED
|
@@ -59,7 +59,13 @@ import { floorPile } from "./floor.js";
|
|
|
59
59
|
import { monsterGroupChangeIndex } from "./mon-group.js";
|
|
60
60
|
import { disturb } from "./player-path.js";
|
|
61
61
|
import { DEFAULT_HITPOINT_WARN } from "./project-cast.js";
|
|
62
|
-
import {
|
|
62
|
+
import {
|
|
63
|
+
REST_ALL_POINTS,
|
|
64
|
+
REST_COMPLETE,
|
|
65
|
+
REST_SOME_POINTS,
|
|
66
|
+
deleteMonster,
|
|
67
|
+
playerRestingIsSpecial,
|
|
68
|
+
} from "./context.js";
|
|
63
69
|
import type { GameState } from "./context.js";
|
|
64
70
|
|
|
65
71
|
/** player-util.h PY_EXERT_* over-exertion bit flags. */
|
|
@@ -126,6 +132,52 @@ export function playerHasWorld(state: GameState, flag: number): boolean {
|
|
|
126
132
|
return state.playerState?.pflags.has(flag) ?? false;
|
|
127
133
|
}
|
|
128
134
|
|
|
135
|
+
/**
|
|
136
|
+
* player_resting_complete_special (player-util.c:1495-1520): decide whether a
|
|
137
|
+
* conditional rest (one of the REST_ special counts) has met its own stopping
|
|
138
|
+
* condition, and disturb() to end it if so. A no-op when not resting on a
|
|
139
|
+
* special count. No RNG.
|
|
140
|
+
*
|
|
141
|
+
* Lives here rather than in context.ts (where state.resting and the REST_
|
|
142
|
+
* constants are declared) because it needs disturb() (player-path.ts) and
|
|
143
|
+
* playerHasWorld (below); context.ts cannot import either without a cycle
|
|
144
|
+
* (player-path.ts and this module both import GameState from context.ts).
|
|
145
|
+
* loop.ts calls this at the top of every process_player equivalent
|
|
146
|
+
* (game-world.c:936, immediately before EVENT_CHECK_INTERRUPT), which is
|
|
147
|
+
* upstream's own call site.
|
|
148
|
+
*/
|
|
149
|
+
export function playerRestingCompleteSpecial(state: GameState): void {
|
|
150
|
+
const r = state.resting;
|
|
151
|
+
if (!r || !playerRestingIsSpecial(r.count)) return;
|
|
152
|
+
const p = state.actor.player;
|
|
153
|
+
const t = p.timed;
|
|
154
|
+
|
|
155
|
+
if (r.count === REST_ALL_POINTS) {
|
|
156
|
+
if (p.chp === p.mhp && p.csp === p.msp) disturb(state);
|
|
157
|
+
} else if (r.count === REST_COMPLETE) {
|
|
158
|
+
if (
|
|
159
|
+
p.chp === p.mhp &&
|
|
160
|
+
(p.csp === p.msp || playerHasWorld(state, PF.COMBAT_REGEN)) &&
|
|
161
|
+
!(t[TMD.BLIND] ?? 0) &&
|
|
162
|
+
!(t[TMD.CONFUSED] ?? 0) &&
|
|
163
|
+
!(t[TMD.POISONED] ?? 0) &&
|
|
164
|
+
!(t[TMD.AFRAID] ?? 0) &&
|
|
165
|
+
!(t[TMD.TERROR] ?? 0) &&
|
|
166
|
+
!(t[TMD.STUN] ?? 0) &&
|
|
167
|
+
!(t[TMD.CUT] ?? 0) &&
|
|
168
|
+
!(t[TMD.SLOW] ?? 0) &&
|
|
169
|
+
!(t[TMD.PARALYZED] ?? 0) &&
|
|
170
|
+
!(t[TMD.IMAGE] ?? 0) &&
|
|
171
|
+
!p.wordRecall &&
|
|
172
|
+
!p.deepDescent
|
|
173
|
+
) {
|
|
174
|
+
disturb(state);
|
|
175
|
+
}
|
|
176
|
+
} else if (r.count === REST_SOME_POINTS) {
|
|
177
|
+
if (p.chp === p.mhp || p.csp === p.msp) disturb(state);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
129
181
|
/** state.dam_red / state.perc_dam_red from the last calc_bonuses (else zero). */
|
|
130
182
|
function worldReduction(state: GameState): DamageReduction {
|
|
131
183
|
const ps = state.playerState;
|
package/src/session/game.ts
CHANGED
|
@@ -215,6 +215,7 @@ import {
|
|
|
215
215
|
} from "../store/transact.js";
|
|
216
216
|
import type { BuyResult, SellResult, TxnKnowledge } from "../store/transact.js";
|
|
217
217
|
import { storeCheckNum } from "../store/store.js";
|
|
218
|
+
import { installStoreCommands } from "../store/store-cmd.js";
|
|
218
219
|
import { priceItem } from "../store/price.js";
|
|
219
220
|
import {
|
|
220
221
|
addMonsterMessage,
|
|
@@ -3803,7 +3804,7 @@ export function startGame(pack: GamePack, opts: StartGameOptions = {}): StartedG
|
|
|
3803
3804
|
changeLevel,
|
|
3804
3805
|
),
|
|
3805
3806
|
wizardBundles: wired.wizardBundles,
|
|
3806
|
-
...makeStoreApi(state, reg, wired.flavor, options),
|
|
3807
|
+
...makeStoreApi(state, reg, wired.flavor, options, wired.registry),
|
|
3807
3808
|
};
|
|
3808
3809
|
}
|
|
3809
3810
|
|
|
@@ -3818,6 +3819,7 @@ function makeStoreApi(
|
|
|
3818
3819
|
reg: CoreRegistries,
|
|
3819
3820
|
flavor: FlavorKnowledge,
|
|
3820
3821
|
options: OptionState,
|
|
3822
|
+
registry: ActionRegistry,
|
|
3821
3823
|
): Pick<StartedGame, "buy" | "sell" | "sellFloor" | "price" | "willBuy"> {
|
|
3822
3824
|
const storeCtx = (): StoreMaintContext => ({
|
|
3823
3825
|
rng: state.rng,
|
|
@@ -3901,7 +3903,7 @@ function makeStoreApi(
|
|
|
3901
3903
|
* stale. Unused by storeBuy, which learns flavour only. */
|
|
3902
3904
|
learnRunes: { env: state.runeEnv, runes: buildRuneList(state.runeEnv) },
|
|
3903
3905
|
});
|
|
3904
|
-
|
|
3906
|
+
const storeApi: Pick<StartedGame, "buy" | "sell" | "sellFloor" | "price" | "willBuy"> = {
|
|
3905
3907
|
buy: (store, obj, amt): BuyResult => {
|
|
3906
3908
|
/* do_cmd_retrieve (store.c:1783): Home Take is free - no price, no
|
|
3907
3909
|
* ORIGIN_STORE, no shuffle/maint RNG. Reuse the existing homeRetrieve. */
|
|
@@ -4055,6 +4057,12 @@ function makeStoreApi(
|
|
|
4055
4057
|
state.storeBehaviour,
|
|
4056
4058
|
),
|
|
4057
4059
|
};
|
|
4060
|
+
/* shop-buy / shop-sell / shop-exit (docs/PLANNED.md, "An agent cannot
|
|
4061
|
+
* trade"): wire the agent-facing store commands through the SAME buy/sell
|
|
4062
|
+
* closures the interactive shop screen calls, so a keystroke and an
|
|
4063
|
+
* agent's command reach identical pack/gold/knowledge effects. */
|
|
4064
|
+
installStoreCommands(registry, { buy: storeApi.buy, sell: storeApi.sell });
|
|
4065
|
+
return storeApi;
|
|
4058
4066
|
}
|
|
4059
4067
|
|
|
4060
4068
|
/**
|
|
@@ -4691,6 +4699,6 @@ export function loadGame(
|
|
|
4691
4699
|
changeLevel,
|
|
4692
4700
|
),
|
|
4693
4701
|
wizardBundles: wired.wizardBundles,
|
|
4694
|
-
...makeStoreApi(state, reg, wired.flavor, options),
|
|
4702
|
+
...makeStoreApi(state, reg, wired.flavor, options, wired.registry),
|
|
4695
4703
|
};
|
|
4696
4704
|
}
|