@datamoc/mw_games 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/CHANGELOG.md +964 -0
  2. package/README.md +43 -3
  3. package/dist/actors/Affix.d.ts +7 -1
  4. package/dist/actors/Affix.d.ts.map +1 -1
  5. package/dist/actors/Affix.js +4 -2
  6. package/dist/actors/Affix.js.map +1 -1
  7. package/dist/actors/StatBlock.d.ts +20 -0
  8. package/dist/actors/StatBlock.d.ts.map +1 -1
  9. package/dist/actors/StatBlock.js +14 -3
  10. package/dist/actors/StatBlock.js.map +1 -1
  11. package/dist/actors/index.d.ts +2 -2
  12. package/dist/actors/index.d.ts.map +1 -1
  13. package/dist/actors/index.js +1 -1
  14. package/dist/actors/index.js.map +1 -1
  15. package/dist/ai/index.d.ts +118 -0
  16. package/dist/ai/index.d.ts.map +1 -0
  17. package/dist/ai/index.js +203 -0
  18. package/dist/ai/index.js.map +1 -0
  19. package/dist/ai/lua.d.ts +47 -0
  20. package/dist/ai/lua.d.ts.map +1 -0
  21. package/dist/ai/lua.js +195 -0
  22. package/dist/ai/lua.js.map +1 -0
  23. package/dist/ai/search.d.ts +59 -0
  24. package/dist/ai/search.d.ts.map +1 -0
  25. package/dist/ai/search.js +100 -0
  26. package/dist/ai/search.js.map +1 -0
  27. package/dist/board/Engine.d.ts +14 -0
  28. package/dist/board/Engine.d.ts.map +1 -1
  29. package/dist/board/Engine.js +33 -45
  30. package/dist/board/Engine.js.map +1 -1
  31. package/dist/board/index.d.ts +1 -1
  32. package/dist/board/index.d.ts.map +1 -1
  33. package/dist/board/index.js +1 -1
  34. package/dist/board/index.js.map +1 -1
  35. package/dist/core/Csv.d.ts +10 -0
  36. package/dist/core/Csv.d.ts.map +1 -1
  37. package/dist/core/Csv.js +34 -3
  38. package/dist/core/Csv.js.map +1 -1
  39. package/dist/core/Entity.d.ts +2 -2
  40. package/dist/core/Entity.d.ts.map +1 -1
  41. package/dist/core/Entity.js +18 -4
  42. package/dist/core/Entity.js.map +1 -1
  43. package/dist/index.d.ts +1 -0
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +1 -0
  46. package/dist/index.js.map +1 -1
  47. package/dist/mw_games.global.js +97 -97
  48. package/dist/mwl/actors.d.ts +3 -1
  49. package/dist/mwl/actors.d.ts.map +1 -1
  50. package/dist/mwl/actors.js +15 -2
  51. package/dist/mwl/actors.js.map +1 -1
  52. package/dist/mwl/catalog.d.ts.map +1 -1
  53. package/dist/mwl/catalog.js +16 -9
  54. package/dist/mwl/catalog.js.map +1 -1
  55. package/dist/mwl/compiler.d.ts +20 -0
  56. package/dist/mwl/compiler.d.ts.map +1 -1
  57. package/dist/mwl/compiler.js +76 -3
  58. package/dist/mwl/compiler.js.map +1 -1
  59. package/dist/mwl/conditions.d.ts +9 -0
  60. package/dist/mwl/conditions.d.ts.map +1 -0
  61. package/dist/mwl/conditions.js +162 -0
  62. package/dist/mwl/conditions.js.map +1 -0
  63. package/dist/mwl/content.d.ts +61 -0
  64. package/dist/mwl/content.d.ts.map +1 -1
  65. package/dist/mwl/content.js +95 -34
  66. package/dist/mwl/content.js.map +1 -1
  67. package/dist/mwl/expression.d.ts.map +1 -1
  68. package/dist/mwl/expression.js +16 -6
  69. package/dist/mwl/expression.js.map +1 -1
  70. package/dist/mwl/fengari.d.ts +8 -0
  71. package/dist/mwl/fengari.d.ts.map +1 -0
  72. package/dist/mwl/fengari.js +136 -0
  73. package/dist/mwl/fengari.js.map +1 -0
  74. package/dist/mwl/hooks.d.ts +1 -1
  75. package/dist/mwl/hooks.d.ts.map +1 -1
  76. package/dist/mwl/hooks.js.map +1 -1
  77. package/dist/mwl/index.d.ts +16 -7
  78. package/dist/mwl/index.d.ts.map +1 -1
  79. package/dist/mwl/index.js +7 -3
  80. package/dist/mwl/index.js.map +1 -1
  81. package/dist/mwl/persistence.d.ts.map +1 -1
  82. package/dist/mwl/persistence.js +10 -2
  83. package/dist/mwl/persistence.js.map +1 -1
  84. package/dist/mwl/readers.d.ts +18 -0
  85. package/dist/mwl/readers.d.ts.map +1 -0
  86. package/dist/mwl/readers.js +58 -0
  87. package/dist/mwl/readers.js.map +1 -0
  88. package/dist/mwl/report.d.ts +9 -0
  89. package/dist/mwl/report.d.ts.map +1 -0
  90. package/dist/mwl/report.js +36 -0
  91. package/dist/mwl/report.js.map +1 -0
  92. package/dist/mwl/runtime.d.ts +65 -2
  93. package/dist/mwl/runtime.d.ts.map +1 -1
  94. package/dist/mwl/runtime.js +245 -26
  95. package/dist/mwl/runtime.js.map +1 -1
  96. package/dist/mwl/schema.d.ts +13 -1
  97. package/dist/mwl/schema.d.ts.map +1 -1
  98. package/dist/mwl/schema.js +222 -7
  99. package/dist/mwl/schema.js.map +1 -1
  100. package/dist/mwl/scripts.d.ts +15 -0
  101. package/dist/mwl/scripts.d.ts.map +1 -0
  102. package/dist/mwl/scripts.js +23 -0
  103. package/dist/mwl/scripts.js.map +1 -0
  104. package/dist/mwl/utils.d.ts +8 -0
  105. package/dist/mwl/utils.d.ts.map +1 -0
  106. package/dist/mwl/utils.js +37 -0
  107. package/dist/mwl/utils.js.map +1 -0
  108. package/dist/two-d/render/Types2D.d.ts +19 -4
  109. package/dist/two-d/render/Types2D.d.ts.map +1 -1
  110. package/dist/two-d/render/Types2D.js +25 -0
  111. package/dist/two-d/render/Types2D.js.map +1 -1
  112. package/dist/two-d/render/index.d.ts +2 -2
  113. package/dist/two-d/render/index.d.ts.map +1 -1
  114. package/dist/two-d/render/index.js +1 -1
  115. package/dist/two-d/render/index.js.map +1 -1
  116. package/dist/version.d.ts +1 -1
  117. package/dist/version.js +1 -1
  118. package/package.json +27 -6
  119. package/tools/extract-html.d.mts +18 -0
  120. package/tools/extract-html.mjs +235 -0
  121. package/tools/mwl.mjs +86 -16
