@rpgm-tools/neo-angband-core 0.32.0 → 0.34.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.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/dist/game/effect-general.d.ts.map +1 -1
  3. package/dist/game/effect-general.js +4 -2
  4. package/dist/game/effect-general.js.map +1 -1
  5. package/dist/game/project-cast.d.ts.map +1 -1
  6. package/dist/game/project-cast.js +7 -0
  7. package/dist/game/project-cast.js.map +1 -1
  8. package/dist/mod/hooks.d.ts +138 -18
  9. package/dist/mod/hooks.d.ts.map +1 -1
  10. package/dist/mod/hooks.js +62 -0
  11. package/dist/mod/hooks.js.map +1 -1
  12. package/dist/mod/save-blocks.d.ts +1 -1
  13. package/dist/obj/knowledge.d.ts +16 -2
  14. package/dist/obj/knowledge.d.ts.map +1 -1
  15. package/dist/obj/knowledge.js +17 -1
  16. package/dist/obj/knowledge.js.map +1 -1
  17. package/dist/player/history.d.ts +7 -2
  18. package/dist/player/history.d.ts.map +1 -1
  19. package/dist/player/history.js +12 -4
  20. package/dist/player/history.js.map +1 -1
  21. package/dist/session/game.d.ts.map +1 -1
  22. package/dist/session/game.js +15 -5
  23. package/dist/session/game.js.map +1 -1
  24. package/dist/session/save.d.ts +2 -0
  25. package/dist/session/save.d.ts.map +1 -1
  26. package/dist/session/save.js +2 -0
  27. package/dist/session/save.js.map +1 -1
  28. package/dist/version.d.ts +1 -1
  29. package/dist/version.js +1 -1
  30. package/dist/world/project.d.ts +16 -1
  31. package/dist/world/project.d.ts.map +1 -1
  32. package/dist/world/project.js +17 -1
  33. package/dist/world/project.js.map +1 -1
  34. package/package.json +1 -1
  35. package/src/game/effect-general.ts +9 -2
  36. package/src/game/project-cast.ts +7 -0
  37. package/src/mod/hooks.ts +216 -14
  38. package/src/obj/knowledge.ts +16 -0
  39. package/src/player/history.ts +21 -6
  40. package/src/session/game.ts +19 -8
  41. package/src/session/save.ts +4 -0
  42. package/src/version.ts +1 -1
  43. package/src/world/project.ts +33 -2
@@ -1132,22 +1132,22 @@ function wireGame(
1132
1132
  * per lethal blow, duplicates included, so with no hook installed the
1133
1133
  * `?? true` below IS the faithful answer. `duplicate` tells a mod what it
1134
1134
  * needs to decide without core deciding anything. */
1135
- const what = `Killed ${mon.race.name}`;
1136
- const wanted =
1137
- state.modHooks?.historyAdd?.({
1138
- what,
1139
- type: HIST.SLAY_UNIQUE,
1140
- duplicate: alreadyDead,
1141
- }) ?? true;
1135
+ const entry: import("../mod/hooks.js").HistoryAddEntry = {
1136
+ what: `Killed ${mon.race.name}`,
1137
+ type: HIST.SLAY_UNIQUE,
1138
+ duplicate: alreadyDead,
1139
+ };
1140
+ const wanted = state.modHooks?.historyAdd?.(entry) ?? true;
1142
1141
  if (wanted) {
1143
1142
  const stamp = historyStamp(state);
1144
1143
  historyAdd(
1145
1144
  state.actor.player,
1146
- what,
1145
+ entry.what,
1147
1146
  HIST.SLAY_UNIQUE,
1148
1147
  stamp.dlev,
1149
1148
  stamp.clev,
1150
1149
  stamp.turn,
1150
+ entry.expandUserInput,
1151
1151
  );
1152
1152
  }
1153
1153
  }
@@ -2659,6 +2659,11 @@ function makeChangeLevel(
2659
2659
  state.traps = stash.traps;
2660
2660
  state.known = stash.known;
2661
2661
  if (stash.decoy) state.decoy = stash.decoy;
2662
+ /* A level restored after single combat is the same kind of revisit as
2663
+ * one recovered from birth_levels_persist. Faithful core leaves every
2664
+ * field untouched; an opt-in mod can update transient per-level state
2665
+ * before the player and its monsters resume. */
2666
+ state.modHooks?.levelRevisited?.(state.chunk, stash.turn, state.turn);
2662
2667
  const back = state.oldGrid ?? state.actor.grid;
2663
2668
  state.actor.grid = back;
2664
2669
  state.chunk.setMon(back, -1);
@@ -2788,6 +2793,12 @@ function makeChangeLevel(
2788
2793
  * the turns the level was frozen (turn - chunk->freeze-turn). */
2789
2794
  restoreMonsters(state, state.turn - stored.turn);
2790
2795
 
2796
+ /* A restored persistent level is live again. Core deliberately does
2797
+ * nothing here, matching 4.2.6; a mod may observe the exact freeze and
2798
+ * resume turns to update level-owned transient state before any monster
2799
+ * can receive a turn. */
2800
+ state.modHooks?.levelRevisited?.(state.chunk, stored.turn, state.turn);
2801
+
2791
2802
  /* Place the player (prepare_next_level non-arena, L1497-1501): sanitize
2792
2803
  * the retained grid into a legal arrival square, then player_place.
2793
2804
  * DEFERRAL: exact stair-connector matching (dun->persist / one_off_*
@@ -1401,6 +1401,8 @@ export interface SavedHistoryInfo {
1401
1401
  artifactName: string;
1402
1402
  turn: number;
1403
1403
  event: string;
1404
+ /** A mod saved `event` as raw user input for display-time expansion. */
1405
+ expandUserInput?: true;
1404
1406
  /** Legacy JSON saves used a numeric aIdx; never written by this version. */
1405
1407
  aIdx?: number;
1406
1408
  }
@@ -1455,6 +1457,7 @@ export function serializePlayer(
1455
1457
  artifactName: ids.artifactName(e.aIdx) ?? "",
1456
1458
  turn: e.turn,
1457
1459
  event: e.event,
1460
+ ...(e.expandUserInput === true ? { expandUserInput: true } : {}),
1458
1461
  })),
