rpg-engine 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +99 -2
- package/dist/campaign/create-campaign.d.ts +6 -0
- package/dist/campaign/create-campaign.js +71 -0
- package/dist/campaign/find-campaign.d.ts +8 -0
- package/dist/campaign/find-campaign.js +28 -0
- package/dist/campaign/host-files.d.ts +16 -0
- package/dist/campaign/host-files.js +131 -0
- package/dist/campaign/index.d.ts +5 -0
- package/dist/campaign/index.js +6 -0
- package/dist/campaign/settings.d.ts +185 -0
- package/dist/campaign/settings.js +399 -0
- package/dist/campaign/state.d.ts +15 -0
- package/dist/campaign/state.js +52 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +19 -0
- package/dist/command-line.d.ts +7 -0
- package/dist/command-line.js +140 -0
- package/dist/commands/advance.d.ts +7 -0
- package/dist/commands/advance.js +80 -0
- package/dist/commands/ask.d.ts +33 -0
- package/dist/commands/ask.js +280 -0
- package/dist/commands/change.d.ts +69 -0
- package/dist/commands/change.js +604 -0
- package/dist/commands/config.d.ts +59 -0
- package/dist/commands/config.js +512 -0
- package/dist/commands/effect-layer.d.ts +22 -0
- package/dist/commands/effect-layer.js +84 -0
- package/dist/commands/effect.d.ts +27 -0
- package/dist/commands/effect.js +369 -0
- package/dist/commands/find.d.ts +34 -0
- package/dist/commands/find.js +64 -0
- package/dist/commands/game-advance.d.ts +50 -0
- package/dist/commands/game-advance.js +103 -0
- package/dist/commands/game-effects.d.ts +68 -0
- package/dist/commands/game-effects.js +196 -0
- package/dist/commands/game-environment.d.ts +16 -0
- package/dist/commands/game-environment.js +24 -0
- package/dist/commands/game-events.d.ts +84 -0
- package/dist/commands/game-events.js +325 -0
- package/dist/commands/game-experience.d.ts +57 -0
- package/dist/commands/game-experience.js +119 -0
- package/dist/commands/game-grid-moves.d.ts +33 -0
- package/dist/commands/game-grid-moves.js +94 -0
- package/dist/commands/game-grids.d.ts +52 -0
- package/dist/commands/game-grids.js +162 -0
- package/dist/commands/game-here.d.ts +40 -0
- package/dist/commands/game-here.js +100 -0
- package/dist/commands/game-images.d.ts +31 -0
- package/dist/commands/game-images.js +87 -0
- package/dist/commands/game-journey.d.ts +62 -0
- package/dist/commands/game-journey.js +163 -0
- package/dist/commands/game-knowledge.d.ts +32 -0
- package/dist/commands/game-knowledge.js +120 -0
- package/dist/commands/game-maps.d.ts +20 -0
- package/dist/commands/game-maps.js +51 -0
- package/dist/commands/game-mode.d.ts +31 -0
- package/dist/commands/game-mode.js +128 -0
- package/dist/commands/game-money.d.ts +17 -0
- package/dist/commands/game-money.js +55 -0
- package/dist/commands/game-moods.d.ts +22 -0
- package/dist/commands/game-moods.js +32 -0
- package/dist/commands/game-moves.d.ts +14 -0
- package/dist/commands/game-moves.js +43 -0
- package/dist/commands/game-open.d.ts +18 -0
- package/dist/commands/game-open.js +47 -0
- package/dist/commands/game-outside.d.ts +14 -0
- package/dist/commands/game-outside.js +151 -0
- package/dist/commands/game-positions.d.ts +52 -0
- package/dist/commands/game-positions.js +244 -0
- package/dist/commands/game-quests.d.ts +46 -0
- package/dist/commands/game-quests.js +97 -0
- package/dist/commands/game-recipes.d.ts +34 -0
- package/dist/commands/game-recipes.js +109 -0
- package/dist/commands/game-reminders.d.ts +22 -0
- package/dist/commands/game-reminders.js +74 -0
- package/dist/commands/game-roads.d.ts +90 -0
- package/dist/commands/game-roads.js +284 -0
- package/dist/commands/game-rules.d.ts +17 -0
- package/dist/commands/game-rules.js +30 -0
- package/dist/commands/game-saves.d.ts +59 -0
- package/dist/commands/game-saves.js +249 -0
- package/dist/commands/game-scene.d.ts +69 -0
- package/dist/commands/game-scene.js +285 -0
- package/dist/commands/game-thresholds.d.ts +13 -0
- package/dist/commands/game-thresholds.js +41 -0
- package/dist/commands/game-time.d.ts +17 -0
- package/dist/commands/game-time.js +42 -0
- package/dist/commands/game-view-journal.d.ts +145 -0
- package/dist/commands/game-view-journal.js +129 -0
- package/dist/commands/game-view-map.d.ts +78 -0
- package/dist/commands/game-view-map.js +266 -0
- package/dist/commands/game-view-place.d.ts +160 -0
- package/dist/commands/game-view-place.js +388 -0
- package/dist/commands/game-world.d.ts +60 -0
- package/dist/commands/game-world.js +204 -0
- package/dist/commands/image.d.ts +19 -0
- package/dist/commands/image.js +185 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/index.js +57 -0
- package/dist/commands/init.d.ts +23 -0
- package/dist/commands/init.js +44 -0
- package/dist/commands/install.d.ts +23 -0
- package/dist/commands/install.js +68 -0
- package/dist/commands/instructions.d.ts +4 -0
- package/dist/commands/instructions.js +97 -0
- package/dist/commands/mode.d.ts +23 -0
- package/dist/commands/mode.js +84 -0
- package/dist/commands/mood.d.ts +34 -0
- package/dist/commands/mood.js +113 -0
- package/dist/commands/move.d.ts +34 -0
- package/dist/commands/move.js +274 -0
- package/dist/commands/notes.d.ts +10 -0
- package/dist/commands/notes.js +37 -0
- package/dist/commands/page-counters.d.ts +24 -0
- package/dist/commands/page-counters.js +84 -0
- package/dist/commands/parts.d.ts +16 -0
- package/dist/commands/parts.js +49 -0
- package/dist/commands/problems.d.ts +5 -0
- package/dist/commands/problems.js +47 -0
- package/dist/commands/progression.d.ts +68 -0
- package/dist/commands/progression.js +206 -0
- package/dist/commands/rename.d.ts +16 -0
- package/dist/commands/rename.js +116 -0
- package/dist/commands/reveal.d.ts +25 -0
- package/dist/commands/reveal.js +193 -0
- package/dist/commands/roll.d.ts +56 -0
- package/dist/commands/roll.js +302 -0
- package/dist/commands/route.d.ts +19 -0
- package/dist/commands/route.js +110 -0
- package/dist/commands/rules.d.ts +48 -0
- package/dist/commands/rules.js +194 -0
- package/dist/commands/save.d.ts +68 -0
- package/dist/commands/save.js +141 -0
- package/dist/commands/scene.d.ts +105 -0
- package/dist/commands/scene.js +741 -0
- package/dist/commands/sheet.d.ts +136 -0
- package/dist/commands/sheet.js +445 -0
- package/dist/commands/show.d.ts +76 -0
- package/dist/commands/show.js +216 -0
- package/dist/commands/status.d.ts +54 -0
- package/dist/commands/status.js +291 -0
- package/dist/commands/translate.d.ts +23 -0
- package/dist/commands/translate.js +108 -0
- package/dist/commands/travel.d.ts +47 -0
- package/dist/commands/travel.js +457 -0
- package/dist/commands/validate.d.ts +4 -0
- package/dist/commands/validate.js +25 -0
- package/dist/commands/view.d.ts +272 -0
- package/dist/commands/view.js +623 -0
- package/dist/commands/worked-out.d.ts +116 -0
- package/dist/commands/worked-out.js +704 -0
- package/dist/commands/write.d.ts +51 -0
- package/dist/commands/write.js +1017 -0
- package/dist/core/command-reference.d.ts +6 -0
- package/dist/core/command-reference.js +47 -0
- package/dist/core/context.d.ts +140 -0
- package/dist/core/context.js +28 -0
- package/dist/core/field-kind.d.ts +6 -0
- package/dist/core/field-kind.js +5 -0
- package/dist/core/files.d.ts +4 -0
- package/dist/core/files.js +30 -0
- package/dist/core/formula.d.ts +164 -0
- package/dist/core/formula.js +590 -0
- package/dist/core/frozen.d.ts +2 -0
- package/dist/core/frozen.js +11 -0
- package/dist/core/length.d.ts +27 -0
- package/dist/core/length.js +49 -0
- package/dist/core/log.d.ts +27 -0
- package/dist/core/log.js +38 -0
- package/dist/core/memo.d.ts +2 -0
- package/dist/core/memo.js +17 -0
- package/dist/core/mood.d.ts +6 -0
- package/dist/core/mood.js +11 -0
- package/dist/core/problem.d.ts +6 -0
- package/dist/core/problem.js +1 -0
- package/dist/core/progression-rule.d.ts +21 -0
- package/dist/core/progression-rule.js +2 -0
- package/dist/core/registry.d.ts +31 -0
- package/dist/core/registry.js +25 -0
- package/dist/core/session-store.d.ts +90 -0
- package/dist/core/session-store.js +289 -0
- package/dist/core/store.d.ts +83 -0
- package/dist/core/store.js +224 -0
- package/dist/core/text.d.ts +12 -0
- package/dist/core/text.js +32 -0
- package/dist/core/version.d.ts +2 -0
- package/dist/core/version.js +6 -0
- package/dist/core/yaml-file.d.ts +10 -0
- package/dist/core/yaml-file.js +42 -0
- package/dist/counters/counter.d.ts +36 -0
- package/dist/counters/counter.js +59 -0
- package/dist/counters/index.d.ts +1 -0
- package/dist/counters/index.js +2 -0
- package/dist/dashboard/address.js +43 -0
- package/dist/dashboard/api.js +185 -0
- package/dist/dashboard/app.js +217 -0
- package/dist/dashboard/asked.js +132 -0
- package/dist/dashboard/assets/dice/dice-sharp.glb +0 -0
- package/dist/dashboard/assets/dice/dice.glb +0 -0
- package/dist/dashboard/assets/dice/dice.json +1 -0
- package/dist/dashboard/assets/dice/textures/ash.jpg +0 -0
- package/dist/dashboard/assets/dice/textures/granite.jpg +0 -0
- package/dist/dashboard/assets/dice/textures/marble.jpg +0 -0
- package/dist/dashboard/assets/dice/textures/onyx.jpg +0 -0
- package/dist/dashboard/assets/dice/textures/walnut.jpg +0 -0
- package/dist/dashboard/assets/favicon.svg +7 -0
- package/dist/dashboard/assets/icon-180.png +0 -0
- package/dist/dashboard/assets/icon-32.png +0 -0
- package/dist/dashboard/assets/master.svg +71 -0
- package/dist/dashboard/assets/sounds/dice-collision.wav +0 -0
- package/dist/dashboard/assets/sounds/dice-touch.wav +0 -0
- package/dist/dashboard/card.js +61 -0
- package/dist/dashboard/columns.js +37 -0
- package/dist/dashboard/combat.js +72 -0
- package/dist/dashboard/data.js +3 -0
- package/dist/dashboard/dice/burn.js +200 -0
- package/dist/dashboard/dice/celebration.js +199 -0
- package/dist/dashboard/dice/floor-cracks.js +149 -0
- package/dist/dashboard/dice/frost.js +51 -0
- package/dist/dashboard/dice/load.js +66 -0
- package/dist/dashboard/dice/models.js +138 -0
- package/dist/dashboard/dice/pool.js +49 -0
- package/dist/dashboard/dice/roller.js +101 -0
- package/dist/dashboard/dice/sets.js +49 -0
- package/dist/dashboard/dice/sounds.js +102 -0
- package/dist/dashboard/dice/stage.js +355 -0
- package/dist/dashboard/dice/throw.js +242 -0
- package/dist/dashboard/dice/tray.js +365 -0
- package/dist/dashboard/faces.js +16 -0
- package/dist/dashboard/footer.js +17 -0
- package/dist/dashboard/free-rolls.js +41 -0
- package/dist/dashboard/game-words.js +164 -0
- package/dist/dashboard/glass.js +12 -0
- package/dist/dashboard/guide/ar.md +306 -0
- package/dist/dashboard/guide/cs.md +306 -0
- package/dist/dashboard/guide/da.md +306 -0
- package/dist/dashboard/guide/de.md +306 -0
- package/dist/dashboard/guide/el.md +306 -0
- package/dist/dashboard/guide/en.md +305 -0
- package/dist/dashboard/guide/es.md +306 -0
- package/dist/dashboard/guide/fi.md +306 -0
- package/dist/dashboard/guide/fr.md +306 -0
- package/dist/dashboard/guide/he.md +306 -0
- package/dist/dashboard/guide/hi.md +306 -0
- package/dist/dashboard/guide/hu.md +306 -0
- package/dist/dashboard/guide/is.md +306 -0
- package/dist/dashboard/guide/it.md +306 -0
- package/dist/dashboard/guide/ja.md +306 -0
- package/dist/dashboard/guide/ko.md +306 -0
- package/dist/dashboard/guide/nb.md +306 -0
- package/dist/dashboard/guide/nl.md +306 -0
- package/dist/dashboard/guide/pl.md +306 -0
- package/dist/dashboard/guide/pt.md +306 -0
- package/dist/dashboard/guide/ro.md +306 -0
- package/dist/dashboard/guide/ru.md +306 -0
- package/dist/dashboard/guide/sv.md +306 -0
- package/dist/dashboard/guide/tr.md +306 -0
- package/dist/dashboard/guide/uk.md +306 -0
- package/dist/dashboard/guide/zh.md +306 -0
- package/dist/dashboard/guide-blocks.js +72 -0
- package/dist/dashboard/guide-markdown.js +63 -0
- package/dist/dashboard/help.js +92 -0
- package/dist/dashboard/i18n/ar.json +670 -0
- package/dist/dashboard/i18n/cs.json +670 -0
- package/dist/dashboard/i18n/da.json +670 -0
- package/dist/dashboard/i18n/de.json +670 -0
- package/dist/dashboard/i18n/el.json +670 -0
- package/dist/dashboard/i18n/es.json +670 -0
- package/dist/dashboard/i18n/fi.json +670 -0
- package/dist/dashboard/i18n/fr.json +670 -0
- package/dist/dashboard/i18n/he.json +670 -0
- package/dist/dashboard/i18n/hi.json +670 -0
- package/dist/dashboard/i18n/hu.json +670 -0
- package/dist/dashboard/i18n/is.json +670 -0
- package/dist/dashboard/i18n/it.json +670 -0
- package/dist/dashboard/i18n/ja.json +670 -0
- package/dist/dashboard/i18n/ko.json +670 -0
- package/dist/dashboard/i18n/nb.json +670 -0
- package/dist/dashboard/i18n/nl.json +670 -0
- package/dist/dashboard/i18n/pl.json +670 -0
- package/dist/dashboard/i18n/pt.json +670 -0
- package/dist/dashboard/i18n/ro.json +670 -0
- package/dist/dashboard/i18n/ru.json +670 -0
- package/dist/dashboard/i18n/sv.json +670 -0
- package/dist/dashboard/i18n/tr.json +670 -0
- package/dist/dashboard/i18n/uk.json +670 -0
- package/dist/dashboard/i18n/zh.json +670 -0
- package/dist/dashboard/icons.js +123 -0
- package/dist/dashboard/journal.js +188 -0
- package/dist/dashboard/labels.js +226 -0
- package/dist/dashboard/language-choice.js +83 -0
- package/dist/dashboard/languages.js +144 -0
- package/dist/dashboard/layout.js +93 -0
- package/dist/dashboard/map-sheet.js +32 -0
- package/dist/dashboard/map-symbols.js +79 -0
- package/dist/dashboard/markdown.js +72 -0
- package/dist/dashboard/master.js +69 -0
- package/dist/dashboard/mood-faces.js +16 -0
- package/dist/dashboard/mood.js +27 -0
- package/dist/dashboard/notes.js +203 -0
- package/dist/dashboard/notices.js +24 -0
- package/dist/dashboard/pages.js +32 -0
- package/dist/dashboard/party.js +65 -0
- package/dist/dashboard/place-map-view.js +362 -0
- package/dist/dashboard/place-map.js +204 -0
- package/dist/dashboard/prefs.js +97 -0
- package/dist/dashboard/reactions.js +28 -0
- package/dist/dashboard/requested.js +32 -0
- package/dist/dashboard/rolls.js +240 -0
- package/dist/dashboard/saves.js +154 -0
- package/dist/dashboard/scene.js +63 -0
- package/dist/dashboard/settings.js +362 -0
- package/dist/dashboard/setup.js +195 -0
- package/dist/dashboard/sheet.js +180 -0
- package/dist/dashboard/status.js +42 -0
- package/dist/dashboard/story.js +51 -0
- package/dist/dashboard/style.css +9355 -0
- package/dist/dashboard/text.js +158 -0
- package/dist/dashboard/theme.js +32 -0
- package/dist/dashboard/top.js +156 -0
- package/dist/dashboard/widgets.js +69 -0
- package/dist/dashboard/world-map.js +523 -0
- package/dist/dashboard/world.js +357 -0
- package/dist/dashboard-server.d.ts +38 -0
- package/dist/dashboard-server.js +818 -0
- package/dist/dice/evaluate.d.ts +30 -0
- package/dist/dice/evaluate.js +90 -0
- package/dist/dice/format.d.ts +3 -0
- package/dist/dice/format.js +26 -0
- package/dist/dice/index.d.ts +4 -0
- package/dist/dice/index.js +5 -0
- package/dist/dice/notation.d.ts +43 -0
- package/dist/dice/notation.js +132 -0
- package/dist/dice/proof.d.ts +4 -0
- package/dist/dice/proof.js +12 -0
- package/dist/dice/roll-dice.d.ts +29 -0
- package/dist/dice/roll-dice.js +43 -0
- package/dist/dice/words.d.ts +12 -0
- package/dist/dice/words.js +21 -0
- package/dist/effects/effect.d.ts +57 -0
- package/dist/effects/effect.js +123 -0
- package/dist/effects/index.d.ts +1 -0
- package/dist/effects/index.js +2 -0
- package/dist/grid/grid.d.ts +53 -0
- package/dist/grid/grid.js +151 -0
- package/dist/grid/index.d.ts +2 -0
- package/dist/grid/index.js +2 -0
- package/dist/grid/paths.d.ts +27 -0
- package/dist/grid/paths.js +128 -0
- package/dist/images/images.d.ts +26 -0
- package/dist/images/images.js +74 -0
- package/dist/images/index.d.ts +1 -0
- package/dist/images/index.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/instructions/index.d.ts +17 -0
- package/dist/instructions/index.js +48 -0
- package/dist/map/geometry.d.ts +22 -0
- package/dist/map/geometry.js +66 -0
- package/dist/map/index.d.ts +3 -0
- package/dist/map/index.js +5 -0
- package/dist/map/looks.d.ts +9 -0
- package/dist/map/looks.js +57 -0
- package/dist/map/ways.d.ts +36 -0
- package/dist/map/ways.js +78 -0
- package/dist/mcp-server.d.ts +22 -0
- package/dist/mcp-server.js +289 -0
- package/dist/money/index.d.ts +1 -0
- package/dist/money/index.js +2 -0
- package/dist/money/money.d.ts +48 -0
- package/dist/money/money.js +218 -0
- package/dist/page-words.d.ts +46 -0
- package/dist/page-words.js +221 -0
- package/dist/pages/check.d.ts +9 -0
- package/dist/pages/check.js +345 -0
- package/dist/pages/containers.d.ts +34 -0
- package/dist/pages/containers.js +181 -0
- package/dist/pages/environment.d.ts +26 -0
- package/dist/pages/environment.js +118 -0
- package/dist/pages/index.d.ts +15 -0
- package/dist/pages/index.js +16 -0
- package/dist/pages/inheritance.d.ts +80 -0
- package/dist/pages/inheritance.js +260 -0
- package/dist/pages/maps.d.ts +11 -0
- package/dist/pages/maps.js +14 -0
- package/dist/pages/markdown.d.ts +59 -0
- package/dist/pages/markdown.js +271 -0
- package/dist/pages/page-file.d.ts +26 -0
- package/dist/pages/page-file.js +166 -0
- package/dist/pages/page-types.d.ts +17 -0
- package/dist/pages/page-types.js +18 -0
- package/dist/pages/positions.d.ts +11 -0
- package/dist/pages/positions.js +23 -0
- package/dist/pages/rename-page.d.ts +39 -0
- package/dist/pages/rename-page.js +109 -0
- package/dist/pages/roads.d.ts +11 -0
- package/dist/pages/roads.js +40 -0
- package/dist/pages/standard-fields.d.ts +31 -0
- package/dist/pages/standard-fields.js +142 -0
- package/dist/pages/system-pages.d.ts +35 -0
- package/dist/pages/system-pages.js +98 -0
- package/dist/pages/world.d.ts +174 -0
- package/dist/pages/world.js +349 -0
- package/dist/pages/write-page.d.ts +27 -0
- package/dist/pages/write-page.js +311 -0
- package/dist/pictures/briefs.d.ts +25 -0
- package/dist/pictures/briefs.js +145 -0
- package/dist/pictures/chosen.d.ts +5 -0
- package/dist/pictures/chosen.js +21 -0
- package/dist/pictures/claude.d.ts +14 -0
- package/dist/pictures/claude.js +129 -0
- package/dist/pictures/codex.d.ts +20 -0
- package/dist/pictures/codex.js +154 -0
- package/dist/pictures/helpers.d.ts +26 -0
- package/dist/pictures/helpers.js +219 -0
- package/dist/pictures/index.d.ts +6 -0
- package/dist/pictures/index.js +8 -0
- package/dist/pictures/stop.d.ts +3 -0
- package/dist/pictures/stop.js +10 -0
- package/dist/pictures/svg.d.ts +4 -0
- package/dist/pictures/svg.js +129 -0
- package/dist/player-notes/index.d.ts +1 -0
- package/dist/player-notes/index.js +2 -0
- package/dist/player-notes/notes.d.ts +21 -0
- package/dist/player-notes/notes.js +50 -0
- package/dist/progression/index.d.ts +1 -0
- package/dist/progression/index.js +1 -0
- package/dist/progression/level-table.d.ts +41 -0
- package/dist/progression/level-table.js +123 -0
- package/dist/rules/index.d.ts +3 -0
- package/dist/rules/index.js +4 -0
- package/dist/rules/rule-book.d.ts +31 -0
- package/dist/rules/rule-book.js +157 -0
- package/dist/rules/search.d.ts +18 -0
- package/dist/rules/search.js +101 -0
- package/dist/rules/shown.d.ts +19 -0
- package/dist/rules/shown.js +44 -0
- package/dist/saves/index.d.ts +1 -0
- package/dist/saves/index.js +2 -0
- package/dist/saves/saves.d.ts +14 -0
- package/dist/saves/saves.js +27 -0
- package/dist/scene/index.d.ts +1 -0
- package/dist/scene/index.js +2 -0
- package/dist/scene/scene.d.ts +46 -0
- package/dist/scene/scene.js +44 -0
- package/dist/systems/game-system.d.ts +236 -0
- package/dist/systems/game-system.js +593 -0
- package/dist/systems/index.d.ts +2 -0
- package/dist/systems/index.js +3 -0
- package/dist/systems/words.d.ts +9 -0
- package/dist/systems/words.js +23 -0
- package/dist/time/game-date.d.ts +20 -0
- package/dist/time/game-date.js +59 -0
- package/dist/time/index.d.ts +1 -0
- package/dist/time/index.js +2 -0
- package/dist/triggers/index.d.ts +1 -0
- package/dist/triggers/index.js +2 -0
- package/dist/triggers/trigger.d.ts +43 -0
- package/dist/triggers/trigger.js +98 -0
- package/instructions/author.md +1 -0
- package/instructions/buttons-physical-dice.md +1 -0
- package/instructions/buttons.md +1 -0
- package/instructions/combat.md +3 -0
- package/instructions/core.md +54 -0
- package/instructions/dashboard.md +7 -0
- package/instructions/mentor-hints.md +1 -0
- package/instructions/mentor-tutorial.md +1 -0
- package/instructions/new-day.md +1 -0
- package/instructions/play.md +23 -0
- package/instructions/prose-literary.md +1 -0
- package/instructions/prose-pulp.md +1 -0
- package/instructions/prose-wry.md +1 -0
- package/instructions/session-zero.md +3 -0
- package/instructions/setup.md +3 -0
- package/instructions/start.md +1 -0
- package/instructions/story-arcs.md +1 -0
- package/instructions/story-episodes.md +1 -0
- package/instructions/story-mystery.md +1 -0
- package/instructions/style.md +1 -0
- package/package.json +85 -4
- package/systems/5e/LICENSE.md +25 -0
- package/systems/5e/images/acid-blob.svg +1 -0
- package/systems/5e/images/angel-outfit.svg +1 -0
- package/systems/5e/images/angler-fish.svg +1 -0
- package/systems/5e/images/angular-spider.svg +1 -0
- package/systems/5e/images/animal-skull.svg +1 -0
- package/systems/5e/images/bad-gnome.svg +1 -0
- package/systems/5e/images/badger.svg +1 -0
- package/systems/5e/images/bandit.svg +1 -0
- package/systems/5e/images/barn-owl.svg +1 -0
- package/systems/5e/images/basset-hound-head.svg +1 -0
- package/systems/5e/images/bat.svg +1 -0
- package/systems/5e/images/bear-face.svg +1 -0
- package/systems/5e/images/bear-head.svg +1 -0
- package/systems/5e/images/beetle-shell.svg +1 -0
- package/systems/5e/images/berry-bush.svg +1 -0
- package/systems/5e/images/bestial-fangs.svg +1 -0
- package/systems/5e/images/big-wave.svg +1 -0
- package/systems/5e/images/black-knight-helm.svg +1 -0
- package/systems/5e/images/boar-tusks.svg +1 -0
- package/systems/5e/images/boar.svg +1 -0
- package/systems/5e/images/bone-gnawer.svg +1 -0
- package/systems/5e/images/breastplate.svg +1 -0
- package/systems/5e/images/brute.svg +1 -0
- package/systems/5e/images/camel-head.svg +1 -0
- package/systems/5e/images/carnivore-mouth.svg +1 -0
- package/systems/5e/images/cassowary-head.svg +1 -0
- package/systems/5e/images/cat.svg +1 -0
- package/systems/5e/images/centaur.svg +1 -0
- package/systems/5e/images/centipede.svg +1 -0
- package/systems/5e/images/charging-bull.svg +1 -0
- package/systems/5e/images/closed-barbute.svg +1 -0
- package/systems/5e/images/cobra.svg +1 -0
- package/systems/5e/images/condor-emblem.svg +1 -0
- package/systems/5e/images/cowled.svg +1 -0
- package/systems/5e/images/crab-claw.svg +1 -0
- package/systems/5e/images/crab.svg +1 -0
- package/systems/5e/images/croc-jaws.svg +1 -0
- package/systems/5e/images/crow-dive.svg +1 -0
- package/systems/5e/images/crown.svg +1 -0
- package/systems/5e/images/crowned-skull.svg +1 -0
- package/systems/5e/images/cube.svg +1 -0
- package/systems/5e/images/cultist-lorc.svg +1 -0
- package/systems/5e/images/cultist-skoll.svg +1 -0
- package/systems/5e/images/curled-tentacle.svg +1 -0
- package/systems/5e/images/daemon-skull.svg +1 -0
- package/systems/5e/images/deer-head.svg +1 -0
- package/systems/5e/images/desert-skull.svg +1 -0
- package/systems/5e/images/devil-mask.svg +1 -0
- package/systems/5e/images/diablo-skull.svg +1 -0
- package/systems/5e/images/dinosaur-rex.svg +1 -0
- package/systems/5e/images/direwolf.svg +1 -0
- package/systems/5e/images/djinn.svg +1 -0
- package/systems/5e/images/donkey.svg +1 -0
- package/systems/5e/images/dragon-head-faithtoken.svg +1 -0
- package/systems/5e/images/dragon-head-lorc.svg +1 -0
- package/systems/5e/images/dragon-shield.svg +1 -0
- package/systems/5e/images/dragon-spiral.svg +1 -0
- package/systems/5e/images/dread-skull.svg +1 -0
- package/systems/5e/images/duality-mask.svg +1 -0
- package/systems/5e/images/dust-cloud.svg +1 -0
- package/systems/5e/images/dwarf-face.svg +1 -0
- package/systems/5e/images/dwarf-helmet.svg +1 -0
- package/systems/5e/images/eagle-emblem.svg +1 -0
- package/systems/5e/images/eagle-head.svg +1 -0
- package/systems/5e/images/egyptian-sphinx.svg +1 -0
- package/systems/5e/images/elephant-head.svg +1 -0
- package/systems/5e/images/elf-ear.svg +1 -0
- package/systems/5e/images/elf-helmet.svg +1 -0
- package/systems/5e/images/ent-mouth.svg +1 -0
- package/systems/5e/images/ermine.svg +1 -0
- package/systems/5e/images/eruption.svg +1 -0
- package/systems/5e/images/evil-bat.svg +1 -0
- package/systems/5e/images/evil-minion.svg +1 -0
- package/systems/5e/images/evil-tree.svg +1 -0
- package/systems/5e/images/evil-wings.svg +1 -0
- package/systems/5e/images/eyestalk.svg +1 -0
- package/systems/5e/images/fairy.svg +1 -0
- package/systems/5e/images/farmer.svg +1 -0
- package/systems/5e/images/feline.svg +1 -0
- package/systems/5e/images/fire-silhouette.svg +1 -0
- package/systems/5e/images/fish-monster.svg +1 -0
- package/systems/5e/images/fleshy-mass.svg +1 -0
- package/systems/5e/images/fluffy-flame.svg +1 -0
- package/systems/5e/images/fly.svg +1 -0
- package/systems/5e/images/fox-head.svg +1 -0
- package/systems/5e/images/frankenstein-creature.svg +1 -0
- package/systems/5e/images/frog.svg +1 -0
- package/systems/5e/images/gargoyle.svg +1 -0
- package/systems/5e/images/gecko.svg +1 -0
- package/systems/5e/images/ghost.svg +1 -0
- package/systems/5e/images/giant.svg +1 -0
- package/systems/5e/images/goat.svg +1 -0
- package/systems/5e/images/goblin-head.svg +1 -0
- package/systems/5e/images/goblin.svg +1 -0
- package/systems/5e/images/golem-head.svg +1 -0
- package/systems/5e/images/gooey-daemon.svg +1 -0
- package/systems/5e/images/gorilla.svg +1 -0
- package/systems/5e/images/greek-sphinx.svg +1 -0
- package/systems/5e/images/griffin-symbol.svg +1 -0
- package/systems/5e/images/grim-reaper.svg +1 -0
- package/systems/5e/images/hanging-spider.svg +1 -0
- package/systems/5e/images/harpy.svg +1 -0
- package/systems/5e/images/hawk-emblem.svg +1 -0
- package/systems/5e/images/hooded-assassin.svg +1 -0
- package/systems/5e/images/horned-reptile.svg +1 -0
- package/systems/5e/images/horse-head-delapouite.svg +1 -0
- package/systems/5e/images/horse-head-lorc.svg +1 -0
- package/systems/5e/images/hound.svg +1 -0
- package/systems/5e/images/hydra.svg +1 -0
- package/systems/5e/images/hyena-head.svg +1 -0
- package/systems/5e/images/icicles-aura.svg +1 -0
- package/systems/5e/images/ifrit.svg +1 -0
- package/systems/5e/images/imp-laugh.svg +1 -0
- package/systems/5e/images/imp.svg +1 -0
- package/systems/5e/images/insect-jaws.svg +1 -0
- package/systems/5e/images/invisible-face.svg +1 -0
- package/systems/5e/images/jumping-dog.svg +1 -0
- package/systems/5e/images/kraken-tentacle.svg +1 -0
- package/systems/5e/images/labrador-head.svg +1 -0
- package/systems/5e/images/lion.svg +1 -0
- package/systems/5e/images/lizard-tongue.svg +1 -0
- package/systems/5e/images/lizardman.svg +1 -0
- package/systems/5e/images/long-antennae-bug.svg +1 -0
- package/systems/5e/images/long-legged-spider.svg +1 -0
- package/systems/5e/images/mammoth.svg +1 -0
- package/systems/5e/images/mandrill-head.svg +1 -0
- package/systems/5e/images/manta-ray.svg +1 -0
- package/systems/5e/images/masked-spider.svg +1 -0
- package/systems/5e/images/medusa-head.svg +1 -0
- package/systems/5e/images/mermaid.svg +1 -0
- package/systems/5e/images/metal-golem-head.svg +1 -0
- package/systems/5e/images/mimic-chest.svg +1 -0
- package/systems/5e/images/minotaur.svg +1 -0
- package/systems/5e/images/monk-face.svg +1 -0
- package/systems/5e/images/moon-bats.svg +1 -0
- package/systems/5e/images/mucous-pillar.svg +1 -0
- package/systems/5e/images/mummy-head.svg +1 -0
- package/systems/5e/images/mushrooms-cluster.svg +1 -0
- package/systems/5e/images/octopus.svg +1 -0
- package/systems/5e/images/ogre.svg +1 -0
- package/systems/5e/images/oni.svg +1 -0
- package/systems/5e/images/orc-head.svg +1 -0
- package/systems/5e/images/owl.svg +1 -0
- package/systems/5e/images/pan-flute.svg +1 -0
- package/systems/5e/images/pegasus.svg +1 -0
- package/systems/5e/images/person.svg +1 -0
- package/systems/5e/images/piranha.svg +1 -0
- package/systems/5e/images/plesiosaurus.svg +1 -0
- package/systems/5e/images/pointy-hat.svg +1 -0
- package/systems/5e/images/polar-bear.svg +1 -0
- package/systems/5e/images/rat.svg +1 -0
- package/systems/5e/images/rattlesnake.svg +1 -0
- package/systems/5e/images/raven.svg +1 -0
- package/systems/5e/images/rhinoceros-horn.svg +1 -0
- package/systems/5e/images/robber.svg +1 -0
- package/systems/5e/images/robin-hood-hat.svg +1 -0
- package/systems/5e/images/robot-golem.svg +1 -0
- package/systems/5e/images/rock-golem.svg +1 -0
- package/systems/5e/images/rolled-cloth.svg +1 -0
- package/systems/5e/images/rooster.svg +1 -0
- package/systems/5e/images/saber-toothed-cat-head.svg +1 -0
- package/systems/5e/images/salamander.svg +1 -0
- package/systems/5e/images/school-of-fish.svg +1 -0
- package/systems/5e/images/scorpion.svg +1 -0
- package/systems/5e/images/sea-serpent.svg +1 -0
- package/systems/5e/images/seahorse.svg +1 -0
- package/systems/5e/images/seated-mouse.svg +1 -0
- package/systems/5e/images/sensuousness.svg +1 -0
- package/systems/5e/images/shadow-follower.svg +1 -0
- package/systems/5e/images/shambling-mound.svg +1 -0
- package/systems/5e/images/shambling-zombie.svg +1 -0
- package/systems/5e/images/shark-fin.svg +1 -0
- package/systems/5e/images/shark-jaws.svg +1 -0
- package/systems/5e/images/skeleton.svg +1 -0
- package/systems/5e/images/slime.svg +1 -0
- package/systems/5e/images/small-fire.svg +1 -0
- package/systems/5e/images/snake-spiral.svg +1 -0
- package/systems/5e/images/snake-tongue.svg +1 -0
- package/systems/5e/images/snake-totem.svg +1 -0
- package/systems/5e/images/snake.svg +1 -0
- package/systems/5e/images/spark-spirit.svg +1 -0
- package/systems/5e/images/spartan.svg +1 -0
- package/systems/5e/images/spectre.svg +1 -0
- package/systems/5e/images/sperm-whale.svg +1 -0
- package/systems/5e/images/spider-alt.svg +1 -0
- package/systems/5e/images/spiked-dragon-head.svg +1 -0
- package/systems/5e/images/spiked-tail.svg +1 -0
- package/systems/5e/images/spotted-mushroom.svg +1 -0
- package/systems/5e/images/spy.svg +1 -0
- package/systems/5e/images/squid.svg +1 -0
- package/systems/5e/images/stag-head.svg +1 -0
- package/systems/5e/images/steam.svg +1 -0
- package/systems/5e/images/sun-priest.svg +1 -0
- package/systems/5e/images/tick.svg +1 -0
- package/systems/5e/images/tiger-head.svg +1 -0
- package/systems/5e/images/tiger.svg +1 -0
- package/systems/5e/images/toad-teeth.svg +1 -0
- package/systems/5e/images/tornado.svg +1 -0
- package/systems/5e/images/transparent-slime.svg +1 -0
- package/systems/5e/images/tree-face.svg +1 -0
- package/systems/5e/images/tribal-mask.svg +1 -0
- package/systems/5e/images/triceratops-head.svg +1 -0
- package/systems/5e/images/triton-head.svg +1 -0
- package/systems/5e/images/troglodyte.svg +1 -0
- package/systems/5e/images/troll.svg +1 -0
- package/systems/5e/images/turtle.svg +1 -0
- package/systems/5e/images/unicorn.svg +1 -0
- package/systems/5e/images/vampire-dracula.svg +1 -0
- package/systems/5e/images/viking-head.svg +1 -0
- package/systems/5e/images/visored-helm.svg +1 -0
- package/systems/5e/images/vulture.svg +1 -0
- package/systems/5e/images/warlord-helmet.svg +1 -0
- package/systems/5e/images/wasp-sting.svg +1 -0
- package/systems/5e/images/werewolf.svg +1 -0
- package/systems/5e/images/winged-sword.svg +1 -0
- package/systems/5e/images/witch-face.svg +1 -0
- package/systems/5e/images/wizard-face.svg +1 -0
- package/systems/5e/images/wolf-head.svg +1 -0
- package/systems/5e/images/wolf-howl.svg +1 -0
- package/systems/5e/images/woman-elf-face.svg +1 -0
- package/systems/5e/images/worm-mouth.svg +1 -0
- package/systems/5e/images/wyvern.svg +1 -0
- package/systems/5e/instructions/level-up.md +1 -0
- package/systems/5e/instructions/mentor-tutorial.md +1 -0
- package/systems/5e/instructions/play.md +5 -0
- package/systems/5e/instructions/quest-ends.md +1 -0
- package/systems/5e/instructions/session-zero.md +1 -0
- package/systems/5e/rules/Adventuring.md +228 -0
- package/systems/5e/rules/Beyond 1st Level.md +241 -0
- package/systems/5e/rules/Combat.md +504 -0
- package/systems/5e/rules/Conditions.md +110 -0
- package/systems/5e/rules/Diseases.md +41 -0
- package/systems/5e/rules/Equipment.md +731 -0
- package/systems/5e/rules/Fantasy-Historical Pantheons.md +112 -0
- package/systems/5e/rules/Madness.md +72 -0
- package/systems/5e/rules/Magic Items.md +142 -0
- package/systems/5e/rules/Monsters.md +403 -0
- package/systems/5e/rules/Nonplayer Characters.md +15 -0
- package/systems/5e/rules/Objects.md +40 -0
- package/systems/5e/rules/Poisons.md +64 -0
- package/systems/5e/rules/Races.md +33 -0
- package/systems/5e/rules/Spellcasting.md +225 -0
- package/systems/5e/rules/The Planes of Existence.md +59 -0
- package/systems/5e/rules/Traps.md +170 -0
- package/systems/5e/rules/Using Ability Scores.md +394 -0
- package/systems/5e/system.yaml +602 -0
- package/systems/5e/words/ar.yaml +421 -0
- package/systems/5e/words/cs.yaml +421 -0
- package/systems/5e/words/da.yaml +421 -0
- package/systems/5e/words/de.yaml +421 -0
- package/systems/5e/words/el.yaml +421 -0
- package/systems/5e/words/es.yaml +421 -0
- package/systems/5e/words/fi.yaml +421 -0
- package/systems/5e/words/fr.yaml +421 -0
- package/systems/5e/words/he.yaml +421 -0
- package/systems/5e/words/hi.yaml +421 -0
- package/systems/5e/words/hu.yaml +421 -0
- package/systems/5e/words/is.yaml +421 -0
- package/systems/5e/words/it.yaml +453 -0
- package/systems/5e/words/ja.yaml +421 -0
- package/systems/5e/words/ko.yaml +421 -0
- package/systems/5e/words/nb.yaml +421 -0
- package/systems/5e/words/nl.yaml +421 -0
- package/systems/5e/words/pl.yaml +421 -0
- package/systems/5e/words/pt.yaml +421 -0
- package/systems/5e/words/ro.yaml +421 -0
- package/systems/5e/words/ru.yaml +421 -0
- package/systems/5e/words/sv.yaml +421 -0
- package/systems/5e/words/tr.yaml +421 -0
- package/systems/5e/words/uk.yaml +421 -0
- package/systems/5e/words/zh.yaml +421 -0
- package/systems/5e/world/creatures/Aboleth.md +65 -0
- package/systems/5e/world/creatures/Acolyte.md +37 -0
- package/systems/5e/world/creatures/Adult Black Dragon.md +69 -0
- package/systems/5e/world/creatures/Adult Blue Dragon.md +66 -0
- package/systems/5e/world/creatures/Adult Brass Dragon.md +70 -0
- package/systems/5e/world/creatures/Adult Bronze Dragon.md +78 -0
- package/systems/5e/world/creatures/Adult Copper Dragon.md +70 -0
- package/systems/5e/world/creatures/Adult Gold Dragon.md +78 -0
- package/systems/5e/world/creatures/Adult Green Dragon.md +70 -0
- package/systems/5e/world/creatures/Adult Red Dragon.md +66 -0
- package/systems/5e/world/creatures/Adult Silver Dragon.md +74 -0
- package/systems/5e/world/creatures/Adult White Dragon.md +70 -0
- package/systems/5e/world/creatures/Air Elemental.md +43 -0
- package/systems/5e/world/creatures/Ancient Black Dragon.md +69 -0
- package/systems/5e/world/creatures/Ancient Blue Dragon.md +66 -0
- package/systems/5e/world/creatures/Ancient Brass Dragon.md +75 -0
- package/systems/5e/world/creatures/Ancient Bronze Dragon.md +78 -0
- package/systems/5e/world/creatures/Ancient Copper Dragon.md +75 -0
- package/systems/5e/world/creatures/Ancient Gold Dragon.md +78 -0
- package/systems/5e/world/creatures/Ancient Green Dragon.md +70 -0
- package/systems/5e/world/creatures/Ancient Red Dragon.md +66 -0
- package/systems/5e/world/creatures/Ancient Silver Dragon.md +74 -0
- package/systems/5e/world/creatures/Ancient White Dragon.md +70 -0
- package/systems/5e/world/creatures/Androsphinx.md +82 -0
- package/systems/5e/world/creatures/Animated Armor.md +39 -0
- package/systems/5e/world/creatures/Ankheg.md +31 -0
- package/systems/5e/world/creatures/Ape.md +32 -0
- package/systems/5e/world/creatures/Archmage.md +63 -0
- package/systems/5e/world/creatures/Assassin.md +48 -0
- package/systems/5e/world/creatures/Awakened Shrub.md +34 -0
- package/systems/5e/world/creatures/Awakened Tree.md +35 -0
- package/systems/5e/world/creatures/Axe Beak.md +26 -0
- package/systems/5e/world/creatures/Azer.md +40 -0
- package/systems/5e/world/creatures/Baboon.md +30 -0
- package/systems/5e/world/creatures/Badger.md +31 -0
- package/systems/5e/world/creatures/Balor.md +55 -0
- package/systems/5e/world/creatures/Bandit Captain.md +45 -0
- package/systems/5e/world/creatures/Bandit.md +33 -0
- package/systems/5e/world/creatures/Barbed Devil.md +52 -0
- package/systems/5e/world/creatures/Basilisk.md +35 -0
- package/systems/5e/world/creatures/Bat.md +34 -0
- package/systems/5e/world/creatures/Bearded Devil.md +48 -0
- package/systems/5e/world/creatures/Behir.md +44 -0
- package/systems/5e/world/creatures/Berserker.md +33 -0
- package/systems/5e/world/creatures/Black Bear.md +38 -0
- package/systems/5e/world/creatures/Black Dragon Wyrmling.md +41 -0
- package/systems/5e/world/creatures/Black Pudding.md +46 -0
- package/systems/5e/world/creatures/Blink Dog.md +36 -0
- package/systems/5e/world/creatures/Blood Hawk.md +36 -0
- package/systems/5e/world/creatures/Blue Dragon Wyrmling.md +36 -0
- package/systems/5e/world/creatures/Boar.md +33 -0
- package/systems/5e/world/creatures/Bone Devil.md +47 -0
- package/systems/5e/world/creatures/Brass Dragon Wyrmling.md +40 -0
- package/systems/5e/world/creatures/Bronze Dragon Wyrmling.md +45 -0
- package/systems/5e/world/creatures/Brown Bear.md +38 -0
- package/systems/5e/world/creatures/Bugbear.md +39 -0
- package/systems/5e/world/creatures/Bulette.md +37 -0
- package/systems/5e/world/creatures/Camel.md +24 -0
- package/systems/5e/world/creatures/Cat.md +31 -0
- package/systems/5e/world/creatures/Centaur.md +40 -0
- package/systems/5e/world/creatures/Chain Devil.md +52 -0
- package/systems/5e/world/creatures/Chimera.md +41 -0
- package/systems/5e/world/creatures/Chuul.md +45 -0
- package/systems/5e/world/creatures/Clay Golem.md +52 -0
- package/systems/5e/world/creatures/Cloaker.md +56 -0
- package/systems/5e/world/creatures/Cloud Giant.md +46 -0
- package/systems/5e/world/creatures/Cockatrice.md +26 -0
- package/systems/5e/world/creatures/Commoner.md +27 -0
- package/systems/5e/world/creatures/Constrictor Snake.md +29 -0
- package/systems/5e/world/creatures/Copper Dragon Wyrmling.md +40 -0
- package/systems/5e/world/creatures/Couatl.md +54 -0
- package/systems/5e/world/creatures/Crab.md +33 -0
- package/systems/5e/world/creatures/Crocodile.md +32 -0
- package/systems/5e/world/creatures/Cult Fanatic.md +46 -0
- package/systems/5e/world/creatures/Cultist.md +34 -0
- package/systems/5e/world/creatures/Darkmantle.md +42 -0
- package/systems/5e/world/creatures/Death Dog.md +36 -0
- package/systems/5e/world/creatures/Deep Gnome (Svirfneblin).md +46 -0
- package/systems/5e/world/creatures/Deer.md +24 -0
- package/systems/5e/world/creatures/Deva.md +57 -0
- package/systems/5e/world/creatures/Dire Wolf.md +34 -0
- package/systems/5e/world/creatures/Djinni.md +50 -0
- package/systems/5e/world/creatures/Doppelganger.md +45 -0
- package/systems/5e/world/creatures/Draft Horse.md +24 -0
- package/systems/5e/world/creatures/Dragon Turtle.md +47 -0
- package/systems/5e/world/creatures/Dragonborn.md +58 -0
- package/systems/5e/world/creatures/Dretch.md +39 -0
- package/systems/5e/world/creatures/Drider.md +58 -0
- package/systems/5e/world/creatures/Drow Elf.md +46 -0
- package/systems/5e/world/creatures/Druid (creature).md +41 -0
- package/systems/5e/world/creatures/Dryad.md +53 -0
- package/systems/5e/world/creatures/Duergar.md +45 -0
- package/systems/5e/world/creatures/Dust Mephit.md +42 -0
- package/systems/5e/world/creatures/Dwarf.md +48 -0
- package/systems/5e/world/creatures/Eagle.md +31 -0
- package/systems/5e/world/creatures/Earth Elemental.md +44 -0
- package/systems/5e/world/creatures/Efreeti.md +48 -0
- package/systems/5e/world/creatures/Elephant.md +33 -0
- package/systems/5e/world/creatures/Elf.md +44 -0
- package/systems/5e/world/creatures/Elk.md +32 -0
- package/systems/5e/world/creatures/Erinyes.md +51 -0
- package/systems/5e/world/creatures/Ettercap.md +48 -0
- package/systems/5e/world/creatures/Ettin.md +42 -0
- package/systems/5e/world/creatures/Fire Elemental.md +43 -0
- package/systems/5e/world/creatures/Fire Giant.md +35 -0
- package/systems/5e/world/creatures/Flesh Golem.md +53 -0
- package/systems/5e/world/creatures/Flying Snake.md +34 -0
- package/systems/5e/world/creatures/Flying Sword.md +38 -0
- package/systems/5e/world/creatures/Frog.md +32 -0
- package/systems/5e/world/creatures/Frost Giant.md +35 -0
- package/systems/5e/world/creatures/Gargoyle.md +42 -0
- package/systems/5e/world/creatures/Gelatinous Cube.md +49 -0
- package/systems/5e/world/creatures/Ghast.md +40 -0
- package/systems/5e/world/creatures/Ghost.md +49 -0
- package/systems/5e/world/creatures/Ghoul.md +31 -0
- package/systems/5e/world/creatures/Giant Ape.md +32 -0
- package/systems/5e/world/creatures/Giant Badger.md +37 -0
- package/systems/5e/world/creatures/Giant Bat.md +34 -0
- package/systems/5e/world/creatures/Giant Boar.md +33 -0
- package/systems/5e/world/creatures/Giant Centipede.md +27 -0
- package/systems/5e/world/creatures/Giant Constrictor Snake.md +30 -0
- package/systems/5e/world/creatures/Giant Crab.md +33 -0
- package/systems/5e/world/creatures/Giant Crocodile.md +38 -0
- package/systems/5e/world/creatures/Giant Eagle.md +40 -0
- package/systems/5e/world/creatures/Giant Elk.md +37 -0
- package/systems/5e/world/creatures/Giant Fire Beetle.md +33 -0
- package/systems/5e/world/creatures/Giant Frog.md +40 -0
- package/systems/5e/world/creatures/Giant Goat.md +33 -0
- package/systems/5e/world/creatures/Giant Hyena.md +30 -0
- package/systems/5e/world/creatures/Giant Lizard.md +29 -0
- package/systems/5e/world/creatures/Giant Octopus.md +41 -0
- package/systems/5e/world/creatures/Giant Owl.md +38 -0
- package/systems/5e/world/creatures/Giant Poisonous Snake.md +27 -0
- package/systems/5e/world/creatures/Giant Rat.md +39 -0
- package/systems/5e/world/creatures/Giant Scorpion.md +32 -0
- package/systems/5e/world/creatures/Giant Sea Horse.md +36 -0
- package/systems/5e/world/creatures/Giant Shark.md +38 -0
- package/systems/5e/world/creatures/Giant Spider.md +45 -0
- package/systems/5e/world/creatures/Giant Toad.md +39 -0
- package/systems/5e/world/creatures/Giant Vulture.md +43 -0
- package/systems/5e/world/creatures/Giant Wasp.md +25 -0
- package/systems/5e/world/creatures/Giant Weasel.md +31 -0
- package/systems/5e/world/creatures/Giant Wolf Spider.md +41 -0
- package/systems/5e/world/creatures/Gibbering Mouther.md +41 -0
- package/systems/5e/world/creatures/Glabrezu.md +48 -0
- package/systems/5e/world/creatures/Gladiator.md +49 -0
- package/systems/5e/world/creatures/Gnoll.md +38 -0
- package/systems/5e/world/creatures/Gnome.md +37 -0
- package/systems/5e/world/creatures/Goat.md +32 -0
- package/systems/5e/world/creatures/Goblin.md +36 -0
- package/systems/5e/world/creatures/Gold Dragon Wyrmling.md +45 -0
- package/systems/5e/world/creatures/Gorgon.md +39 -0
- package/systems/5e/world/creatures/Gray Ooze.md +42 -0
- package/systems/5e/world/creatures/Green Dragon Wyrmling.md +42 -0
- package/systems/5e/world/creatures/Green Hag.md +49 -0
- package/systems/5e/world/creatures/Grick.md +39 -0
- package/systems/5e/world/creatures/Griffon.md +38 -0
- package/systems/5e/world/creatures/Grimlock.md +39 -0
- package/systems/5e/world/creatures/Guard.md +29 -0
- package/systems/5e/world/creatures/Guardian Naga.md +55 -0
- package/systems/5e/world/creatures/Gynosphinx.md +71 -0
- package/systems/5e/world/creatures/Half-Elf.md +40 -0
- package/systems/5e/world/creatures/Half-Orc.md +44 -0
- package/systems/5e/world/creatures/Half-Red Dragon Veteran.md +42 -0
- package/systems/5e/world/creatures/Halfling.md +39 -0
- package/systems/5e/world/creatures/Harpy.md +39 -0
- package/systems/5e/world/creatures/Hawk.md +31 -0
- package/systems/5e/world/creatures/Hell Hound.md +40 -0
- package/systems/5e/world/creatures/Hezrou.md +45 -0
- package/systems/5e/world/creatures/High Elf.md +22 -0
- package/systems/5e/world/creatures/Hill Dwarf.md +16 -0
- package/systems/5e/world/creatures/Hill Giant.md +33 -0
- package/systems/5e/world/creatures/Hippogriff.md +37 -0
- package/systems/5e/world/creatures/Hobgoblin.md +35 -0
- package/systems/5e/world/creatures/Homunculus.md +35 -0
- package/systems/5e/world/creatures/Horned Devil.md +49 -0
- package/systems/5e/world/creatures/Human.md +29 -0
- package/systems/5e/world/creatures/Hunter Shark.md +38 -0
- package/systems/5e/world/creatures/Hydra.md +49 -0
- package/systems/5e/world/creatures/Hyena.md +30 -0
- package/systems/5e/world/creatures/Ice Devil.md +56 -0
- package/systems/5e/world/creatures/Ice Mephit.md +45 -0
- package/systems/5e/world/creatures/Imp.md +45 -0
- package/systems/5e/world/creatures/Invisible Stalker.md +42 -0
- package/systems/5e/world/creatures/Iron Golem.md +52 -0
- package/systems/5e/world/creatures/Jackal.md +33 -0
- package/systems/5e/world/creatures/Killer Whale.md +39 -0
- package/systems/5e/world/creatures/Knight.md +48 -0
- package/systems/5e/world/creatures/Kobold.md +37 -0
- package/systems/5e/world/creatures/Kraken.md +69 -0
- package/systems/5e/world/creatures/Lamia.md +46 -0
- package/systems/5e/world/creatures/Lemure.md +37 -0
- package/systems/5e/world/creatures/Lich.md +82 -0
- package/systems/5e/world/creatures/Lightfoot Halfling.md +18 -0
- package/systems/5e/world/creatures/Lion.md +42 -0
- package/systems/5e/world/creatures/Lizard.md +26 -0
- package/systems/5e/world/creatures/Lizardfolk.md +45 -0
- package/systems/5e/world/creatures/Mage.md +47 -0
- package/systems/5e/world/creatures/Magma Mephit.md +45 -0
- package/systems/5e/world/creatures/Magmin.md +37 -0
- package/systems/5e/world/creatures/Mammoth.md +35 -0
- package/systems/5e/world/creatures/Manticore.md +42 -0
- package/systems/5e/world/creatures/Marilith.md +56 -0
- package/systems/5e/world/creatures/Mastiff.md +32 -0
- package/systems/5e/world/creatures/Medusa.md +46 -0
- package/systems/5e/world/creatures/Merfolk.md +32 -0
- package/systems/5e/world/creatures/Merrow.md +42 -0
- package/systems/5e/world/creatures/Mimic.md +46 -0
- package/systems/5e/world/creatures/Minotaur Skeleton.md +38 -0
- package/systems/5e/world/creatures/Minotaur.md +42 -0
- package/systems/5e/world/creatures/Mule.md +32 -0
- package/systems/5e/world/creatures/Mummy Lord.md +82 -0
- package/systems/5e/world/creatures/Mummy.md +38 -0
- package/systems/5e/world/creatures/Nalfeshnee.md +49 -0
- package/systems/5e/world/creatures/Night Hag.md +50 -0
- package/systems/5e/world/creatures/Nightmare.md +39 -0
- package/systems/5e/world/creatures/Noble.md +36 -0
- package/systems/5e/world/creatures/Ochre Jelly.md +42 -0
- package/systems/5e/world/creatures/Octopus.md +41 -0
- package/systems/5e/world/creatures/Ogre Zombie.md +34 -0
- package/systems/5e/world/creatures/Ogre.md +30 -0
- package/systems/5e/world/creatures/Oni.md +53 -0
- package/systems/5e/world/creatures/Orc.md +36 -0
- package/systems/5e/world/creatures/Otyugh.md +42 -0
- package/systems/5e/world/creatures/Owl.md +35 -0
- package/systems/5e/world/creatures/Owlbear.md +38 -0
- package/systems/5e/world/creatures/Panther.md +37 -0
- package/systems/5e/world/creatures/Pegasus.md +28 -0
- package/systems/5e/world/creatures/Phase Spider.md +41 -0
- package/systems/5e/world/creatures/Pit Fiend.md +61 -0
- package/systems/5e/world/creatures/Planetar.md +56 -0
- package/systems/5e/world/creatures/Plesiosaurus.md +32 -0
- package/systems/5e/world/creatures/Poisonous Snake.md +26 -0
- package/systems/5e/world/creatures/Polar Bear.md +38 -0
- package/systems/5e/world/creatures/Pony.md +24 -0
- package/systems/5e/world/creatures/Priest.md +47 -0
- package/systems/5e/world/creatures/Pseudodragon.md +44 -0
- package/systems/5e/world/creatures/Purple Worm.md +42 -0
- package/systems/5e/world/creatures/Quasit.md +44 -0
- package/systems/5e/world/creatures/Quipper.md +36 -0
- package/systems/5e/world/creatures/Rakshasa.md +45 -0
- package/systems/5e/world/creatures/Rat.md +30 -0
- package/systems/5e/world/creatures/Raven.md +31 -0
- package/systems/5e/world/creatures/Red Dragon Wyrmling.md +36 -0
- package/systems/5e/world/creatures/Reef Shark.md +38 -0
- package/systems/5e/world/creatures/Remorhaz.md +39 -0
- package/systems/5e/world/creatures/Rhinoceros.md +30 -0
- package/systems/5e/world/creatures/Riding Horse.md +24 -0
- package/systems/5e/world/creatures/Roc.md +39 -0
- package/systems/5e/world/creatures/Rock Gnome.md +27 -0
- package/systems/5e/world/creatures/Roper.md +48 -0
- package/systems/5e/world/creatures/Rug of Smothering.md +38 -0
- package/systems/5e/world/creatures/Rust Monster.md +39 -0
- package/systems/5e/world/creatures/Saber-Toothed Tiger.md +36 -0
- package/systems/5e/world/creatures/Sahuagin.md +49 -0
- package/systems/5e/world/creatures/Salamander.md +44 -0
- package/systems/5e/world/creatures/Satyr.md +38 -0
- package/systems/5e/world/creatures/Scorpion.md +26 -0
- package/systems/5e/world/creatures/Scout.md +40 -0
- package/systems/5e/world/creatures/Sea Hag.md +46 -0
- package/systems/5e/world/creatures/Sea Horse.md +25 -0
- package/systems/5e/world/creatures/Shadow.md +45 -0
- package/systems/5e/world/creatures/Shambling Mound.md +42 -0
- package/systems/5e/world/creatures/Shield Guardian.md +49 -0
- package/systems/5e/world/creatures/Shrieker.md +31 -0
- package/systems/5e/world/creatures/Silver Dragon Wyrmling.md +39 -0
- package/systems/5e/world/creatures/Skeleton.md +33 -0
- package/systems/5e/world/creatures/Solar.md +76 -0
- package/systems/5e/world/creatures/Specter.md +38 -0
- package/systems/5e/world/creatures/Spider.md +38 -0
- package/systems/5e/world/creatures/Spirit Naga.md +50 -0
- package/systems/5e/world/creatures/Sprite.md +37 -0
- package/systems/5e/world/creatures/Spy.md +42 -0
- package/systems/5e/world/creatures/Steam Mephit.md +40 -0
- package/systems/5e/world/creatures/Stirge.md +29 -0
- package/systems/5e/world/creatures/Stone Giant.md +45 -0
- package/systems/5e/world/creatures/Stone Golem.md +46 -0
- package/systems/5e/world/creatures/Storm Giant.md +51 -0
- package/systems/5e/world/creatures/Succubus-Incubus.md +50 -0
- package/systems/5e/world/creatures/Swarm of Bats.md +39 -0
- package/systems/5e/world/creatures/Swarm of Insects.md +52 -0
- package/systems/5e/world/creatures/Swarm of Poisonous Snakes.md +33 -0
- package/systems/5e/world/creatures/Swarm of Quippers.md +39 -0
- package/systems/5e/world/creatures/Swarm of Rats.md +35 -0
- package/systems/5e/world/creatures/Swarm of Ravens.md +33 -0
- package/systems/5e/world/creatures/Tarrasque.md +76 -0
- package/systems/5e/world/creatures/Thug.md +40 -0
- package/systems/5e/world/creatures/Tiefling.md +40 -0
- package/systems/5e/world/creatures/Tiger.md +37 -0
- package/systems/5e/world/creatures/Treant.md +45 -0
- package/systems/5e/world/creatures/Tribal Warrior.md +33 -0
- package/systems/5e/world/creatures/Triceratops.md +33 -0
- package/systems/5e/world/creatures/Troll.md +42 -0
- package/systems/5e/world/creatures/Tyrannosaurus Rex.md +32 -0
- package/systems/5e/world/creatures/Unicorn.md +70 -0
- package/systems/5e/world/creatures/Vampire Spawn.md +55 -0
- package/systems/5e/world/creatures/Vampire.md +91 -0
- package/systems/5e/world/creatures/Veteran.md +38 -0
- package/systems/5e/world/creatures/Violet Fungus.md +34 -0
- package/systems/5e/world/creatures/Vrock.md +49 -0
- package/systems/5e/world/creatures/Vulture.md +34 -0
- package/systems/5e/world/creatures/Warhorse Skeleton.md +29 -0
- package/systems/5e/world/creatures/Warhorse.md +29 -0
- package/systems/5e/world/creatures/Water Elemental.md +47 -0
- package/systems/5e/world/creatures/Weasel.md +30 -0
- package/systems/5e/world/creatures/Werebear.md +45 -0
- package/systems/5e/world/creatures/Wereboar.md +45 -0
- package/systems/5e/world/creatures/Wererat.md +45 -0
- package/systems/5e/world/creatures/Weretiger.md +51 -0
- package/systems/5e/world/creatures/Werewolf.md +45 -0
- package/systems/5e/world/creatures/White Dragon Wyrmling.md +37 -0
- package/systems/5e/world/creatures/Wight.md +47 -0
- package/systems/5e/world/creatures/Will-o'-Wisp.md +47 -0
- package/systems/5e/world/creatures/Winter Wolf.md +44 -0
- package/systems/5e/world/creatures/Wolf.md +34 -0
- package/systems/5e/world/creatures/Worg.md +35 -0
- package/systems/5e/world/creatures/Wraith.md +41 -0
- package/systems/5e/world/creatures/Wyvern.md +37 -0
- package/systems/5e/world/creatures/Xorn.md +48 -0
- package/systems/5e/world/creatures/Young Black Dragon.md +47 -0
- package/systems/5e/world/creatures/Young Blue Dragon.md +42 -0
- package/systems/5e/world/creatures/Young Brass Dragon.md +46 -0
- package/systems/5e/world/creatures/Young Bronze Dragon.md +51 -0
- package/systems/5e/world/creatures/Young Copper Dragon.md +46 -0
- package/systems/5e/world/creatures/Young Gold Dragon.md +51 -0
- package/systems/5e/world/creatures/Young Green Dragon.md +48 -0
- package/systems/5e/world/creatures/Young Red Dragon.md +42 -0
- package/systems/5e/world/creatures/Young Silver Dragon.md +45 -0
- package/systems/5e/world/creatures/Young White Dragon.md +48 -0
- package/systems/5e/world/creatures/Zombie.md +34 -0
- package/systems/5e/world/items/Abacus.md +5 -0
- package/systems/5e/world/items/Acid.md +8 -0
- package/systems/5e/world/items/Adamantine Armor.md +6 -0
- package/systems/5e/world/items/Alchemist's Fire.md +8 -0
- package/systems/5e/world/items/Alchemist's Supplies.md +7 -0
- package/systems/5e/world/items/Ammunition, +1, +2, or +3.md +6 -0
- package/systems/5e/world/items/Amulet of Health.md +7 -0
- package/systems/5e/world/items/Amulet of Proof against Detection and Location.md +7 -0
- package/systems/5e/world/items/Amulet of the Planes.md +7 -0
- package/systems/5e/world/items/Amulet.md +7 -0
- package/systems/5e/world/items/Animated Shield.md +7 -0
- package/systems/5e/world/items/Antitoxin.md +7 -0
- package/systems/5e/world/items/Apparatus of the Crab.md +37 -0
- package/systems/5e/world/items/Armor of Invulnerability.md +7 -0
- package/systems/5e/world/items/Armor of Resistance.md +15 -0
- package/systems/5e/world/items/Armor of Vulnerability.md +9 -0
- package/systems/5e/world/items/Armor, +1, +2, or +3.md +6 -0
- package/systems/5e/world/items/Arrow of Slaying.md +10 -0
- package/systems/5e/world/items/Arrow-Catching Shield.md +7 -0
- package/systems/5e/world/items/Arrows.md +6 -0
- package/systems/5e/world/items/Backpack.md +5 -0
- package/systems/5e/world/items/Bag of Beans.md +25 -0
- package/systems/5e/world/items/Bag of Devouring.md +12 -0
- package/systems/5e/world/items/Bag of Holding.md +10 -0
- package/systems/5e/world/items/Bag of Tricks.md +51 -0
- package/systems/5e/world/items/Bagpipes.md +7 -0
- package/systems/5e/world/items/Ball Bearings.md +8 -0
- package/systems/5e/world/items/Barding.md +7 -0
- package/systems/5e/world/items/Barrel.md +5 -0
- package/systems/5e/world/items/Basic Poison.md +7 -0
- package/systems/5e/world/items/Basket.md +5 -0
- package/systems/5e/world/items/Battleaxe.md +10 -0
- package/systems/5e/world/items/Bead of Force.md +10 -0
- package/systems/5e/world/items/Bedroll.md +5 -0
- package/systems/5e/world/items/Bell.md +4 -0
- package/systems/5e/world/items/Belt of Dwarvenkind.md +18 -0
- package/systems/5e/world/items/Belt of Giant Strength.md +17 -0
- package/systems/5e/world/items/Berserker Axe.md +11 -0
- package/systems/5e/world/items/Bit and Bridle.md +5 -0
- package/systems/5e/world/items/Blanket.md +5 -0
- package/systems/5e/world/items/Block and Tackle.md +7 -0
- package/systems/5e/world/items/Blowgun Needles.md +6 -0
- package/systems/5e/world/items/Blowgun.md +10 -0
- package/systems/5e/world/items/Book.md +7 -0
- package/systems/5e/world/items/Boots of Elvenkind.md +6 -0
- package/systems/5e/world/items/Boots of Levitation.md +7 -0
- package/systems/5e/world/items/Boots of Speed.md +9 -0
- package/systems/5e/world/items/Boots of Striding and Springing.md +7 -0
- package/systems/5e/world/items/Boots of the Winterlands.md +11 -0
- package/systems/5e/world/items/Bowl of Commanding Water Elementals.md +8 -0
- package/systems/5e/world/items/Bracers of Archery.md +7 -0
- package/systems/5e/world/items/Bracers of Defense.md +7 -0
- package/systems/5e/world/items/Brazier of Commanding Fire Elementals.md +8 -0
- package/systems/5e/world/items/Breastplate.md +9 -0
- package/systems/5e/world/items/Brewer's Supplies.md +7 -0
- package/systems/5e/world/items/Brooch of Shielding.md +7 -0
- package/systems/5e/world/items/Broom of Flying.md +8 -0
- package/systems/5e/world/items/Bucket.md +5 -0
- package/systems/5e/world/items/Bullseye Lantern.md +8 -0
- package/systems/5e/world/items/Burglar's Pack.md +6 -0
- package/systems/5e/world/items/Calligrapher's Supplies.md +7 -0
- package/systems/5e/world/items/Caltrops.md +8 -0
- package/systems/5e/world/items/Candle of Invocation.md +25 -0
- package/systems/5e/world/items/Candle.md +6 -0
- package/systems/5e/world/items/Cape of the Mountebank.md +8 -0
- package/systems/5e/world/items/Carpenter's Tools.md +7 -0
- package/systems/5e/world/items/Carpet of Flying.md +17 -0
- package/systems/5e/world/items/Carriage.md +5 -0
- package/systems/5e/world/items/Cart.md +5 -0
- package/systems/5e/world/items/Cartographer's Tools.md +7 -0
- package/systems/5e/world/items/Censer of Controlling Air Elementals.md +8 -0
- package/systems/5e/world/items/Chain Mail.md +11 -0
- package/systems/5e/world/items/Chain Shirt.md +9 -0
- package/systems/5e/world/items/Chain.md +9 -0
- package/systems/5e/world/items/Chalk.md +5 -0
- package/systems/5e/world/items/Chariot.md +5 -0
- package/systems/5e/world/items/Chest.md +5 -0
- package/systems/5e/world/items/Chime of Opening.md +8 -0
- package/systems/5e/world/items/Circlet of Blasting.md +6 -0
- package/systems/5e/world/items/Climber's Kit.md +7 -0
- package/systems/5e/world/items/Cloak of Arachnida.md +13 -0
- package/systems/5e/world/items/Cloak of Displacement.md +7 -0
- package/systems/5e/world/items/Cloak of Elvenkind.md +7 -0
- package/systems/5e/world/items/Cloak of Protection.md +7 -0
- package/systems/5e/world/items/Cloak of the Bat.md +9 -0
- package/systems/5e/world/items/Cloak of the Manta Ray.md +6 -0
- package/systems/5e/world/items/Club.md +10 -0
- package/systems/5e/world/items/Cobbler's Tools.md +7 -0
- package/systems/5e/world/items/Common Clothes.md +6 -0
- package/systems/5e/world/items/Component Pouch.md +7 -0
- package/systems/5e/world/items/Cook's Utensils.md +7 -0
- package/systems/5e/world/items/Costume Clothes.md +6 -0
- package/systems/5e/world/items/Crossbow Bolt Case.md +8 -0
- package/systems/5e/world/items/Crossbow Bolts.md +6 -0
- package/systems/5e/world/items/Crowbar.md +7 -0
- package/systems/5e/world/items/Crystal Ball.md +15 -0
- package/systems/5e/world/items/Crystal.md +7 -0
- package/systems/5e/world/items/Cube of Force.md +32 -0
- package/systems/5e/world/items/Cubic Gate.md +10 -0
- package/systems/5e/world/items/Dagger of Venom.md +8 -0
- package/systems/5e/world/items/Dagger.md +10 -0
- package/systems/5e/world/items/Dancing Sword.md +11 -0
- package/systems/5e/world/items/Dart.md +10 -0
- package/systems/5e/world/items/Decanter of Endless Water.md +12 -0
- package/systems/5e/world/items/Deck of Illusions.md +48 -0
- package/systems/5e/world/items/Deck of Many Things.md +113 -0
- package/systems/5e/world/items/Defender.md +9 -0
- package/systems/5e/world/items/Demon Armor.md +9 -0
- package/systems/5e/world/items/Dice Set.md +6 -0
- package/systems/5e/world/items/Dimensional Shackles.md +8 -0
- package/systems/5e/world/items/Diplomat's Pack.md +6 -0
- package/systems/5e/world/items/Disguise Kit.md +7 -0
- package/systems/5e/world/items/Dragon Scale Mail.md +19 -0
- package/systems/5e/world/items/Dragon Slayer.md +8 -0
- package/systems/5e/world/items/Drum.md +7 -0
- package/systems/5e/world/items/Dulcimer.md +7 -0
- package/systems/5e/world/items/Dungeoneer's Pack.md +6 -0
- package/systems/5e/world/items/Dust of Disappearance.md +6 -0
- package/systems/5e/world/items/Dust of Dryness.md +10 -0
- package/systems/5e/world/items/Dust of Sneezing and Choking.md +8 -0
- package/systems/5e/world/items/Dwarven Plate.md +6 -0
- package/systems/5e/world/items/Dwarven Thrower.md +7 -0
- package/systems/5e/world/items/Efficient Quiver.md +8 -0
- package/systems/5e/world/items/Efreeti Bottle.md +14 -0
- package/systems/5e/world/items/Elemental Gem.md +13 -0
- package/systems/5e/world/items/Elven Chain.md +6 -0
- package/systems/5e/world/items/Emblem.md +6 -0
- package/systems/5e/world/items/Entertainer's Pack.md +6 -0
- package/systems/5e/world/items/Eversmoking Bottle.md +8 -0
- package/systems/5e/world/items/Exotic Saddle.md +7 -0
- package/systems/5e/world/items/Explorer's Pack.md +6 -0
- package/systems/5e/world/items/Eyes of Charming.md +7 -0
- package/systems/5e/world/items/Eyes of Minute Seeing.md +6 -0
- package/systems/5e/world/items/Eyes of the Eagle.md +7 -0
- package/systems/5e/world/items/Feather Token.md +26 -0
- package/systems/5e/world/items/Feed.md +6 -0
- package/systems/5e/world/items/Figurine of Wondrous Power.md +52 -0
- package/systems/5e/world/items/Fine Clothes.md +6 -0
- package/systems/5e/world/items/Fishing Tackle.md +7 -0
- package/systems/5e/world/items/Flail.md +9 -0
- package/systems/5e/world/items/Flame Tongue.md +7 -0
- package/systems/5e/world/items/Flask or Tankard.md +5 -0
- package/systems/5e/world/items/Flute.md +7 -0
- package/systems/5e/world/items/Folding Boat.md +14 -0
- package/systems/5e/world/items/Forgery Kit.md +7 -0
- package/systems/5e/world/items/Frost Brand.md +11 -0
- package/systems/5e/world/items/Galley.md +5 -0
- package/systems/5e/world/items/Gauntlets of Ogre Power.md +7 -0
- package/systems/5e/world/items/Gem of Brightness.md +12 -0
- package/systems/5e/world/items/Gem of Seeing.md +9 -0
- package/systems/5e/world/items/Giant Slayer.md +8 -0
- package/systems/5e/world/items/Glaive.md +10 -0
- package/systems/5e/world/items/Glamoured Studded Leather.md +6 -0
- package/systems/5e/world/items/Glass Bottle.md +6 -0
- package/systems/5e/world/items/Glassblower's Tools.md +7 -0
- package/systems/5e/world/items/Gloves of Missile Snaring.md +7 -0
- package/systems/5e/world/items/Gloves of Swimming and Climbing.md +7 -0
- package/systems/5e/world/items/Goggles of Night.md +6 -0
- package/systems/5e/world/items/Grappling Hook.md +5 -0
- package/systems/5e/world/items/Greataxe.md +10 -0
- package/systems/5e/world/items/Greatclub.md +10 -0
- package/systems/5e/world/items/Greatsword.md +10 -0
- package/systems/5e/world/items/Halberd.md +10 -0
- package/systems/5e/world/items/Half Plate Armor.md +11 -0
- package/systems/5e/world/items/Hammer of Thunderbolts.md +10 -0
- package/systems/5e/world/items/Hammer.md +5 -0
- package/systems/5e/world/items/Hand Crossbow.md +11 -0
- package/systems/5e/world/items/Handaxe.md +10 -0
- package/systems/5e/world/items/Handy Haversack.md +12 -0
- package/systems/5e/world/items/Hat of Disguise.md +7 -0
- package/systems/5e/world/items/Headband of Intellect.md +7 -0
- package/systems/5e/world/items/Healer's Kit.md +8 -0
- package/systems/5e/world/items/Heavy Crossbow.md +11 -0
- package/systems/5e/world/items/Helm of Brilliance.md +16 -0
- package/systems/5e/world/items/Helm of Comprehending Languages.md +6 -0
- package/systems/5e/world/items/Helm of Telepathy.md +9 -0
- package/systems/5e/world/items/Helm of Teleportation.md +7 -0
- package/systems/5e/world/items/Hempen Rope.md +9 -0
- package/systems/5e/world/items/Herbalism Kit.md +7 -0
- package/systems/5e/world/items/Hide Armor.md +10 -0
- package/systems/5e/world/items/Holy Avenger.md +9 -0
- package/systems/5e/world/items/Holy Water.md +10 -0
- package/systems/5e/world/items/Hooded Lantern.md +8 -0
- package/systems/5e/world/items/Horn of Blasting.md +8 -0
- package/systems/5e/world/items/Horn of Valhalla.md +17 -0
- package/systems/5e/world/items/Horn.md +7 -0
- package/systems/5e/world/items/Horseshoes of Speed.md +6 -0
- package/systems/5e/world/items/Horseshoes of a Zephyr.md +6 -0
- package/systems/5e/world/items/Hourglass.md +5 -0
- package/systems/5e/world/items/Hunting Trap.md +7 -0
- package/systems/5e/world/items/Immovable Rod.md +6 -0
- package/systems/5e/world/items/Ink Pen.md +4 -0
- package/systems/5e/world/items/Ink.md +5 -0
- package/systems/5e/world/items/Instant Fortress.md +12 -0
- package/systems/5e/world/items/Ioun Stone.md +47 -0
- package/systems/5e/world/items/Iron Bands of Binding.md +12 -0
- package/systems/5e/world/items/Iron Flask.md +33 -0
- package/systems/5e/world/items/Iron Pot.md +6 -0
- package/systems/5e/world/items/Iron Spikes.md +6 -0
- package/systems/5e/world/items/Javelin of Lightning.md +8 -0
- package/systems/5e/world/items/Javelin.md +10 -0
- package/systems/5e/world/items/Jeweler's Tools.md +7 -0
- package/systems/5e/world/items/Jug or Pitcher.md +5 -0
- package/systems/5e/world/items/Keelboat.md +7 -0
- package/systems/5e/world/items/Ladder.md +7 -0
- package/systems/5e/world/items/Lamp.md +7 -0
- package/systems/5e/world/items/Lance.md +12 -0
- package/systems/5e/world/items/Lantern of Revealing.md +6 -0
- package/systems/5e/world/items/Leather Armor.md +10 -0
- package/systems/5e/world/items/Leatherworker's Tools.md +7 -0
- package/systems/5e/world/items/Light Crossbow.md +11 -0
- package/systems/5e/world/items/Light Hammer.md +10 -0
- package/systems/5e/world/items/Lock.md +7 -0
- package/systems/5e/world/items/Longbow.md +10 -0
- package/systems/5e/world/items/Longship.md +5 -0
- package/systems/5e/world/items/Longsword.md +10 -0
- package/systems/5e/world/items/Luck Blade.md +11 -0
- package/systems/5e/world/items/Lute.md +7 -0
- package/systems/5e/world/items/Lyre.md +7 -0
- package/systems/5e/world/items/Mace of Disruption.md +9 -0
- package/systems/5e/world/items/Mace of Smiting.md +8 -0
- package/systems/5e/world/items/Mace of Terror.md +9 -0
- package/systems/5e/world/items/Mace.md +9 -0
- package/systems/5e/world/items/Magnifying Glass.md +6 -0
- package/systems/5e/world/items/Manacles.md +7 -0
- package/systems/5e/world/items/Mantle of Spell Resistance.md +7 -0
- package/systems/5e/world/items/Manual of Bodily Health.md +6 -0
- package/systems/5e/world/items/Manual of Gainful Exercise.md +6 -0
- package/systems/5e/world/items/Manual of Golems.md +17 -0
- package/systems/5e/world/items/Manual of Quickness of Action.md +6 -0
- package/systems/5e/world/items/Map or Scroll Case.md +8 -0
- package/systems/5e/world/items/Marvelous Pigments.md +14 -0
- package/systems/5e/world/items/Mason's Tools.md +7 -0
- package/systems/5e/world/items/Maul.md +10 -0
- package/systems/5e/world/items/Medallion of Thoughts.md +7 -0
- package/systems/5e/world/items/Merchant's Scale.md +8 -0
- package/systems/5e/world/items/Mess Kit.md +7 -0
- package/systems/5e/world/items/Military Saddle.md +7 -0
- package/systems/5e/world/items/Miner's Pick.md +6 -0
- package/systems/5e/world/items/Mirror of Life Trapping.md +18 -0
- package/systems/5e/world/items/Mithral Armor.md +6 -0
- package/systems/5e/world/items/Morningstar.md +9 -0
- package/systems/5e/world/items/Navigator's Tools.md +7 -0
- package/systems/5e/world/items/Necklace of Adaptation.md +7 -0
- package/systems/5e/world/items/Necklace of Fireballs.md +8 -0
- package/systems/5e/world/items/Necklace of Prayer Beads.md +18 -0
- package/systems/5e/world/items/Net.md +12 -0
- package/systems/5e/world/items/Nine Lives Stealer.md +9 -0
- package/systems/5e/world/items/Oathbow.md +11 -0
- package/systems/5e/world/items/Oil of Etherealness.md +6 -0
- package/systems/5e/world/items/Oil of Sharpness.md +6 -0
- package/systems/5e/world/items/Oil of Slipperiness.md +8 -0
- package/systems/5e/world/items/Oil.md +8 -0
- package/systems/5e/world/items/Orb of Dragonkind.md +31 -0
- package/systems/5e/world/items/Orb.md +7 -0
- package/systems/5e/world/items/Pack Saddle.md +7 -0
- package/systems/5e/world/items/Padded Armor.md +11 -0
- package/systems/5e/world/items/Painter's Supplies.md +7 -0
- package/systems/5e/world/items/Pan Flute.md +7 -0
- package/systems/5e/world/items/Paper.md +5 -0
- package/systems/5e/world/items/Parchment.md +5 -0
- package/systems/5e/world/items/Pearl of Power.md +7 -0
- package/systems/5e/world/items/Perfume.md +5 -0
- package/systems/5e/world/items/Periapt of Health.md +6 -0
- package/systems/5e/world/items/Periapt of Proof against Poison.md +6 -0
- package/systems/5e/world/items/Periapt of Wound Closure.md +7 -0
- package/systems/5e/world/items/Philter of Love.md +6 -0
- package/systems/5e/world/items/Pike.md +10 -0
- package/systems/5e/world/items/Pipes of Haunting.md +6 -0
- package/systems/5e/world/items/Pipes of the Sewers.md +11 -0
- package/systems/5e/world/items/Piton.md +5 -0
- package/systems/5e/world/items/Plate Armor of Etherealness.md +7 -0
- package/systems/5e/world/items/Plate Armor.md +12 -0
- package/systems/5e/world/items/Playing Card Set.md +6 -0
- package/systems/5e/world/items/Poisoner's Kit.md +7 -0
- package/systems/5e/world/items/Pole.md +7 -0
- package/systems/5e/world/items/Portable Hole.md +14 -0
- package/systems/5e/world/items/Portable Ram.md +8 -0
- package/systems/5e/world/items/Potion of Animal Friendship.md +6 -0
- package/systems/5e/world/items/Potion of Clairvoyance.md +6 -0
- package/systems/5e/world/items/Potion of Climbing.md +6 -0
- package/systems/5e/world/items/Potion of Diminution.md +6 -0
- package/systems/5e/world/items/Potion of Flying.md +6 -0
- package/systems/5e/world/items/Potion of Gaseous Form.md +6 -0
- package/systems/5e/world/items/Potion of Giant Strength.md +16 -0
- package/systems/5e/world/items/Potion of Growth.md +6 -0
- package/systems/5e/world/items/Potion of Healing.md +19 -0
- package/systems/5e/world/items/Potion of Heroism.md +6 -0
- package/systems/5e/world/items/Potion of Invisibility.md +6 -0
- package/systems/5e/world/items/Potion of Mind Reading.md +6 -0
- package/systems/5e/world/items/Potion of Poison.md +8 -0
- package/systems/5e/world/items/Potion of Resistance.md +14 -0
- package/systems/5e/world/items/Potion of Speed.md +6 -0
- package/systems/5e/world/items/Potion of Water Breathing.md +6 -0
- package/systems/5e/world/items/Potter's Tools.md +7 -0
- package/systems/5e/world/items/Pouch.md +7 -0
- package/systems/5e/world/items/Priest's Pack.md +6 -0
- package/systems/5e/world/items/Quarterstaff.md +10 -0
- package/systems/5e/world/items/Quiver.md +7 -0
- package/systems/5e/world/items/Rapier.md +10 -0
- package/systems/5e/world/items/Rations.md +8 -0
- package/systems/5e/world/items/Reliquary.md +7 -0
- package/systems/5e/world/items/Restorative Ointment.md +8 -0
- package/systems/5e/world/items/Riding Saddle.md +7 -0
- package/systems/5e/world/items/Ring Mail.md +10 -0
- package/systems/5e/world/items/Ring of Animal Influence.md +10 -0
- package/systems/5e/world/items/Ring of Djinni Summoning.md +11 -0
- package/systems/5e/world/items/Ring of Elemental Command.md +41 -0
- package/systems/5e/world/items/Ring of Evasion.md +7 -0
- package/systems/5e/world/items/Ring of Feather Falling.md +7 -0
- package/systems/5e/world/items/Ring of Free Action.md +7 -0
- package/systems/5e/world/items/Ring of Invisibility.md +7 -0
- package/systems/5e/world/items/Ring of Jumping.md +7 -0
- package/systems/5e/world/items/Ring of Mind Shielding.md +11 -0
- package/systems/5e/world/items/Ring of Protection.md +7 -0
- package/systems/5e/world/items/Ring of Regeneration.md +7 -0
- package/systems/5e/world/items/Ring of Resistance.md +20 -0
- package/systems/5e/world/items/Ring of Shooting Stars.md +26 -0
- package/systems/5e/world/items/Ring of Spell Storing.md +11 -0
- package/systems/5e/world/items/Ring of Spell Turning.md +7 -0
- package/systems/5e/world/items/Ring of Swimming.md +6 -0
- package/systems/5e/world/items/Ring of Telekinesis.md +7 -0
- package/systems/5e/world/items/Ring of Three Wishes.md +6 -0
- package/systems/5e/world/items/Ring of Warmth.md +7 -0
- package/systems/5e/world/items/Ring of Water Walking.md +6 -0
- package/systems/5e/world/items/Ring of X-ray Vision.md +9 -0
- package/systems/5e/world/items/Ring of the Ram.md +9 -0
- package/systems/5e/world/items/Robe of Eyes.md +15 -0
- package/systems/5e/world/items/Robe of Scintillating Colors.md +7 -0
- package/systems/5e/world/items/Robe of Stars.md +11 -0
- package/systems/5e/world/items/Robe of Useful Items.md +33 -0
- package/systems/5e/world/items/Robe of the Archmagi.md +13 -0
- package/systems/5e/world/items/Robes.md +5 -0
- package/systems/5e/world/items/Rod of Absorption.md +13 -0
- package/systems/5e/world/items/Rod of Alertness.md +15 -0
- package/systems/5e/world/items/Rod of Lordly Might.md +27 -0
- package/systems/5e/world/items/Rod of Rulership.md +7 -0
- package/systems/5e/world/items/Rod of Security.md +10 -0
- package/systems/5e/world/items/Rod.md +7 -0
- package/systems/5e/world/items/Rope of Climbing.md +10 -0
- package/systems/5e/world/items/Rope of Entanglement.md +10 -0
- package/systems/5e/world/items/Rowboat.md +7 -0
- package/systems/5e/world/items/Sack.md +5 -0
- package/systems/5e/world/items/Saddlebags.md +5 -0
- package/systems/5e/world/items/Sailing Ship.md +5 -0
- package/systems/5e/world/items/Scale Mail.md +10 -0
- package/systems/5e/world/items/Scarab of Protection.md +10 -0
- package/systems/5e/world/items/Scholar's Pack.md +6 -0
- package/systems/5e/world/items/Scimitar of Speed.md +7 -0
- package/systems/5e/world/items/Scimitar.md +10 -0
- package/systems/5e/world/items/Sealing Wax.md +4 -0
- package/systems/5e/world/items/Shawm.md +7 -0
- package/systems/5e/world/items/Shield of Missile Attraction.md +9 -0
- package/systems/5e/world/items/Shield, +1, +2, or +3.md +6 -0
- package/systems/5e/world/items/Shield.md +9 -0
- package/systems/5e/world/items/Shortbow.md +10 -0
- package/systems/5e/world/items/Shortsword.md +10 -0
- package/systems/5e/world/items/Shovel.md +5 -0
- package/systems/5e/world/items/Sickle.md +10 -0
- package/systems/5e/world/items/Signal Whistle.md +4 -0
- package/systems/5e/world/items/Signet Ring.md +4 -0
- package/systems/5e/world/items/Silk Rope.md +9 -0
- package/systems/5e/world/items/Sled.md +5 -0
- package/systems/5e/world/items/Sledgehammer.md +6 -0
- package/systems/5e/world/items/Sling Bullets.md +6 -0
- package/systems/5e/world/items/Sling.md +9 -0
- package/systems/5e/world/items/Slippers of Spider Climbing.md +7 -0
- package/systems/5e/world/items/Smith's Tools.md +7 -0
- package/systems/5e/world/items/Soap.md +4 -0
- package/systems/5e/world/items/Sovereign Glue.md +8 -0
- package/systems/5e/world/items/Spear.md +10 -0
- package/systems/5e/world/items/Spell Scroll.md +27 -0
- package/systems/5e/world/items/Spellbook.md +7 -0
- package/systems/5e/world/items/Spellguard Shield.md +7 -0
- package/systems/5e/world/items/Sphere of Annihilation.md +20 -0
- package/systems/5e/world/items/Splint Armor.md +12 -0
- package/systems/5e/world/items/Sprig of Mistletoe.md +6 -0
- package/systems/5e/world/items/Spyglass.md +7 -0
- package/systems/5e/world/items/Staff of Charming.md +11 -0
- package/systems/5e/world/items/Staff of Fire.md +11 -0
- package/systems/5e/world/items/Staff of Frost.md +11 -0
- package/systems/5e/world/items/Staff of Healing.md +9 -0
- package/systems/5e/world/items/Staff of Power.md +23 -0
- package/systems/5e/world/items/Staff of Striking.md +9 -0
- package/systems/5e/world/items/Staff of Swarming Insects.md +11 -0
- package/systems/5e/world/items/Staff of Thunder and Lightning.md +17 -0
- package/systems/5e/world/items/Staff of Withering.md +9 -0
- package/systems/5e/world/items/Staff of the Magi.md +25 -0
- package/systems/5e/world/items/Staff of the Python.md +11 -0
- package/systems/5e/world/items/Staff of the Woodlands.md +15 -0
- package/systems/5e/world/items/Staff.md +7 -0
- package/systems/5e/world/items/Steel Mirror.md +6 -0
- package/systems/5e/world/items/Stone of Controlling Earth Elementals.md +6 -0
- package/systems/5e/world/items/Stone of Good Luck.md +8 -0
- package/systems/5e/world/items/Studded Leather Armor.md +10 -0
- package/systems/5e/world/items/Sun Blade.md +11 -0
- package/systems/5e/world/items/Sword of Life Stealing.md +7 -0
- package/systems/5e/world/items/Sword of Sharpness.md +11 -0
- package/systems/5e/world/items/Sword of Wounding.md +9 -0
- package/systems/5e/world/items/Talisman of Pure Good.md +11 -0
- package/systems/5e/world/items/Talisman of Ultimate Evil.md +11 -0
- package/systems/5e/world/items/Talisman of the Sphere.md +7 -0
- package/systems/5e/world/items/Thieves' Tools.md +7 -0
- package/systems/5e/world/items/Tinderbox.md +7 -0
- package/systems/5e/world/items/Tinker's Tools.md +7 -0
- package/systems/5e/world/items/Tome of Clear Thought.md +6 -0
- package/systems/5e/world/items/Tome of Leadership and Influence.md +6 -0
- package/systems/5e/world/items/Tome of Understanding.md +6 -0
- package/systems/5e/world/items/Torch.md +7 -0
- package/systems/5e/world/items/Totem.md +6 -0
- package/systems/5e/world/items/Traveler's Clothes.md +6 -0
- package/systems/5e/world/items/Trident of Fish Command.md +7 -0
- package/systems/5e/world/items/Trident.md +10 -0
- package/systems/5e/world/items/Two-Person Tent.md +8 -0
- package/systems/5e/world/items/Unarmed Strike.md +7 -0
- package/systems/5e/world/items/Universal Solvent.md +6 -0
- package/systems/5e/world/items/Vial.md +4 -0
- package/systems/5e/world/items/Vicious Weapon.md +6 -0
- package/systems/5e/world/items/Viol.md +7 -0
- package/systems/5e/world/items/Vorpal Sword.md +9 -0
- package/systems/5e/world/items/Wagon.md +5 -0
- package/systems/5e/world/items/Wand of Binding.md +11 -0
- package/systems/5e/world/items/Wand of Enemy Detection.md +9 -0
- package/systems/5e/world/items/Wand of Fear.md +11 -0
- package/systems/5e/world/items/Wand of Fireballs.md +9 -0
- package/systems/5e/world/items/Wand of Lightning Bolts.md +9 -0
- package/systems/5e/world/items/Wand of Magic Detection.md +6 -0
- package/systems/5e/world/items/Wand of Magic Missiles.md +8 -0
- package/systems/5e/world/items/Wand of Paralysis.md +9 -0
- package/systems/5e/world/items/Wand of Polymorph.md +9 -0
- package/systems/5e/world/items/Wand of Secrets.md +6 -0
- package/systems/5e/world/items/Wand of Web.md +9 -0
- package/systems/5e/world/items/Wand of Wonder.md +38 -0
- package/systems/5e/world/items/Wand of the War Mage, +1, +2, or +3.md +7 -0
- package/systems/5e/world/items/Wand.md +7 -0
- package/systems/5e/world/items/War Pick.md +9 -0
- package/systems/5e/world/items/Warhammer.md +10 -0
- package/systems/5e/world/items/Warship.md +5 -0
- package/systems/5e/world/items/Waterskin.md +5 -0
- package/systems/5e/world/items/Weapon, +1, +2, or +3.md +6 -0
- package/systems/5e/world/items/Weaver's Tools.md +7 -0
- package/systems/5e/world/items/Well of Many Worlds.md +8 -0
- package/systems/5e/world/items/Whetstone.md +5 -0
- package/systems/5e/world/items/Whip.md +10 -0
- package/systems/5e/world/items/Wind Fan.md +6 -0
- package/systems/5e/world/items/Winged Boots.md +9 -0
- package/systems/5e/world/items/Wings of Flying.md +7 -0
- package/systems/5e/world/items/Woodcarver's Tools.md +7 -0
- package/systems/5e/world/items/Wooden Staff.md +7 -0
- package/systems/5e/world/items/Yew Wand.md +7 -0
- package/systems/5e/world/topics/Acid Arrow.md +14 -0
- package/systems/5e/world/topics/Acid Splash.md +14 -0
- package/systems/5e/world/topics/Acolyte (background).md +103 -0
- package/systems/5e/world/topics/Aid.md +14 -0
- package/systems/5e/world/topics/Alarm.md +17 -0
- package/systems/5e/world/topics/Alter Self.md +19 -0
- package/systems/5e/world/topics/Animal Friendship.md +14 -0
- package/systems/5e/world/topics/Animal Messenger.md +17 -0
- package/systems/5e/world/topics/Animal Shapes.md +17 -0
- package/systems/5e/world/topics/Animate Dead.md +18 -0
- package/systems/5e/world/topics/Animate Objects.md +31 -0
- package/systems/5e/world/topics/Antilife Shell.md +17 -0
- package/systems/5e/world/topics/Antimagic Field.md +31 -0
- package/systems/5e/world/topics/Antipathy-Sympathy.md +23 -0
- package/systems/5e/world/topics/Arcane Eye.md +17 -0
- package/systems/5e/world/topics/Arcane Hand.md +27 -0
- package/systems/5e/world/topics/Arcane Lock.md +14 -0
- package/systems/5e/world/topics/Arcane Sword.md +15 -0
- package/systems/5e/world/topics/Arcanist's Magic Aura.md +18 -0
- package/systems/5e/world/topics/Astral Projection.md +22 -0
- package/systems/5e/world/topics/Augury.md +22 -0
- package/systems/5e/world/topics/Awaken.md +14 -0
- package/systems/5e/world/topics/Bane.md +15 -0
- package/systems/5e/world/topics/Banishment.md +19 -0
- package/systems/5e/world/topics/Barbarian.md +123 -0
- package/systems/5e/world/topics/Bard Spells.md +147 -0
- package/systems/5e/world/topics/Bard.md +139 -0
- package/systems/5e/world/topics/Barkskin.md +13 -0
- package/systems/5e/world/topics/Beacon of Hope.md +13 -0
- package/systems/5e/world/topics/Bestow Curse.md +22 -0
- package/systems/5e/world/topics/Black Tentacles.md +17 -0
- package/systems/5e/world/topics/Blade Barrier.md +15 -0
- package/systems/5e/world/topics/Bless.md +15 -0
- package/systems/5e/world/topics/Blight.md +18 -0
- package/systems/5e/world/topics/Blindness-Deafness.md +15 -0
- package/systems/5e/world/topics/Blink.md +14 -0
- package/systems/5e/world/topics/Blur.md +13 -0
- package/systems/5e/world/topics/Branding Smite.md +15 -0
- package/systems/5e/world/topics/Burning Hands.md +16 -0
- package/systems/5e/world/topics/Call Lightning.md +19 -0
- package/systems/5e/world/topics/Calm Emotions.md +17 -0
- package/systems/5e/world/topics/Chain Lightning.md +16 -0
- package/systems/5e/world/topics/Champion.md +24 -0
- package/systems/5e/world/topics/Charm Person.md +14 -0
- package/systems/5e/world/topics/Chill Touch.md +16 -0
- package/systems/5e/world/topics/Circle of Death.md +14 -0
- package/systems/5e/world/topics/Circle of the Land.md +96 -0
- package/systems/5e/world/topics/Clairvoyance.md +17 -0
- package/systems/5e/world/topics/Cleric Spells.md +140 -0
- package/systems/5e/world/topics/Cleric.md +141 -0
- package/systems/5e/world/topics/Clone.md +14 -0
- package/systems/5e/world/topics/Cloudkill.md +19 -0
- package/systems/5e/world/topics/College of Lore.md +23 -0
- package/systems/5e/world/topics/Color Spray.md +16 -0
- package/systems/5e/world/topics/Command.md +26 -0
- package/systems/5e/world/topics/Commune with Nature.md +23 -0
- package/systems/5e/world/topics/Commune.md +17 -0
- package/systems/5e/world/topics/Comprehend Languages.md +15 -0
- package/systems/5e/world/topics/Compulsion.md +15 -0
- package/systems/5e/world/topics/Cone of Cold.md +16 -0
- package/systems/5e/world/topics/Confusion.md +26 -0
- package/systems/5e/world/topics/Conjure Animals.md +26 -0
- package/systems/5e/world/topics/Conjure Celestial.md +19 -0
- package/systems/5e/world/topics/Conjure Elemental.md +21 -0
- package/systems/5e/world/topics/Conjure Fey.md +21 -0
- package/systems/5e/world/topics/Conjure Minor Elementals.md +26 -0
- package/systems/5e/world/topics/Conjure Woodland Beings.md +26 -0
- package/systems/5e/world/topics/Contact Other Plane.md +15 -0
- package/systems/5e/world/topics/Contagion.md +28 -0
- package/systems/5e/world/topics/Contingency.md +16 -0
- package/systems/5e/world/topics/Continual Flame.md +12 -0
- package/systems/5e/world/topics/Control Water.md +29 -0
- package/systems/5e/world/topics/Control Weather.md +48 -0
- package/systems/5e/world/topics/Counterspell.md +14 -0
- package/systems/5e/world/topics/Create Food and Water.md +12 -0
- package/systems/5e/world/topics/Create Undead.md +18 -0
- package/systems/5e/world/topics/Create or Destroy Water.md +18 -0
- package/systems/5e/world/topics/Creation.md +26 -0
- package/systems/5e/world/topics/Cure Wounds.md +14 -0
- package/systems/5e/world/topics/Dancing Lights.md +15 -0
- package/systems/5e/world/topics/Darkness.md +17 -0
- package/systems/5e/world/topics/Darkvision.md +12 -0
- package/systems/5e/world/topics/Daylight.md +16 -0
- package/systems/5e/world/topics/Death Ward.md +16 -0
- package/systems/5e/world/topics/Delayed Blast Fireball.md +21 -0
- package/systems/5e/world/topics/Demiplane.md +14 -0
- package/systems/5e/world/topics/Detect Evil and Good.md +15 -0
- package/systems/5e/world/topics/Detect Magic.md +16 -0
- package/systems/5e/world/topics/Detect Poison and Disease.md +16 -0
- package/systems/5e/world/topics/Detect Thoughts.md +21 -0
- package/systems/5e/world/topics/Dimension Door.md +16 -0
- package/systems/5e/world/topics/Disguise Self.md +16 -0
- package/systems/5e/world/topics/Disintegrate.md +20 -0
- package/systems/5e/world/topics/Dispel Evil and Good.md +19 -0
- package/systems/5e/world/topics/Dispel Magic.md +14 -0
- package/systems/5e/world/topics/Divination.md +17 -0
- package/systems/5e/world/topics/Divine Favor.md +13 -0
- package/systems/5e/world/topics/Divine Word.md +19 -0
- package/systems/5e/world/topics/Dominate Beast.md +21 -0
- package/systems/5e/world/topics/Dominate Monster.md +21 -0
- package/systems/5e/world/topics/Dominate Person.md +21 -0
- package/systems/5e/world/topics/Draconic Bloodline.md +43 -0
- package/systems/5e/world/topics/Dream.md +18 -0
- package/systems/5e/world/topics/Druid Spells.md +140 -0
- package/systems/5e/world/topics/Druid.md +146 -0
- package/systems/5e/world/topics/Druidcraft.md +17 -0
- package/systems/5e/world/topics/Earthquake.md +25 -0
- package/systems/5e/world/topics/Eldritch Blast.md +14 -0
- package/systems/5e/world/topics/Enhance Ability.md +27 -0
- package/systems/5e/world/topics/Enlarge-Reduce.md +20 -0
- package/systems/5e/world/topics/Entangle.md +17 -0
- package/systems/5e/world/topics/Enthrall.md +12 -0
- package/systems/5e/world/topics/Etherealness.md +22 -0
- package/systems/5e/world/topics/Expeditious Retreat.md +13 -0
- package/systems/5e/world/topics/Eyebite.md +19 -0
- package/systems/5e/world/topics/Fabricate.md +16 -0
- package/systems/5e/world/topics/Faerie Fire.md +15 -0
- package/systems/5e/world/topics/Faithful Hound.md +16 -0
- package/systems/5e/world/topics/False Life.md +14 -0
- package/systems/5e/world/topics/Fear.md +15 -0
- package/systems/5e/world/topics/Feather Fall.md +12 -0
- package/systems/5e/world/topics/Feeblemind.md +18 -0
- package/systems/5e/world/topics/Fighter.md +109 -0
- package/systems/5e/world/topics/Find Familiar.md +25 -0
- package/systems/5e/world/topics/Find Steed.md +20 -0
- package/systems/5e/world/topics/Find Traps.md +14 -0
- package/systems/5e/world/topics/Find the Path.md +15 -0
- package/systems/5e/world/topics/Finger of Death.md +14 -0
- package/systems/5e/world/topics/Fire Bolt.md +14 -0
- package/systems/5e/world/topics/Fire Shield.md +16 -0
- package/systems/5e/world/topics/Fire Storm.md +14 -0
- package/systems/5e/world/topics/Fireball.md +16 -0
- package/systems/5e/world/topics/Flame Blade.md +19 -0
- package/systems/5e/world/topics/Flame Strike.md +14 -0
- package/systems/5e/world/topics/Flaming Sphere.md +19 -0
- package/systems/5e/world/topics/Flesh to Stone.md +19 -0
- package/systems/5e/world/topics/Floating Disk.md +17 -0
- package/systems/5e/world/topics/Fly.md +15 -0
- package/systems/5e/world/topics/Fog Cloud.md +15 -0
- package/systems/5e/world/topics/Forbiddance.md +19 -0
- package/systems/5e/world/topics/Forcecage.md +22 -0
- package/systems/5e/world/topics/Foresight.md +14 -0
- package/systems/5e/world/topics/Freedom of Movement.md +14 -0
- package/systems/5e/world/topics/Freezing Sphere.md +18 -0
- package/systems/5e/world/topics/Gaseous Form.md +17 -0
- package/systems/5e/world/topics/Gate.md +19 -0
- package/systems/5e/world/topics/Geas.md +18 -0
- package/systems/5e/world/topics/Gentle Repose.md +15 -0
- package/systems/5e/world/topics/Giant Insect.md +19 -0
- package/systems/5e/world/topics/Glibness.md +12 -0
- package/systems/5e/world/topics/Globe of Invulnerability.md +17 -0
- package/systems/5e/world/topics/Glyph of Warding.md +26 -0
- package/systems/5e/world/topics/Goodberry.md +14 -0
- package/systems/5e/world/topics/Grappler.md +17 -0
- package/systems/5e/world/topics/Grease.md +14 -0
- package/systems/5e/world/topics/Greater Invisibility.md +13 -0
- package/systems/5e/world/topics/Greater Restoration.md +17 -0
- package/systems/5e/world/topics/Guardian of Faith.md +14 -0
- package/systems/5e/world/topics/Guards and Wards.md +34 -0
- package/systems/5e/world/topics/Guidance.md +13 -0
- package/systems/5e/world/topics/Guiding Bolt.md +14 -0
- package/systems/5e/world/topics/Gust of Wind.md +19 -0
- package/systems/5e/world/topics/Hallow.md +36 -0
- package/systems/5e/world/topics/Hallucinatory Terrain.md +14 -0
- package/systems/5e/world/topics/Harm.md +12 -0
- package/systems/5e/world/topics/Haste.md +15 -0
- package/systems/5e/world/topics/Heal.md +14 -0
- package/systems/5e/world/topics/Healing Word.md +14 -0
- package/systems/5e/world/topics/Heat Metal.md +17 -0
- package/systems/5e/world/topics/Hellish Rebuke.md +14 -0
- package/systems/5e/world/topics/Heroes' Feast.md +14 -0
- package/systems/5e/world/topics/Heroism.md +15 -0
- package/systems/5e/world/topics/Hideous Laughter.md +15 -0
- package/systems/5e/world/topics/Hold Monster.md +15 -0
- package/systems/5e/world/topics/Hold Person.md +15 -0
- package/systems/5e/world/topics/Holy Aura.md +13 -0
- package/systems/5e/world/topics/Hunter's Mark.md +15 -0
- package/systems/5e/world/topics/Hunter.md +41 -0
- package/systems/5e/world/topics/Hypnotic Pattern.md +15 -0
- package/systems/5e/world/topics/Ice Storm.md +16 -0
- package/systems/5e/world/topics/Identify.md +15 -0
- package/systems/5e/world/topics/Illusory Script.md +19 -0
- package/systems/5e/world/topics/Imprisonment.md +38 -0
- package/systems/5e/world/topics/Incendiary Cloud.md +17 -0
- package/systems/5e/world/topics/Inflict Wounds.md +14 -0
- package/systems/5e/world/topics/Insect Plague.md +17 -0
- package/systems/5e/world/topics/Instant Summons.md +19 -0
- package/systems/5e/world/topics/Invisibility.md +15 -0
- package/systems/5e/world/topics/Irresistible Dance.md +15 -0
- package/systems/5e/world/topics/Jump.md +12 -0
- package/systems/5e/world/topics/Knock.md +18 -0
- package/systems/5e/world/topics/Legend Lore.md +14 -0
- package/systems/5e/world/topics/Lesser Restoration.md +12 -0
- package/systems/5e/world/topics/Levitate.md +17 -0
- package/systems/5e/world/topics/Life Domain.md +38 -0
- package/systems/5e/world/topics/Light.md +14 -0
- package/systems/5e/world/topics/Lightning Bolt.md +16 -0
- package/systems/5e/world/topics/Locate Animals or Plants.md +13 -0
- package/systems/5e/world/topics/Locate Creature.md +17 -0
- package/systems/5e/world/topics/Locate Object.md +17 -0
- package/systems/5e/world/topics/Longstrider.md +14 -0
- package/systems/5e/world/topics/Mage Armor.md +12 -0
- package/systems/5e/world/topics/Mage Hand.md +16 -0
- package/systems/5e/world/topics/Magic Circle.md +22 -0
- package/systems/5e/world/topics/Magic Jar.md +24 -0
- package/systems/5e/world/topics/Magic Missile.md +14 -0
- package/systems/5e/world/topics/Magic Mouth.md +17 -0
- package/systems/5e/world/topics/Magic Weapon.md +15 -0
- package/systems/5e/world/topics/Magnificent Mansion.md +16 -0
- package/systems/5e/world/topics/Major Image.md +19 -0
- package/systems/5e/world/topics/Mass Cure Wounds.md +14 -0
- package/systems/5e/world/topics/Mass Heal.md +12 -0
- package/systems/5e/world/topics/Mass Healing Word.md +14 -0
- package/systems/5e/world/topics/Mass Suggestion.md +20 -0
- package/systems/5e/world/topics/Maze.md +17 -0
- package/systems/5e/world/topics/Meld into Stone.md +17 -0
- package/systems/5e/world/topics/Mending.md +14 -0
- package/systems/5e/world/topics/Message.md +14 -0
- package/systems/5e/world/topics/Meteor Swarm.md +14 -0
- package/systems/5e/world/topics/Mind Blank.md +12 -0
- package/systems/5e/world/topics/Minor Illusion.md +18 -0
- package/systems/5e/world/topics/Mirage Arcane.md +18 -0
- package/systems/5e/world/topics/Mirror Image.md +20 -0
- package/systems/5e/world/topics/Mislead.md +17 -0
- package/systems/5e/world/topics/Misty Step.md +12 -0
- package/systems/5e/world/topics/Modify Memory.md +23 -0
- package/systems/5e/world/topics/Monk.md +153 -0
- package/systems/5e/world/topics/Moonbeam.md +21 -0
- package/systems/5e/world/topics/Move Earth.md +21 -0
- package/systems/5e/world/topics/Nondetection.md +12 -0
- package/systems/5e/world/topics/Oath of Devotion.md +62 -0
- package/systems/5e/world/topics/Paladin Spells.md +51 -0
- package/systems/5e/world/topics/Paladin.md +173 -0
- package/systems/5e/world/topics/Pass without Trace.md +13 -0
- package/systems/5e/world/topics/Passwall.md +14 -0
- package/systems/5e/world/topics/Path of the Berserker.md +21 -0
- package/systems/5e/world/topics/Phantasmal Killer.md +15 -0
- package/systems/5e/world/topics/Phantom Steed.md +15 -0
- package/systems/5e/world/topics/Planar Ally.md +22 -0
- package/systems/5e/world/topics/Planar Binding.md +16 -0
- package/systems/5e/world/topics/Plane Shift.md +16 -0
- package/systems/5e/world/topics/Plant Growth.md +18 -0
- package/systems/5e/world/topics/Poison Spray.md +14 -0
- package/systems/5e/world/topics/Polymorph.md +21 -0
- package/systems/5e/world/topics/Power Word Kill.md +12 -0
- package/systems/5e/world/topics/Power Word Stun.md +14 -0
- package/systems/5e/world/topics/Prayer of Healing.md +14 -0
- package/systems/5e/world/topics/Prestidigitation.md +21 -0
- package/systems/5e/world/topics/Prismatic Spray.md +28 -0
- package/systems/5e/world/topics/Prismatic Wall.md +34 -0
- package/systems/5e/world/topics/Private Sanctum.md +25 -0
- package/systems/5e/world/topics/Produce Flame.md +16 -0
- package/systems/5e/world/topics/Programmed Illusion.md +18 -0
- package/systems/5e/world/topics/Project Image.md +19 -0
- package/systems/5e/world/topics/Protection from Energy.md +13 -0
- package/systems/5e/world/topics/Protection from Evil and Good.md +15 -0
- package/systems/5e/world/topics/Protection from Poison.md +14 -0
- package/systems/5e/world/topics/Purify Food and Drink.md +13 -0
- package/systems/5e/world/topics/Raise Dead.md +18 -0
- package/systems/5e/world/topics/Ranger Spells.md +57 -0
- package/systems/5e/world/topics/Ranger.md +162 -0
- package/systems/5e/world/topics/Ray of Enfeeblement.md +15 -0
- package/systems/5e/world/topics/Ray of Frost.md +14 -0
- package/systems/5e/world/topics/Regenerate.md +14 -0
- package/systems/5e/world/topics/Reincarnate.md +33 -0
- package/systems/5e/world/topics/Remove Curse.md +12 -0
- package/systems/5e/world/topics/Resilient Sphere.md +19 -0
- package/systems/5e/world/topics/Resistance.md +13 -0
- package/systems/5e/world/topics/Resurrection.md +20 -0
- package/systems/5e/world/topics/Reverse Gravity.md +17 -0
- package/systems/5e/world/topics/Revivify.md +12 -0
- package/systems/5e/world/topics/Rogue.md +114 -0
- package/systems/5e/world/topics/Rope Trick.md +18 -0
- package/systems/5e/world/topics/Sacred Flame.md +14 -0
- package/systems/5e/world/topics/Sanctuary.md +14 -0
- package/systems/5e/world/topics/School of Evocation.md +24 -0
- package/systems/5e/world/topics/Scorching Ray.md +16 -0
- package/systems/5e/world/topics/Scrying.md +31 -0
- package/systems/5e/world/topics/Secret Chest.md +16 -0
- package/systems/5e/world/topics/See Invisibility.md +12 -0
- package/systems/5e/world/topics/Seeming.md +18 -0
- package/systems/5e/world/topics/Sending.md +14 -0
- package/systems/5e/world/topics/Sequester.md +16 -0
- package/systems/5e/world/topics/Shapechange.md +23 -0
- package/systems/5e/world/topics/Shatter.md +16 -0
- package/systems/5e/world/topics/Shield (spell).md +13 -0
- package/systems/5e/world/topics/Shield of Faith.md +13 -0
- package/systems/5e/world/topics/Shillelagh.md +12 -0
- package/systems/5e/world/topics/Shocking Grasp.md +14 -0
- package/systems/5e/world/topics/Silence.md +14 -0
- package/systems/5e/world/topics/Silent Image.md +17 -0
- package/systems/5e/world/topics/Simulacrum.md +18 -0
- package/systems/5e/world/topics/Sleep.md +18 -0
- package/systems/5e/world/topics/Sleet Storm.md +17 -0
- package/systems/5e/world/topics/Slow.md +19 -0
- package/systems/5e/world/topics/Sorcerer Spells.md +155 -0
- package/systems/5e/world/topics/Sorcerer.md +166 -0
- package/systems/5e/world/topics/Spare the Dying.md +12 -0
- package/systems/5e/world/topics/Speak with Animals.md +13 -0
- package/systems/5e/world/topics/Speak with Dead.md +14 -0
- package/systems/5e/world/topics/Speak with Plants.md +20 -0
- package/systems/5e/world/topics/Spider Climb.md +13 -0
- package/systems/5e/world/topics/Spike Growth.md +15 -0
- package/systems/5e/world/topics/Spirit Guardians.md +17 -0
- package/systems/5e/world/topics/Spiritual Weapon.md +18 -0
- package/systems/5e/world/topics/Stinking Cloud.md +17 -0
- package/systems/5e/world/topics/Stone Shape.md +12 -0
- package/systems/5e/world/topics/Stoneskin.md +13 -0
- package/systems/5e/world/topics/Storm of Vengeance.md +23 -0
- package/systems/5e/world/topics/Suggestion.md +19 -0
- package/systems/5e/world/topics/Sunbeam.md +17 -0
- package/systems/5e/world/topics/Sunburst.md +16 -0
- package/systems/5e/world/topics/Symbol.md +36 -0
- package/systems/5e/world/topics/Telekinesis.md +23 -0
- package/systems/5e/world/topics/Telepathic Bond.md +15 -0
- package/systems/5e/world/topics/Teleport.md +38 -0
- package/systems/5e/world/topics/Teleportation Circle.md +16 -0
- package/systems/5e/world/topics/Thaumaturgy.md +21 -0
- package/systems/5e/world/topics/The Fiend.md +47 -0
- package/systems/5e/world/topics/Thief.md +24 -0
- package/systems/5e/world/topics/Thunderwave.md +16 -0
- package/systems/5e/world/topics/Time Stop.md +14 -0
- package/systems/5e/world/topics/Tiny Hut.md +17 -0
- package/systems/5e/world/topics/Tongues.md +12 -0
- package/systems/5e/world/topics/Transport via Plants.md +12 -0
- package/systems/5e/world/topics/Tree Stride.md +15 -0
- package/systems/5e/world/topics/True Polymorph.md +29 -0
- package/systems/5e/world/topics/True Resurrection.md +16 -0
- package/systems/5e/world/topics/True Seeing.md +12 -0
- package/systems/5e/world/topics/True Strike.md +13 -0
- package/systems/5e/world/topics/Unseen Servant.md +17 -0
- package/systems/5e/world/topics/Vampiric Touch.md +15 -0
- package/systems/5e/world/topics/Vicious Mockery.md +14 -0
- package/systems/5e/world/topics/Wall of Fire.md +19 -0
- package/systems/5e/world/topics/Wall of Force.md +15 -0
- package/systems/5e/world/topics/Wall of Ice.md +19 -0
- package/systems/5e/world/topics/Wall of Stone.md +23 -0
- package/systems/5e/world/topics/Wall of Thorns.md +19 -0
- package/systems/5e/world/topics/Warding Bond.md +14 -0
- package/systems/5e/world/topics/Warlock Spells.md +99 -0
- package/systems/5e/world/topics/Warlock.md +283 -0
- package/systems/5e/world/topics/Water Breathing.md +13 -0
- package/systems/5e/world/topics/Water Walk.md +15 -0
- package/systems/5e/world/topics/Way of the Open Hand.md +25 -0
- package/systems/5e/world/topics/Web.md +21 -0
- package/systems/5e/world/topics/Weird.md +13 -0
- package/systems/5e/world/topics/Wind Walk.md +14 -0
- package/systems/5e/world/topics/Wind Wall.md +17 -0
- package/systems/5e/world/topics/Wish.md +26 -0
- package/systems/5e/world/topics/Wizard Spells.md +239 -0
- package/systems/5e/world/topics/Wizard.md +141 -0
- package/systems/5e/world/topics/Word of Recall.md +14 -0
- package/systems/5e/world/topics/Zone of Truth.md +14 -0
|
@@ -0,0 +1,1017 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { requireCampaign } from "../campaign/index.js";
|
|
3
|
+
import { fieldsRead, listItemNames, numberOf } from "../core/formula.js";
|
|
4
|
+
import { appendLogEntry } from "../core/log.js";
|
|
5
|
+
import { defineCommand } from "../core/registry.js";
|
|
6
|
+
import { comparableText, sameName, valueNamed, withArticle } from "../core/text.js";
|
|
7
|
+
import { formatCounter, readCounter } from "../counters/index.js";
|
|
8
|
+
import { readEffects } from "../effects/index.js";
|
|
9
|
+
import { formatAmount, namesCoins, readAmount } from "../money/index.js";
|
|
10
|
+
import { alwaysInList, baseFieldOf, baseValue, changedEnvironment, changedFields, checkFieldChange, concernsPlayer, declaredKind, derivedPages, environmentAt, equippedNotes, findPage, followersOf, formatEnvironmentEntry, hidingMessage, holdsField, inheritedFields, introductionFollowersOf, introductionText, isCounterField, isDeclaredUncounted, isEmptyField, isNeverInherited, isPartyMember, isStandardField, keepersOf, linksIn, listFieldNames, misspelledFieldProblem, misspelledStandardField, namesakes, newBaseProblem, newPageFields, ownFieldChange, pageNamed, pagesWithName, pageTypeNames, parsePageReference, readEnvironment, readFieldValue, readOnlyMessage, removedFieldName, sameAsBase, sectionFollowersOf, systemKnowsReason, systemLabel, systemPageNamed, textParts, withPage, writePage, writtenFieldName, } from "../pages/index.js";
|
|
11
|
+
import { featureParts, mergedFeatures } from "../progression/index.js";
|
|
12
|
+
import { effectiveFields, effectsField } from "./effect-layer.js";
|
|
13
|
+
import { conditionNotes, consequencesNotes, eventFieldsProblem, fireEvents, heldNote, nowDueNote, plannedEventDateProblem } from "./game-events.js";
|
|
14
|
+
import { experienceOf, leastNotes, newLevelNotes } from "./game-experience.js";
|
|
15
|
+
import { gridNotes } from "./game-grids.js";
|
|
16
|
+
import { hereFields, hereOf, placeHere, positionToWrite, writeOrigin } from "./game-here.js";
|
|
17
|
+
import { questPictureNote } from "./game-images.js";
|
|
18
|
+
import { campaignCoins } from "./game-money.js";
|
|
19
|
+
import { mapLookNotes, shortRoadNotes } from "./game-positions.js";
|
|
20
|
+
import { logQuestChange, newQuestNotes, tickedQuestNotes } from "./game-quests.js";
|
|
21
|
+
import { deathNotes, isPlayerDead } from "./game-saves.js";
|
|
22
|
+
import { checkNotCopied, copyChanges, copyLink, copyName, copyPage, readOpenScene, updateScene } from "./game-scene.js";
|
|
23
|
+
import { thresholdNotes } from "./game-thresholds.js";
|
|
24
|
+
import { newDatedLogEntry, readPresent } from "./game-time.js";
|
|
25
|
+
import { campaignSystem, campaignWorld, foreignSystemLanguage, leftOutNote, missingSystemHint, systemBaseHint, systemProblems, systemSearchHint } from "./game-world.js";
|
|
26
|
+
import { followersNote, keepersNote, skippedPagesNote } from "./notes.js";
|
|
27
|
+
import { partText, situationPart } from "./parts.js";
|
|
28
|
+
import { givenItems, progressionOf } from "./progression.js";
|
|
29
|
+
import { checkListValues, joiningNotes } from "./sheet.js";
|
|
30
|
+
import { computedField, counterLimits, hasSheet, isUse, itemLines, maximumHint, maximumSources, missingUses, signedText, startingAtZero, workedMaximum, workedParts } from "./worked-out.js";
|
|
31
|
+
// How the output names each way of writing text.
|
|
32
|
+
const textChanges = { replace: "wrote", append: "added to", remove: "removed" };
|
|
33
|
+
// Fields arrive as field=value strings from a command line, or as an object through MCP.
|
|
34
|
+
const fieldValues = z.union([z.array(z.string()), z.record(z.string(), z.unknown())]);
|
|
35
|
+
export const write = defineCommand({
|
|
36
|
+
name: "write",
|
|
37
|
+
description: "Create or change a page of the world. "
|
|
38
|
+
+ "Pages are written only with this command: use it as soon as something becomes true in the story, such as a new character, place or item, or a change. "
|
|
39
|
+
+ "The introduction and sections are what the player reads once their character knows the page: what the character has not found out goes in a secret. "
|
|
40
|
+
+ 'A list field changes one item at a time with add and remove; a place\'s environment one line at a time, such as add environment="light: dark". '
|
|
41
|
+
+ 'A place\'s position is given from a place that has one, such as set position="12 km NE of [[Guadoscuro]]"; a region has a radius, such as set radius=10 km. '
|
|
42
|
+
+ 'A place with a map of its own, as another plane, has set map=own; map="under [[Guadoscuro]]" for the underdark, "mirror of [[Guadoscuro]]" for a plane that mirrors the land; '
|
|
43
|
+
+ "a passage between maps is a road with kind=portal. Moving someone or something goes with move, what a character learns with reveal, conditions and forms with effect. "
|
|
44
|
+
+ 'Example: page Bruna, type character, set role=npc, text "A ferry keeper who knows every current of the river."',
|
|
45
|
+
group: "game",
|
|
46
|
+
positionals: ["page"],
|
|
47
|
+
input: z.object({
|
|
48
|
+
page: z.string().describe("name of the page, or of a copy in the open scene; Page#Name writes one section or one secret"),
|
|
49
|
+
type: z.enum(pageTypeNames).optional().describe(`type of a new page, or the new type of one that exists: ${pageTypeNames.map(typeText).join(", ")}`),
|
|
50
|
+
set: fieldValues.optional().describe('fields to set: field=value, repeatable, or an object such as {"role": "npc"}. A link is [[Page]], links are [[A]], [[B]], a list is [a, b]'),
|
|
51
|
+
unset: z.array(z.string()).optional().describe("fields to remove, repeatable"),
|
|
52
|
+
add: fieldValues.optional().describe("items to add to list fields: field=value, repeatable, such as factions=[[Barcaioli]], or an object"),
|
|
53
|
+
remove: fieldValues.optional().describe("items to remove from list fields: field=value, repeatable, or an object"),
|
|
54
|
+
text: z.string().optional().describe("text of the introduction, the part before the first heading; or of the section or secret given"),
|
|
55
|
+
section: z.string().optional().describe("section or secret to write; a missing one is added at the end of the page, as a secret if secret is given; empty text removes it"),
|
|
56
|
+
append: z.boolean().optional().describe("add the text at the end instead of replacing"),
|
|
57
|
+
secret: z.boolean().optional().describe("with a section: create it, if missing, as a secret block > [!secret] Name"),
|
|
58
|
+
here: z.boolean().optional().describe("a new place where the journey stopped: on a road it becomes a stop there, in the open it takes the point; those who travel go into it"),
|
|
59
|
+
}),
|
|
60
|
+
run(input, context) {
|
|
61
|
+
const root = requireCampaign(context.folder);
|
|
62
|
+
const reference = parsePageReference(input.page);
|
|
63
|
+
if (input.section !== undefined && reference.section !== undefined) {
|
|
64
|
+
throw new Error("Give the section either in the page name (Page#Section) or as section, not both.");
|
|
65
|
+
}
|
|
66
|
+
const section = input.section ?? reference.section;
|
|
67
|
+
if ((section !== undefined || input.append) && input.text === undefined) {
|
|
68
|
+
throw new Error("Give the text to write.");
|
|
69
|
+
}
|
|
70
|
+
const { coins, problem: coinsProblem } = campaignCoins(root, context);
|
|
71
|
+
const world = campaignWorld(root, context);
|
|
72
|
+
const system = campaignSystem(root, context);
|
|
73
|
+
const open = readOpenScene(root);
|
|
74
|
+
const copy = copyName(world, open.scene, reference.name);
|
|
75
|
+
// A field the game system works out is never written, on a page or a copy: the rest is (FOR-7, D-200).
|
|
76
|
+
const target = open.scene !== undefined && copy !== undefined ? copyPage(copy, open.scene.copies[copy]) : findPage(world, reference.name);
|
|
77
|
+
const given = input.set === undefined ? undefined : withoutComputed(world, target, oneItemLists(world, readFields(input.set), input.type ?? target?.type), reference.name, input.type);
|
|
78
|
+
const added = listsWithoutComputed(world, target, input.add === undefined ? undefined : readListItems(input.add));
|
|
79
|
+
const removed = listsWithoutComputed(world, target, input.remove === undefined ? undefined : readListItems(input.remove));
|
|
80
|
+
let fields = {
|
|
81
|
+
set: given === undefined || Object.keys(given.set).length === 0 ? undefined : asAmounts(world, given.set, coins),
|
|
82
|
+
unset: input.unset,
|
|
83
|
+
add: added.lists,
|
|
84
|
+
remove: removed.lists,
|
|
85
|
+
};
|
|
86
|
+
const computedNotes = [given?.note, added.note, removed.note].filter((note) => note !== undefined);
|
|
87
|
+
// Only fields the system works out, and nothing else to write: said as such.
|
|
88
|
+
if (computedNotes.length > 0 && [fields.set, fields.add, fields.remove, input.type, input.unset, input.text].every((part) => part === undefined)) {
|
|
89
|
+
throw new Error(`${computedNotes.join(" ")} Nothing changed.`);
|
|
90
|
+
}
|
|
91
|
+
const moneyNotes = [...computedNotes, ...moneyNotesOf(world, reference.name, fields.set, coins, coinsProblem)];
|
|
92
|
+
if (open.scene !== undefined && copy !== undefined) {
|
|
93
|
+
checkNoEffects(fields, copyPage(copy, open.scene.copies[copy]));
|
|
94
|
+
const written = writeCopy(root, world, context, open.scene, copy, { type: input.type, text: input.text, section }, fields, coins);
|
|
95
|
+
return { ...written, notes: [...written.notes, ...moneyNotes] };
|
|
96
|
+
}
|
|
97
|
+
const existing = findPage(world, reference.name);
|
|
98
|
+
// The campaign only reads the pages of its game system, and never hides one with a page of its own (SYS-4, SYS-6).
|
|
99
|
+
if (existing?.system !== undefined) {
|
|
100
|
+
const given = givenFields(fields.set, baseFieldOf(input.type ?? existing.type));
|
|
101
|
+
throw new Error(input.type === undefined || alwaysInList(world, existing) !== undefined ? readOnlyMessage(existing, given, world) : hidingMessage(existing, input.type, given));
|
|
102
|
+
}
|
|
103
|
+
checkNoEffects(fields, existing);
|
|
104
|
+
if (existing === undefined && input.type === undefined) {
|
|
105
|
+
const copies = open.scene === undefined ? "" : ", nor a copy of that name in the open scene";
|
|
106
|
+
const unreadable = open.problem === undefined ? "" : " The open scene in state.yaml cannot be read, so a copy in it cannot be written: validate shows how to fix it.";
|
|
107
|
+
const aliased = pageNamed(world, reference.name);
|
|
108
|
+
// An alias of a page of the system leads to a page that is read-only (SYS-6).
|
|
109
|
+
const readOnly = aliased?.system === undefined ? "" : ` ${readOnlyMessage(aliased).replace(/ Nothing changed\.$/, "")}`;
|
|
110
|
+
const alias = aliased === undefined ? "" : readOnly !== "" ? ` ${reference.name} is an alias of ${aliased.name}.${readOnly}` : ` ${reference.name} is an alias of ${aliased.name}: to change that page, write ${aliased.name}.`;
|
|
111
|
+
throw new Error(`There is no page named ${reference.name}${copies}.${alias} To create it, give its type: ${pageTypeNames.join(", ")}.${unreadable}${missingSystemHint(world) || systemSearchHint(world)}`);
|
|
112
|
+
}
|
|
113
|
+
const type = input.type ?? existing?.type;
|
|
114
|
+
// A place written where the journey stopped, and a position given as relations, turned into the coordinates to write (TRV-14, POS-2).
|
|
115
|
+
const here = input.here === true ? hereOf(root, world, reference.name, existing, type, fields) : undefined;
|
|
116
|
+
const positioned = type === "place" ? positionToWrite(world, reference.name, here === undefined ? fields : hereFields(here, fields)) : { change: fields, placed: { notes: [] } };
|
|
117
|
+
fields = positioned.change;
|
|
118
|
+
conditionsAsList(existing, fields);
|
|
119
|
+
checkFieldNames(type, fields);
|
|
120
|
+
checkFieldChange(type, fields);
|
|
121
|
+
environmentAsSet(world, existing, type, reference.name, fields);
|
|
122
|
+
checkValues(fields);
|
|
123
|
+
checkListValues(system, fields.set ?? {});
|
|
124
|
+
checkListValues(system, fields.add ?? {});
|
|
125
|
+
checkKnows(world, fields);
|
|
126
|
+
if (existing !== undefined) {
|
|
127
|
+
if (changedFieldNames(fields).some(passesOn)) {
|
|
128
|
+
checkNotCopied(world, open, existing);
|
|
129
|
+
}
|
|
130
|
+
checkCounters(world, existing, fields.set, coins);
|
|
131
|
+
checkUses(world, system, existing, fields.set);
|
|
132
|
+
checkClearedCounters(world, existing, ownFieldChange(world, existing, fields), coins);
|
|
133
|
+
checkLists(world, existing, fields);
|
|
134
|
+
}
|
|
135
|
+
checkBase(world, existing?.name ?? reference.name, type, fields.set);
|
|
136
|
+
checkEventFields(world, existing, type, fields);
|
|
137
|
+
const written = writePage(world, reference.name, input.type, { ...fields, text: input.text, section, append: input.append, secret: input.secret });
|
|
138
|
+
const { created, changed, mode, retyped } = written;
|
|
139
|
+
if (changed) {
|
|
140
|
+
const data = { page: written.page.name, type: written.page.type, was: retyped?.type, ...fields, section, secret: input.secret, text: input.text, mode };
|
|
141
|
+
appendLogEntry(root, newDatedLogEntry(root, context, created ? "page-created" : "page-updated", "hidden", data));
|
|
142
|
+
}
|
|
143
|
+
// The maximum of a counter follows what the game system works out from the page as it is now (FOR-6).
|
|
144
|
+
// While what campaign.yaml adds does not read, the maxima are not judged by the system alone (CAM-3).
|
|
145
|
+
const additionsRead = systemProblems(root, context).length === 0;
|
|
146
|
+
const limits = changed && additionsRead ? workedLimitsChange(system, world, existing, withPage(world, existing, written.page), written.page) : { set: {}, unset: [], notes: [] };
|
|
147
|
+
const worked = Object.keys(limits.set).length > 0 || limits.unset.length > 0;
|
|
148
|
+
const page = worked ? writePage(withPage(world, existing, written.page), written.page.name, undefined, { set: limits.set, unset: limits.unset }).page : written.page;
|
|
149
|
+
if (page !== written.page) {
|
|
150
|
+
appendLogEntry(root, newDatedLogEntry(root, context, "page-updated", "hidden", { page: page.name, set: limits.set, unset: limits.unset, why: "worked out by the game system" }));
|
|
151
|
+
}
|
|
152
|
+
const placed = positioned.placed.origin === undefined ? withPage(world, existing, page) : writeOrigin(root, context, withPage(world, existing, page), positioned.placed.origin);
|
|
153
|
+
const hereDone = here === undefined || !changed ? undefined : placeHere(root, context, placed, page, here);
|
|
154
|
+
const now = hereDone?.world ?? placed;
|
|
155
|
+
const followers = changed ? followersOfWrite(now, existing, page) : { pages: [], why: "" };
|
|
156
|
+
if (changed && existing !== undefined) {
|
|
157
|
+
const self = changedNames(existing, page).some((field) => comparableText(field) === baseFieldOf(page.type)) ? [[existing, page]] : [];
|
|
158
|
+
logCounterChanges(root, context, world, now, [...self, ...followers.pages.map((follower) => [follower, follower])], coins);
|
|
159
|
+
}
|
|
160
|
+
const notes = !changed ? [] : existing === undefined ? [...derivesFromNothingNote(now, page), ...noBaseNote(now, page)] : [...keepersNotes(now, existing, page), ...ownValuesNote(now, existing, page, coins)];
|
|
161
|
+
const dropped = changed ? droppedFromBaseNotes(now, page, fields.set) : [];
|
|
162
|
+
const story = changed && changedFieldNames(fields).length > 0 ? storyNotes(root, context, world, now, existing, page) : [];
|
|
163
|
+
const was = retyped === undefined ? undefined : `${retyped.type ?? "free page"} (${retyped.path})`;
|
|
164
|
+
const result = { page: page.name, type: page.type, path: page.path, created, changed, ...writtenNames(world, existing, fields, page), section, mode, was };
|
|
165
|
+
// Said when the character joins the party, here or by being created in it; then status says it until the sheet is whole (DAT-2).
|
|
166
|
+
const joined = changed && (created || existing?.fields.role !== page.fields.role);
|
|
167
|
+
const sheet = joined ? joiningNotes(system, now, page) : [];
|
|
168
|
+
const gained = changed && existing !== undefined ? progressionNotes(context, system, world, now, existing, page, Object.keys(fields.set ?? {})) : [];
|
|
169
|
+
const thresholds = changed && isPartyMember(page) ? writtenThresholdNotes(root, system, world, now, existing, page, fields.set ?? {}) : [];
|
|
170
|
+
// Points that reach a level the classes have not, or fall short of the level's (PRO-6); a field the settings leave out (DAT-10).
|
|
171
|
+
const [before, after] = [existing === undefined ? undefined : experienceOf(world, effectiveFields(world, existing)), experienceOf(now, effectiveFields(now, page))];
|
|
172
|
+
const companion = page.fields.role === "companion";
|
|
173
|
+
const levelUp = () => situationPart("level-up", context, system);
|
|
174
|
+
const levels = changed && isPartyMember(page) ? [...newLevelNotes(page.name, before, after, companion, levelUp), ...leastNotes(page.name, before, after)] : [];
|
|
175
|
+
const leftOut = changed ? changedFieldNames(fields).flatMap((field) => leftOutNote(root, context, field) ?? []) : [];
|
|
176
|
+
const opened = newQuestNotes(existing, page);
|
|
177
|
+
const picture = opened.length === 0 ? undefined : questPictureNote(root, context, now, page);
|
|
178
|
+
const quest = changed ? [...questEndNotes(context, system, existing, page), ...tickedQuestNotes(existing, page), ...opened, ...(picture === undefined ? [] : [picture])] : [];
|
|
179
|
+
// The story tells a quest taken, done or failed (D-369).
|
|
180
|
+
if (changed) {
|
|
181
|
+
logQuestChange(root, context, existing, page);
|
|
182
|
+
}
|
|
183
|
+
const twice = changed && page.type === "character" ? givenListNotes(now, page, changedFieldNames(fields)) : [];
|
|
184
|
+
const shadows = changed && section !== undefined ? featureSectionNotes(now, page, section) : [];
|
|
185
|
+
const links = changed ? linkNotes(now, fields) : [];
|
|
186
|
+
// What a character has equipped and does not carry, as after its armor left its inventory (CON-12); what a formula reads of two items (PRO-5).
|
|
187
|
+
const carried = changed && page.type === "character" && changedFieldNames(fields).some((field) => ["inventory", "equipped"].includes(comparableText(field))) ? equippedNotes(now, page) : [];
|
|
188
|
+
const firsts = changed && page.type === "character" ? firstItemNotes(world, now, existing, page) : [];
|
|
189
|
+
const map = changed
|
|
190
|
+
? [
|
|
191
|
+
...positioned.placed.notes,
|
|
192
|
+
...(hereDone?.notes ?? []),
|
|
193
|
+
...(["stops", "position"].some((field) => changedFieldNames(fields).includes(field)) ? shortRoadNotes(now, page) : []),
|
|
194
|
+
...mapLookNotes(world, existing, now, page),
|
|
195
|
+
...(page.type === "place" ? gridNotes(existing, page) : []),
|
|
196
|
+
]
|
|
197
|
+
: [];
|
|
198
|
+
// The death of the last living player character: what the saves allow, said at once (SAV-7).
|
|
199
|
+
const death = changed && isPlayerDead(now) && !isPlayerDead(world) ? deathNotes(root, page.name) : [];
|
|
200
|
+
return {
|
|
201
|
+
...result,
|
|
202
|
+
followers: followers.pages.map((follower) => follower.name),
|
|
203
|
+
followersWhy: followers.why,
|
|
204
|
+
notes: [...story, ...notes, ...dropped, ...limits.notes, ...gained, ...thresholds, ...levels, ...leftOut, ...quest, ...twice, ...shadows, ...links, ...carried, ...firsts, ...map, ...sheet, ...death, ...moneyNotes],
|
|
205
|
+
problems: world.problems,
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
format(result) {
|
|
209
|
+
const where = result.section === undefined ? "the introduction" : `"${result.section}"`;
|
|
210
|
+
const changes = [
|
|
211
|
+
result.set.length > 0 ? `set ${result.set.join(", ")}` : "",
|
|
212
|
+
...result.again,
|
|
213
|
+
result.unset.length > 0 ? `removed ${result.unset.join(", ")}` : "",
|
|
214
|
+
result.add.length > 0 ? `added to ${result.add.join(", ")}` : "",
|
|
215
|
+
result.remove.length > 0 ? `removed from ${result.remove.join(", ")}` : "",
|
|
216
|
+
result.mode === undefined ? "" : `${textChanges[result.mode]} ${where}`,
|
|
217
|
+
result.was === undefined ? "" : `was ${withArticle(result.was)}`,
|
|
218
|
+
].filter((change) => change !== "");
|
|
219
|
+
const summary = result.changed ? changeSummary(result, changes) : `Nothing changed on ${result.page}: it already reads like that.`;
|
|
220
|
+
const followers = followersNote(result.followers, result.followersWhy, "write");
|
|
221
|
+
return [[summary, followers, ...result.notes].filter((line) => line !== "").join("\n"), skippedPagesNote(result.problems)].filter((block) => block !== "").join("\n\n");
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
/** A type as the parameter names it, with what tells a place from an item (D-155, D-156). */
|
|
225
|
+
function typeText(type) {
|
|
226
|
+
return type === "place" ? "place (a room, a house, a ship, a region)" : type === "item" ? "item (a thing, even one with someone inside, such as a cart)" : type;
|
|
227
|
+
}
|
|
228
|
+
/** Writes the fields of a copy in the open scene (INH-14): only what differs from its creature, never its text, the creature it copies or fixed fields. */
|
|
229
|
+
function writeCopy(root, world, context, scene, name, input, requested, coins) {
|
|
230
|
+
const creature = copyLink(scene.copies[name]);
|
|
231
|
+
if (input.type !== undefined) {
|
|
232
|
+
throw new Error(`${name} is a copy of ${creature} in the open scene, not a page: leave out type to write its fields, or give the new page another name.`);
|
|
233
|
+
}
|
|
234
|
+
if (input.text !== undefined || input.section !== undefined) {
|
|
235
|
+
throw new Error(`${name} is a copy of ${creature} in the open scene: it has no text of its own, only fields that differ from its creature, such as set frightened=true. ${promotion(name, creature, "text")}`);
|
|
236
|
+
}
|
|
237
|
+
const names = changedFieldNames(requested);
|
|
238
|
+
if (names.length === 0) {
|
|
239
|
+
throw new Error(`Nothing to write on ${name}: give fields to set, unset, add or remove.`);
|
|
240
|
+
}
|
|
241
|
+
names.forEach((field) => checkCopyField(name, creature, field));
|
|
242
|
+
checkFieldChange("creature", requested);
|
|
243
|
+
checkValues(requested);
|
|
244
|
+
const before = copyPage(name, scene.copies[name]);
|
|
245
|
+
checkCounters(world, before, requested.set, coins);
|
|
246
|
+
const change = ownFieldChange(world, before, requested);
|
|
247
|
+
checkClearedCounters(world, before, change, coins);
|
|
248
|
+
checkLists(world, before, requested);
|
|
249
|
+
(requested.unset ?? []).forEach((field, index) => {
|
|
250
|
+
if (!Object.hasOwn(before.fields, change.unset?.[index] ?? field)) {
|
|
251
|
+
const base = baseValue(world, before, field);
|
|
252
|
+
throw new Error(`${name} has no field "${field}" to remove${base === undefined ? "" : `: it reads ${base.name} from ${base.base.name}, and holds no value of its own`}.`);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
const own = copyChanges(scene.copies[name]);
|
|
256
|
+
const written = changedFields(own, change);
|
|
257
|
+
const virtual = copyPage(name, { creature, ...written });
|
|
258
|
+
const touched = [...Object.keys(change.set ?? {}), ...Object.keys(change.add ?? {}), ...Object.keys(change.remove ?? {})];
|
|
259
|
+
const after = Object.fromEntries(Object.entries(written).filter(([field]) => !touched.includes(field) || !sameAsBase(world, virtual, field)));
|
|
260
|
+
const changed = [...new Set([...Object.keys(own), ...Object.keys(after)])].filter((field) => JSON.stringify(own[field]) !== JSON.stringify(after[field]));
|
|
261
|
+
if (changed.length > 0) {
|
|
262
|
+
updateScene(root, changed.map((field) => [["copies", name, field], after[field]]));
|
|
263
|
+
appendLogEntry(root, newDatedLogEntry(root, context, "page-updated", "hidden", { page: name, copy: true, ...requested }));
|
|
264
|
+
}
|
|
265
|
+
const result = { page: name, type: before.type, path: "state.yaml", created: false, changed: changed.length > 0, ...writtenNames(world, before, requested, copyPage(name, { creature, ...after })) };
|
|
266
|
+
return { ...result, copyOf: creature, followers: [], followersWhy: "", notes: [], problems: world.problems };
|
|
267
|
+
}
|
|
268
|
+
/** A copy holds only what it would otherwise read from its creature: not the creature it copies, nor fixed fields or relations (INH-8, INH-14). */
|
|
269
|
+
function checkCopyField(name, creature, field) {
|
|
270
|
+
if (comparableText(field) === "creature") {
|
|
271
|
+
throw new Error(`creature is what ${name} copies, and it does not change: to bring in another creature, add a copy of it with scene add.`);
|
|
272
|
+
}
|
|
273
|
+
if (isStandardField("creature", field) || misspelledStandardField("creature", field) !== undefined || isNeverInherited(field)) {
|
|
274
|
+
throw new Error(`${field} is not a field a copy holds: a copy holds only what differs from its creature, ${creature}, such as a counter (conditions and forms go with effect). ${promotion(name, creature, field)}`);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/** How a copy becomes a page of its own, to hold text, relations or a status: it leaves the scene and comes back as a character (D-110, INH-14). */
|
|
278
|
+
function promotion(name, creature, what) {
|
|
279
|
+
return `To give it ${what} of its own, make it a character: show it to read its values, let it leave with scene leave, write ${name} as a character with creature=${creature} and those values, then bring it back with scene add.`;
|
|
280
|
+
}
|
|
281
|
+
/** Effects change only with the effect command, which works out when they end and says what they change (EFF-6). */
|
|
282
|
+
function checkNoEffects(change, page) {
|
|
283
|
+
// Effects that do not read can only be removed by hand, or with unset: effect will not write over them (EFF-12).
|
|
284
|
+
const unreadable = page !== undefined && "problem" in readEffects(effectsField(page));
|
|
285
|
+
const onlyUnset = Object.keys(change.set ?? {}).every((name) => comparableText(name) !== "effects") && [change.add, change.remove].every((items) => Object.keys(items ?? {}).every((name) => comparableText(name) !== "effects"));
|
|
286
|
+
if (unreadable && onlyUnset) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
if (changedFieldNames(change).some((name) => comparableText(name) === "effects")) {
|
|
290
|
+
throw new Error('effects change only with the effect command: action add with a name, for how long and the values it gives, such as name "Scudo della fede", for 10m, set bonus-ac=2; action end to take one away. Nothing changed.');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/** A standard field written in another case, such as Status, would be a field the engine does not read: refused before any other check. */
|
|
294
|
+
function checkFieldNames(type, change) {
|
|
295
|
+
const names = [...Object.keys(change.set ?? {}), ...Object.keys(change.add ?? {}), ...Object.keys(change.remove ?? {})];
|
|
296
|
+
const problem = names.map((name) => misspelledFieldProblem(type, name)).find((found) => found !== undefined);
|
|
297
|
+
if (problem !== undefined) {
|
|
298
|
+
throw new Error(problem);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/** A counter written as current/max changes only with change, which keeps its limits and logs why (D-92, INH-11). A value that is not a counter makes it something else: on a page that holds it alone, only after unset. */
|
|
302
|
+
function checkCounters(world, page, set, coins) {
|
|
303
|
+
const fields = inheritedFields(world, page);
|
|
304
|
+
for (const [field, value] of Object.entries(set ?? {}).filter(([, candidate]) => !isEmptyField(candidate))) {
|
|
305
|
+
const name = sameName(Object.keys(fields), field);
|
|
306
|
+
const declared = name !== undefined && startingAtZero(world, name) !== undefined;
|
|
307
|
+
const current = name === undefined || (isStandardField(page.type, name) && !isCounterField(page.type, name)) || isDeclaredUncounted(world, name) ? undefined : guarded(fields[name], coins, declared);
|
|
308
|
+
const next = current === undefined ? undefined : sameKind(current.kind, value, coins);
|
|
309
|
+
const base = baseValue(world, page, field);
|
|
310
|
+
if (name === undefined || current === undefined || next === current.text || (next === undefined && base !== undefined)) {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
const hint = current.kind === "counter" ? maximumHint(world, effectiveFields(world, page), name) : undefined;
|
|
314
|
+
throw new Error(hint === undefined ? guardedMessage(page, name, current.kind, next, holdsField(page, name) ? undefined : base?.base, base !== undefined) : workedCounterMessage(page, name, hint));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* A use of the system that the character does not get, as a warlock's spell-slots on a wizard or slots on a warlock, is never written by hand (DAT-9): only on who
|
|
319
|
+
* keeps the sheet and has the system's lists, and never for one the sheet asks to write, as hp.
|
|
320
|
+
*/
|
|
321
|
+
function checkUses(world, system, page, set) {
|
|
322
|
+
const list = world.system?.progression?.list;
|
|
323
|
+
const after = { ...page, fields: { ...page.fields, ...set } };
|
|
324
|
+
const fields = inheritedFields(world, after);
|
|
325
|
+
const classes = list === undefined ? undefined : valueNamed(fields, list);
|
|
326
|
+
if (list === undefined || isEmptyField(classes) || !hasSheet(world, after)) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const asked = new Set((system?.sheet ?? []).flatMap((group) => group.entries.filter((entry) => !entry.optional).map((entry) => comparableText(entry.field))));
|
|
330
|
+
for (const field of Object.keys(set ?? {}).filter((name) => isUse(world, name) && !asked.has(comparableText(name)))) {
|
|
331
|
+
const counted = Object.keys(world.system?.formulas?.counters ?? {}).some((name) => comparableText(name) === comparableText(field));
|
|
332
|
+
const { max } = counterLimits(world, fields, field);
|
|
333
|
+
if (counted && (max === undefined || max <= 0)) {
|
|
334
|
+
const sources = maximumSources(world, field, fields);
|
|
335
|
+
const whence = sources.length === 0 ? `the tables of its ${list}` : sources.join(", ");
|
|
336
|
+
throw new Error(`${field} is a use the system ${world.system?.name ?? ""} gives from ${whence}, and ${page.name} has none of it: it is not written by hand. Nothing changed.`);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* A value that changes only with change, as it is compared: a current/max counter (D-92), a number the game system declares a counter with no maximum (CNT-7, D-266), or money
|
|
342
|
+
* (MON-6); undefined for any other.
|
|
343
|
+
*/
|
|
344
|
+
function guarded(value, coins, declared) {
|
|
345
|
+
const counter = readCounter(value);
|
|
346
|
+
if (counter !== undefined && (counter.max !== undefined || declared)) {
|
|
347
|
+
return { kind: "counter", text: String(formatCounter(counter)) };
|
|
348
|
+
}
|
|
349
|
+
const purse = coins === undefined ? undefined : readAmount(value, coins);
|
|
350
|
+
return purse === undefined || coins === undefined ? undefined : { kind: "money", text: formatAmount(purse, coins) };
|
|
351
|
+
}
|
|
352
|
+
/** A new value read as the kind it would replace: any counter for a counter, coins for money; undefined when it is something else. */
|
|
353
|
+
function sameKind(kind, value, coins) {
|
|
354
|
+
const counter = kind === "counter" ? readCounter(value) : undefined;
|
|
355
|
+
const purse = kind === "money" && coins !== undefined ? readAmount(value, coins) : undefined;
|
|
356
|
+
return counter !== undefined ? String(formatCounter(counter)) : purse !== undefined && coins !== undefined ? formatAmount(purse, coins) : undefined;
|
|
357
|
+
}
|
|
358
|
+
/** "hp is a counter whose maximum the game system works out": what to change instead, the current value with change and the maximum through what it comes from (D-226). */
|
|
359
|
+
function workedCounterMessage(page, field, hint) {
|
|
360
|
+
const raise = hint.raise === undefined ? "" : `; to raise the maximum, write ${hint.raise}`;
|
|
361
|
+
return `${field} is a counter whose maximum the game system works out: ${hint.made}. Change its current value with change, page "${page.name}", field ${field}${raise}. Nothing changed.`;
|
|
362
|
+
}
|
|
363
|
+
function guardedMessage(page, field, kind, next, readFrom, hasBase) {
|
|
364
|
+
const what = kind === "counter" ? "a counter" : "money";
|
|
365
|
+
const other = hasBase ? `To make it something else, set a value that is not ${what}.` : "To make it something else, unset it, then set it.";
|
|
366
|
+
if (kind === "money") {
|
|
367
|
+
const from = readFrom === undefined ? "" : `, which it reads from ${readFrom.name},`;
|
|
368
|
+
const purse = next === undefined ? "sets the purse" : `"${next}" sets the purse`;
|
|
369
|
+
return `${page.name}'s ${field}${from} changes only with change, page "${page.name}", field ${field}: spend pays, by adds coins, to ${purse}${readFrom === undefined ? "" : `, on ${page.name} only`}. ${other}`;
|
|
370
|
+
}
|
|
371
|
+
const use = `use change, page "${page.name}", field ${field}, to ${next ?? "a value"} (or by an amount)`;
|
|
372
|
+
return readFrom === undefined ? `${field} is ${what}: ${use}. ${other}` : `${field} is ${what} that ${page.name} reads from ${readFrom.name}: ${use}, which writes it on ${page.name} only. ${other}`;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Removing a counter that a base also holds as current/max would bring back the base's value with no line in the log: change does it, and logs it (INH-11).
|
|
376
|
+
* `change` names the fields as the page writes them. The same field written twice in another case may lose one of the two: validate asks for it.
|
|
377
|
+
*/
|
|
378
|
+
function checkClearedCounters(world, page, change, coins) {
|
|
379
|
+
for (const own of (change.unset ?? []).filter((field) => Object.hasOwn(page.fields, field))) {
|
|
380
|
+
const current = isStandardField(page.type, own) || isDeclaredUncounted(world, own) ? undefined : guarded(page.fields[own], coins, false);
|
|
381
|
+
const base = baseValue(world, page, own);
|
|
382
|
+
const fromBase = base === undefined ? undefined : guarded(base.value, coins, false);
|
|
383
|
+
const alsoHeld = Object.keys(page.fields).some((other) => other !== own && comparableText(other) === comparableText(own) && !isEmptyField(page.fields[other]));
|
|
384
|
+
if (!alsoHeld && base !== undefined && current !== undefined && fromBase?.kind === current.kind) {
|
|
385
|
+
const value = current.kind === "counter" ? fromBase.text : `"${fromBase.text}"`;
|
|
386
|
+
const start = current.kind === "counter" ? `${own} on ${page.name} is a counter: without it,` : `Without ${own},`;
|
|
387
|
+
throw new Error(`${start} ${page.name} would read ${fromBase.text} from ${base.base.name}, with no line in the log. Use change, page "${page.name}", field ${own}, to ${value}: it takes the field off, and ${page.name} follows ${base.base.name} again.`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/** knows lists pages and secrets of the campaign: a page of the game system, which anyone may know, never (SYS-2). */
|
|
392
|
+
function checkKnows(world, change) {
|
|
393
|
+
const known = [...linksIn(change.set?.knows), ...linksIn(change.add?.knows)].map((name) => systemPageNamed(world, name)).find((page) => page !== undefined && findPage(world, page.name) === page);
|
|
394
|
+
if (known !== undefined) {
|
|
395
|
+
throw new Error(`${known.name} is ${systemLabel(known)}, ${systemKnowsReason(known)}: knows lists pages and secrets of the campaign. Nothing changed.`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
/** A character or creature derives only from a creature, an item only from an item, never in a loop (INH-2, INH-3). */
|
|
399
|
+
function checkBase(world, name, type, set) {
|
|
400
|
+
const field = baseFieldOf(type);
|
|
401
|
+
const link = field === undefined ? undefined : set?.[field];
|
|
402
|
+
const problem = typeof link === "string" ? newBaseProblem(world, { name, type }, link) : undefined;
|
|
403
|
+
if (problem !== undefined) {
|
|
404
|
+
throw new Error(`${problem} Nothing changed.`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* A planned event comes due at its date, so a write that sets its date or status cannot leave a date the engine does not read (D-100);
|
|
409
|
+
* nor a clock or conditions that do not read (EVT-10).
|
|
410
|
+
*/
|
|
411
|
+
function checkEventFields(world, page, type, change) {
|
|
412
|
+
const changed = changedFieldNames(change);
|
|
413
|
+
if (type !== "event" || !["date", "status", "clock", "when"].some((field) => changed.includes(field))) {
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const fields = changedFields(page?.fields ?? newPageFields(type), change);
|
|
417
|
+
const date = changed.includes("date") || changed.includes("status") ? plannedEventDateProblem(type, fields) : undefined;
|
|
418
|
+
const problem = date ?? eventFieldsProblem(world, fields, changed);
|
|
419
|
+
if (problem !== undefined) {
|
|
420
|
+
throw new Error(problem);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* What the write set off in the story: the events it made due or that wait for the date it gave them (EVT-4, EVT-5), conditions on pages that do not exist yet,
|
|
425
|
+
* and the planned events and quests linked to what changed (D-117, EVT-7).
|
|
426
|
+
*/
|
|
427
|
+
function storyNotes(root, context, before, after, existing, page) {
|
|
428
|
+
const present = readPresent(root).date;
|
|
429
|
+
const fired = fireEvents(root, context, before, after, present);
|
|
430
|
+
const written = fired.world.pages.find((candidate) => candidate.path === page.path) ?? page;
|
|
431
|
+
const conditions = page.type === "event" && JSON.stringify(existing?.fields.when) !== JSON.stringify(page.fields.when) ? conditionNotes(fired.world, page.fields.when) : [];
|
|
432
|
+
const consequences = consequencesNotes(fired.world, existing, written, fired.due.map((event) => event.name), present);
|
|
433
|
+
return [nowDueNote(fired.due), heldNote(fired.held), ...conditions, ...consequences].filter((note) => note !== "");
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* A place's environment changes entry by entry (ENV-4): add puts a line in, or in place of the one with its name; remove takes one out by name; set gives them all,
|
|
437
|
+
* each read as a line. The lines it ends with are set whole, or the field goes when none is left.
|
|
438
|
+
*/
|
|
439
|
+
function environmentAsSet(world, page, type, name, change) {
|
|
440
|
+
const add = change.add?.environment;
|
|
441
|
+
const remove = change.remove?.environment;
|
|
442
|
+
// On a character or an item environment would hold for nothing (ENV-6); taking it away is always allowed.
|
|
443
|
+
if ((type === "character" || type === "item") && (change.set?.environment !== undefined || add !== undefined)) {
|
|
444
|
+
throw new Error(`${name} is ${withArticle(type)}: an environment holds only on a place, for all it contains. What a thing does around itself, such as a lantern's light, is a field or an effect of its own.`);
|
|
445
|
+
}
|
|
446
|
+
if (type !== "place") {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (change.set?.environment !== undefined) {
|
|
450
|
+
const read = readEnvironment(change.set.environment);
|
|
451
|
+
if ("problem" in read) {
|
|
452
|
+
throw new Error(read.problem);
|
|
453
|
+
}
|
|
454
|
+
// Written back as lines, which Obsidian edits, whatever way they were given.
|
|
455
|
+
change.set.environment = read.entries.map(formatEnvironmentEntry);
|
|
456
|
+
}
|
|
457
|
+
if (add === undefined && remove === undefined) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
checkOwnEntries(world, page, name, remove ?? []);
|
|
461
|
+
const lines = changedEnvironment(page?.fields.environment, add ?? [], remove ?? [], page?.name ?? name);
|
|
462
|
+
delete change.add?.environment;
|
|
463
|
+
delete change.remove?.environment;
|
|
464
|
+
if (lines.length > 0) {
|
|
465
|
+
change.set = { ...change.set, environment: lines };
|
|
466
|
+
}
|
|
467
|
+
else if (page !== undefined && Object.hasOwn(page.fields, "environment")) {
|
|
468
|
+
change.unset = [...(change.unset ?? []), "environment"];
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
/** An entry to remove that the place does not write but reads from a place around it: removing it there would change it for all that place holds. */
|
|
472
|
+
function checkOwnEntries(world, page, name, remove) {
|
|
473
|
+
const own = page === undefined ? [] : readEnvironmentNames(page.fields.environment);
|
|
474
|
+
for (const entry of remove.map((item) => String(item).split(":")[0].trim())) {
|
|
475
|
+
const outer = page === undefined || own.some((other) => comparableText(other) === comparableText(entry)) ? undefined : environmentAt(world, page).find((found) => comparableText(found.entry.name) === comparableText(entry));
|
|
476
|
+
if (outer !== undefined) {
|
|
477
|
+
throw new Error(`${entry} holds in ${name} from ${outer.place.name}: to change it in ${name} only, add environment="${outer.entry.name}: …" on ${name}; to remove it everywhere ${outer.place.name} holds, write ${outer.place.name}, remove environment=${outer.entry.name}.`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/** The names a place's environment writes, as far as its lines read. */
|
|
482
|
+
function readEnvironmentNames(value) {
|
|
483
|
+
const read = readEnvironment(value);
|
|
484
|
+
return "entries" in read ? read.entries.map((entry) => entry.name) : [];
|
|
485
|
+
}
|
|
486
|
+
/** Adding a condition to an event that has one makes a list of the two, of which one is enough (EVT-3). */
|
|
487
|
+
function conditionsAsList(page, change) {
|
|
488
|
+
const current = page?.type === "event" ? page.fields.when : undefined;
|
|
489
|
+
if (change.add?.when !== undefined && typeof current === "string" && current.trim() !== "" && change.set?.when === undefined) {
|
|
490
|
+
change.set = { ...change.set, when: [current, ...change.add.when] };
|
|
491
|
+
delete change.add.when;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* The pages that change with a page a write changed (INH-11): all those deriving from it when it is new or derives from another creature now;
|
|
496
|
+
* else those that read a field it changed, or show the section it wrote.
|
|
497
|
+
*/
|
|
498
|
+
function followersOfWrite(now, before, after) {
|
|
499
|
+
if (!isBaseType(after.type)) {
|
|
500
|
+
return { pages: [], why: "" };
|
|
501
|
+
}
|
|
502
|
+
const changed = [...new Set([...Object.keys(before?.fields ?? {}), ...Object.keys(after.fields)])].filter((field) => JSON.stringify(before?.fields[field]) !== JSON.stringify(after.fields[field]));
|
|
503
|
+
if (before === undefined || changed.some((field) => comparableText(field) === baseFieldOf(after.type))) {
|
|
504
|
+
return { pages: derivedPages(now, after), why: `since they derive from ${after.name}` };
|
|
505
|
+
}
|
|
506
|
+
const byField = changed.map((field) => ({ name: field, pages: followersOf(now, after, field) }));
|
|
507
|
+
const bySection = changedParts(before.body, after.body).map((part) => ({ name: part === "" ? "a secret without a name" : `#${part}`, pages: part === "" ? derivedPages(now, after) : sectionFollowersOf(now, after, part) }));
|
|
508
|
+
const byIntroduction = introductionText(before.body) === introductionText(after.body) ? [] : [{ name: "the introduction", pages: introductionFollowersOf(now, after) }];
|
|
509
|
+
const reasons = [...byField, ...bySection, ...byIntroduction].filter((reason) => reason.pages.length > 0);
|
|
510
|
+
const pages = new Set(reasons.flatMap((reason) => reason.pages));
|
|
511
|
+
return { pages: now.pages.filter((page) => pages.has(page)), why: `since they read ${reasons.map((reason) => reason.name).join(", ")} from ${after.name}` };
|
|
512
|
+
}
|
|
513
|
+
/** The names of the sections and secrets that a text has with another text, gained or no longer has; "" when its secrets without a name changed, which every derived page shows. */
|
|
514
|
+
function changedParts(before, after) {
|
|
515
|
+
const texts = (body) => new Map(textParts(body).filter((part) => part.name !== "").map((part) => [comparableText(part.name), part.text]));
|
|
516
|
+
const unnamed = (body) => textParts(body).filter((part) => part.name === "").map((part) => part.text).join("\n");
|
|
517
|
+
const old = texts(before);
|
|
518
|
+
const now = texts(after);
|
|
519
|
+
const names = new Map(textParts(`${before}\n\n${after}`).filter((part) => part.name !== "").map((part) => [comparableText(part.name), part.name]));
|
|
520
|
+
const named = [...names].filter(([key]) => old.get(key) !== now.get(key)).map(([, name]) => name);
|
|
521
|
+
return unnamed(before) === unnamed(after) ? named : [...named, ""];
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* One public line for each counter that a write changed through inheritance on a page that concerns the player: a page that follows the base written,
|
|
525
|
+
* or the page itself when its creature changes. Its name, the field, before and after (INH-10, INH-11). Each page comes as it was and as it is.
|
|
526
|
+
*/
|
|
527
|
+
function logCounterChanges(root, context, world, now, pages, coins) {
|
|
528
|
+
for (const [before, after] of pages.filter(([, page]) => concernsPlayer(now, page))) {
|
|
529
|
+
const old = inheritedFields(world, before);
|
|
530
|
+
for (const [field, value] of Object.entries(inheritedFields(now, after)).filter(([name]) => !isStandardField(after.type, name) && !isDeclaredUncounted(now, name))) {
|
|
531
|
+
const was = countedText(valueNamed(old, field), coins);
|
|
532
|
+
const is = countedText(value, coins);
|
|
533
|
+
if (was !== undefined && is !== undefined && was !== is) {
|
|
534
|
+
appendLogEntry(root, newDatedLogEntry(root, context, "counter-changed", "public", { page: after.name, field, before: was, after: is }));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
/** A counter or money as the log writes it; undefined for any other value. */
|
|
540
|
+
function countedText(value, coins) {
|
|
541
|
+
const purse = coins === undefined ? undefined : readAmount(value, coins);
|
|
542
|
+
const counter = readCounter(value);
|
|
543
|
+
return purse !== undefined && coins !== undefined ? formatAmount(purse, coins) : counter === undefined ? undefined : formatCounter(counter);
|
|
544
|
+
}
|
|
545
|
+
/** Money given to set, written as the engine writes it (MON-2): from the coin worth most, by first names. A field the game system declares is never money (SYS-8). */
|
|
546
|
+
function asAmounts(world, set, coins) {
|
|
547
|
+
return Object.fromEntries(Object.entries(set).map(([field, value]) => {
|
|
548
|
+
const purse = coins === undefined || declaredKind(world, field) !== undefined ? undefined : readAmount(value, coins);
|
|
549
|
+
return [field, purse === undefined || coins === undefined ? value : formatAmount(purse, coins)];
|
|
550
|
+
}));
|
|
551
|
+
}
|
|
552
|
+
/** What a write should say about money: a value that names coins but does not read as money, and coins that cannot be read (MON-2, MON-8). */
|
|
553
|
+
function moneyNotesOf(world, page, set, coins, problem) {
|
|
554
|
+
// A field the game system declares is never money (SYS-8).
|
|
555
|
+
const texts = Object.entries(set ?? {}).filter(([field, value]) => typeof value === "string" && declaredKind(world, field) === undefined);
|
|
556
|
+
const unread = coins === undefined ? [] : texts.filter(([, value]) => readAmount(value, coins) === undefined && namesCoins(value, coins));
|
|
557
|
+
const notes = unread.map(([field, value]) => `${field} on ${page} is "${value}", which is not money: write the coins one after the other, such as ${field}=12 ${coins?.[0].name ?? ""}, to count them.`);
|
|
558
|
+
const looksLikeMoney = texts.some(([, value]) => /\d+\s*\p{L}/u.test(value));
|
|
559
|
+
return problem !== undefined && looksLikeMoney ? [...notes, `The campaign's coins cannot be read, so no money is counted: ${problem.message} To fix it, ${problem.fix}.`] : notes;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* A list written whole that leaves out items the page read from a base, as an elf's Perception from its race: said, since the page no longer reads them (INH-7).
|
|
563
|
+
*/
|
|
564
|
+
function droppedFromBaseNotes(now, page, set) {
|
|
565
|
+
return Object.entries(set ?? {}).filter(([, value]) => Array.isArray(value)).flatMap(([field, value]) => {
|
|
566
|
+
const base = baseValue(now, page, field);
|
|
567
|
+
if (base === undefined || !Array.isArray(base.value)) {
|
|
568
|
+
return [];
|
|
569
|
+
}
|
|
570
|
+
const held = new Set(listItemNames(value).map(comparableText));
|
|
571
|
+
const missing = listItemNames(base.value).filter((name) => !held.has(comparableText(name)));
|
|
572
|
+
const them = missing.length === 1 ? "it" : "them";
|
|
573
|
+
return missing.length === 0 ? [] : [`${field} on ${page.name} leaves out ${missing.join(", ")}, which it read from ${base.base.name}: if the character keeps ${them}, add ${them}, such as add ${field}=${missing[0]}.`];
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
/** A page created with a name that other pages give as their creature or item, though it is not one: they derive from nothing (INH-2). */
|
|
577
|
+
function derivesFromNothingNote(now, page) {
|
|
578
|
+
return ["creature", "item"].filter((field) => field !== page.type).flatMap((field) => {
|
|
579
|
+
const linking = now.pages.filter((other) => baseFieldOf(other.type) === field && linksIn(other.fields[field]).some((name) => comparableText(name) === comparableText(page.name)));
|
|
580
|
+
return linking.length === 0
|
|
581
|
+
? []
|
|
582
|
+
: [`${linking.map((other) => other.name).join(", ")} name ${page.name} as their ${field}, but it is ${withArticle(page.type ?? "free page")}: they derive from nothing until ${field} links to ${withArticle(field)}.`];
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* For a creature or an item created without a base, when the game system has pages of its type: it may be a kind of one of them (D-165, SYS-7).
|
|
587
|
+
* When another field already links to one, the field to use instead.
|
|
588
|
+
*/
|
|
589
|
+
function noBaseNote(now, page) {
|
|
590
|
+
const field = baseFieldOf(page.type);
|
|
591
|
+
if (page.type === "character" || field === undefined || !isEmptyField(page.fields[field])) {
|
|
592
|
+
return [];
|
|
593
|
+
}
|
|
594
|
+
// Only a free field links to its base by mistake: a field the system declares, such as magic, links to something else (D-164).
|
|
595
|
+
const free = Object.entries(page.fields).filter(([name]) => declaredKind(now, name) === undefined);
|
|
596
|
+
const elsewhere = free.flatMap(([name, value]) => linksIn(value).map((link) => ({ name, page: systemPageNamed(now, link) }))).find((link) => link.page?.type === field);
|
|
597
|
+
if (elsewhere?.page !== undefined) {
|
|
598
|
+
const linked = elsewhere.page;
|
|
599
|
+
const system = linked.system ?? "";
|
|
600
|
+
return [
|
|
601
|
+
`${page.name} links to ${linked.name}, ${withArticle(field)} of the system ${system}, in ${elsewhere.name}: if it is a kind of it, the field is ${field}, `
|
|
602
|
+
+ `such as write "${page.name}", set ${field}=[[${linked.name}]], unset ${elsewhere.name}.`,
|
|
603
|
+
];
|
|
604
|
+
}
|
|
605
|
+
// A story item, such as a letter, writes no field of the system: it is no kind of one of its items.
|
|
606
|
+
if (page.type === "item" && !Object.keys(page.fields).some((name) => declaredKind(now, name) !== undefined)) {
|
|
607
|
+
return [];
|
|
608
|
+
}
|
|
609
|
+
const hint = systemBaseHint(now, field, field);
|
|
610
|
+
return hint === "" ? [] : [`${page.name} derives from no ${field}:${hint.replace(" If it", " if it")}`];
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* What a write should say of the links it gave in fields the game system declares, such as spells (D-216): those to pages that do not exist yet, which the world
|
|
614
|
+
* gets with write, and those to a page that shares its name with another, as Shield the item beside Shield (spell). A campaign played in another language is
|
|
615
|
+
* told the system's. The core's fields, as contains or knows, link ahead to pages still to write.
|
|
616
|
+
*/
|
|
617
|
+
function linkNotes(world, change) {
|
|
618
|
+
// The base a creature or an item derives from too, as item=[[Spada lunga]] for Longsword.
|
|
619
|
+
const told = (field) => declaredKind(world, field) !== undefined || ["creature", "item"].includes(comparableText(field));
|
|
620
|
+
const given = [...Object.entries(change.set ?? {}), ...Object.entries(change.add ?? {})].filter(([field]) => told(field)).flatMap(([, value]) => linksIn(value));
|
|
621
|
+
const names = [...new Set(given)];
|
|
622
|
+
const exists = (name) => pagesWithName(world, name)[0] ?? systemPageNamed(world, name);
|
|
623
|
+
const missing = names.filter((name) => exists(name) === undefined);
|
|
624
|
+
const system = world.system;
|
|
625
|
+
const foreign = foreignSystemLanguage(world);
|
|
626
|
+
const language = system === undefined || foreign === undefined ? "" : ` The system ${system.name} names its pages in ${foreign}: find looks them up.`;
|
|
627
|
+
const absent = missing.length === 0
|
|
628
|
+
? []
|
|
629
|
+
: [`No page for ${missing.map((name) => `[[${name}]]`).join(", ")} yet: create ${missing.length === 1 ? "it" : "them"} with write if ${missing.length === 1 ? "it belongs" : "they belong"} to this world, or link a page that exists.${language}`];
|
|
630
|
+
const twins = names.flatMap((name) => {
|
|
631
|
+
const page = exists(name);
|
|
632
|
+
const others = page === undefined ? [] : namesakes(world, page);
|
|
633
|
+
return page === undefined || others.length === 0 ? [] : [`[[${name}]] is ${page.system === undefined ? withArticle(page.type ?? "page") : systemLabel(page)}; there is also ${others.map((other) => other.name).join(", ")}.`];
|
|
634
|
+
});
|
|
635
|
+
return [...absent, ...twins];
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* When the items of a list give a field another formula reads, as two armors in equipped or Unarmored Defense from two classes, the formula reads the first
|
|
639
|
+
* (PRO-5): said when a write makes it so.
|
|
640
|
+
*/
|
|
641
|
+
function firstItemNotes(before, after, existing, page) {
|
|
642
|
+
const computed = after.system?.formulas?.computed ?? [];
|
|
643
|
+
return Object.entries(after.system?.itemFormulas ?? {}).flatMap(([list, names]) => names.flatMap((field) => {
|
|
644
|
+
const readers = computed.filter(([, formula]) => fieldsRead(formula).some((name) => comparableText(name) === comparableText(field))).map(([name]) => name);
|
|
645
|
+
const giving = (world, fields) => itemLines(world, fields, list, [field]).map((line) => line.item);
|
|
646
|
+
const is = readers.length === 0 ? [] : giving(after, effectiveFields(after, page));
|
|
647
|
+
const was = is.length < 2 || existing === undefined ? [] : giving(before, effectiveFields(before, existing));
|
|
648
|
+
if (is.length < 2 || (was.length === is.length && was.every((name, index) => comparableText(name) === comparableText(is[index])))) {
|
|
649
|
+
return [];
|
|
650
|
+
}
|
|
651
|
+
return [`${readers.join(", ")} ${readers.length === 1 ? "reads" : "read"} ${field} from ${is[0]}, the first in ${page.name}'s ${list} that gives it, not from ${is.slice(1).join(", ")}.`];
|
|
652
|
+
}));
|
|
653
|
+
}
|
|
654
|
+
/** "hp=15/15": the fields a refused write gave, for the call to make instead, without the base field, which that call gives. */
|
|
655
|
+
function givenFields(set, baseField) {
|
|
656
|
+
return Object.entries(set ?? {})
|
|
657
|
+
.filter(([field]) => comparableText(field) !== baseField)
|
|
658
|
+
.map(([field, value]) => `${field}=${Array.isArray(value) ? `[${value.map(String).join(", ")}]` : String(value)}`);
|
|
659
|
+
}
|
|
660
|
+
/** Whether pages of this type can be bases: creatures and items (INH-1). */
|
|
661
|
+
function isBaseType(type) {
|
|
662
|
+
return type === "creature" || type === "item";
|
|
663
|
+
}
|
|
664
|
+
/** "Not changed, since they hold their own fiuto: Grigio (fiuto 2)": for each field a write changed on a base, the pages deriving from it that keep their own value. */
|
|
665
|
+
function keepersNotes(now, before, after) {
|
|
666
|
+
if (!isBaseType(after.type)) {
|
|
667
|
+
return [];
|
|
668
|
+
}
|
|
669
|
+
return changedNames(before, after).flatMap((field) => {
|
|
670
|
+
const keepers = keepersOf(now, after, field).map((keeper) => `${keeper.name} (${field} ${valueText(valueNamed(inheritedFields(now, keeper), field))})`);
|
|
671
|
+
const note = keepersNote(keepers, field);
|
|
672
|
+
return note === "" ? [] : [note];
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
/** After its creature changes, the values a page keeps that its new bases give otherwise: they may belong to the old one (D-125). */
|
|
676
|
+
function ownValuesNote(now, before, after, coins) {
|
|
677
|
+
const field = baseFieldOf(after.type);
|
|
678
|
+
if (field === undefined || !changedNames(before, after).some((name) => comparableText(name) === field)) {
|
|
679
|
+
return [];
|
|
680
|
+
}
|
|
681
|
+
const kept = Object.entries(after.fields).filter(([name, value]) => !isEmptyField(value) && !isNeverInherited(name)).flatMap(([name, value]) => {
|
|
682
|
+
const base = baseValue(now, after, name);
|
|
683
|
+
return base === undefined || JSON.stringify(base.value) === JSON.stringify(value) ? [] : [{ field: name, value, base }];
|
|
684
|
+
});
|
|
685
|
+
if (kept.length === 0) {
|
|
686
|
+
return [];
|
|
687
|
+
}
|
|
688
|
+
// A current/max counter or money goes back to the base's value with change; anything else is removed.
|
|
689
|
+
const back = kept.map(({ field, value, base }) => {
|
|
690
|
+
const mine = guarded(value, coins, false);
|
|
691
|
+
const theirs = guarded(base.value, coins, false);
|
|
692
|
+
return mine !== undefined && mine.kind === theirs?.kind ? `change ${field} to ${mine.kind === "money" ? `"${theirs.text}"` : theirs.text}` : `unset ${field}`;
|
|
693
|
+
});
|
|
694
|
+
const listed = kept.map(({ field, value, base }) => `${field} ${valueText(value)} (${base.base.name} gives ${valueText(base.value)})`);
|
|
695
|
+
return [`${after.name} keeps its own ${listed.join(", ")}. If they came from its old ${field}: ${back.join(", ")}.`];
|
|
696
|
+
}
|
|
697
|
+
/** The fields whose value a write changed on a page. */
|
|
698
|
+
function changedNames(before, after) {
|
|
699
|
+
return [...new Set([...Object.keys(before.fields), ...Object.keys(after.fields)])].filter((field) => JSON.stringify(before.fields[field]) !== JSON.stringify(after.fields[field]));
|
|
700
|
+
}
|
|
701
|
+
/** The value a page reads for a field, written in any case. */
|
|
702
|
+
/** A value as the outputs show it: a list as its items, anything else as written. */
|
|
703
|
+
function valueText(value) {
|
|
704
|
+
return Array.isArray(value) ? value.map(String).join(", ") : typeof value === "object" && value !== null ? JSON.stringify(value) : String(value);
|
|
705
|
+
}
|
|
706
|
+
/** Whether changing a field reaches the pages and copies deriving from a page: a field they inherit, or the creature or item it derives from. */
|
|
707
|
+
function passesOn(field) {
|
|
708
|
+
return !isNeverInherited(field) || comparableText(field) === "creature" || comparableText(field) === "item";
|
|
709
|
+
}
|
|
710
|
+
function changedFieldNames(change) {
|
|
711
|
+
return [...Object.keys(change.set ?? {}), ...(change.unset ?? []), ...Object.keys(change.add ?? {}), ...Object.keys(change.remove ?? {})];
|
|
712
|
+
}
|
|
713
|
+
/** "hp, ac again as Wolf": fields grouped by the base they are read from. */
|
|
714
|
+
function byBase(fields, base, how) {
|
|
715
|
+
return [...new Set(fields.map(base))].map((name) => `${fields.filter((field) => base(field) === name).join(", ")} ${how} ${name ?? ""}`);
|
|
716
|
+
}
|
|
717
|
+
/** A field is given a value: an empty one would count as missing (INH-6), and removing a field is unset, which checks what it would bring back. */
|
|
718
|
+
function checkValues(change) {
|
|
719
|
+
const [empty] = Object.entries(change.set ?? {}).filter(([, value]) => isEmptyField(value));
|
|
720
|
+
if (empty !== undefined) {
|
|
721
|
+
throw new Error(`Give ${empty[0]} a value. To remove a field, use unset.`);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
/** Items are added to or removed from a list: one that the page reads as something else, itself or from a base, is set whole first (INH-7). */
|
|
725
|
+
function checkLists(world, page, change) {
|
|
726
|
+
const fields = inheritedFields(world, page);
|
|
727
|
+
for (const field of [...Object.keys(change.add ?? {}), ...Object.keys(change.remove ?? {})].filter((name) => !Object.hasOwn(change.set ?? {}, name))) {
|
|
728
|
+
const [name, value] = Object.entries(fields).find(([key]) => comparableText(key) === comparableText(field)) ?? [];
|
|
729
|
+
if (name !== undefined && !Array.isArray(value)) {
|
|
730
|
+
const base = holdsField(page, name) ? undefined : baseValue(world, page, name)?.base;
|
|
731
|
+
const from = base === undefined ? "" : `, which ${page.name} reads from ${base.name}`;
|
|
732
|
+
throw new Error(`${name} on ${page.name} is ${JSON.stringify(value)}${from}, not a list: items cannot be added to it or removed from it. To make it a list, set it whole, such as set ${name}=[a, b].`);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* The names of the fields a write changed, as the page writes them (INH-5). A value equal to the base's is not written (D-125): it is no change, unless the page
|
|
738
|
+
* held another, which it holds no more ("ac again as Lupo").
|
|
739
|
+
*/
|
|
740
|
+
function writtenNames(world, page, change, after) {
|
|
741
|
+
const names = (fields) => fields.map((field) => (page === undefined ? field : writtenFieldName(world, page, field)));
|
|
742
|
+
const removed = (change.unset ?? []).map((field) => (page === undefined ? field : removedFieldName(page, field)));
|
|
743
|
+
const [set, add, remove] = [Object.keys(change.set ?? {}), Object.keys(change.add ?? {}), Object.keys(change.remove ?? {})].map(names);
|
|
744
|
+
const now = withPage(world, page, after);
|
|
745
|
+
const base = (field) => baseValue(now, after, field)?.base.name;
|
|
746
|
+
// What the page held and holds no more, without being removed: it reads it from a base again. What it was given and does not hold: it reads it from a base all the same.
|
|
747
|
+
const gone = page === undefined ? [] : Object.keys(page.fields).filter((field) => !isEmptyField(page.fields[field]) && !Object.hasOwn(after.fields, field) && !removed.includes(field) && base(field) !== undefined);
|
|
748
|
+
const asBase = [...set, ...add, ...remove].filter((field, index, all) => all.indexOf(field) === index && !gone.includes(field) && !Object.hasOwn(after.fields, field) && base(field) !== undefined);
|
|
749
|
+
const shown = (field) => !gone.includes(field) && !asBase.includes(field);
|
|
750
|
+
return {
|
|
751
|
+
set: set.filter((field) => shown(field) && JSON.stringify(page?.fields[field]) !== JSON.stringify(after.fields[field])),
|
|
752
|
+
again: [...byBase(gone, base, "again as"), ...byBase(asBase, base, "as")],
|
|
753
|
+
unset: removed,
|
|
754
|
+
add: add.filter(shown),
|
|
755
|
+
remove: remove.filter(shown),
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
function changeSummary(result, changes) {
|
|
759
|
+
const what = result.copyOf === undefined ? `${result.created ? "Created" : "Updated"} ${result.page} (${result.type ?? "free page"}, ${result.path})` : `Updated ${result.page} (copy of ${result.copyOf} in the open scene)`;
|
|
760
|
+
return changes.length > 0 ? `${what}: ${changes.join("; ")}.` : `${what}.`;
|
|
761
|
+
}
|
|
762
|
+
/** Fields from field=value pairs, each value read as a link, links, a number, true or false, a list or text; or from an object, as they are. */
|
|
763
|
+
function readFields(fields) {
|
|
764
|
+
if (!Array.isArray(fields)) {
|
|
765
|
+
return Object.fromEntries(Object.entries(fields).map(([name, value]) => [name, listFromText(name, value)]));
|
|
766
|
+
}
|
|
767
|
+
// The same name twice would be lost in one object; the same field in another case is refused later, with the other changes.
|
|
768
|
+
const entries = fields.map(readAssignment);
|
|
769
|
+
const repeated = entries.find(([name], index) => entries.findIndex(([other]) => other === name) !== index);
|
|
770
|
+
if (repeated !== undefined) {
|
|
771
|
+
throw new Error(`The field ${repeated[0]} is given twice: give each field once.`);
|
|
772
|
+
}
|
|
773
|
+
return Object.fromEntries(entries);
|
|
774
|
+
}
|
|
775
|
+
/** Items for list fields: each value gives one item, or several when it is a list; the same field may come more than once. */
|
|
776
|
+
function readListItems(fields) {
|
|
777
|
+
const items = {};
|
|
778
|
+
for (const [name, given] of Array.isArray(fields) ? fields.map(readAssignment) : Object.entries(fields)) {
|
|
779
|
+
const value = listFromText(name, given);
|
|
780
|
+
items[name] = [...(items[name] ?? []), ...(Array.isArray(value) ? value : [value])];
|
|
781
|
+
}
|
|
782
|
+
return items;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* The fields given to set without those the game system works out on the page as the write leaves it, a copy in the open scene or a page that gets its base in
|
|
786
|
+
* the same write included (FOR-7), with a note that gives the values the system works out and what they come from.
|
|
787
|
+
*/
|
|
788
|
+
function withoutComputed(world, target, set, name, type) {
|
|
789
|
+
const after = { ...(target ?? { name, path: "", body: "" }), type: type ?? target?.type, fields: { ...(target?.fields ?? {}), ...set } };
|
|
790
|
+
const fields = effectiveFields(world, after);
|
|
791
|
+
const computed = Object.entries(set).flatMap(([field, given]) => {
|
|
792
|
+
const found = computedField(world, field, fields);
|
|
793
|
+
return found === undefined ? [] : [{ ...found, given }];
|
|
794
|
+
});
|
|
795
|
+
if (computed.length === 0) {
|
|
796
|
+
return { set };
|
|
797
|
+
}
|
|
798
|
+
const kept = Object.fromEntries(Object.entries(set).filter(([field]) => !computed.some((found) => comparableText(found.name) === comparableText(field))));
|
|
799
|
+
const values = computed.map((found) => {
|
|
800
|
+
const value = signedText(world, found.name, found.value);
|
|
801
|
+
return `${found.name} ${value}${numberOf(found.given) === found.value ? ", as you gave it" : ` (you gave ${String(found.given)})`}`;
|
|
802
|
+
});
|
|
803
|
+
const sources = [...new Set(computed.flatMap((found) => found.from))];
|
|
804
|
+
const bonuses = sources.filter((field) => declaredKind(world, field) === "bonus");
|
|
805
|
+
const bonus = bonuses.length === 0 ? "" : ` A bonus such as ${bonuses[0]} comes from an effect, for what gives one, such as a magic item.`;
|
|
806
|
+
const note = `Not written, the system ${world.system?.name ?? ""} works ${computed.length === 1 ? "it" : "them"} out: ${values.join("; ")}. They come from ${sources.join(", ")}.${bonus}`;
|
|
807
|
+
return { set: kept, note };
|
|
808
|
+
}
|
|
809
|
+
/** The items given to add or remove without the fields the game system works out on the page, which no list change writes (FOR-7, D-200), with a note. */
|
|
810
|
+
function listsWithoutComputed(world, target, lists) {
|
|
811
|
+
if (lists === undefined || target === undefined) {
|
|
812
|
+
return { lists };
|
|
813
|
+
}
|
|
814
|
+
const fields = effectiveFields(world, target);
|
|
815
|
+
const computed = Object.keys(lists).flatMap((field) => computedField(world, field, fields) ?? []);
|
|
816
|
+
if (computed.length === 0) {
|
|
817
|
+
return { lists };
|
|
818
|
+
}
|
|
819
|
+
const kept = Object.fromEntries(Object.entries(lists).filter(([field]) => !computed.some((found) => comparableText(found.name) === comparableText(field))));
|
|
820
|
+
const values = computed.map((found) => `${found.name} ${signedText(world, found.name, found.value)}`).join("; ");
|
|
821
|
+
const sources = [...new Set(computed.flatMap((found) => found.from))].join(", ");
|
|
822
|
+
return {
|
|
823
|
+
lists: Object.keys(kept).length === 0 ? undefined : kept,
|
|
824
|
+
note: `Not written, the system ${world.system?.name ?? ""} works ${computed.length === 1 ? "it" : "them"} out: ${values}. ${computed.length === 1 ? "It comes" : "They come"} from ${sources}.`,
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
/** The part quest-ends when a quest is done or failed, such as the experience the story gives (D-233), once in a chat (D-97). */
|
|
828
|
+
function questEndNotes(context, system, existing, page) {
|
|
829
|
+
const ended = (candidate) => ["done", "failed"].includes(String(candidate?.fields.status));
|
|
830
|
+
if (page.type !== "quest" || !ended(page) || ended(existing)) {
|
|
831
|
+
return [];
|
|
832
|
+
}
|
|
833
|
+
const part = situationPart("quest-ends", context, system);
|
|
834
|
+
return part === "" ? [] : [`${page.name} is ${String(page.fields.status)}. ${part}`];
|
|
835
|
+
}
|
|
836
|
+
/** Items written in a list where the parts of the classes already put them, as a domain's spell in prepared: always there, they do not count with the rest (PRO-7). */
|
|
837
|
+
function givenListNotes(world, page, changed) {
|
|
838
|
+
const progression = progressionOf(world, effectiveFields(world, page));
|
|
839
|
+
return changed.flatMap((field) => {
|
|
840
|
+
const written = listItemNames(valueNamed(page.fields, field));
|
|
841
|
+
const twice = givenItems(progression, field).filter((item) => sameName(written, item.name) !== undefined);
|
|
842
|
+
const pages = [...new Set(twice.map((item) => item.page.name))].join(" and ");
|
|
843
|
+
return twice.length === 0
|
|
844
|
+
? []
|
|
845
|
+
: [`${twice.map((item) => item.name).join(", ")} ${twice.length === 1 ? "is" : "are"} always in ${field}, from ${pages}, and not counted with the rest: write ${page.name}, remove ${field}=${twice[0].name}, to choose another in its place.`];
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
/** A section of a character with the name of a feature its classes give: show gives it in place of the feature's text (PRO-4). */
|
|
849
|
+
function featureSectionNotes(world, page, section) {
|
|
850
|
+
const feature = progressionOf(world, effectiveFields(world, page))?.features.find((candidate) => sameFeature(candidate.name, section));
|
|
851
|
+
return feature === undefined ? [] : [`show ${page.name}#${section} now gives this section, not the text of the feature ${featureParts(feature.name).name} its classes give.`];
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* What a change of classes gives or takes (PRO-4): the features of the rows it reaches, new, gained again or with a new value, those no longer had, the values of
|
|
855
|
+
* the rows and of the formulas of the classes' pages that change, and, when a class rises, the system's note for a new level (D-226), unless the same write sets
|
|
856
|
+
* a field it names, as hp-base. Nothing when the classes give the same.
|
|
857
|
+
*/
|
|
858
|
+
function progressionNotes(context, system, before, after, existing, page, set) {
|
|
859
|
+
const was = progressionOf(before, effectiveFields(before, existing));
|
|
860
|
+
const is = progressionOf(after, effectiveFields(after, page));
|
|
861
|
+
const gains = rowGains(was, is);
|
|
862
|
+
const lost = (was?.features ?? []).map((feature) => feature.name).filter((name) => !(is?.features ?? []).some((other) => sameFeature(other.name, name)));
|
|
863
|
+
// The maximum of a use the rows give is said with the counters (FOR-6).
|
|
864
|
+
const counters = Object.keys(after.system?.formulas?.counters ?? {});
|
|
865
|
+
const valueOf = (progression, field) => {
|
|
866
|
+
const found = progression?.values.find((value) => comparableText(value.field) === comparableText(field));
|
|
867
|
+
return found === undefined ? undefined : signedText(after, found.field, found.value);
|
|
868
|
+
};
|
|
869
|
+
const fields = [...new Set([...(was?.values ?? []), ...(is?.values ?? [])].map((value) => value.field))].filter((field) => !counters.some((counter) => comparableText(counter) === comparableText(field)));
|
|
870
|
+
const values = fields.flatMap((field) => {
|
|
871
|
+
const [from, to] = [valueOf(was, field), valueOf(is, field)];
|
|
872
|
+
return from === to ? [] : [`${field} ${from ?? "none"} → ${to ?? "none"}`];
|
|
873
|
+
});
|
|
874
|
+
// What the formulas of the classes' pages give, as the prepared spells of a new level (PRO-5).
|
|
875
|
+
const [then, now] = [effectiveFields(before, existing), effectiveFields(after, page)];
|
|
876
|
+
const formulaValues = (after.system?.itemFormulas?.[after.system.progression?.list ?? ""] ?? []).flatMap((field) => {
|
|
877
|
+
const [from, to] = [valueNamed(then, field), valueNamed(now, field)];
|
|
878
|
+
const text = (value) => (value === undefined ? "none" : typeof value === "number" ? signedText(after, field, value) : String(value));
|
|
879
|
+
return text(from) === text(to) ? [] : [`${field} ${text(from)} → ${text(to)}`];
|
|
880
|
+
});
|
|
881
|
+
const shown = gains.fresh[0] ?? gains.better[0]?.name;
|
|
882
|
+
const parts = [
|
|
883
|
+
gains.fresh.length === 0 ? "" : `${gains.fresh.join(", ")} ${gains.fresh.length === 1 ? "is" : "are"} new`,
|
|
884
|
+
gains.again.length === 0 ? "" : `${gains.again.join(", ")} again`,
|
|
885
|
+
gains.better.map((feature) => feature.text).join(", "),
|
|
886
|
+
lost.length === 0 ? "" : `no longer ${lost.join(", ")}`,
|
|
887
|
+
[...values, ...formulaValues].join(", "),
|
|
888
|
+
].filter((part) => part !== "");
|
|
889
|
+
const hint = shown === undefined ? "" : ` show ${page.name}#${featureParts(shown).name} gives a feature's text.`;
|
|
890
|
+
const levelUp = partText("level-up", system);
|
|
891
|
+
// A new level is one more in all: a class changed for another of a lower level is none.
|
|
892
|
+
const levels = (progression) => (progression?.from ?? []).reduce((total, item) => total + (item.level ?? 0), 0);
|
|
893
|
+
// Not said when the same write sets a field the note names, as hp-base: it is already done.
|
|
894
|
+
const named = new Set((levelUp?.match(/[\p{L}\p{N}-]+/gu) ?? []).map(comparableText));
|
|
895
|
+
const done = set.some((field) => named.has(comparableText(field)));
|
|
896
|
+
// The part level-up, once in a chat (D-97).
|
|
897
|
+
const risen = levelUp !== undefined && levels(is) > levels(was) && !done ? situationPart("level-up", context, system) : "";
|
|
898
|
+
// A value that ends in a period, such as +10 ft., ends the line.
|
|
899
|
+
const said = parts.length === 0 ? [] : [`What ${page.name}'s ${is?.lists[0] ?? was?.lists[0] ?? "classes"} give: ${parts.join("; ").replace(/\.$/, "")}.${hint}`];
|
|
900
|
+
return [...said, ...(risen === "" ? [] : [risen])];
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* The features the rows and parts give now that they did not before, each name once (PRO-3): new to the character, gained again, as a later Ability Score
|
|
904
|
+
* Improvement, or with a new value, as "Bardic Inspiration (d6 → d8)".
|
|
905
|
+
*/
|
|
906
|
+
function rowGains(was, is) {
|
|
907
|
+
const key = (gain) => `${gain.page.path}|${gain.level}|${comparableText(gain.name)}`;
|
|
908
|
+
const old = new Set((was?.gains ?? []).map(key));
|
|
909
|
+
const had = was?.features ?? [];
|
|
910
|
+
const result = { fresh: [], again: [], better: [] };
|
|
911
|
+
for (const gain of mergedFeatures((is?.gains ?? []).filter((candidate) => !old.has(key(candidate))))) {
|
|
912
|
+
const before = had.find((feature) => sameFeature(feature.name, gain.name))?.name;
|
|
913
|
+
const [then, now] = [featureParts(before ?? "").value, featureParts(gain.name).value];
|
|
914
|
+
if (before === undefined) {
|
|
915
|
+
result.fresh.push(gain.name);
|
|
916
|
+
}
|
|
917
|
+
else if (now !== undefined && then !== undefined && comparableText(now) !== comparableText(then)) {
|
|
918
|
+
result.better.push({ name: gain.name, text: `${featureParts(gain.name).name} (${then.slice(1, -1)} → ${now.slice(1, -1)})` });
|
|
919
|
+
}
|
|
920
|
+
else {
|
|
921
|
+
result.again.push(featureParts(gain.name).name);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
return result;
|
|
925
|
+
}
|
|
926
|
+
/** Whether two names are the same feature, with or without a value in parentheses (PRO-3). */
|
|
927
|
+
function sameFeature(first, second) {
|
|
928
|
+
return comparableText(featureParts(first).name) === comparableText(featureParts(second).name);
|
|
929
|
+
}
|
|
930
|
+
/** The thresholds the fields a write gives touch on the player character or a companion, as change says them (DAT-3): exhaustion written with write shows its rule. */
|
|
931
|
+
function writtenThresholdNotes(root, system, before, after, existing, page, set) {
|
|
932
|
+
const read = (fields, field) => valueNamed(fields, field);
|
|
933
|
+
const was = existing === undefined ? {} : effectiveFields(before, existing);
|
|
934
|
+
const is = effectiveFields(after, page);
|
|
935
|
+
return Object.keys(set).flatMap((field) => {
|
|
936
|
+
const [from, to] = [read(was, field), read(is, field)];
|
|
937
|
+
return String(from) === String(to) ? [] : thresholdNotes(root, system, field, { before: from, after: to, amount: {} });
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* The counters of a page whose written maximum is not what the game system works out from the page without its effects, set to it, and a note (FOR-6). A use
|
|
942
|
+
* that only the lists give and that the page no longer has, with a maximum of 0 or none where it had one, is taken off (DAT-9).
|
|
943
|
+
*/
|
|
944
|
+
function workedLimitsChange(system, before, existing, world, page) {
|
|
945
|
+
const fields = inheritedFields(world, page);
|
|
946
|
+
const old = existing === undefined ? undefined : inheritedFields(before, existing);
|
|
947
|
+
const changes = Object.keys(world.system?.formulas?.counters ?? {}).flatMap((field) => {
|
|
948
|
+
const key = sameName(Object.keys(page.fields), field);
|
|
949
|
+
const counter = key === undefined ? undefined : readCounter(page.fields[key]);
|
|
950
|
+
const { max } = counterLimits(world, fields, field);
|
|
951
|
+
const had = old === undefined || max !== undefined ? undefined : counterLimits(before, old, field).max;
|
|
952
|
+
const limit = max ?? (had === undefined ? undefined : 0);
|
|
953
|
+
if (key === undefined || counter === undefined || limit === undefined || counter.max === limit) {
|
|
954
|
+
return [];
|
|
955
|
+
}
|
|
956
|
+
// Of a use, what was spent stays spent, as a table counts it: a maximum that grows by 3 gives 3 more; one that falls only caps the current, as the SRD does,
|
|
957
|
+
// but for what an effect allows above the written maximum (FOR-6, D-228). Any other counter keeps its current, which reads no higher than the maximum.
|
|
958
|
+
const grows = counter.max !== undefined && limit > counter.max;
|
|
959
|
+
const capped = counter.max !== undefined && counter.value <= counter.max ? Math.max(0, Math.min(counter.value, limit)) : counter.value;
|
|
960
|
+
const value = !isUse(world, key) || counter.max === undefined ? counter.value : grows ? counter.value + limit - counter.max : capped;
|
|
961
|
+
return [{ key, before: counter.max, value: formatCounter({ value, max: limit }), reads: formatCounter({ value: Math.min(value, limit), max: limit }), max: limit, lost: max === undefined }];
|
|
962
|
+
});
|
|
963
|
+
// Only a use that the lists give, such as rages from the classes, never one the sheet asks to write, as hp: it stays at any maximum.
|
|
964
|
+
const asked = new Set((system?.sheet ?? []).flatMap((group) => group.entries.filter((entry) => !entry.optional).map((entry) => comparableText(entry.field))));
|
|
965
|
+
// A maximum from a class's row, with no formula, has no sources: the lists give it.
|
|
966
|
+
const givenByLists = (field) => {
|
|
967
|
+
const sources = maximumSources(world, field, fields);
|
|
968
|
+
return !asked.has(comparableText(field)) && (sources.length === 0 || sources.some((name) => declaredKind(world, name) === "list"));
|
|
969
|
+
};
|
|
970
|
+
// A use whose maximum has no value now, where it had one, as the slots of a caster who is one no more, is no longer the character's either.
|
|
971
|
+
const gone = changes.filter((change) => change.max <= 0 && isUse(world, change.key) && !asked.has(comparableText(change.key)) && (change.lost || givenByLists(change.key)));
|
|
972
|
+
const kept = changes.filter((change) => !gone.includes(change));
|
|
973
|
+
// "hp 10/17 (max 10 → 17: hp-base 10, level 2, con-mod +2)": what the maximum comes from, so that a written maximum is not taken for the dice.
|
|
974
|
+
const said = kept.map((change) => {
|
|
975
|
+
const parts = workedParts(world, workedMaximum(world, fields, change.key)?.reads ?? []);
|
|
976
|
+
return `${change.key} ${change.reads} (max ${change.before ?? "none"} → ${change.max}${parts === "" ? "" : `: ${parts}`})`;
|
|
977
|
+
});
|
|
978
|
+
// A use the page gets, such as a new level's channel-divinity, is written full on who keeps the sheet (DAT-9, D-225).
|
|
979
|
+
const added = hasSheet(world, page) ? missingUses(world, fields).map((use) => ({ key: use.field, value: formatCounter({ value: use.max, max: use.max }) })) : [];
|
|
980
|
+
const name = world.system?.name ?? "";
|
|
981
|
+
const notes = [
|
|
982
|
+
said.length === 0 ? "" : `${said.join(", ")}: worked out by the system ${name}.`,
|
|
983
|
+
added.length === 0 ? "" : `${page.name} has ${added.map((use) => `${use.key} ${use.value}`).join(", ")}, full: uses of the system ${name} that come back with a rest.`,
|
|
984
|
+
gone.length === 0 ? "" : `${page.name} no longer has ${gone.map((change) => change.key).join(", ")}: the system ${name} gives no uses of ${gone.length === 1 ? "it" : "them"} now.`,
|
|
985
|
+
];
|
|
986
|
+
return {
|
|
987
|
+
set: Object.fromEntries([...kept, ...added].map((change) => [change.key, change.value])),
|
|
988
|
+
unset: gone.map((change) => change.key),
|
|
989
|
+
notes: notes.filter((note) => note !== ""),
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* A field that is always a list, or that the game system declares one, given one item, such as contains=[[Locanda]] or tags=boss, holds a list of that one item;
|
|
994
|
+
* text with commas is left to the checks that ask for a list, but a road's stops, links and lengths taking turns, which are split at the commas (ROA-6).
|
|
995
|
+
*/
|
|
996
|
+
function oneItemLists(world, set, type) {
|
|
997
|
+
const isList = (name) => listFieldNames.some((field) => comparableText(field) === comparableText(name)) || declaredKind(world, name) === "list";
|
|
998
|
+
const listOf = (name, value) => {
|
|
999
|
+
if (typeof value !== "string" || !isList(name) || value.trim() === "") {
|
|
1000
|
+
return value;
|
|
1001
|
+
}
|
|
1002
|
+
return comparableText(name) === "stops" && type === "place" ? value.split(",").map((item) => item.trim()) : value.includes(",") ? value : [value];
|
|
1003
|
+
};
|
|
1004
|
+
return Object.fromEntries(Object.entries(set).map(([name, value]) => [name, listOf(name, value)]));
|
|
1005
|
+
}
|
|
1006
|
+
/** Text given through MCP to a field that is always a list, such as "[[A]], [[B]]", read as the command line reads it (D-136); any other value as it is. */
|
|
1007
|
+
function listFromText(name, value) {
|
|
1008
|
+
return typeof value === "string" && listFieldNames.some((field) => comparableText(field) === comparableText(name)) ? readFieldValue(value) : value;
|
|
1009
|
+
}
|
|
1010
|
+
function readAssignment(assignment) {
|
|
1011
|
+
const separator = assignment.indexOf("=");
|
|
1012
|
+
const value = assignment.slice(separator + 1).trim();
|
|
1013
|
+
if (separator <= 0 || value === "") {
|
|
1014
|
+
throw new Error(`Write a field as field=value, not "${assignment}". To remove a field, use unset.`);
|
|
1015
|
+
}
|
|
1016
|
+
return [assignment.slice(0, separator).trim(), readFieldValue(value)];
|
|
1017
|
+
}
|