@@ -0,0 +1,58 @@
1
+ /** Read a compiled node with one shared coercion and diagnostic policy. */
2
+ export function readAttributes(node, fields) {
3
+ const value = {};
4
+ const diagnostics = [];
5
+ for (const [name, spec] of Object.entries(fields)) {
6
+ const source = spec.source ?? name;
7
+ const raw = node.attributes[source];
8
+ if (raw === undefined) {
9
+ if (spec.required)
10
+ diagnostics.push(diagnostic('MWL_REQUIRED_FIELD', `missing required attribute "${source}"`, node));
11
+ else if (spec.default !== undefined)
12
+ value[name] = spec.default;
13
+ continue;
14
+ }
15
+ const converted = coerce(raw, spec.type);
16
+ if (converted === undefined)
17
+ diagnostics.push(diagnostic('MWL_FIELD_TYPE', `invalid ${spec.type} attribute "${source}": "${raw}"`, node));
18
+ else
19
+ value[name] = converted;
20
+ }
21
+ return { value: value, diagnostics };
22
+ }
23
+ /** Collect children without making every game adapter repeat this filter. */
24
+ export function readChildren(node, tag, read) {
25
+ const values = [];
26
+ const diagnostics = [];
27
+ for (const child of node.children) {
28
+ if (child.tag !== tag)
29
+ continue;
30
+ const result = read(child);
31
+ values.push(result.value);
32
+ diagnostics.push(...result.diagnostics);
33
+ }
34
+ return { value: values, diagnostics };
35
+ }
36
+ function coerce(raw, type) {
37
+ if (type === 'string')
38
+ return raw;
39
+ if (type === 'id')
40
+ return /^[A-Za-z_][\w.-]*$/.test(raw) ? raw : undefined;
41
+ if (type === 'number')
42
+ return Number.isFinite(Number(raw)) ? Number(raw) : undefined;
43
+ if (type === 'integer')
44
+ return /^-?\d+$/.test(raw) ? Number(raw) : undefined;
45
+ if (type === 'boolean')
46
+ return raw === 'true' || raw === 'yes' ? true : raw === 'false' || raw === 'no' ? false : undefined;
47
+ const parts = raw
48
+ .split(',')
49
+ .map((part) => part.trim())
50
+ .filter(Boolean);
51
+ if (type === 'id-list')
52
+ return parts.every((part) => /^[A-Za-z_][\w.-]*$/.test(part)) ? parts : undefined;
53
+ return parts.every((part) => Number.isFinite(Number(part))) ? parts.map(Number) : undefined;
54
+ }
55
+ function diagnostic(code, message, node) {
56
+ return { code, message, location: node.location ?? { file: '<mwl>', line: 1, column: 1 } };
57
+ }
58
+ //# sourceMappingURL=readers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readers.js","sourceRoot":"","sources":["../../src/mwl/readers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { MwlCompiledGame } from './compiler.ts';
2
+ export interface MwlContentReport {
3
+ readonly tags: Readonly<Record<string, number>>;
4
+ readonly opaqueTags: readonly string[];
5
+ readonly danglingReferences: readonly string[];
6
+ }
7
+ /** Summarise a compiled catalog for parity checks and CI output. */
8
+ export declare function contentReport(game: MwlCompiledGame): MwlContentReport;
9
+ //# sourceMappingURL=report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/mwl/report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAIrD,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAChD,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/C;AAED,oEAAoE;AACpE,wBAAgB,aAAa,CAAC,IAAI,EAAE,eAAe,GAAG,gBAAgB,CA8BrE"}
@@ -0,0 +1,36 @@
1
+ import { schema01 } from './schema.js';
2
+ import { flattenNodes } from './utils.js';
3
+ /** Summarise a compiled catalog for parity checks and CI output. */
4
+ export function contentReport(game) {
5
+ const nodes = flattenNodes(game.roots);
6
+ const tags = {};
7
+ const ids = new Set();
8
+ for (const node of nodes) {
9
+ tags[node.tag] = (tags[node.tag] ?? 0) + 1;
10
+ if (node.attributes.id)
11
+ ids.add(node.attributes.id);
12
+ }
13
+ const knownTags = new Set(Object.keys(schema01));
14
+ const opaqueTags = nodes
15
+ .filter((node) => !knownTags.has(node.tag))
16
+ .map((node) => node.tag)
17
+ .filter((tag, index, all) => all.indexOf(tag) === index)
18
+ .sort();
19
+ const references = new Set();
20
+ for (const node of nodes) {
21
+ for (const [key, value] of Object.entries(node.attributes)) {
22
+ if (/^(?:ability|item|terrain|unit|unit_type|weapon)(?:_id)?$/.test(key))
23
+ for (const reference of value
24
+ .split(',')
25
+ .map((part) => part.trim())
26
+ .filter(Boolean))
27
+ references.add(reference);
28
+ }
29
+ }
30
+ return {
31
+ tags: Object.fromEntries(Object.entries(tags).sort(([a], [b]) => a.localeCompare(b))),
32
+ opaqueTags,
33
+ danglingReferences: [...references].filter((reference) => !ids.has(reference)).sort(),
34
+ };
35
+ }
36
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/mwl/report.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAQ1C,oEAAoE;AACpE,MAAM,UAAU,aAAa,CAAC,IAAqB;IAClD,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvC,MAAM,IAAI,GAA2B,EAAE,CAAC;IACxC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,IAAI,CAAC,UAAU,CAAC,EAAE;YAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;IACrD,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;IACjD,MAAM,UAAU,GAAG,KAAK;SACtB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;SAC1C,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC;SACvB,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC;SACvD,IAAI,EAAE,CAAC;IACT,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QAC1B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5D,IAAI,0DAA0D,CAAC,IAAI,CAAC,GAAG,CAAC;gBACvE,KAAK,MAAM,SAAS,IAAI,KAAK;qBAC3B,KAAK,CAAC,GAAG,CAAC;qBACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;qBAC1B,MAAM,CAAC,OAAO,CAAC;oBAChB,UAAU,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC7B,CAAC;IACF,CAAC;IACD,OAAO;QACN,IAAI,EAAE,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;QACrF,UAAU;QACV,kBAAkB,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE;KACrF,CAAC;AACH,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { MwlCompiledGame } from './compiler.ts';
1
+ import type { MwlCompiledGame, MwlCompiledNode } from './compiler.ts';
2
2
  import type { AiHook, CommandHook, GeneratorHook, MigrationHook, ModifierHook, PredicateHook } from './hooks.ts';