1459
1462
  equipment: [...p.equipment],
1460
1463
  objKnown: {
@@ -1581,6 +1584,7 @@ export function deserializePlayer(
1581
1584
  aIdx,
1582
1585
  turn: e.turn,
1583
1586
  event: e.event,
1587
+ ...(e.expandUserInput === true ? { expandUserInput: true } : {}),
1584
1588
  });
1585
1589
  }
1586
1590
  p.equipment = [...data.equipment];
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.32.0";
26
+ export const ENGINE_VERSION = "0.34.0";
@@ -290,7 +290,13 @@ export interface ProjectParams {
290
290
  origin: Loc;
291
291
  /** finish: the target grid, or a grid to travel toward. */
292
292
  finish: Loc;
293
- /** rad: 0 = bolt/beam, 1..20 = ball radius, or max arc length. */
293
+ /**
294
+ * rad: 0 = bolt/beam, 1..20 = ball radius, or max arc length.
295
+ *
296
+ * The upper end is a convention rather than a rule - nothing in 4.2.6 checks
297
+ * it, and a radius above `maxRange` produces distances the damage table has
298
+ * no entry for. `resolveRadius` is where that can be decided.
299
+ */
294
300
  rad: number;
295
301
  /** typ: GF_ projection type, opaque to the driver; passed to the hooks. */
296
302
  typ: number;
@@ -312,6 +318,15 @@ export interface ProjectParams {
312
318
  believedWall?: (grid: Loc) => boolean;
313
319
  /** cave->decoy: PROJECT_STOP breaks the path here (project.c:218). */
314
320
  decoy?: Loc | null;
321
+ /**
322
+ * The mod behaviour seam for `rad` (ModHooks.projectionRadius), threaded in
323
+ * by the caller because this driver holds no GameState and must not learn
324
+ * about one. Absent is the faithful path: `rad` is used exactly as given.
325
+ *
326
+ * Called once, before any geometry is built, and given the same `maxRange`
327
+ * the damage table below is sized by.
328
+ */
329
+ resolveRadius?: (rad: number, maxRange: number) => number;
315
330
  }
316
331
 
317
332
  /** The computed blast: pure geometry + damage, no side effects. */
@@ -354,6 +369,15 @@ export function computeProjection(c: Chunk, p: ProjectParams): Projection {
354
369
  const degreesOfArc = p.degreesOfArc ?? 0;
355
370
  const diameterOfSource = p.diameterOfSource ?? 0;
356
371
 
372
+ /* THE RADIUS SEAM (ModHooks.projectionRadius, threaded in as resolveRadius).
373
+ * Faithful 4.2.6 takes the radius as given and never checks it against
374
+ * max_range, which is what leaves `damAtDist` below - sized max_range + 1 -
375
+ * short of the distances a larger radius produces. The upstream fix for that
376
+ * (issue #6671) is one clamp here, and it is a mod's to install: core carries
377
+ * the tag's behaviour, warts included, and holds only the point at which the
378
+ * radius can be decided. */
379
+ if (p.resolveRadius) rad = p.resolveRadius(rad, maxRange);
380
+
357
381
  /* No projection path - jump to target. */
358
382
  let start: Loc;
359
383
  if (flg & PROJECT.JUMP) {
@@ -518,7 +542,14 @@ export function computeProjection(c: Chunk, p: ProjectParams): Projection {
518
542
  }
519
543
  }
520
544
 
521
- /* Calculate and store the actual damage at each distance. */
545
+ /* Calculate and store the actual damage at each distance.
546
+ *
547
+ * SIZED BY max_range, NOT BY rad, exactly as upstream sizes dam_at_dist. A
548
+ * radius above max_range therefore leaves the far distances with no entry at
549
+ * all, and every read of one is a hole the caller cannot see: the C reads
550
+ * stale memory, and this reads `undefined` straight into the damage a handler
551
+ * is handed. Whether the radius can get there is decided above, by
552
+ * resolveRadius. */
522
553
  const damAtDist: number[] = new Array(maxRange + 1);
523
554
  for (let i = 0; i <= maxRange; i++) {
524
555
  let damTemp: number;