@rpgm-tools/neo-angband-core 0.19.0 → 0.20.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/cmd.d.ts +44 -2
- package/dist/cmd.d.ts.map +1 -1
- package/dist/cmd.js +59 -3
- package/dist/cmd.js.map +1 -1
- package/dist/game/context.d.ts +31 -0
- package/dist/game/context.d.ts.map +1 -1
- package/dist/game/context.js.map +1 -1
- package/dist/game/display.d.ts +26 -0
- package/dist/game/display.d.ts.map +1 -1
- package/dist/game/display.js +116 -22
- package/dist/game/display.js.map +1 -1
- package/dist/game/dump-level.d.ts +1 -1
- package/dist/game/dump-level.js +1 -1
- package/dist/game/effect-attack.d.ts.map +1 -1
- package/dist/game/effect-attack.js +1 -3
- package/dist/game/effect-attack.js.map +1 -1
- package/dist/game/effect-general.d.ts.map +1 -1
- package/dist/game/effect-general.js +12 -11
- package/dist/game/effect-general.js.map +1 -1
- package/dist/game/effect-item.d.ts.map +1 -1
- package/dist/game/effect-item.js +10 -4
- package/dist/game/effect-item.js.map +1 -1
- package/dist/game/effect-teleport.d.ts.map +1 -1
- package/dist/game/effect-teleport.js +21 -12
- package/dist/game/effect-teleport.js.map +1 -1
- package/dist/game/effect-terrain.d.ts +1 -1
- package/dist/game/effect-terrain.js +1 -1
- package/dist/game/equip-cmp.d.ts +17 -6
- package/dist/game/equip-cmp.d.ts.map +1 -1
- package/dist/game/equip-cmp.js +43 -24
- package/dist/game/equip-cmp.js.map +1 -1
- package/dist/game/floor.d.ts +32 -0
- package/dist/game/floor.d.ts.map +1 -1
- package/dist/game/floor.js +42 -0
- package/dist/game/floor.js.map +1 -1
- package/dist/game/gear.d.ts +17 -3
- package/dist/game/gear.d.ts.map +1 -1
- package/dist/game/gear.js +2 -2
- package/dist/game/gear.js.map +1 -1
- package/dist/game/inscription-confirm.d.ts +13 -6
- package/dist/game/inscription-confirm.d.ts.map +1 -1
- package/dist/game/inscription-confirm.js +13 -6
- package/dist/game/inscription-confirm.js.map +1 -1
- package/dist/game/known.d.ts +31 -6
- package/dist/game/known.d.ts.map +1 -1
- package/dist/game/known.js +81 -13
- package/dist/game/known.js.map +1 -1
- package/dist/game/mon-cast.d.ts.map +1 -1
- package/dist/game/mon-cast.js +9 -5
- package/dist/game/mon-cast.js.map +1 -1
- package/dist/game/mon-cmd.d.ts.map +1 -1
- package/dist/game/mon-cmd.js +4 -7
- package/dist/game/mon-cmd.js.map +1 -1
- package/dist/game/mon-message.d.ts.map +1 -1
- package/dist/game/mon-message.js +0 -1
- package/dist/game/mon-message.js.map +1 -1
- package/dist/game/mon-side.d.ts.map +1 -1
- package/dist/game/mon-side.js +4 -7
- package/dist/game/mon-side.js.map +1 -1
- package/dist/game/monster-turn.d.ts +4 -1
- package/dist/game/monster-turn.d.ts.map +1 -1
- package/dist/game/monster-turn.js +8 -4
- package/dist/game/monster-turn.js.map +1 -1
- package/dist/game/obj-cmd.d.ts.map +1 -1
- package/dist/game/obj-cmd.js +21 -3
- package/dist/game/obj-cmd.js.map +1 -1
- package/dist/game/player-turn.d.ts +2 -1
- package/dist/game/player-turn.d.ts.map +1 -1
- package/dist/game/player-turn.js +3 -3
- package/dist/game/player-turn.js.map +1 -1
- package/dist/game/project-cast.d.ts.map +1 -1
- package/dist/game/project-cast.js +9 -0
- package/dist/game/project-cast.js.map +1 -1
- package/dist/game/project-monster.d.ts +10 -0
- package/dist/game/project-monster.d.ts.map +1 -1
- package/dist/game/project-monster.js +11 -2
- package/dist/game/project-monster.js.map +1 -1
- package/dist/game/projection-handlers.d.ts +9 -1
- package/dist/game/projection-handlers.d.ts.map +1 -1
- package/dist/game/projection-handlers.js +9 -1
- package/dist/game/projection-handlers.js.map +1 -1
- package/dist/game/ranged-cmd.d.ts.map +1 -1
- package/dist/game/ranged-cmd.js +6 -2
- package/dist/game/ranged-cmd.js.map +1 -1
- package/dist/game/repeat.d.ts +38 -1
- package/dist/game/repeat.d.ts.map +1 -1
- package/dist/game/repeat.js +24 -1
- package/dist/game/repeat.js.map +1 -1
- package/dist/game/shape-inspect.d.ts +2 -2
- package/dist/game/shape-inspect.js +3 -3
- package/dist/game/spoil.js +1 -1
- package/dist/game/take-hit-hooks.d.ts.map +1 -1
- package/dist/game/take-hit-hooks.js +4 -12
- package/dist/game/take-hit-hooks.js.map +1 -1
- package/dist/game/target-loop.d.ts +13 -8
- package/dist/game/target-loop.d.ts.map +1 -1
- package/dist/game/target-loop.js +43 -14
- package/dist/game/target-loop.js.map +1 -1
- package/dist/game/ui-entry-registry.d.ts +124 -0
- package/dist/game/ui-entry-registry.d.ts.map +1 -0
- package/dist/game/ui-entry-registry.js +102 -0
- package/dist/game/ui-entry-registry.js.map +1 -0
- package/dist/game/ui-entry.d.ts +90 -20
- package/dist/game/ui-entry.d.ts.map +1 -1
- package/dist/game/ui-entry.js +510 -315
- package/dist/game/ui-entry.js.map +1 -1
- package/dist/game/wizard.d.ts +27 -23
- package/dist/game/wizard.d.ts.map +1 -1
- package/dist/game/wizard.js +38 -34
- package/dist/game/wizard.js.map +1 -1
- package/dist/gen/gen-monster.d.ts +4 -2
- package/dist/gen/gen-monster.d.ts.map +1 -1
- package/dist/gen/gen-monster.js +11 -8
- package/dist/gen/gen-monster.js.map +1 -1
- package/dist/gen/util.d.ts +1 -1
- package/dist/gen/util.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/mod/extension.d.ts +20 -0
- package/dist/mod/extension.d.ts.map +1 -1
- package/dist/mod/extension.js +12 -2
- package/dist/mod/extension.js.map +1 -1
- package/dist/mod/ids.d.ts +12 -0
- package/dist/mod/ids.d.ts.map +1 -1
- package/dist/mod/ids.js +119 -14
- package/dist/mod/ids.js.map +1 -1
- package/dist/mod/message-declarations.d.ts +132 -0
- package/dist/mod/message-declarations.d.ts.map +1 -0
- package/dist/mod/message-declarations.js +223 -0
- package/dist/mod/message-declarations.js.map +1 -0
- package/dist/mod/record-keys.d.ts +4 -3
- package/dist/mod/record-keys.d.ts.map +1 -1
- package/dist/mod/record-keys.js +4 -3
- package/dist/mod/record-keys.js.map +1 -1
- package/dist/mod/registry-host.d.ts +167 -1
- package/dist/mod/registry-host.d.ts.map +1 -1
- package/dist/mod/registry-host.js +95 -1
- package/dist/mod/registry-host.js.map +1 -1
- package/dist/mod/save-blocks.d.ts +8 -3
- package/dist/mod/save-blocks.d.ts.map +1 -1
- package/dist/mod/save-blocks.js +153 -14
- package/dist/mod/save-blocks.js.map +1 -1
- package/dist/mon/bind.d.ts +5 -0
- package/dist/mon/bind.d.ts.map +1 -1
- package/dist/mon/bind.js +12 -10
- package/dist/mon/bind.js.map +1 -1
- package/dist/mon/knowledge-groups.js +1 -1
- package/dist/mon/lore-describe.d.ts.map +1 -1
- package/dist/mon/lore-describe.js +3 -2
- package/dist/mon/lore-describe.js.map +1 -1
- package/dist/mon/lore-file.d.ts +4 -4
- package/dist/mon/lore-file.d.ts.map +1 -1
- package/dist/mon/lore-file.js +22 -12
- package/dist/mon/lore-file.js.map +1 -1
- package/dist/mon/lore.d.ts +6 -0
- package/dist/mon/lore.d.ts.map +1 -1
- package/dist/mon/lore.js +10 -3
- package/dist/mon/lore.js.map +1 -1
- package/dist/mon/predicate.d.ts.map +1 -1
- package/dist/mon/predicate.js +3 -2
- package/dist/mon/predicate.js.map +1 -1
- package/dist/mon/project-mon.d.ts +48 -4
- package/dist/mon/project-mon.d.ts.map +1 -1
- package/dist/mon/project-mon.js +47 -4
- package/dist/mon/project-mon.js.map +1 -1
- package/dist/mon/spell-declarations.d.ts +44 -0
- package/dist/mon/spell-declarations.d.ts.map +1 -0
- package/dist/mon/spell-declarations.js +57 -0
- package/dist/mon/spell-declarations.js.map +1 -0
- package/dist/mon/spell-registry.d.ts +136 -0
- package/dist/mon/spell-registry.d.ts.map +1 -0
- package/dist/mon/spell-registry.js +178 -0
- package/dist/mon/spell-registry.js.map +1 -0
- package/dist/mon/spell.js +2 -2
- package/dist/mon/spell.js.map +1 -1
- package/dist/mon/types.d.ts +12 -1
- package/dist/mon/types.d.ts.map +1 -1
- package/dist/mon/types.js +18 -1
- package/dist/mon/types.js.map +1 -1
- package/dist/msg.d.ts +26 -0
- package/dist/msg.d.ts.map +1 -1
- package/dist/msg.js +28 -1
- package/dist/msg.js.map +1 -1
- package/dist/obj/artifact-fake.d.ts +1 -1
- package/dist/obj/artifact-fake.js +1 -1
- package/dist/obj/artifact-known.d.ts +4 -4
- package/dist/obj/artifact-known.js +3 -3
- package/dist/obj/bind.d.ts +2 -2
- package/dist/obj/bind.d.ts.map +1 -1
- package/dist/obj/bind.js +5 -4
- package/dist/obj/bind.js.map +1 -1
- package/dist/obj/desc.d.ts +1 -1
- package/dist/obj/desc.js +1 -1
- package/dist/obj/fake-object.d.ts +2 -2
- package/dist/obj/fake-object.js +2 -2
- package/dist/obj/knowledge.d.ts +2 -2
- package/dist/obj/knowledge.js +2 -2
- package/dist/obj/make.d.ts +1 -1
- package/dist/obj/make.d.ts.map +1 -1
- package/dist/obj/make.js +3 -3
- package/dist/obj/make.js.map +1 -1
- package/dist/obj/object-info.d.ts +1 -1
- package/dist/obj/object-info.js +2 -2
- package/dist/obj/object-info.js.map +1 -1
- package/dist/obj/object.d.ts +12 -5
- package/dist/obj/object.d.ts.map +1 -1
- package/dist/obj/object.js +15 -6
- package/dist/obj/object.js.map +1 -1
- package/dist/obj/power.d.ts +2 -2
- package/dist/obj/power.js +4 -4
- package/dist/obj/power.js.map +1 -1
- package/dist/obj/randart-build.d.ts.map +1 -1
- package/dist/obj/randart-build.js +22 -46
- package/dist/obj/randart-build.js.map +1 -1
- package/dist/obj/randart-data.d.ts.map +1 -1
- package/dist/obj/randart-data.js +16 -33
- package/dist/obj/randart-data.js.map +1 -1
- package/dist/obj/types.d.ts +25 -0
- package/dist/obj/types.d.ts.map +1 -1
- package/dist/parser.d.ts +26 -46
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +26 -115
- package/dist/parser.js.map +1 -1
- package/dist/player/birth.d.ts +1 -1
- package/dist/player/birth.js +2 -2
- package/dist/player/options-file.d.ts +55 -29
- package/dist/player/options-file.d.ts.map +1 -1
- package/dist/player/options-file.js +125 -139
- package/dist/player/options-file.js.map +1 -1
- package/dist/player/options.d.ts +1 -1
- package/dist/player/options.js +1 -1
- package/dist/player/shape-lore.d.ts +1 -1
- package/dist/player/shape-lore.d.ts.map +1 -1
- package/dist/player/shape-lore.js +16 -13
- package/dist/player/shape-lore.js.map +1 -1
- package/dist/player/spell.d.ts.map +1 -1
- package/dist/player/spell.js +24 -2
- package/dist/player/spell.js.map +1 -1
- package/dist/score/display.d.ts.map +1 -1
- package/dist/score/display.js +39 -18
- package/dist/score/display.js.map +1 -1
- package/dist/score/score.d.ts +7 -7
- package/dist/score/score.js +3 -3
- package/dist/score/types.d.ts +51 -0
- package/dist/score/types.d.ts.map +1 -1
- package/dist/score/types.js.map +1 -1
- package/dist/session/boot.d.ts +2 -0
- package/dist/session/boot.d.ts.map +1 -1
- package/dist/session/boot.js +22 -0
- package/dist/session/boot.js.map +1 -1
- package/dist/session/game.d.ts.map +1 -1
- package/dist/session/game.js +113 -6
- package/dist/session/game.js.map +1 -1
- package/dist/session/save-migrate.d.ts +3 -3
- package/dist/session/save-migrate.d.ts.map +1 -1
- package/dist/session/save-migrate.js +429 -21
- package/dist/session/save-migrate.js.map +1 -1
- package/dist/session/save.d.ts +442 -28
- package/dist/session/save.d.ts.map +1 -1
- package/dist/session/save.js +691 -51
- package/dist/session/save.js.map +1 -1
- package/dist/sound/engine.d.ts +17 -0
- package/dist/sound/engine.d.ts.map +1 -1
- package/dist/sound/engine.js +55 -6
- package/dist/sound/engine.js.map +1 -1
- package/dist/sound/index.d.ts +2 -0
- package/dist/sound/index.d.ts.map +1 -1
- package/dist/sound/index.js +2 -0
- package/dist/sound/index.js.map +1 -1
- package/dist/sound/message-types.d.ts +89 -0
- package/dist/sound/message-types.d.ts.map +1 -0
- package/dist/sound/message-types.js +126 -0
- package/dist/sound/message-types.js.map +1 -0
- package/dist/sound/sound-registry.d.ts +93 -0
- package/dist/sound/sound-registry.d.ts.map +1 -0
- package/dist/sound/sound-registry.js +117 -0
- package/dist/sound/sound-registry.js.map +1 -0
- package/dist/store/store.d.ts +12 -11
- package/dist/store/store.d.ts.map +1 -1
- package/dist/store/store.js +16 -16
- package/dist/store/store.js.map +1 -1
- package/dist/store/transact.d.ts +25 -7
- package/dist/store/transact.d.ts.map +1 -1
- package/dist/store/transact.js +27 -9
- package/dist/store/transact.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/visuals/glyph-table.d.ts +2 -2
- package/dist/visuals/glyph-table.js +2 -2
- package/dist/visuals/hallucination.d.ts +140 -0
- package/dist/visuals/hallucination.d.ts.map +1 -0
- package/dist/visuals/hallucination.js +122 -0
- package/dist/visuals/hallucination.js.map +1 -0
- package/dist/visuals/index.d.ts +1 -0
- package/dist/visuals/index.d.ts.map +1 -1
- package/dist/visuals/index.js +1 -0
- package/dist/visuals/index.js.map +1 -1
- package/dist/visuals/map-text.d.ts +5 -4
- package/dist/visuals/map-text.d.ts.map +1 -1
- package/dist/visuals/map-text.js +5 -4
- package/dist/visuals/map-text.js.map +1 -1
- package/dist/visuals/object-glyph.d.ts +5 -0
- package/dist/visuals/object-glyph.d.ts.map +1 -1
- package/dist/visuals/object-glyph.js +7 -2
- package/dist/visuals/object-glyph.js.map +1 -1
- package/dist/visuals/prefs.d.ts +172 -17
- package/dist/visuals/prefs.d.ts.map +1 -1
- package/dist/visuals/prefs.js +221 -25
- package/dist/visuals/prefs.js.map +1 -1
- package/dist/world/trap.d.ts +1 -1
- package/package.json +1 -1
- package/src/cmd.ts +67 -3
- package/src/effects/effect-info.ts +1005 -1005
- package/src/game/context.ts +31 -0
- package/src/game/display.ts +144 -22
- package/src/game/dump-level.ts +1 -1
- package/src/game/effect-attack.ts +1 -2
- package/src/game/effect-general.ts +12 -11
- package/src/game/effect-item.ts +1080 -1075
- package/src/game/effect-teleport.ts +21 -12
- package/src/game/effect-terrain.ts +1 -1
- package/src/game/equip-cmp.ts +62 -32
- package/src/game/floor.ts +52 -0
- package/src/game/gear.ts +18 -4
- package/src/game/inscription-confirm.ts +15 -7
- package/src/game/known.ts +83 -12
- package/src/game/loop.ts +695 -695
- package/src/game/mon-cast.ts +8 -6
- package/src/game/mon-cmd.ts +4 -7
- package/src/game/mon-message.ts +0 -1
- package/src/game/mon-side.ts +4 -6
- package/src/game/monster-turn.ts +8 -4
- package/src/game/obj-cmd.ts +19 -2
- package/src/game/player-turn.ts +2 -3
- package/src/game/project-cast.ts +9 -0
- package/src/game/project-monster.ts +24 -3
- package/src/game/projection-handlers.ts +12 -1
- package/src/game/ranged-cmd.ts +5 -3
- package/src/game/repeat.ts +56 -1
- package/src/game/shape-inspect.ts +3 -3
- package/src/game/spoil.ts +1 -1
- package/src/game/take-hit-hooks.ts +4 -11
- package/src/game/target-loop.ts +44 -12
- package/src/game/ui-entry-registry.ts +159 -0
- package/src/game/ui-entry.ts +2528 -2222
- package/src/game/wizard.ts +39 -35
- package/src/gen/cave.ts +3057 -3057
- package/src/gen/gen-monster.ts +11 -8
- package/src/gen/generate.ts +576 -576
- package/src/gen/util.ts +1 -1
- package/src/index.ts +311 -307
- package/src/mod/extension.ts +37 -2
- package/src/mod/hooks.ts +521 -521
- package/src/mod/ids.ts +160 -13
- package/src/mod/message-declarations.ts +293 -0
- package/src/mod/record-keys.ts +4 -3
- package/src/mod/registry-host.ts +1274 -1009
- package/src/mod/save-blocks.ts +162 -15
- package/src/mon/bind.ts +14 -10
- package/src/mon/knowledge-groups.ts +1 -1
- package/src/mon/lore-describe.ts +3 -2
- package/src/mon/lore-file.ts +22 -12
- package/src/mon/lore.ts +10 -3
- package/src/mon/predicate.ts +3 -2
- package/src/mon/project-mon.ts +65 -4
- package/src/mon/spell-declarations.ts +80 -0
- package/src/mon/spell-registry.ts +221 -0
- package/src/mon/spell.ts +2 -2
- package/src/mon/types.ts +27 -1
- package/src/msg.ts +29 -1
- package/src/obj/artifact-fake.ts +1 -1
- package/src/obj/artifact-known.ts +4 -4
- package/src/obj/bind.ts +4 -4
- package/src/obj/desc.ts +1 -1
- package/src/obj/fake-object.ts +2 -2
- package/src/obj/knowledge.ts +2 -2
- package/src/obj/make.ts +3 -2
- package/src/obj/object-info.ts +2 -2
- package/src/obj/object.ts +15 -6
- package/src/obj/power.ts +4 -4
- package/src/obj/randart-build.ts +1847 -1875
- package/src/obj/randart-data.ts +1519 -1547
- package/src/obj/randart-registry.ts +247 -247
- package/src/obj/randart-vectors.ts +369 -369
- package/src/obj/randart.ts +722 -722
- package/src/obj/types.ts +22 -0
- package/src/parser.ts +26 -115
- package/src/player/birth.ts +2 -2
- package/src/player/options-file.ts +128 -164
- package/src/player/options.ts +1 -1
- package/src/player/shape-lore.ts +16 -13
- package/src/player/spell.ts +31 -2
- package/src/score/display.ts +38 -17
- package/src/score/score.ts +8 -8
- package/src/score/types.ts +52 -0
- package/src/session/boot.ts +50 -0
- package/src/session/game.ts +117 -6
- package/src/session/save-migrate.ts +517 -22
- package/src/session/save.ts +917 -74
- package/src/sound/engine.ts +59 -4
- package/src/sound/index.ts +2 -0
- package/src/sound/message-types.ts +164 -0
- package/src/sound/sound-registry.ts +149 -0
- package/src/store/store.ts +21 -19
- package/src/store/transact.ts +30 -12
- package/src/version.ts +1 -1
- package/src/visuals/glyph-table.ts +2 -2
- package/src/visuals/hallucination.ts +200 -0
- package/src/visuals/index.ts +1 -0
- package/src/visuals/map-text.ts +5 -4
- package/src/visuals/object-glyph.ts +7 -2
- package/src/visuals/prefs.ts +1236 -971
- package/src/visuals/tile-prefs.ts +215 -215
- package/src/world/trap.ts +1 -1
package/dist/session/save.js
CHANGED
|
@@ -38,9 +38,12 @@
|
|
|
38
38
|
* ported - mon/lore-file.ts - and is laid
|
|
39
39
|
* over this on load, exactly as upstream's
|
|
40
40
|
* startup order does. The save keeps carrying
|
|
41
|
-
* the whole record
|
|
42
|
-
* SAVE_VERSION change
|
|
43
|
-
*
|
|
41
|
+
* the whole record; narrowing it would be a
|
|
42
|
+
* SAVE_VERSION change with nothing to gain.
|
|
43
|
+
* The observed SPELL set is written by NAME
|
|
44
|
+
* as of version 5 (SavedLore.spellsKnown) and
|
|
45
|
+
* the known race FLAGS as of version 6
|
|
46
|
+
* (SavedLore.flagsKnown).)
|
|
44
47
|
* wr_object_memory (save.c:377) -> flavor.aware / .tried, everseen.kinds,
|
|
45
48
|
* ignore.kindAware / .kindUnaware
|
|
46
49
|
* wr_quests (save.c:405) -> player.quests
|
|
@@ -77,15 +80,22 @@ import { loc } from "../loc.js";
|
|
|
77
80
|
import { FlagSet } from "../bitflag.js";
|
|
78
81
|
import { Chunk, SQUARE_SIZE } from "../world/chunk.js";
|
|
79
82
|
import { objectNew, tvalIsMoney } from "../obj/object.js";
|
|
83
|
+
import { ELEMENT_NAMES, OBJ_MOD_NAMES } from "../obj/bind.js";
|
|
84
|
+
import { ELEM_MAX, OBJ_MOD_MAX, OF_SIZE, newElemInfo } from "../obj/types.js";
|
|
80
85
|
import { buildRuneList, runeKey } from "../obj/knowledge.js";
|
|
81
86
|
import { blankMonster, GROUP_MAX } from "../mon/monster.js";
|
|
87
|
+
import { RF_FLAG_NAMES } from "../mon/lore-file.js";
|
|
88
|
+
import { MFLAG, MON_TMD, OF, SQUARE_FLAG_ENTRIES, STAT, TMD, TRF, } from "../generated/index.js";
|
|
89
|
+
import { MFLAG_SIZE, RF_SIZE } from "../mon/types.js";
|
|
90
|
+
import { rsfMax, rsfSize, spellIndexOf, spellNameAt, } from "../mon/spell-registry.js";
|
|
82
91
|
import { blankPlayer } from "../player/player.js";
|
|
83
92
|
import { playerQuestsReset } from "../game/quest.js";
|
|
93
|
+
import { TRF_SIZE } from "../world/trap.js";
|
|
84
94
|
import { MessageLog } from "../msg.js";
|
|
85
95
|
import { newKnownMap } from "../game/known.js";
|
|
86
96
|
import { fnv1aIntegrity, stampSavefile, verifyStampedSavefile, } from "../save/integrity.js";
|
|
87
97
|
import { applyCodec, findCodec, stripCodec } from "../save/compress.js";
|
|
88
|
-
import { PY_MAX_LEVEL, TMD_MAX } from "../player/types.js";
|
|
98
|
+
import { PY_MAX_LEVEL, SKILL, SKILL_MAX, STAT_MAX, TMD_MAX, } from "../player/types.js";
|
|
89
99
|
/**
|
|
90
100
|
* The save format version this build writes. Version 2 replaced every numeric
|
|
91
101
|
* content index (kidx/eidx/aidx/ridx/tidx/feat, and the positional curse/
|
|
@@ -93,7 +103,38 @@ import { PY_MAX_LEVEL, TMD_MAX } from "../player/types.js";
|
|
|
93
103
|
* load-bearing rule of the mod substrate (MOD_LIFECYCLE decision 1). Version 3
|
|
94
104
|
* finished that job: `flavor`, `everseen` and `ignore` were the last blocks
|
|
95
105
|
* still keyed by raw kidx/eidx, and the rune-autoinscription block of wr_ignore
|
|
96
|
-
* (save.c:586-605) arrived with them.
|
|
106
|
+
* (save.c:586-605) arrived with them. Version 5 started on the POSITIONS a save
|
|
107
|
+
* held: monster lore recorded which spells the player had seen as RSF bit
|
|
108
|
+
* positions, so `MON_SPELL_ENTRIES` could never grow without renumbering an
|
|
109
|
+
* existing character's memory. It is written by name now
|
|
110
|
+
* (`SavedLore.spellsKnown`).
|
|
111
|
+
*
|
|
112
|
+
* Version 5's own comment claimed that was the LAST position a save held. It was
|
|
113
|
+
* not, and version 6 (#273) finished the count: race lore still wrote RF bit
|
|
114
|
+
* positions, and every object-property carrier in the document - `SavedObject`,
|
|
115
|
+
* `SavedPlayer.objKnown`, `SavedMonster.knownPstate` - wrote OF bit positions,
|
|
116
|
+
* OBJ_MOD indices and ELEM indices. All four tables are persisted by NAME now;
|
|
117
|
+
* see "Position-free persistence" below.
|
|
118
|
+
*
|
|
119
|
+
* VERSION 6'S COMMENT MADE THE SAME CLAIM, and version 7 (#274) is the count
|
|
120
|
+
* actually finishing. Seven more tables were still persisted by position, and
|
|
121
|
+
* V5_TO_V6's own discriminator comment named two of them as "out of scope":
|
|
122
|
+
*
|
|
123
|
+
* MFLAG `SavedMonster.mflag` -> `mflagNames` (bit positions)
|
|
124
|
+
* TRF `SavedTrap.flags` -> `trapFlagNames` (bit positions)
|
|
125
|
+
* SQUARE every chunk's per-grid info -> `squareInfoLegend` (bit positions)
|
|
126
|
+
* MON_TMD `SavedMonster.mTimed` -> `monsterTimed` (dense indices)
|
|
127
|
+
* TMD `SavedPlayer.timed` -> `timedValues` (dense indices)
|
|
128
|
+
* SKILL `SavedPlayer.skills` -> `skillValues` (dense indices)
|
|
129
|
+
* STAT `SavedPlayer.stat*` -> `stat*Values` / `statMapNames`
|
|
130
|
+
*
|
|
131
|
+
* plus `SavedPlayer.objKnownModifiers`, the version-1 legacy rune block, which
|
|
132
|
+
* every migration since has carried forward untouched as a dense OBJ_MOD array
|
|
133
|
+
* (`objKnownModifierValues` now). Version 6 converted the modern spelling of
|
|
134
|
+
* that field and left the legacy one behind, which is precisely the shape of
|
|
135
|
+
* defect this ticket exists to end.
|
|
136
|
+
*
|
|
137
|
+
* SQUARE IS THE ONE THAT IS NOT A LIST OF NAMES. See `SQUARE_INFO_LEGEND`.
|
|
97
138
|
*
|
|
98
139
|
* OLDER SAVES ARE MIGRATED, NOT REJECTED. Every version below this one has a
|
|
99
140
|
* conversion step in session/save-migrate.ts, and `saveMigrationsAreComplete()`
|
|
@@ -103,7 +144,561 @@ import { PY_MAX_LEVEL, TMD_MAX } from "../player/types.js";
|
|
|
103
144
|
* character into "Could not read the save; starting a new game", which in a
|
|
104
145
|
* permadeath game reads as "your character is gone".
|
|
105
146
|
*/
|
|
106
|
-
export const SAVE_VERSION =
|
|
147
|
+
export const SAVE_VERSION = 7;
|
|
148
|
+
/* ------------------------------------------------------------------ *
|
|
149
|
+
* Position-free persistence for the generated tables.
|
|
150
|
+
*
|
|
151
|
+
* A save that stores a flag as its BIT POSITION - or a value at its enum's
|
|
152
|
+
* INDEX - can only be read by a build whose table has the identical length and
|
|
153
|
+
* order. Remove or reorder one entry and every existing character's data
|
|
154
|
+
* silently re-points at a different flag; that is why `MON_SPELL_ENTRIES` could
|
|
155
|
+
* not be opened to mods until #269 (MOD_REACH row 22), and it was equally true
|
|
156
|
+
* of `MON_RACE_FLAG_ENTRIES`, `OBJECT_FLAG_ENTRIES`, `OBJECT_MODIFIER_ENTRIES`
|
|
157
|
+
* and `ELEMENT_ENTRIES` until #273 - and of `MON_TEMP_FLAG_ENTRIES`,
|
|
158
|
+
* `TRAP_FLAG_ENTRIES`, `SQUARE_FLAG_ENTRIES`, `MON_TIMED_ENTRIES`,
|
|
159
|
+
* `PLAYER_TIMED_ENTRIES`, `SKILL` and `STAT_ENTRIES` until #274.
|
|
160
|
+
*
|
|
161
|
+
* Names have no position. A build whose table is larger, smaller or reordered
|
|
162
|
+
* reads back exactly what was written, and a name it no longer has is DROPPED
|
|
163
|
+
* rather than mis-resolved - the same rule `deserializeLore` already applies to
|
|
164
|
+
* a race whose mod is gone. session/save-flag-names.test.ts is the control:
|
|
165
|
+
* it renumbers each table and reads the same data under both schemes.
|
|
166
|
+
*
|
|
167
|
+
* TWO ENCODINGS, AND THE RULE FOR PICKING ONE.
|
|
168
|
+
*
|
|
169
|
+
* A FLAG SET writes `string[]` - the set flags, ascending, so an unchanged
|
|
170
|
+
* entity writes identical bytes. Absence is the whole meaning of an unset
|
|
171
|
+
* flag, so there is nothing to write for one.
|
|
172
|
+
*
|
|
173
|
+
* A DENSE VALUE ARRAY writes `Record<name, value>`. Where zero genuinely
|
|
174
|
+
* means "no such effect" (`timed`, `mTimed`, the object modifiers) the zeroes
|
|
175
|
+
* are omitted and the block stays proportional to what the entity HAS rather
|
|
176
|
+
* than to the table's length. Where the vector is small and always fully
|
|
177
|
+
* populated (`skills`, the four stat arrays) every entry is written, because
|
|
178
|
+
* zero there is a value and not an absence; omitting it would round-trip
|
|
179
|
+
* correctly today and read as a lie the first time someone inspects a save.
|
|
180
|
+
*
|
|
181
|
+
* AND ONE THAT IS NEITHER: see `SQUARE_INFO_LEGEND`, where a name per grid was
|
|
182
|
+
* measured and rejected.
|
|
183
|
+
* ------------------------------------------------------------------ */
|
|
184
|
+
/**
|
|
185
|
+
* A flag/enum value -> name table, INVERTED FROM THE ENUM rather than read off
|
|
186
|
+
* the generated entry tuple.
|
|
187
|
+
*
|
|
188
|
+
* THE THREE ENTRY TUPLES DO NOT SHARE A BASE, and every call site that reaches
|
|
189
|
+
* for one has to know which it is holding:
|
|
190
|
+
* `MON_RACE_FLAG_ENTRIES[flag]` (RF_NONE kept at [0]), `OBJECT_FLAG_ENTRIES
|
|
191
|
+
* [flag - 1]` (OF_NONE dropped), `OBJECT_MODIFIER_ENTRIES[value - 5]` (the five
|
|
192
|
+
* list-stats.h entries come first). The enum is generated from the same header
|
|
193
|
+
* as the values themselves, so inverting it cannot be off by one in any
|
|
194
|
+
* direction - and save-flag-names.test.ts pins each inverted table against its
|
|
195
|
+
* own ENTRIES tuple AT ITS OWN BASE, because a comment claiming a base is
|
|
196
|
+
* exactly the kind of thing that has been wrong here before.
|
|
197
|
+
*/
|
|
198
|
+
function nameTable(en) {
|
|
199
|
+
const out = [];
|
|
200
|
+
for (const [name, value] of Object.entries(en))
|
|
201
|
+
out[value] = name;
|
|
202
|
+
return out;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* OF_ names by flag number. [0] is the OF_NONE sentinel, [OF.MAX] is the "MAX"
|
|
206
|
+
* one. Nothing in the tree exported this before; RF_FLAG_NAMES and
|
|
207
|
+
* RSF_FLAG_NAMES (mon/lore-file.ts) are its siblings and are built the same way.
|
|
208
|
+
*/
|
|
209
|
+
export const OF_FLAG_NAMES = nameTable(OF);
|
|
210
|
+
/*
|
|
211
|
+
* OBJ_MOD and ELEM already have exported name tables - `OBJ_MOD_NAMES` and
|
|
212
|
+
* `ELEMENT_NAMES` in obj/bind.ts, the port of the C `obj_mods[]` and
|
|
213
|
+
* `element_names[]` string arrays, both indexed by the enum VALUE. A second
|
|
214
|
+
* copy here would be a second thing to keep right, so this file uses those; the
|
|
215
|
+
* inverted enum is applied to them AS A CHECK in save-flag-names.test.ts
|
|
216
|
+
* instead, which is the only place the two derivations can be compared.
|
|
217
|
+
*/
|
|
218
|
+
/**
|
|
219
|
+
* The set object flags as OF_ names, ascending by flag number so the save is
|
|
220
|
+
* byte-stable for an unchanged object.
|
|
221
|
+
*
|
|
222
|
+
* The bound is OF.MAX for the reason serializeLoreSpells bounds on RSF.MAX:
|
|
223
|
+
* OF_SIZE rounds 39 flags up to 5 bytes, so a set has 40 addressable bits and
|
|
224
|
+
* index 39 reads back as the enum's own `"MAX"` sentinel. Writing that would
|
|
225
|
+
* put a non-flag in the save and, on the way back, set a bit no property owns.
|
|
226
|
+
*/
|
|
227
|
+
export function serializeObjectFlags(flags) {
|
|
228
|
+
const out = [];
|
|
229
|
+
for (const flag of flags) {
|
|
230
|
+
if (flag >= OF.MAX)
|
|
231
|
+
break;
|
|
232
|
+
const name = OF_FLAG_NAMES[flag];
|
|
233
|
+
if (name !== undefined)
|
|
234
|
+
out.push(name);
|
|
235
|
+
}
|
|
236
|
+
return out;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* The inverse: an OF_SIZE FlagSet with exactly the named flags on. A name this
|
|
240
|
+
* build does not have (a mod's property, uninstalled - or the `"MAX"` sentinel)
|
|
241
|
+
* is dropped, which is how the whole scheme stays safe: an unknown NAME cannot
|
|
242
|
+
* land on some other flag's bit the way an out-of-range index would.
|
|
243
|
+
*/
|
|
244
|
+
export function deserializeObjectFlags(names) {
|
|
245
|
+
const set = new FlagSet(OF_SIZE);
|
|
246
|
+
for (const name of names ?? []) {
|
|
247
|
+
const flag = OF_FLAG_NAMES.indexOf(name);
|
|
248
|
+
if (flag > 0 && flag < OF.MAX)
|
|
249
|
+
set.on(flag);
|
|
250
|
+
}
|
|
251
|
+
return set;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* The known race flags as RF_ names, ascending.
|
|
255
|
+
*
|
|
256
|
+
* RF has NO `MAX` member - the enum stops at NO_SLOW and `RF_SIZE` is
|
|
257
|
+
* `flagSize(MON_RACE_FLAG_ENTRIES.length)`, so a set has a few unnamed padding
|
|
258
|
+
* bits above the last flag and nothing else. `RF_FLAG_NAMES[flag] === undefined`
|
|
259
|
+
* is therefore the entire bound, and it is derived from the table rather than
|
|
260
|
+
* from a hand-written constant that could part company with it.
|
|
261
|
+
*/
|
|
262
|
+
export function serializeLoreFlags(flags) {
|
|
263
|
+
const out = [];
|
|
264
|
+
for (const flag of flags) {
|
|
265
|
+
const name = RF_FLAG_NAMES[flag];
|
|
266
|
+
if (name !== undefined)
|
|
267
|
+
out.push(name);
|
|
268
|
+
}
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* The inverse: an RF_SIZE FlagSet with exactly the named flags on. Index 0 is
|
|
273
|
+
* upstream's RF_NONE, which is never set, so `flag > 0` excludes it; every
|
|
274
|
+
* other named flag fits by construction, because RF_SIZE is sized from the very
|
|
275
|
+
* table the name was looked up in.
|
|
276
|
+
*/
|
|
277
|
+
export function deserializeLoreFlags(names) {
|
|
278
|
+
const set = new FlagSet(RF_SIZE);
|
|
279
|
+
for (const name of names ?? []) {
|
|
280
|
+
const flag = RF_FLAG_NAMES.indexOf(name);
|
|
281
|
+
if (flag > 0)
|
|
282
|
+
set.on(flag);
|
|
283
|
+
}
|
|
284
|
+
return set;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* The non-zero modifiers as an OBJ_MOD_ name -> value map, in ascending index
|
|
288
|
+
* order so an unchanged object writes identical bytes.
|
|
289
|
+
*
|
|
290
|
+
* NOT A FLAG SET: index 0 is OBJ_MOD_STR, a real modifier, so there is no
|
|
291
|
+
* sentinel to skip at either end and the `flag > 0` guard the flag helpers use
|
|
292
|
+
* would silently drop every strength bonus in the game. A zero IS the absence
|
|
293
|
+
* of a modifier everywhere it is read (`obj.modifiers[k] ?? 0`), so omitting
|
|
294
|
+
* zeroes loses nothing and keeps the block proportional to what the object
|
|
295
|
+
* actually has rather than to the table's length.
|
|
296
|
+
*/
|
|
297
|
+
export function serializeObjectModifiers(mods) {
|
|
298
|
+
const out = {};
|
|
299
|
+
for (let i = 0; i < mods.length; i++) {
|
|
300
|
+
const value = mods[i] ?? 0;
|
|
301
|
+
if (value === 0)
|
|
302
|
+
continue;
|
|
303
|
+
const name = OBJ_MOD_NAMES[i];
|
|
304
|
+
if (name !== undefined)
|
|
305
|
+
out[name] = value;
|
|
306
|
+
}
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* The inverse: a full-length OBJ_MOD array, zero wherever the save said
|
|
311
|
+
* nothing. The length comes from THIS build's table, never from the document -
|
|
312
|
+
* a save written against a longer table hands back an array the engine's own
|
|
313
|
+
* loops would run off the end of.
|
|
314
|
+
*/
|
|
315
|
+
export function deserializeObjectModifiers(saved) {
|
|
316
|
+
const out = new Array(OBJ_MOD_MAX).fill(0);
|
|
317
|
+
for (const [name, value] of Object.entries(saved ?? {})) {
|
|
318
|
+
const i = OBJ_MOD_NAMES.indexOf(name);
|
|
319
|
+
if (i >= 0 && i < out.length && typeof value === "number")
|
|
320
|
+
out[i] = value;
|
|
321
|
+
}
|
|
322
|
+
return out;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* The element info as an ELEM_ name -> {resLevel, flags} map, ascending, with
|
|
326
|
+
* the untouched elements omitted. `flags` stays a raw number: EL_INFO_HATES /
|
|
327
|
+
* EL_INFO_IGNORE are hand-written constants in obj/types.ts, not a generated
|
|
328
|
+
* list, so nothing a mod can do renumbers them.
|
|
329
|
+
*/
|
|
330
|
+
export function serializeObjectElements(elInfo) {
|
|
331
|
+
const out = {};
|
|
332
|
+
for (let i = 0; i < elInfo.length; i++) {
|
|
333
|
+
const e = elInfo[i];
|
|
334
|
+
if (e === undefined || (e.resLevel === 0 && e.flags === 0))
|
|
335
|
+
continue;
|
|
336
|
+
const name = ELEMENT_NAMES[i];
|
|
337
|
+
if (name !== undefined)
|
|
338
|
+
out[name] = { resLevel: e.resLevel, flags: e.flags };
|
|
339
|
+
}
|
|
340
|
+
return out;
|
|
341
|
+
}
|
|
342
|
+
/** The inverse: a zeroed ELEM_MAX array with the named elements filled in. */
|
|
343
|
+
export function deserializeObjectElements(saved) {
|
|
344
|
+
const out = newElemInfo();
|
|
345
|
+
for (const [name, e] of Object.entries(saved ?? {})) {
|
|
346
|
+
const i = ELEMENT_NAMES.indexOf(name);
|
|
347
|
+
if (i < 0 || i >= out.length || e === null || typeof e !== "object")
|
|
348
|
+
continue;
|
|
349
|
+
out[i] = { resLevel: e.resLevel ?? 0, flags: e.flags ?? 0 };
|
|
350
|
+
}
|
|
351
|
+
return out;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* The el_info[].res_level half on its own, for the one carrier that stores
|
|
355
|
+
* resistance levels without the EL_INFO_ flags beside them: a monster's
|
|
356
|
+
* `knownPstate.elInfo`, which is an Int16Array (mon/monster.ts).
|
|
357
|
+
*/
|
|
358
|
+
export function serializeElementLevels(levels) {
|
|
359
|
+
const out = {};
|
|
360
|
+
for (let i = 0; i < levels.length; i++) {
|
|
361
|
+
const value = levels[i] ?? 0;
|
|
362
|
+
if (value === 0)
|
|
363
|
+
continue;
|
|
364
|
+
const name = ELEMENT_NAMES[i];
|
|
365
|
+
if (name !== undefined)
|
|
366
|
+
out[name] = value;
|
|
367
|
+
}
|
|
368
|
+
return out;
|
|
369
|
+
}
|
|
370
|
+
/** The inverse: a zeroed ELEM_MAX array of resistance levels. */
|
|
371
|
+
export function deserializeElementLevels(saved) {
|
|
372
|
+
const out = new Array(ELEM_MAX).fill(0);
|
|
373
|
+
for (const [name, value] of Object.entries(saved ?? {})) {
|
|
374
|
+
const i = ELEMENT_NAMES.indexOf(name);
|
|
375
|
+
if (i >= 0 && i < out.length && typeof value === "number")
|
|
376
|
+
out[i] = value;
|
|
377
|
+
}
|
|
378
|
+
return out;
|
|
379
|
+
}
|
|
380
|
+
/* ------------------------------------------------------------------ *
|
|
381
|
+
* #274: the seven remaining position-persisted tables.
|
|
382
|
+
* ------------------------------------------------------------------ */
|
|
383
|
+
/**
|
|
384
|
+
* MFLAG_ names by flag number. [0] is upstream's MFLAG_NONE; there is no MAX
|
|
385
|
+
* member, so "has a name" is the whole bound, exactly as for RF.
|
|
386
|
+
*/
|
|
387
|
+
export const MFLAG_NAMES = nameTable(MFLAG);
|
|
388
|
+
/** TRF_ names by flag number. [0] is TRF_NONE; no MAX member, as for MFLAG. */
|
|
389
|
+
export const TRF_NAMES = nameTable(TRF);
|
|
390
|
+
/**
|
|
391
|
+
* MON_TMD_ names by index. Unlike TMD, this enum DOES carry its own `MAX`
|
|
392
|
+
* member (mon-timed.h ends with MON_TMD_MAX), and `mon.mTimed` is exactly
|
|
393
|
+
* `MON_TMD.MAX` long - so index `MON_TMD.MAX` is the sentinel and never a slot.
|
|
394
|
+
*/
|
|
395
|
+
export const MON_TMD_NAMES = nameTable(MON_TMD);
|
|
396
|
+
/**
|
|
397
|
+
* TMD_ names by index. The player-timed enum has NO sentinel at either end:
|
|
398
|
+
* `TMD_MAX` is `PLAYER_TIMED_ENTRIES.length` rather than an enum member, so
|
|
399
|
+
* every index in the table names a real effect.
|
|
400
|
+
*/
|
|
401
|
+
export const TMD_NAMES = nameTable(TMD);
|
|
402
|
+
/** SKILL_ names by index. No sentinels; `SKILL_MAX` is a hand-written 10. */
|
|
403
|
+
export const SKILL_NAMES = nameTable(SKILL);
|
|
404
|
+
/** STAT_ names by index. No sentinels; five entries, from list-stats.h. */
|
|
405
|
+
export const STAT_NAMES = nameTable(STAT);
|
|
406
|
+
/**
|
|
407
|
+
* A FlagSet -> ascending names, bounded by "this build has a name for it".
|
|
408
|
+
* Index 0 is the NONE sentinel in every table that has one and is never set,
|
|
409
|
+
* so it costs nothing to let the name table decide.
|
|
410
|
+
*/
|
|
411
|
+
function flagNames(flags, names) {
|
|
412
|
+
const out = [];
|
|
413
|
+
for (const flag of flags) {
|
|
414
|
+
if (flag === 0)
|
|
415
|
+
continue;
|
|
416
|
+
const name = names[flag];
|
|
417
|
+
if (name !== undefined)
|
|
418
|
+
out.push(name);
|
|
419
|
+
}
|
|
420
|
+
return out;
|
|
421
|
+
}
|
|
422
|
+
/** The inverse. An unknown name is dropped, never landed on another bit. */
|
|
423
|
+
function flagsFromNames(saved, names, size) {
|
|
424
|
+
const set = new FlagSet(size);
|
|
425
|
+
for (const name of saved ?? []) {
|
|
426
|
+
const flag = names.indexOf(name);
|
|
427
|
+
if (flag > 0 && flag < size * 8)
|
|
428
|
+
set.on(flag);
|
|
429
|
+
}
|
|
430
|
+
return set;
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* A dense value array -> a name -> value map, ascending by index, omitting
|
|
434
|
+
* zeroes. `all` writes every named slot instead, for the vectors where zero is
|
|
435
|
+
* a value rather than an absence (see the encoding rule above).
|
|
436
|
+
*/
|
|
437
|
+
function valuesByName(values, names, all = false) {
|
|
438
|
+
const out = {};
|
|
439
|
+
for (let i = 0; i < values.length; i++) {
|
|
440
|
+
const value = values[i] ?? 0;
|
|
441
|
+
if (!all && value === 0)
|
|
442
|
+
continue;
|
|
443
|
+
const name = names[i];
|
|
444
|
+
if (name !== undefined)
|
|
445
|
+
out[name] = value;
|
|
446
|
+
}
|
|
447
|
+
return out;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* The inverse: a full-length array, zero where the document said nothing. The
|
|
451
|
+
* LENGTH IS THIS BUILD'S, never the document's - a save written against a
|
|
452
|
+
* longer table would otherwise hand back an array the engine's own loops run
|
|
453
|
+
* off the end of.
|
|
454
|
+
*/
|
|
455
|
+
function valuesFromNames(saved, names, length) {
|
|
456
|
+
const out = new Array(length).fill(0);
|
|
457
|
+
for (const [name, value] of Object.entries(saved ?? {})) {
|
|
458
|
+
const i = names.indexOf(name);
|
|
459
|
+
if (i >= 0 && i < length && typeof value === "number")
|
|
460
|
+
out[i] = value;
|
|
461
|
+
}
|
|
462
|
+
return out;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* MFLAG: a monster's transient flags (save.c:228-229, one byte per MFLAG_SIZE).
|
|
466
|
+
*
|
|
467
|
+
* Upstream reads exactly `mflag_size` bytes - THIS build's size, off a stream
|
|
468
|
+
* that was written with the writing build's - so a table that grew by one flag
|
|
469
|
+
* desynchronises the whole rest of the monster block rather than merely
|
|
470
|
+
* mis-naming a flag. There is no discard rule to preserve here because upstream
|
|
471
|
+
* has no notion of a flag it does not recognise; the name form gives it one.
|
|
472
|
+
*/
|
|
473
|
+
export function serializeMonsterFlags(flags) {
|
|
474
|
+
return flagNames(flags, MFLAG_NAMES);
|
|
475
|
+
}
|
|
476
|
+
/** The inverse: an MFLAG_SIZE FlagSet with exactly the named flags on. */
|
|
477
|
+
export function deserializeMonsterFlags(names) {
|
|
478
|
+
return flagsFromNames(names, MFLAG_NAMES, MFLAG_SIZE);
|
|
479
|
+
}
|
|
480
|
+
/** TRF: a trap instance's flags (save.c:275-276, rd_trap at load.c:376-377). */
|
|
481
|
+
export function serializeTrapFlags(flags) {
|
|
482
|
+
return flagNames(flags, TRF_NAMES);
|
|
483
|
+
}
|
|
484
|
+
/** The inverse: a TRF_SIZE FlagSet with exactly the named flags on. */
|
|
485
|
+
export function deserializeTrapFlags(names) {
|
|
486
|
+
return flagsFromNames(names, TRF_NAMES, TRF_SIZE);
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* MON_TMD: a monster's timed effects (save.c:223-226).
|
|
490
|
+
*
|
|
491
|
+
* Zero means "not affected" everywhere `m_timed` is read, so zeroes are
|
|
492
|
+
* omitted. UPSTREAM HAS NO DISCARD RULE HERE AND THAT IS A BUG, not a
|
|
493
|
+
* behaviour to reproduce: save.c:223 writes `MON_TMD_MAX` and load.c:290-292
|
|
494
|
+
* reads that count straight into `mon->m_timed[j]`, which is `MON_TMD_MAX`
|
|
495
|
+
* long in the READING build - a longer table overruns the array. Names cannot
|
|
496
|
+
* overrun anything, so the port's reader simply drops an effect it does not
|
|
497
|
+
* have, which is what upstream's rd_player does two blocks later for the
|
|
498
|
+
* player's own timed effects and evidently meant to do here.
|
|
499
|
+
*/
|
|
500
|
+
export function serializeMonsterTimed(timed) {
|
|
501
|
+
return valuesByName(timed, MON_TMD_NAMES);
|
|
502
|
+
}
|
|
503
|
+
/** The inverse: a MON_TMD.MAX-long array, zero where the save said nothing. */
|
|
504
|
+
export function deserializeMonsterTimed(saved) {
|
|
505
|
+
return valuesFromNames(saved, MON_TMD_NAMES, MON_TMD.MAX);
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* TMD: the player's timed effects (save.c:508-511).
|
|
509
|
+
*
|
|
510
|
+
* THE ONE FIELD WITH AN EXPLICIT UPSTREAM DISCARD RULE. load.c:811-829 reads
|
|
511
|
+
* the saved count and, when it exceeds TMD_MAX, keeps the ones it supports,
|
|
512
|
+
* strips the rest and notes "Discarded unsupported timed effects" - an extra
|
|
513
|
+
* timed effect is not an error. `valuesFromNames` drops an unrecognised name
|
|
514
|
+
* for exactly that reason, and the pre-#274 reader's `slice(0, TMD_MAX)` said
|
|
515
|
+
* the same thing positionally.
|
|
516
|
+
*/
|
|
517
|
+
export function serializePlayerTimed(timed) {
|
|
518
|
+
return valuesByName(timed, TMD_NAMES);
|
|
519
|
+
}
|
|
520
|
+
/** The inverse: a TMD_MAX-long array, zero where the save said nothing. */
|
|
521
|
+
export function deserializePlayerTimed(saved) {
|
|
522
|
+
return valuesFromNames(saved, TMD_NAMES, TMD_MAX);
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* SKILL: the derived level-based skills.
|
|
526
|
+
*
|
|
527
|
+
* NOT AN UPSTREAM FIELD AT ALL - `grep -i skill save.c load.c` is empty, and
|
|
528
|
+
* player->state.skills is rebuilt by calc_bonuses on every load. The port
|
|
529
|
+
* persists them anyway (player/player.ts `skills`), so the same position
|
|
530
|
+
* defect applied; there is no upstream discard rule to match because there is
|
|
531
|
+
* no upstream block. Every slot is written: a skill of 0 is a value, and the
|
|
532
|
+
* whole vector is ten entries.
|
|
533
|
+
*/
|
|
534
|
+
export function serializePlayerSkills(skills) {
|
|
535
|
+
return valuesByName(skills, SKILL_NAMES, true);
|
|
536
|
+
}
|
|
537
|
+
/** The inverse: a SKILL_MAX-long array. */
|
|
538
|
+
export function deserializePlayerSkills(saved) {
|
|
539
|
+
return valuesFromNames(saved, SKILL_NAMES, SKILL_MAX);
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* STAT: one of stat_max / stat_cur / stat_birth (save.c:443-446).
|
|
543
|
+
*
|
|
544
|
+
* Upstream's rule is NOT the timed one: load.c:723-727 reads the saved count
|
|
545
|
+
* and FAILS THE LOAD when it exceeds STAT_MAX ("Too many stats (%d)."), while
|
|
546
|
+
* a shorter count is read as far as it goes and the rest left at their blank
|
|
547
|
+
* values. So "extra" is an error there and "missing" is not. By name that
|
|
548
|
+
* becomes: a stat this build does not have contributes nothing (it has no slot
|
|
549
|
+
* to corrupt), and a stat the document does not mention keeps its blank value.
|
|
550
|
+
* Refusing the load over a name would be strictly worse than upstream, which
|
|
551
|
+
* only refuses because a longer array would overrun a fixed C buffer.
|
|
552
|
+
*/
|
|
553
|
+
export function serializeStatValues(stats) {
|
|
554
|
+
return valuesByName(stats, STAT_NAMES, true);
|
|
555
|
+
}
|
|
556
|
+
/** The inverse: a STAT_MAX-long array. */
|
|
557
|
+
export function deserializeStatValues(saved) {
|
|
558
|
+
return valuesFromNames(saved, STAT_NAMES, STAT_MAX);
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* STAT, THE PERMUTATION. `player->stat_map` (save.c:445) is the trap in this
|
|
562
|
+
* set: it is the only one of the four stat arrays whose VALUES are themselves
|
|
563
|
+
* stat indices rather than magnitudes.
|
|
564
|
+
*
|
|
565
|
+
* player-util.c player_scramble_stats/player_fix_scramble is the authority -
|
|
566
|
+
* `new_cur[stat_map[i]] = stat_cur[i]`, so slot `i` currently holds the value
|
|
567
|
+
* that belongs to stat `stat_map[i]`, and at birth the map is the identity.
|
|
568
|
+
* Naming only the KEYS would have left every value a raw STAT index, so a
|
|
569
|
+
* reordered stat table would still have re-pointed a scrambled character's
|
|
570
|
+
* strength at their intelligence. Both halves are names: `{ STR: "INT" }` reads
|
|
571
|
+
* "the value in the STR slot really belongs to INT".
|
|
572
|
+
*
|
|
573
|
+
* A name this build does not have leaves that slot at the identity, which is
|
|
574
|
+
* what player_fix_scramble resets it to anyway.
|
|
575
|
+
*/
|
|
576
|
+
export function serializeStatMap(map) {
|
|
577
|
+
const out = {};
|
|
578
|
+
for (let i = 0; i < map.length; i++) {
|
|
579
|
+
const from = STAT_NAMES[i];
|
|
580
|
+
const to = STAT_NAMES[map[i] ?? i];
|
|
581
|
+
if (from !== undefined && to !== undefined)
|
|
582
|
+
out[from] = to;
|
|
583
|
+
}
|
|
584
|
+
return out;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* The inverse, with a permutation check.
|
|
588
|
+
*
|
|
589
|
+
* A HALF-APPLIED MAP IS WORSE THAN NO MAP. Dropping one unresolvable name from
|
|
590
|
+
* a permutation leaves two slots pointing at the same stat and one at none, and
|
|
591
|
+
* player_fix_scramble would then duplicate one stat's value and lose another's
|
|
592
|
+
* - a silent, permanent stat loss. So the result is validated as a permutation
|
|
593
|
+
* of 0..STAT_MAX-1 and falls back WHOLE to the identity if it is not, which is
|
|
594
|
+
* the value the map legitimately holds for every unscrambled character.
|
|
595
|
+
*/
|
|
596
|
+
export function deserializeStatMap(saved) {
|
|
597
|
+
const out = Array.from({ length: STAT_MAX }, (_, i) => i);
|
|
598
|
+
if (saved === undefined)
|
|
599
|
+
return out;
|
|
600
|
+
for (const [from, to] of Object.entries(saved)) {
|
|
601
|
+
const i = STAT_NAMES.indexOf(from);
|
|
602
|
+
const j = STAT_NAMES.indexOf(to);
|
|
603
|
+
if (i >= 0 && i < STAT_MAX && j >= 0 && j < STAT_MAX)
|
|
604
|
+
out[i] = j;
|
|
605
|
+
}
|
|
606
|
+
const seen = new Set(out);
|
|
607
|
+
if (seen.size !== STAT_MAX)
|
|
608
|
+
return Array.from({ length: STAT_MAX }, (_, i) => i);
|
|
609
|
+
return out;
|
|
610
|
+
}
|
|
611
|
+
/* ------------------------------------------------------------------ *
|
|
612
|
+
* SQUARE: the one table a per-entity name list could not carry.
|
|
613
|
+
* ------------------------------------------------------------------ */
|
|
614
|
+
/**
|
|
615
|
+
* THE MEASUREMENT, AND WHY THIS ONE IS A LEGEND.
|
|
616
|
+
*
|
|
617
|
+
* Every other table in #273/#274 belongs to an entity there are tens or
|
|
618
|
+
* hundreds of. `square.info` belongs to a GRID, and a 63x188 level is 11,844 of
|
|
619
|
+
* them - written once for the level in play, once for the Town, and once more
|
|
620
|
+
* for every level in the birth_levels_persist cache.
|
|
621
|
+
*
|
|
622
|
+
* Measured on a real generated level (seed 4242, depth 3, 11,844 grids):
|
|
623
|
+
*
|
|
624
|
+
* numeric, 3 bytes/grid, as written today 97,731 bytes
|
|
625
|
+
* one name list per grid, freshly generated 76,624 bytes
|
|
626
|
+
* one name list per grid, EXPLORED 462,289 bytes (4.7x)
|
|
627
|
+
* one name list per grid, every flag set 2,463,553 bytes (25x)
|
|
628
|
+
* this build's whole ordered name list, ONCE 214 bytes
|
|
629
|
+
*
|
|
630
|
+
* The freshly-generated figure is the trap: an unexplored level averages 0.49
|
|
631
|
+
* set flags per grid, so `[]` beats `[0,0,0]` and the name form looks FREE. It
|
|
632
|
+
* is not. The instant a character walks the level, MARK / GLOW / ROOM / SEEN /
|
|
633
|
+
* VIEW land on most grids and the same encoding costs 4.7x - a third of a
|
|
634
|
+
* megabyte per level, on a web build whose storage risk is already the reason
|
|
635
|
+
* saves are compressed at all.
|
|
636
|
+
*
|
|
637
|
+
* So the per-grid payload stays numeric and the DOCUMENT carries the legend:
|
|
638
|
+
* the ordered SQUARE_ names, once, alongside the bits that index into them. The
|
|
639
|
+
* legend travels with the save, so it describes the WRITING build's numbering
|
|
640
|
+
* no matter which build reads it - which is the whole property names were
|
|
641
|
+
* bought for, at 0.2% of one level's cost instead of 470%.
|
|
642
|
+
*
|
|
643
|
+
* Upstream reached the same shape by accident and stopped one step short:
|
|
644
|
+
* save.c:967 writes `SQUARE_SIZE` into the file and load.c:1512 reads it back
|
|
645
|
+
* into a file-scope `square_size` that rd_dungeon_aux then uses as its loop
|
|
646
|
+
* bound (load.c:1306). The file already describes its own square encoding
|
|
647
|
+
* there; it just describes the LENGTH and not the ORDER, so a reordered table
|
|
648
|
+
* still lands every flag somewhere else.
|
|
649
|
+
*/
|
|
650
|
+
export const SQUARE_INFO_LEGEND = SQUARE_FLAG_ENTRIES.map((e) => e.name);
|
|
651
|
+
/**
|
|
652
|
+
* A saved legend -> this build's bit numbering, or `null` when the two agree
|
|
653
|
+
* and every byte can be taken as-is (the overwhelmingly common case: the same
|
|
654
|
+
* build, or any build whose SQUARE table has not moved).
|
|
655
|
+
*
|
|
656
|
+
* An absent legend also reads as `null`. That is deliberate and it is safe for
|
|
657
|
+
* one reason only: `V6_TO_V7` stamps the legend onto every document it
|
|
658
|
+
* converts, and no build that could have reordered SQUARE can have written a
|
|
659
|
+
* document at version 6 or below - the same argument `savedFlagSet` rests on in
|
|
660
|
+
* save-migrate.ts.
|
|
661
|
+
*/
|
|
662
|
+
export function buildSquareInfoRemap(legend) {
|
|
663
|
+
if (legend === undefined)
|
|
664
|
+
return null;
|
|
665
|
+
let moved = false;
|
|
666
|
+
const remap = [];
|
|
667
|
+
for (let bit = 0; bit < legend.length; bit++) {
|
|
668
|
+
const name = legend[bit];
|
|
669
|
+
const now = name === undefined ? -1 : SQUARE_INFO_LEGEND.indexOf(name);
|
|
670
|
+
remap[bit] = now < 0 ? undefined : now;
|
|
671
|
+
if (now !== bit)
|
|
672
|
+
moved = true;
|
|
673
|
+
}
|
|
674
|
+
if (!moved && legend.length === SQUARE_INFO_LEGEND.length)
|
|
675
|
+
return null;
|
|
676
|
+
return remap;
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* One grid's info bytes, re-expressed in this build's bit numbering. A bit
|
|
680
|
+
* whose name this build no longer has is dropped, exactly as an unknown flag
|
|
681
|
+
* NAME is dropped everywhere else in this file.
|
|
682
|
+
*/
|
|
683
|
+
export function remapSquareInfo(bytes, remap) {
|
|
684
|
+
const out = new Array(SQUARE_SIZE).fill(0);
|
|
685
|
+
for (let b = 0; b < bytes.length; b++) {
|
|
686
|
+
const byte = bytes[b] ?? 0;
|
|
687
|
+
if (byte === 0)
|
|
688
|
+
continue;
|
|
689
|
+
for (let k = 0; k < 8; k++) {
|
|
690
|
+
if ((byte & (1 << k)) === 0)
|
|
691
|
+
continue;
|
|
692
|
+
const now = remap[b * 8 + k];
|
|
693
|
+
if (now === undefined)
|
|
694
|
+
continue;
|
|
695
|
+
const target = now >> 3;
|
|
696
|
+
if (target < SQUARE_SIZE)
|
|
697
|
+
out[target] = (out[target] ?? 0) | (1 << (now & 7));
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return out;
|
|
701
|
+
}
|
|
107
702
|
export function serializeObject(obj, ids) {
|
|
108
703
|
return {
|
|
109
704
|
kindId: ids.kindId(obj.kind.kidx),
|
|
@@ -121,9 +716,9 @@ export function serializeObject(obj, ids) {
|
|
|
121
716
|
toA: obj.toA,
|
|
122
717
|
toH: obj.toH,
|
|
123
718
|
toD: obj.toD,
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
719
|
+
flagNames: serializeObjectFlags(obj.flags),
|
|
720
|
+
modifierValues: serializeObjectModifiers(obj.modifiers),
|
|
721
|
+
elementInfo: serializeObjectElements(obj.elInfo),
|
|
127
722
|
brands: serializeBrandList(obj.brands, ids),
|
|
128
723
|
slays: serializeSlayList(obj.slays, ids),
|
|
129
724
|
curses: serializeCurseList(obj.curses, ids),
|
|
@@ -221,9 +816,9 @@ export function deserializeObject(data, reg, ids) {
|
|
|
221
816
|
toA: data.toA,
|
|
222
817
|
toH: data.toH,
|
|
223
818
|
toD: data.toD,
|
|
224
|
-
flags:
|
|
225
|
-
modifiers:
|
|
226
|
-
elInfo: data.
|
|
819
|
+
flags: deserializeObjectFlags(data.flagNames),
|
|
820
|
+
modifiers: deserializeObjectModifiers(data.modifierValues),
|
|
821
|
+
elInfo: deserializeObjectElements(data.elementInfo),
|
|
227
822
|
brands: deserializeBrandList(data.brands, reg, ids),
|
|
228
823
|
slays: deserializeSlayList(data.slays, reg, ids),
|
|
229
824
|
curses: deserializeCurseList(data.curses, reg, ids),
|
|
@@ -309,11 +904,11 @@ export function serializeMonster(mon, ids) {
|
|
|
309
904
|
grid: { x: mon.grid.x, y: mon.grid.y },
|
|
310
905
|
hp: mon.hp,
|
|
311
906
|
maxhp: mon.maxhp,
|
|
312
|
-
|
|
907
|
+
monsterTimed: serializeMonsterTimed(mon.mTimed),
|
|
313
908
|
mspeed: mon.mspeed,
|
|
314
909
|
energy: mon.energy,
|
|
315
910
|
cdis: mon.cdis,
|
|
316
|
-
|
|
911
|
+
mflagNames: serializeMonsterFlags(mon.mflag),
|
|
317
912
|
mimickedObj: mon.mimickedObj,
|
|
318
913
|
heldObj: mon.heldObj.map((o) => serializeObject(o, ids)),
|
|
319
914
|
attr: mon.attr,
|
|
@@ -324,8 +919,8 @@ export function serializeMonster(mon, ids) {
|
|
|
324
919
|
groupInfo: mon.groupInfo.map((g) => ({ ...g })),
|
|
325
920
|
minRange: mon.minRange,
|
|
326
921
|
bestRange: mon.bestRange,
|
|
327
|
-
|
|
328
|
-
|
|
922
|
+
knownPstateFlagNames: serializeObjectFlags(mon.knownPstate.flags),
|
|
923
|
+
knownPstateElementRes: serializeElementLevels(mon.knownPstate.elInfo),
|
|
329
924
|
};
|
|
330
925
|
}
|
|
331
926
|
/** rd_monster (load.c:259), over JSON instead of the binary block. */
|
|
@@ -343,18 +938,18 @@ export function deserializeMonster(data, monsters, objects, ids) {
|
|
|
343
938
|
mon.grid = loc(data.grid.x, data.grid.y);
|
|
344
939
|
mon.hp = data.hp;
|
|
345
940
|
mon.maxhp = data.maxhp;
|
|
346
|
-
mon.mTimed.set(data.
|
|
941
|
+
mon.mTimed.set(deserializeMonsterTimed(data.monsterTimed));
|
|
347
942
|
mon.mspeed = data.mspeed;
|
|
348
943
|
mon.energy = data.energy;
|
|
349
944
|
mon.cdis = data.cdis;
|
|
350
|
-
mon.mflag.
|
|
351
|
-
if (data.
|
|
945
|
+
mon.mflag.copy(deserializeMonsterFlags(data.mflagNames));
|
|
946
|
+
if (data.knownPstateFlagNames) {
|
|
352
947
|
/* load.c:302 restores known_pstate.flags before the monster goes live. */
|
|
353
|
-
mon.knownPstate.flags.
|
|
948
|
+
mon.knownPstate.flags.copy(deserializeObjectFlags(data.knownPstateFlagNames));
|
|
354
949
|
}
|
|
355
|
-
if (data.
|
|
950
|
+
if (data.knownPstateElementRes) {
|
|
356
951
|
/* load.c:305 restores known_pstate.el_info[].res_level before live use. */
|
|
357
|
-
mon.knownPstate.elInfo.set(data.
|
|
952
|
+
mon.knownPstate.elInfo.set(deserializeElementLevels(data.knownPstateElementRes));
|
|
358
953
|
}
|
|
359
954
|
mon.mimickedObj = data.mimickedObj;
|
|
360
955
|
mon.heldObj = data.heldObj.map((o) => deserializeObject(o, objects, ids));
|
|
@@ -397,11 +992,11 @@ export function serializePlayer(p, ids) {
|
|
|
397
992
|
msp: p.msp,
|
|
398
993
|
csp: p.csp,
|
|
399
994
|
cspFrac: p.cspFrac,
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
995
|
+
statMaxValues: serializeStatValues(p.statMax),
|
|
996
|
+
statCurValues: serializeStatValues(p.statCur),
|
|
997
|
+
statMapNames: serializeStatMap(p.statMap),
|
|
998
|
+
statBirthValues: serializeStatValues(p.statBirth),
|
|
999
|
+
timedValues: serializePlayerTimed(p.timed),
|
|
405
1000
|
spellFlags: [...p.spellFlags],
|
|
406
1001
|
spellOrder: [...p.spellOrder],
|
|
407
1002
|
playerHp: [...p.playerHp],
|
|
@@ -422,22 +1017,22 @@ export function serializePlayer(p, ids) {
|
|
|
422
1017
|
})),
|
|
423
1018
|
equipment: [...p.equipment],
|
|
424
1019
|
objKnown: {
|
|
425
|
-
|
|
1020
|
+
modifierValues: serializeObjectModifiers(p.objKnown.modifiers),
|
|
426
1021
|
toA: p.objKnown.toA,
|
|
427
1022
|
toH: p.objKnown.toH,
|
|
428
1023
|
toD: p.objKnown.toD,
|
|
429
1024
|
dd: p.objKnown.dd,
|
|
430
1025
|
ds: p.objKnown.ds,
|
|
431
1026
|
ac: p.objKnown.ac,
|
|
432
|
-
|
|
433
|
-
|
|
1027
|
+
elementInfo: serializeObjectElements(p.objKnown.elInfo),
|
|
1028
|
+
flagNames: serializeObjectFlags(p.objKnown.flags),
|
|
434
1029
|
/* The learned runes save as the ids of the known brands/slays/curses. */
|
|
435
1030
|
brands: serializeBrandList(p.objKnown.brands, ids) ?? [],
|
|
436
1031
|
slays: serializeSlayList(p.objKnown.slays, ids) ?? [],
|
|
437
1032
|
curses: serializeKnownCurseList(p.objKnown.curses, ids),
|
|
438
1033
|
},
|
|
439
1034
|
shapeName: p.shape ? p.shape.name : null,
|
|
440
|
-
|
|
1035
|
+
skillValues: serializePlayerSkills(p.skills),
|
|
441
1036
|
/* THE THREE DECLARED FIELDS, NAMED. `{ ...p.upkeep }` wrote whatever the live
|
|
442
1037
|
* object happened to hold, which is not what the type above says: `notice`,
|
|
443
1038
|
* `dropping`, `repeatPrevAllowed` and `lastCmdUsedFloorItem` are transients
|
|
@@ -500,12 +1095,15 @@ quests) {
|
|
|
500
1095
|
p.msp = data.msp;
|
|
501
1096
|
p.csp = data.csp;
|
|
502
1097
|
p.cspFrac = data.cspFrac;
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
p.
|
|
506
|
-
p.
|
|
507
|
-
|
|
508
|
-
p.
|
|
1098
|
+
/* load.c:723-732: a saved stat this build does not have contributes nothing,
|
|
1099
|
+
* and one the save omits keeps its blank value - see serializeStatValues. */
|
|
1100
|
+
p.statMax = deserializeStatValues(data.statMaxValues);
|
|
1101
|
+
p.statCur = deserializeStatValues(data.statCurValues);
|
|
1102
|
+
p.statMap = deserializeStatMap(data.statMapNames);
|
|
1103
|
+
p.statBirth = deserializeStatValues(data.statBirthValues);
|
|
1104
|
+
/* load.c:811-829 reads only supported timed entries and discards extras. By
|
|
1105
|
+
* name that is a name this build does not have, dropped without complaint. */
|
|
1106
|
+
p.timed.set(deserializePlayerTimed(data.timedValues));
|
|
509
1107
|
p.spellFlags = [...data.spellFlags];
|
|
510
1108
|
p.spellOrder = [...data.spellOrder];
|
|
511
1109
|
p.playerHp = [...data.playerHp];
|
|
@@ -545,7 +1143,7 @@ quests) {
|
|
|
545
1143
|
p.equipment = [...data.equipment];
|
|
546
1144
|
if (data.objKnown) {
|
|
547
1145
|
p.objKnown = {
|
|
548
|
-
modifiers:
|
|
1146
|
+
modifiers: deserializeObjectModifiers(data.objKnown.modifierValues),
|
|
549
1147
|
toA: data.objKnown.toA,
|
|
550
1148
|
toH: data.objKnown.toH,
|
|
551
1149
|
toD: data.objKnown.toD,
|
|
@@ -554,22 +1152,23 @@ quests) {
|
|
|
554
1152
|
dd: data.objKnown.dd ?? 1,
|
|
555
1153
|
ds: data.objKnown.ds ?? 1,
|
|
556
1154
|
ac: data.objKnown.ac ?? 1,
|
|
557
|
-
elInfo: data.objKnown.
|
|
558
|
-
flags:
|
|
1155
|
+
elInfo: deserializeObjectElements(data.objKnown.elementInfo),
|
|
1156
|
+
flags: deserializeObjectFlags(data.objKnown.flagNames),
|
|
559
1157
|
brands: deserializeBrandList(data.objKnown.brands, objReg, ids) ?? [],
|
|
560
1158
|
slays: deserializeSlayList(data.objKnown.slays, objReg, ids) ?? [],
|
|
561
1159
|
curses: deserializeKnownCurseList(data.objKnown.curses, objReg, ids),
|
|
562
1160
|
};
|
|
563
1161
|
}
|
|
564
|
-
else if (data.
|
|
565
|
-
/* Legacy pre-#13 save: only the modifier runes were tracked.
|
|
566
|
-
|
|
1162
|
+
else if (data.objKnownModifierValues) {
|
|
1163
|
+
/* Legacy pre-#13 save: only the modifier runes were tracked. By name as of
|
|
1164
|
+
* version 7, through the same helper the modern field uses. */
|
|
1165
|
+
p.objKnown.modifiers = deserializeObjectModifiers(data.objKnownModifierValues);
|
|
567
1166
|
}
|
|
568
1167
|
p.shape =
|
|
569
1168
|
data.shapeName !== null
|
|
570
1169
|
? (players.shapes.find((s) => s.name === data.shapeName) ?? null)
|
|
571
1170
|
: null;
|
|
572
|
-
p.skills =
|
|
1171
|
+
p.skills = deserializePlayerSkills(data.skillValues);
|
|
573
1172
|
/* notice is NOT in the savefile, exactly as upstream's is not: it is a queue
|
|
574
1173
|
* of work owed within a turn, and a save can only happen with the queue
|
|
575
1174
|
* drained. A loaded character therefore starts it at 0 rather than at
|
|
@@ -699,6 +1298,43 @@ function deserializeKnownObject(m, floor, reg, ids) {
|
|
|
699
1298
|
obj.grid = null;
|
|
700
1299
|
return { obj, sensed: m.sensed === true };
|
|
701
1300
|
}
|
|
1301
|
+
/**
|
|
1302
|
+
* The observed spell set as RSF_ names, ascending by flag number so the save is
|
|
1303
|
+
* byte-stable for an unchanged record.
|
|
1304
|
+
*
|
|
1305
|
+
* The bound is rsfMax() (live, including a mod's spells), not the module-load
|
|
1306
|
+
* RSF.MAX. spellNameAt returns null for an unknown index the same way the old
|
|
1307
|
+
* RSF_FLAG_NAMES array returned undefined - including the MAX sentinel when no
|
|
1308
|
+
* mod occupies that slot - so a non-spell never reaches the save.
|
|
1309
|
+
*/
|
|
1310
|
+
export function serializeLoreSpells(spellFlags) {
|
|
1311
|
+
const out = [];
|
|
1312
|
+
for (const flag of spellFlags) {
|
|
1313
|
+
if (flag >= rsfMax())
|
|
1314
|
+
break;
|
|
1315
|
+
const name = spellNameAt(flag);
|
|
1316
|
+
if (name !== null)
|
|
1317
|
+
out.push(name);
|
|
1318
|
+
}
|
|
1319
|
+
return out;
|
|
1320
|
+
}
|
|
1321
|
+
/**
|
|
1322
|
+
* The inverse: a lore-sized FlagSet with exactly the named spells on. A name
|
|
1323
|
+
* this build does not have (a mod's spell, uninstalled - or the `"MAX"`
|
|
1324
|
+
* sentinel) is dropped, which is how the whole scheme stays safe: an unknown
|
|
1325
|
+
* NAME cannot land on some other spell's bit the way an out-of-range index
|
|
1326
|
+
* would. spellIndexOf returns -1 for unknown, matching the existing flag > 0
|
|
1327
|
+
* guard's intent.
|
|
1328
|
+
*/
|
|
1329
|
+
export function deserializeLoreSpells(names) {
|
|
1330
|
+
const set = new FlagSet(rsfSize());
|
|
1331
|
+
for (const name of names ?? []) {
|
|
1332
|
+
const flag = spellIndexOf(name);
|
|
1333
|
+
if (flag > 0 && flag < rsfMax())
|
|
1334
|
+
set.on(flag);
|
|
1335
|
+
}
|
|
1336
|
+
return set;
|
|
1337
|
+
}
|
|
702
1338
|
/** Build the feature legend for every fidx appearing in the terrain grids. */
|
|
703
1339
|
function buildFeatLegend(feats, known, ids) {
|
|
704
1340
|
const present = new Set();
|
|
@@ -762,7 +1398,7 @@ export function serializeGame(state, flavor, seedFlavor, ids, randartSeed = 0, e
|
|
|
762
1398
|
grid: { x: t.grid.x, y: t.grid.y },
|
|
763
1399
|
power: t.power,
|
|
764
1400
|
timeout: t.timeout,
|
|
765
|
-
|
|
1401
|
+
trapFlagNames: serializeTrapFlags(t.flags),
|
|
766
1402
|
})),
|
|
767
1403
|
});
|
|
768
1404
|
}
|
|
@@ -793,6 +1429,10 @@ export function serializeGame(state, flavor, seedFlavor, ids, randartSeed = 0, e
|
|
|
793
1429
|
const runeNotes = runeNoteEntries.length > 0 ? runeNoteEntries : undefined;
|
|
794
1430
|
return {
|
|
795
1431
|
version: SAVE_VERSION,
|
|
1432
|
+
/* Written unconditionally, including for a dead save that carries no
|
|
1433
|
+
* chunk: a legend costs 214 bytes and a document that describes its own
|
|
1434
|
+
* square encoding in only some cases is a document with two rules. */
|
|
1435
|
+
squareInfoLegend: [...SQUARE_INFO_LEGEND],
|
|
796
1436
|
player: serializePlayer(state.actor.player, ids),
|
|
797
1437
|
actor: {
|
|
798
1438
|
grid: { x: state.actor.grid.x, y: state.actor.grid.y },
|
|
@@ -950,8 +1590,8 @@ export function serializeGame(state, flavor, seedFlavor, ids, randartSeed = 0, e
|
|
|
950
1590
|
castSpell: l.castSpell,
|
|
951
1591
|
blowTimesSeen: [...l.blowTimesSeen],
|
|
952
1592
|
blowKnown: [...l.blowKnown],
|
|
953
|
-
|
|
954
|
-
|
|
1593
|
+
flagsKnown: serializeLoreFlags(l.flags),
|
|
1594
|
+
spellsKnown: serializeLoreSpells(l.spellFlags),
|
|
955
1595
|
allKnown: l.allKnown,
|
|
956
1596
|
armourKnown: l.armourKnown,
|
|
957
1597
|
dropKnown: l.dropKnown,
|
|
@@ -985,8 +1625,8 @@ export function deserializeLore(data, ids) {
|
|
|
985
1625
|
castSpell: l.castSpell,
|
|
986
1626
|
blowTimesSeen: [...l.blowTimesSeen],
|
|
987
1627
|
blowKnown: [...l.blowKnown],
|
|
988
|
-
flags:
|
|
989
|
-
spellFlags:
|
|
1628
|
+
flags: deserializeLoreFlags(l.flagsKnown),
|
|
1629
|
+
spellFlags: deserializeLoreSpells(l.spellsKnown),
|
|
990
1630
|
allKnown: l.allKnown,
|
|
991
1631
|
armourKnown: l.armourKnown,
|
|
992
1632
|
dropKnown: l.dropKnown,
|
|
@@ -1222,7 +1862,7 @@ export function deserializeTraps(data, kinds, width, ids, onDecoy) {
|
|
|
1222
1862
|
grid,
|
|
1223
1863
|
power: t.power,
|
|
1224
1864
|
timeout: t.timeout,
|
|
1225
|
-
flags:
|
|
1865
|
+
flags: deserializeTrapFlags(t.trapFlagNames),
|
|
1226
1866
|
};
|
|
1227
1867
|
}));
|
|
1228
1868
|
}
|
|
@@ -1338,7 +1978,7 @@ export function serializeStoredLevel(depth, level, ids) {
|
|
|
1338
1978
|
grid: { x: t.grid.x, y: t.grid.y },
|
|
1339
1979
|
power: t.power,
|
|
1340
1980
|
timeout: t.timeout,
|
|
1341
|
-
|
|
1981
|
+
trapFlagNames: serializeTrapFlags(t.flags),
|
|
1342
1982
|
})),
|
|
1343
1983
|
});
|
|
1344
1984
|
}
|