3
3
  import { type MwlPersistenceOptions } from './persistence.ts';
4
4
  export interface MwlMapStart {
@@ -44,6 +44,30 @@ export interface MwlWorld {
44
44
  /** the current schedule entry, by id */
45
45
  timeOfDay?: string;
46
46
  scheduleIndex?: number;
47
+ /** ids of one-shot events that already fired, so a restore keeps them spent */
48
+ firedEvents?: string[];
49
+ pendingDialogue?: {
50
+ id: string;
51
+ choices: readonly MwlDialogueChoice[];
52
+ };
53
+ }
54
+ /** One `[message]`: the text plus whatever a game needs to show it. */
55
+ export interface MwlMessage {
56
+ readonly text: string;
57
+ readonly speaker?: string;
58
+ readonly portrait?: string;
59
+ readonly side?: number;
60
+ /** choices waiting on the player; answer with `answerDialogue` */
61
+ readonly choices?: readonly MwlDialogueChoice[];
62
+ /** identifies the pending dialogue this message's choices belong to */
63
+ readonly dialogueId?: string;
64
+ }
65
+ /** One offered choice inside a `dialogue`: its label and the event answering runs. */
66
+ export interface MwlDialogueChoice {
67
+ readonly text: string;
68
+ readonly event?: string;
69
+ /** inline branch commands, used when a choice does not name a separate event */
70
+ readonly branch?: readonly MwlCompiledNode[];
47
71
  }
48
72
  /** The hook implementations a game provides to the runtime. */
49
73
  export interface MwlHookRegistry {
@@ -56,7 +80,8 @@ export interface MwlHookRegistry {
56
80
  }
57
81
  export interface MwlRuntimeOptions {
58
82
  readonly world?: MwlWorld;
59
- readonly onMessage?: (text: string) => void;
83
+ /** called for every `[message]` command, with its speaker and portrait */
84
+ readonly onMessage?: (message: MwlMessage) => void;
60
85
  /** resolve a `[map] file=` reference to its text */
61
86
  readonly resolveMap?: (file: string) => string;
62
87
  readonly hooks?: MwlHookRegistry;
@@ -129,8 +154,43 @@ export declare class MwlRuntime {
129
154
  private readonly persistence;
130
155
  private readonly unitTypes;
131
156
  private schedule;
157
+ private pendingDialogue;
158
+ private dialogueCounter;
132
159
  constructor(game: MwlCompiledGame, options?: MwlRuntimeOptions);
133
160
  run(trigger: string): void;
161
+ /** Fire one named event through the same filter, claim, and execution path as a trigger. */
162
+ fireEvent(id: string): boolean;
163
+ /**
164
+ * Fire the `on=moveto` events for a unit that has just arrived somewhere.
165
+ * The runtime's own `[move]` command calls this, and so does a game whose
166
+ * engine moved the unit: write the new position into `world.units` first,
167
+ * then call this with the unit id.
168
+ *
169
+ * An event matches when `x`/`y` (when given) are the unit's position, and
170
+ * `unit`/`side` (when given) are the unit's. A moveto event fires once by
171
+ * default; pass `once=false` on the event to let it fire every time.
172
+ */
173
+ fireMoveto(id: string): void;
174
+ /** A `moveto` event is a story beat: it fires once unless told otherwise. */
175
+ private claimEvent;
176
+ private eventFiltersMatch;
177
+ private executeEvent;
178
+ /**
179
+ * Deliver a `dialogue`: its `message`/`say` lines first, then one message
180
+ * carrying the offered `choice`s. A choice names the event answering runs;
181
+ * the game answers later with `answerDialogue`, so content keeps any
182
+ * follow-up commands inside the referenced events rather than after the
183
+ * dialogue. Choices gated by `variable`/`equals` are skipped unless the
184
+ * world variable matches.
185
+ */
186
+ private showDialogue;
187
+ private showSay;
188
+ /**
189
+ * Answer a pending dialogue: run the chosen choice's event and clear the
190
+ * pending state. Returns false when there is no such pending dialogue or
191
+ * choice; throws when the referenced event does not exist (content error).
192
+ */
193
+ answerDialogue(dialogueId: string, choiceIndex: number): boolean;
134
194
  /**
135
195
  * Evaluate `[objectives]` now and return the resulting status. A game whose
136
196
  * own engine resolved the actions (real movement, combat) calls this after
@@ -150,6 +210,9 @@ export declare class MwlRuntime {
150
210
  private executeNode;
151
211
  private runHook;
152
212
  private applyMove;
213
+ /** Deliver a `[message]` to the game, with speaker and portrait when given. */
214
+ private showMessage;
215
+ private setVariable;
153
216
  private spawnUnit;
154
217
  private killUnit;
155
218
  private addGold;
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/mwl/runtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/mwl/runtime.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,8 @@
1
1
  import { decodeSave, encodeSave } from './persistence.js';
2
+ import { evaluateExpression } from './expression.js';
3
+ import { integerAttribute, requiredAttribute } from './utils.js';
4
+ const required = requiredAttribute;
5
+ const integer = (node, attribute, fallback) => integerAttribute(node, attribute, fallback) ?? fallback;
2
6
  export function createWorld() {
3
7
  return {
4
8
  variables: {},
@@ -54,6 +58,8 @@ export class MwlRuntime {
54
58
  constructor(game, options = {}) {
55
59
  this.unitTypes = new Map();
56
60
  this.schedule = [];
61
+ this.pendingDialogue = null;
62
+ this.dialogueCounter = 0;
57
63
  this.game = game;
58
64
  this.world = options.world ?? createWorld();
59
65
  this.onMessage = options.onMessage;
@@ -70,19 +76,183 @@ export class MwlRuntime {
70
76
  for (const event of this.nodes('event')) {
71
77
  if ((event.attributes.on ?? event.attributes.trigger) !== trigger)
72
78
  continue;
73
- const condition = event.children.find((child) => child.tag === 'condition');
74
- if (condition && this.world.variables[condition.attributes.variable] !== condition.attributes.equals)
79
+ if (!this.eventFiltersMatch(event))
75
80
  continue;
76
- const filter = event.children.find((child) => child.tag === 'filter');
77
- if (filter && !this.filterMatches(filter))
81
+ if (!this.claimEvent(event))
78
82
  continue;
79
- for (const command of event.children.filter((child) => child.tag !== 'condition' &&
80
- child.tag !== 'filter' &&
81
- child.tag !== 'dialogue' &&
82
- child.tag !== 'say'))
83
+ this.executeEvent(event);
84
+ }
85
+ this.checkObjectives();
86
+ }
87
+ /** Fire one named event through the same filter, claim, and execution path as a trigger. */
88
+ fireEvent(id) {
89
+ const event = this.nodes('event').find((candidate) => candidate.attributes.id === id);
90
+ if (!event || !this.eventFiltersMatch(event) || !this.claimEvent(event))
91
+ return false;
92
+ this.executeEvent(event);
93
+ this.checkObjectives();
94
+ return true;
95
+ }
96
+ /**
97
+ * Fire the `on=moveto` events for a unit that has just arrived somewhere.
98
+ * The runtime's own `[move]` command calls this, and so does a game whose
99
+ * engine moved the unit: write the new position into `world.units` first,
100
+ * then call this with the unit id.
101
+ *
102
+ * An event matches when `x`/`y` (when given) are the unit's position, and
103
+ * `unit`/`side` (when given) are the unit's. A moveto event fires once by
104
+ * default; pass `once=false` on the event to let it fire every time.
105
+ */
106
+ fireMoveto(id) {
107
+ const unit = this.world.units[id];
108
+ if (!unit || !unit.alive)
109
+ return;
110
+ for (const event of this.nodes('event')) {
111
+ if ((event.attributes.on ?? event.attributes.trigger) !== 'moveto')
112
+ continue;
113
+ if (event.attributes.x !== undefined && !coordinateMatches(event.attributes.x, unit.x))
114
+ continue;
115
+ if (event.attributes.y !== undefined && !coordinateMatches(event.attributes.y, unit.y))
116
+ continue;
117
+ if (event.attributes.unit !== undefined && event.attributes.unit !== id)
118
+ continue;
119
+ if (event.attributes.side !== undefined && integer(event, 'side', Number.NaN) !== unit.side)
120
+ continue;
121
+ if (!this.eventFiltersMatch(event))
122
+ continue;
123
+ if (!this.claimEvent(event))
124
+ continue;
125
+ this.executeEvent(event);
126
+ }
127
+ this.checkObjectives();
128
+ }
129
+ /** A `moveto` event is a story beat: it fires once unless told otherwise. */
130
+ claimEvent(event) {
131
+ const once = event.attributes.once === undefined ? event.attributes.on === 'moveto' : event.attributes.once !== 'false';
132
+ if (!once)
133
+ return true;
134
+ const key = event.attributes.id ??
135
+ `${event.attributes.on ?? event.attributes.trigger ?? 'event'}@${event.location?.line ?? 0}`;
136
+ const fired = (this.world.firedEvents ??= []);
137
+ if (fired.includes(key))
138
+ return false;
139
+ fired.push(key);
140
+ return true;
141
+ }
142
+ eventFiltersMatch(event) {
143
+ const condition = event.children.find((child) => child.tag === 'condition');
144
+ if (condition && !sameValue(this.world.variables[condition.attributes.variable], condition.attributes.equals))
145
+ return false;
146
+ const filter = event.children.find((child) => child.tag === 'filter');
147
+ return !filter || this.filterMatches(filter);
148
+ }
149
+ executeEvent(event) {
150
+ for (const command of event.children.filter((child) => child.tag !== 'condition' && child.tag !== 'filter')) {
151
+ if (command.tag === 'say') {
152
+ this.showSay(command);
153
+ continue;
154
+ }
155
+ if (command.tag === 'dialogue') {
156
+ this.showDialogue(command);
157
+ continue;
158
+ }
159
+ this.executeNode(command);
160
+ }
161
+ }
162
+ /**
163
+ * Deliver a `dialogue`: its `message`/`say` lines first, then one message
164
+ * carrying the offered `choice`s. A choice names the event answering runs;
165
+ * the game answers later with `answerDialogue`, so content keeps any
166
+ * follow-up commands inside the referenced events rather than after the
167
+ * dialogue. Choices gated by `variable`/`equals` are skipped unless the
168
+ * world variable matches.
169
+ */
170
+ showDialogue(node) {
171
+ let last = null;
172
+ for (const child of node.children) {
173
+ if (child.tag === 'message') {
174
+ this.showMessage(child.attributes);
175
+ const side = Number.parseInt(child.attributes.side ?? '', 10);
176
+ last = {
177
+ text: child.attributes.text ?? child.attributes.value ?? '',
178
+ ...(child.attributes.speaker === undefined ? {} : { speaker: child.attributes.speaker }),
179
+ ...(child.attributes.portrait === undefined ? {} : { portrait: child.attributes.portrait }),
180
+ ...(Number.isFinite(side) ? { side } : {}),
181
+ };
182
+ }
183
+ else if (child.tag === 'say') {
184
+ this.showSay(child);
185
+ last = { text: child.attributes.text ?? '', speaker: child.attributes.speaker };
186
+ }
187
+ }
188
+ const choices = [];
189
+ for (const child of node.children) {
190
+ if (child.tag !== 'choice' || !child.attributes.text)
191
+ continue;
192
+ if (child.attributes.variable !== undefined &&
193
+ !sameValue(this.world.variables[child.attributes.variable], child.attributes.equals))
194
+ continue;
195
+ const branches = child.children.filter((branch) => branch.tag === 'branch');
196
+ if (branches.length) {
197
+ for (const branch of branches) {
198
+ choices.push({
199
+ text: branch.attributes.text ?? child.attributes.text,
200
+ branch: branch.children,
201
+ });
202
+ }
203
+ }
204
+ else if (child.attributes.event)
205
+ choices.push({ text: child.attributes.text, event: child.attributes.event });
206
+ }
207
+ if (choices.length === 0)
208
+ return;
209
+ this.dialogueCounter += 1;
210
+ const id = `dialogue-${this.dialogueCounter}`;
211
+ this.pendingDialogue = { id, choices };
212
+ this.world.pendingDialogue = { id, choices };
213
+ this.onMessage?.({
214
+ text: last?.text ?? '',
215
+ ...(last?.speaker === undefined ? {} : { speaker: last.speaker }),
216
+ choices,
217
+ dialogueId: id,
218
+ });
219
+ }
220
+ showSay(node) {
221
+ if (!this.onMessage)
222
+ return;
223
+ this.onMessage({
224
+ text: node.attributes.text ?? '',
225
+ ...(node.attributes.speaker === undefined ? {} : { speaker: node.attributes.speaker }),
226
+ });
227
+ }
228
+ /**
229
+ * Answer a pending dialogue: run the chosen choice's event and clear the
230
+ * pending state. Returns false when there is no such pending dialogue or
231
+ * choice; throws when the referenced event does not exist (content error).
232
+ */
233
+ answerDialogue(dialogueId, choiceIndex) {
234
+ const pending = this.pendingDialogue;
235
+ if (!pending || pending.id !== dialogueId)
236
+ return false;
237
+ const choice = pending.choices[choiceIndex];
238
+ if (!choice)
239
+ return false;
240
+ this.pendingDialogue = null;
241
+ delete this.world.pendingDialogue;
242
+ if (choice.event) {
243
+ const event = this.nodes('event').find((candidate) => candidate.attributes.id === choice.event);
244
+ if (!event)
245
+ throw new Error(`MWL dialogue choice points at unknown event: ${choice.event}`);
246
+ if (!this.claimEvent(event))
247
+ return true;
248
+ this.executeEvent(event);
249
+ }
250
+ else {
251
+ for (const command of choice.branch ?? [])
83
252
  this.executeNode(command);
84
253
  }
85
254
  this.checkObjectives();
255
+ return true;
86
256
  }
87
257
  /**
88
258
  * Evaluate `[objectives]` now and return the resulting status. A game whose
@@ -111,6 +281,15 @@ export class MwlRuntime {
111
281
  this.world.map = restored.map ?? null;
112
282
  this.world.timeOfDay = restored.timeOfDay ?? this.schedule[0] ?? '';
113
283
  this.world.scheduleIndex = restored.scheduleIndex ?? 0;
284
+ // One-shot events stay spent across a load.
285
+ this.world.firedEvents = [...(restored.firedEvents ?? [])];
286
+ this.world.pendingDialogue = restored.pendingDialogue;
287
+ this.pendingDialogue = restored.pendingDialogue
288
+ ? { id: restored.pendingDialogue.id, choices: [...restored.pendingDialogue.choices] }
289
+ : null;
290
+ const dialogueNumber = restored.pendingDialogue?.id.match(/^dialogue-(\d+)$/);
291
+ if (dialogueNumber)
292
+ this.dialogueCounter = Math.max(this.dialogueCounter, Number(dialogueNumber[1]));
114
293
  }
115
294
  loadUnitTypes() {
116
295
  for (const node of this.nodes('unit_type')) {
@@ -225,9 +404,10 @@ export class MwlRuntime {
225
404
  if (!name)
226
405
  throw new Error('MWL command is missing name');
227
406
  switch (name) {
228
- case 'message':
229
- this.onMessage?.(node.attributes.text ?? node.attributes.value ?? '');
407
+ case 'message': {
408
+ this.showMessage(node.attributes);
230
409
  break;
410
+ }
231
411
  case 'spawn':
232
412
  this.spawnUnit(node.attributes.type ?? '', node.attributes.side === undefined ? undefined : integer(node, 'side', 0), integer(node, 'x', 0), integer(node, 'y', 0), node.attributes.id ?? node.attributes.target, node.attributes.hp === undefined ? undefined : integer(node, 'hp', 1));
233
413
  break;
@@ -247,8 +427,7 @@ export class MwlRuntime {
247
427
  this.addGold(required(node, 'side'), integer(node, 'delta', integer(node, 'amount', 0)));
248
428
  break;
249
429
  case 'set_variable':
250
- this.world.variables[node.tag === 'command' ? required(node, 'target') : required(node, 'name')] =
251
- node.attributes.value ?? '';
430
+ this.setVariable(node.tag === 'command' ? required(node, 'target') : required(node, 'name'), node.attributes.value ?? '');
252
431
  break;
253
432
  case 'end_turn':
254
433
  this.endTurn();
@@ -303,6 +482,33 @@ export class MwlRuntime {
303
482
  }
304
483
  unit.x = x;
305
484
  unit.y = y;
485
+ this.fireMoveto(id);
486
+ }
487
+ /** Deliver a `[message]` to the game, with speaker and portrait when given. */
488
+ showMessage(attributes) {
489
+ if (!this.onMessage)
490
+ return;
491
+ const side = Number.parseInt(attributes.side ?? '', 10);
492
+ const message = {
493
+ text: attributes.text ?? attributes.value ?? '',
494
+ ...(attributes.speaker === undefined ? {} : { speaker: attributes.speaker }),
495
+ ...(attributes.portrait === undefined ? {} : { portrait: attributes.portrait }),
496
+ ...(Number.isFinite(side) ? { side } : {}),
497
+ };
498
+ this.onMessage(message);
499
+ }
500
+ setVariable(name, raw) {
501
+ const numeric = Number(raw);
502
+ if (raw.trim() !== '' && Number.isFinite(numeric)) {
503
+ this.world.variables[name] = numeric;
504
+ return;
505
+ }
506
+ const context = Object.fromEntries(Object.entries(this.world.variables).filter((entry) => typeof entry[1] === 'number'));
507
+ if (/[+*/^()]|\s-\s/.test(raw) || Object.prototype.hasOwnProperty.call(context, raw.trim())) {
508
+ this.world.variables[name] = evaluateExpression(raw, context);
509
+ return;
510
+ }
511
+ this.world.variables[name] = raw;
306
512
  }
307
513
  spawnUnit(type, side, x, y, id, hp) {
308
514
  const stats = type ? this.unitTypes.get(type) : undefined;
@@ -349,15 +555,16 @@ export class MwlRuntime {
349
555
  }
350
556
  }
351
557
  filterMatches(node) {
352
- const candidates = Object.values(this.world.units).filter((unit) => unit.alive);
353
- return candidates.some((unit) => (node.attributes.side === undefined || unit.side === Number(node.attributes.side)) &&
558
+ return Object.entries(this.world.units).some(([id, unit]) => unit.alive &&
559
+ (node.attributes.unit === undefined || id === node.attributes.unit) &&
560
+ (node.attributes.side === undefined || unit.side === Number(node.attributes.side)) &&
354
561
  (node.attributes.type === undefined || unit.type === node.attributes.type) &&
355
562
  (node.attributes.x === undefined || unit.x === Number(node.attributes.x)) &&
356
563
  (node.attributes.y === undefined || unit.y === Number(node.attributes.y)));
357
564
  }
358
565
  conditionMatches(node) {
359
566
  const condition = node.children.find((child) => child.tag === 'condition');
360
- return !condition || this.world.variables[condition.attributes.variable] === condition.attributes.equals;
567
+ return (!condition || sameValue(this.world.variables[condition.attributes.variable], condition.attributes.equals));
361
568
  }
362
569
  conditionMet(node) {
363
570
  const condition = node.attributes.condition ?? '';
@@ -382,7 +589,9 @@ export class MwlRuntime {
382
589
  case 'unit_at': {
383
590
  const x = optionalInteger(node, 'x');
384
591
  const y = optionalInteger(node, 'y');
592
+ const targetSide = optionalInteger(node, 'side_filter') ?? side;
385
593
  return Object.values(this.world.units).some((unit) => unit.alive &&
594
+ (targetSide === undefined || unit.side === targetSide) &&
386
595
  (node.attributes.type === undefined || unit.type === node.attributes.type) &&
387
596
  unit.x === x &&
388
597
  unit.y === y);
@@ -431,7 +640,7 @@ export class MwlRuntime {
431
640
  setVariable: (name, value) => {
432
641
  this.world.variables[name] = value;
433
642
  },
434
- message: (_speaker, text) => this.onMessage?.(text),
643
+ message: (speaker, text) => this.onMessage?.({ text, ...(speaker ? { speaker } : {}) }),
435
644
  endTurn: () => this.endTurn(),
436
645
  win: (_side) => {
437
646
  this.world.status = 'won';
@@ -499,16 +708,6 @@ function requireUnit(world, id) {
499
708
  throw new Error(`MWL unit is not alive: ${id}`);
500
709
  return unit;
501
710
  }
502
- function required(node, attribute) {
503
- const value = node.attributes[attribute];
504
- if (!value)
505
- throw new Error(`MWL ${node.tag} is missing ${attribute}`);
506
- return value;
507
- }
508
- function integer(node, attribute, fallback) {
509
- const value = node.attributes[attribute];
510
- return value === undefined ? fallback : Number.parseInt(value, 10);
511
- }
512
711
  function optionalInteger(node, attribute) {
513
712
  const value = node.attributes[attribute];
514
713
  if (value === undefined || value === '')
@@ -516,4 +715,24 @@ function optionalInteger(node, attribute) {
516
715
  const parsed = Number.parseInt(value, 10);
517
716
  return Number.isFinite(parsed) ? parsed : undefined;
518
717
  }
718
+ function sameValue(value, expected) {
719
+ if (value === undefined || expected === undefined)
720
+ return value === expected;
721
+ const numeric = Number(expected);
722
+ return typeof value === 'number' && expected.trim() !== '' && Number.isFinite(numeric)
723
+ ? value === numeric
724
+ : value === expected;
725
+ }
726
+ function coordinateMatches(specification, coordinate) {
727
+ return specification.split(',').some((part) => {
728
+ const value = part.trim();
729
+ const range = value.match(/^(-?\d+)\s*-\s*(-?\d+)$/);
730
+ if (range) {
731
+ const from = Number(range[1]);
732
+ const to = Number(range[2]);
733
+ return coordinate >= Math.min(from, to) && coordinate <= Math.max(from, to);
734
+ }
735
+ return /^-?\d+$/.test(value) && Number(value) === coordinate;
736
+ });
737
+ }
519
738
  //# sourceMappingURL=runtime.js.map