@datamoc/mw_games 0.7.2 → 0.7.4

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 (116) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +1 -1
  3. package/dist/board/FogOfWar.d.ts +6 -0
  4. package/dist/board/FogOfWar.d.ts.map +1 -1
  5. package/dist/board/FogOfWar.js +15 -0
  6. package/dist/board/FogOfWar.js.map +1 -1
  7. package/dist/core/ActionJournal.d.ts +36 -0
  8. package/dist/core/ActionJournal.d.ts.map +1 -0
  9. package/dist/core/ActionJournal.js +60 -0
  10. package/dist/core/ActionJournal.js.map +1 -0
  11. package/dist/core/Pathfinding.d.ts +41 -0
  12. package/dist/core/Pathfinding.d.ts.map +1 -0
  13. package/dist/core/Pathfinding.js +86 -0
  14. package/dist/core/Pathfinding.js.map +1 -0
  15. package/dist/core/State.d.ts +59 -0
  16. package/dist/core/State.d.ts.map +1 -0
  17. package/dist/core/State.js +100 -0
  18. package/dist/core/State.js.map +1 -0
  19. package/dist/core/index.d.ts +6 -0
  20. package/dist/core/index.d.ts.map +1 -1
  21. package/dist/core/index.js +3 -0
  22. package/dist/core/index.js.map +1 -1
  23. package/dist/mw_games.global.js +97 -97
  24. package/dist/mwl/hooks.d.ts +3 -2
  25. package/dist/mwl/hooks.d.ts.map +1 -1
  26. package/dist/mwl/hooks.js.map +1 -1
  27. package/dist/mwl/index.d.ts +3 -3
  28. package/dist/mwl/index.d.ts.map +1 -1
  29. package/dist/mwl/index.js +1 -1
  30. package/dist/mwl/index.js.map +1 -1
  31. package/dist/mwl/report.d.ts +17 -1
  32. package/dist/mwl/report.d.ts.map +1 -1
  33. package/dist/mwl/report.js +33 -0
  34. package/dist/mwl/report.js.map +1 -1
  35. package/dist/mwl/runtime.d.ts +46 -1
  36. package/dist/mwl/runtime.d.ts.map +1 -1
  37. package/dist/mwl/runtime.js +237 -19
  38. package/dist/mwl/runtime.js.map +1 -1
  39. package/dist/mwl/schema.d.ts.map +1 -1
  40. package/dist/mwl/schema.js +136 -1
  41. package/dist/mwl/schema.js.map +1 -1
  42. package/dist/roguelike/FieldOfView.d.ts +3 -2
  43. package/dist/roguelike/FieldOfView.d.ts.map +1 -1
  44. package/dist/roguelike/FieldOfView.js +3 -2
  45. package/dist/roguelike/FieldOfView.js.map +1 -1
  46. package/dist/roguelike/Level.d.ts +9 -1
  47. package/dist/roguelike/Level.d.ts.map +1 -1
  48. package/dist/roguelike/Level.js +6 -2
  49. package/dist/roguelike/Level.js.map +1 -1
  50. package/dist/roguelike/MultiTurnBeam.d.ts +70 -0
  51. package/dist/roguelike/MultiTurnBeam.d.ts.map +1 -0
  52. package/dist/roguelike/MultiTurnBeam.js +117 -0
  53. package/dist/roguelike/MultiTurnBeam.js.map +1 -0
  54. package/dist/roguelike/Scheduler.d.ts +5 -1
  55. package/dist/roguelike/Scheduler.d.ts.map +1 -1
  56. package/dist/roguelike/Scheduler.js +10 -3
  57. package/dist/roguelike/Scheduler.js.map +1 -1
  58. package/dist/roguelike/Targeting.d.ts +51 -0
  59. package/dist/roguelike/Targeting.d.ts.map +1 -1
  60. package/dist/roguelike/Targeting.js +87 -2
  61. package/dist/roguelike/Targeting.js.map +1 -1
  62. package/dist/roguelike/index.d.ts +4 -2
  63. package/dist/roguelike/index.d.ts.map +1 -1
  64. package/dist/roguelike/index.js +2 -1
  65. package/dist/roguelike/index.js.map +1 -1
  66. package/dist/rpg/automap.d.ts +4 -0
  67. package/dist/rpg/automap.d.ts.map +1 -1
  68. package/dist/rpg/automap.js +5 -2
  69. package/dist/rpg/automap.js.map +1 -1
  70. package/dist/simulation/Campaign.d.ts +60 -0
  71. package/dist/simulation/Campaign.d.ts.map +1 -0
  72. package/dist/simulation/Campaign.js +85 -0
  73. package/dist/simulation/Campaign.js.map +1 -0
  74. package/dist/simulation/Harness.d.ts +26 -0
  75. package/dist/simulation/Harness.d.ts.map +1 -0
  76. package/dist/simulation/Harness.js +25 -0
  77. package/dist/simulation/Harness.js.map +1 -0
  78. package/dist/simulation/index.d.ts +4 -0
  79. package/dist/simulation/index.d.ts.map +1 -1
  80. package/dist/simulation/index.js +2 -0
  81. package/dist/simulation/index.js.map +1 -1
  82. package/dist/two-d/render/ImageModifiers.d.ts +58 -0
  83. package/dist/two-d/render/ImageModifiers.d.ts.map +1 -0
  84. package/dist/two-d/render/ImageModifiers.js +145 -0
  85. package/dist/two-d/render/ImageModifiers.js.map +1 -0
  86. package/dist/two-d/render/Minimap.d.ts +24 -0
  87. package/dist/two-d/render/Minimap.d.ts.map +1 -1
  88. package/dist/two-d/render/Minimap.js +52 -14
  89. package/dist/two-d/render/Minimap.js.map +1 -1
  90. package/dist/two-d/render/Particles.d.ts +16 -0
  91. package/dist/two-d/render/Particles.d.ts.map +1 -1
  92. package/dist/two-d/render/Particles.js +15 -2
  93. package/dist/two-d/render/Particles.js.map +1 -1
  94. package/dist/two-d/render/index.d.ts +4 -2
  95. package/dist/two-d/render/index.d.ts.map +1 -1
  96. package/dist/two-d/render/index.js +2 -1
  97. package/dist/two-d/render/index.js.map +1 -1
  98. package/dist/two-d/ui/Bar.d.ts +21 -2
  99. package/dist/two-d/ui/Bar.d.ts.map +1 -1
  100. package/dist/two-d/ui/Bar.js +27 -5
  101. package/dist/two-d/ui/Bar.js.map +1 -1
  102. package/dist/two-d/ui/FloatingText.d.ts +42 -1
  103. package/dist/two-d/ui/FloatingText.d.ts.map +1 -1
  104. package/dist/two-d/ui/FloatingText.js +52 -4
  105. package/dist/two-d/ui/FloatingText.js.map +1 -1
  106. package/dist/two-d/ui/FloatingTextStack.d.ts +77 -0
  107. package/dist/two-d/ui/FloatingTextStack.d.ts.map +1 -0
  108. package/dist/two-d/ui/FloatingTextStack.js +99 -0
  109. package/dist/two-d/ui/FloatingTextStack.js.map +1 -0
  110. package/dist/two-d/ui/index.d.ts +3 -1
  111. package/dist/two-d/ui/index.d.ts.map +1 -1
  112. package/dist/two-d/ui/index.js +2 -1
  113. package/dist/two-d/ui/index.js.map +1 -1
  114. package/dist/version.d.ts +1 -1
  115. package/dist/version.js +1 -1
  116. package/package.json +5 -1
@@ -0,0 +1,60 @@
1
+ import type { StateValue } from '../core/State.ts';
2
+ export type CampaignOutcome = 'completed' | 'failed' | 'abandoned';
3
+ export interface CampaignLevel<State extends StateValue, Result extends StateValue = StateValue> {
4
+ readonly id: string;
5
+ readonly run: (state: State, context: {
6
+ readonly levelId: string;
7
+ }) => CampaignLevelResult<State, Result>;
8
+ }
9
+ export interface CampaignLevelResult<State extends StateValue, Result extends StateValue = StateValue> {
10
+ readonly outcome: CampaignOutcome;
11
+ readonly state: State;
12
+ readonly result?: Result;
13
+ readonly next?: string | null;
14
+ readonly reminders?: readonly string[];
15
+ }
16
+ export interface CampaignSnapshot<State extends StateValue, Result extends StateValue = StateValue> {
17
+ readonly currentLevel: string | null;
18
+ readonly state: State;
19
+ readonly reminders: readonly string[];
20
+ readonly results: Readonly<Record<string, Result>>;
21
+ }
22
+ /**
23
+ * Game-neutral level sequencing and carry-over state. A campaign adapter supplies the
24
+ * actual level runner and decides what a level result means; MWG only preserves order,
25
+ * transitions, reminders and serialisable state.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * import { Campaign } from '@datamoc/mw_games/simulation';
30
+ * const campaign = new Campaign({
31
+ * levels: [{ id: 'intro', run: (state) => ({ outcome: 'completed', state, next: null }) }],
32
+ * start: 'intro', state: { gold: 0 },
33
+ * });
34
+ * campaign.completeCurrent({ outcome: 'completed', state: { gold: 10 }, next: null });
35
+ * ```
36
+ */
37
+ export declare class Campaign<State extends StateValue, Result extends StateValue = StateValue> {
38
+ private readonly levels;
39
+ private _currentLevel;
40
+ private _state;
41
+ private _reminders;
42
+ private _results;
43
+ constructor(options: {
44
+ readonly levels: readonly CampaignLevel<State, Result>[];
45
+ readonly start: string;
46
+ readonly state: State;
47
+ });
48
+ get currentLevel(): string | null;
49
+ get state(): State;
50
+ get reminders(): readonly string[];
51
+ get results(): Readonly<Record<string, Result>>;
52
+ /** Runs the current level and applies its carry-over state and transition. */
53
+ playCurrent(): CampaignLevelResult<State, Result>;
54
+ completeCurrent(result: CampaignLevelResult<State, Result>): CampaignLevelResult<State, Result>;
55
+ snapshot(): CampaignSnapshot<State, Result>;
56
+ static restore<State extends StateValue, Result extends StateValue = StateValue>(snapshot: CampaignSnapshot<State, Result>, options: {
57
+ readonly levels: readonly CampaignLevel<State, Result>[];
58
+ }): Campaign<State, Result>;
59
+ }
60
+ //# sourceMappingURL=Campaign.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Campaign.d.ts","sourceRoot":"","sources":["../../src/simulation/Campaign.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Game-neutral level sequencing and carry-over state. A campaign adapter supplies the
3
+ * actual level runner and decides what a level result means; MWG only preserves order,
4
+ * transitions, reminders and serialisable state.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { Campaign } from '@datamoc/mw_games/simulation';
9
+ * const campaign = new Campaign({
10
+ * levels: [{ id: 'intro', run: (state) => ({ outcome: 'completed', state, next: null }) }],
11
+ * start: 'intro', state: { gold: 0 },
12
+ * });
13
+ * campaign.completeCurrent({ outcome: 'completed', state: { gold: 10 }, next: null });
14
+ * ```
15
+ */
16
+ export class Campaign {
17
+ constructor(options) {
18
+ this._reminders = [];
19
+ this._results = {};
20
+ this.levels = new Map(options.levels.map((level) => [level.id, level]));
21
+ if (this.levels.size !== options.levels.length)
22
+ throw new Error('campaign level ids must be unique');
23
+ if (!this.levels.has(options.start))
24
+ throw new Error(`unknown campaign start level: ${options.start}`);
25
+ this._currentLevel = options.start;
26
+ this._state = structuredClone(options.state);
27
+ }
28
+ get currentLevel() {
29
+ return this._currentLevel;
30
+ }
31
+ get state() {
32
+ return structuredClone(this._state);
33
+ }
34
+ get reminders() {
35
+ return [...this._reminders];
36
+ }
37
+ get results() {
38
+ return structuredClone(this._results);
39
+ }
40
+ /** Runs the current level and applies its carry-over state and transition. */
41
+ playCurrent() {
42
+ if (!this._currentLevel)
43
+ throw new Error('campaign has no current level');
44
+ const level = this.levels.get(this._currentLevel);
45
+ return this.completeCurrent(level.run(this.state, { levelId: level.id }));
46
+ }
47
+ completeCurrent(result) {
48
+ if (!this._currentLevel)
49
+ throw new Error('campaign has no current level');
50
+ const completedId = this._currentLevel;
51
+ this._state = structuredClone(result.state);
52
+ if (result.result !== undefined)
53
+ this._results[completedId] = structuredClone(result.result);
54
+ this._reminders = [...(result.reminders ?? [])];
55
+ if (result.outcome !== 'completed')
56
+ this._currentLevel = null;
57
+ else if (result.next === undefined)
58
+ throw new Error(`campaign level ${completedId} did not choose a next level`);
59
+ else {
60
+ if (result.next !== null && !this.levels.has(result.next))
61
+ throw new Error(`unknown campaign transition: ${completedId} -> ${result.next}`);
62
+ this._currentLevel = result.next;
63
+ }
64
+ return { ...result, state: this.state };
65
+ }
66
+ snapshot() {
67
+ return {
68
+ currentLevel: this._currentLevel,
69
+ state: this.state,
70
+ reminders: this.reminders,
71
+ results: this.results,
72
+ };
73
+ }
74
+ static restore(snapshot, options) {
75
+ const start = snapshot.currentLevel ?? options.levels[0]?.id;
76
+ if (!start)
77
+ throw new Error('campaign restore requires at least one level');
78
+ const campaign = new Campaign({ levels: options.levels, start, state: snapshot.state });
79
+ campaign._currentLevel = snapshot.currentLevel;
80
+ campaign._reminders = [...snapshot.reminders];
81
+ campaign._results = structuredClone(snapshot.results);
82
+ return campaign;
83
+ }
84
+ }
85
+ //# sourceMappingURL=Campaign.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Campaign.js","sourceRoot":"","sources":["../../src/simulation/Campaign.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { Generator } from '../core/Random.ts';
2
+ import { type Scenario, type ScenarioResult } from './Scenario.ts';
3
+ export interface HeadlessScenario<State, Command, Event> {
4
+ readonly seed: number;
5
+ readonly initialState: State;
6
+ readonly commands: readonly Command[];
7
+ readonly step: Scenario<State, Command, Event, Generator>['step'];
8
+ readonly status?: Scenario<State, Command, Event, Generator>['status'];
9
+ }
10
+ export interface HeadlessScenarioResult<State, Event> extends ScenarioResult<State, Event> {
11
+ readonly seed: number;
12
+ readonly random: readonly [number, number, number, number];
13
+ }
14
+ /**
15
+ * Execute a deterministic scenario without a renderer, returning state, outputs and RNG state for CI.
16
+ * @example
17
+ * ```ts
18
+ * import { runHeadlessScenario } from '@datamoc/mw_games/simulation';
19
+ * const result = runHeadlessScenario({
20
+ * seed: 7, initialState: { score: 0 }, commands: [1],
21
+ * step: (state, command) => ({ state: { score: state.score + command }, events: [], status: 'ready' }),
22
+ * });
23
+ * ```
24
+ */
25
+ export declare function runHeadlessScenario<State, Command, Event>(scenario: HeadlessScenario<State, Command, Event>): HeadlessScenarioResult<State, Event>;
26
+ //# sourceMappingURL=Harness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Harness.d.ts","sourceRoot":"","sources":["../../src/simulation/Harness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAe,KAAK,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAEhF,MAAM,WAAW,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK;IACtD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,YAAY,EAAE,KAAK,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;IACtC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;IAClE,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC;CACvE;AAED,MAAM,WAAW,sBAAsB,CAAC,KAAK,EAAE,KAAK,CAAE,SAAQ,cAAc,CAAC,KAAK,EAAE,KAAK,CAAC;IACzF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CAC3D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EACxD,QAAQ,EAAE,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,GAC/C,sBAAsB,CAAC,KAAK,EAAE,KAAK,CAAC,CAUtC"}
@@ -0,0 +1,25 @@
1
+ import { Generator } from '../core/Random.js';
2
+ import { runScenario } from './Scenario.js';
3
+ /**
4
+ * Execute a deterministic scenario without a renderer, returning state, outputs and RNG state for CI.
5
+ * @example
6
+ * ```ts
7
+ * import { runHeadlessScenario } from '@datamoc/mw_games/simulation';
8
+ * const result = runHeadlessScenario({
9
+ * seed: 7, initialState: { score: 0 }, commands: [1],
10
+ * step: (state, command) => ({ state: { score: state.score + command }, events: [], status: 'ready' }),
11
+ * });
12
+ * ```
13
+ */
14
+ export function runHeadlessScenario(scenario) {
15
+ const random = new Generator(scenario.seed);
16
+ const result = runScenario({
17
+ state: structuredClone(scenario.initialState),
18
+ commands: scenario.commands,
19
+ status: scenario.status,
20
+ random,
21
+ step: scenario.step,
22
+ });
23
+ return { ...result, seed: scenario.seed, random: random.getState() };
24
+ }
25
+ //# sourceMappingURL=Harness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Harness.js","sourceRoot":"","sources":["../../src/simulation/Harness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAsC,MAAM,eAAe,CAAC;AAehF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAClC,QAAiD;IAEjD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,WAAW,CAAC;QAC1B,KAAK,EAAE,eAAe,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC7C,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,MAAM;QACN,IAAI,EAAE,QAAQ,CAAC,IAAI;KACnB,CAAC,CAAC;IACH,OAAO,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;AACtE,CAAC"}
@@ -2,6 +2,10 @@ export { advanceToInput } from './Turns.ts';
2
2
  export type { ScheduledTurns, TurnRules, TurnResult } from './Turns.ts';
3
3
  export { runScenario } from './Scenario.ts';
4
4
  export type { SimulationStatus, SimulationStep, SimulationRule, Scenario, ScenarioResult } from './Scenario.ts';
5
+ export { Campaign } from './Campaign.ts';
6
+ export type { CampaignOutcome, CampaignLevel, CampaignLevelResult, CampaignSnapshot } from './Campaign.ts';
7
+ export { runHeadlessScenario } from './Harness.ts';
8
+ export type { HeadlessScenario, HeadlessScenarioResult } from './Harness.ts';
5
9
  export { SimulationRuntime } from './Runtime.ts';
6
10
  export type { SimulationContext, SimulationOutcome, SimulationRuntimeRule, SimulationSnapshot } from './Runtime.ts';
7
11
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/simulation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAChH,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEpH;;;;;GAKG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,YAAY,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/simulation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,YAAY,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAChH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC3G,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,YAAY,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEpH;;;;;GAKG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,YAAY,EAAE,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC"}
@@ -1,5 +1,7 @@
1
1
  export { advanceToInput } from './Turns.js';
2
2
  export { runScenario } from './Scenario.js';
3
+ export { Campaign } from './Campaign.js';
4
+ export { runHeadlessScenario } from './Harness.js';
3
5
  export { SimulationRuntime } from './Runtime.js';
4
6
  /**
5
7
  * Re-exported from `roguelike` (its own home - it also drives FOV/pathfinding turn order
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/simulation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGjD;;;;;GAKG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/simulation/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE5C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGjD;;;;;GAKG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,58 @@
1
+ import { ColorMatrixFilter, Sprite, Texture } from 'pixi.js';
2
+ /** A parsed `image~MOD(args)` path, usable by any MWG game content format. */
3
+ export interface ImageModifier {
4
+ readonly name: string;
5
+ readonly args: readonly string[];
6
+ }
7
+ export interface ParsedImagePath {
8
+ readonly path: string;
9
+ readonly modifiers: readonly ImageModifier[];
10
+ }
11
+ /** Split top-level modifiers while keeping nested `~` paths inside arguments.
12
+ * @example
13
+ * ```ts
14
+ * import { parseImagePath } from '@datamoc/mw_games/two-d/render';
15
+ * const parsed = parseImagePath('hero.png~FL(horizontal)~GS');
16
+ * ```
17
+ */
18
+ export declare function parseImagePath(value: string): ParsedImagePath;
19
+ /** Return one named modifier from a parsed image path.
20
+ * @example
21
+ * ```ts
22
+ * import { imageModifier, parseImagePath } from '@datamoc/mw_games/two-d/render';
23
+ * const flip = imageModifier(parseImagePath('hero.png~FL(horizontal)'), 'FL');
24
+ * ```
25
+ */
26
+ export declare function imageModifier(path: ParsedImagePath, name: string): ImageModifier | undefined;
27
+ /**
28
+ * Build the Pixi 5x4 colour-matrix for a Wesnoth `CS(r,g,b)` shift.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import { colorShiftMatrix } from '@datamoc/mw_games/two-d/render';
33
+ * const matrix = colorShiftMatrix(-20, 0, 30);
34
+ * console.log(matrix.length); // 20
35
+ * ```
36
+ */
37
+ export declare function colorShiftMatrix(red: number, green: number, blue: number): ColorMatrixFilter['matrix'];
38
+ /** Apply the renderer-neutral subset supported by the Pixi backend. */
39
+ /**
40
+ * @example
41
+ * ```ts
42
+ * import { Sprite } from 'pixi.js';
43
+ * import { applyImageModifiers, parseImagePath } from '@datamoc/mw_games/two-d/render';
44
+ * applyImageModifiers(new Sprite(), parseImagePath('hero.png~FL(horizontal)'));
45
+ * ```
46
+ */
47
+ export declare function applyImageModifiers(sprite: Sprite, parsed: ParsedImagePath, scale?: number): void;
48
+ /** Make a cropped view without mutating the shared source texture. */
49
+ /**
50
+ * @example
51
+ * ```ts
52
+ * import { Texture } from 'pixi.js';
53
+ * import { croppedTexture, parseImagePath } from '@datamoc/mw_games/two-d/render';
54
+ * const cropped = croppedTexture(Texture.EMPTY, parseImagePath('atlas.png~CROP(0,0,16,16)'));
55
+ * ```
56
+ */
57
+ export declare function croppedTexture(texture: Texture, parsed: ParsedImagePath): Texture;
58
+ //# sourceMappingURL=ImageModifiers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageModifiers.d.ts","sourceRoot":"","sources":["../../../src/two-d/render/ImageModifiers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAa,MAAM,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAExE,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,SAAS,aAAa,EAAE,CAAC;CAC7C;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,eAAe,CAyB7D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAE5F;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAuBtG;AAED,uEAAuE;AACvE;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,KAAK,SAAI,GAAG,IAAI,CA0B5F;AAED,sEAAsE;AACtE;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAYjF"}
@@ -0,0 +1,145 @@
1
+ import { ColorMatrixFilter, Rectangle, Sprite, Texture } from 'pixi.js';
2
+ /** Split top-level modifiers while keeping nested `~` paths inside arguments.
3
+ * @example
4
+ * ```ts
5
+ * import { parseImagePath } from '@datamoc/mw_games/two-d/render';
6
+ * const parsed = parseImagePath('hero.png~FL(horizontal)~GS');
7
+ * ```
8
+ */
9
+ export function parseImagePath(value) {
10
+ value = value.replace(/^"|"$/g, '');
11
+ const parts = [];
12
+ let start = 0;
13
+ let depth = 0;
14
+ for (let index = 0; index < value.length; index += 1) {
15
+ if (value[index] === '(')
16
+ depth += 1;
17
+ else if (value[index] === ')')
18
+ depth = Math.max(0, depth - 1);
19
+ else if (value[index] === '~' && depth === 0) {
20
+ parts.push(value.slice(start, index));
21
+ start = index + 1;
22
+ }
23
+ }
24
+ parts.push(value.slice(start));
25
+ const path = (parts.shift() ?? '').trim();
26
+ const modifiers = [];
27
+ for (const source of parts) {
28
+ const match = /^([A-Za-z][A-Za-z0-9_-]*)(?:\((.*)\))?$/.exec(source.trim());
29
+ if (!match)
30
+ continue;
31
+ modifiers.push({
32
+ name: match[1].toUpperCase(),
33
+ args: match[2] === undefined ? [] : match[2].split(',').map((arg) => arg.trim()),
34
+ });
35
+ }
36
+ return { path, modifiers };
37
+ }
38
+ /** Return one named modifier from a parsed image path.
39
+ * @example
40
+ * ```ts
41
+ * import { imageModifier, parseImagePath } from '@datamoc/mw_games/two-d/render';
42
+ * const flip = imageModifier(parseImagePath('hero.png~FL(horizontal)'), 'FL');
43
+ * ```
44
+ */
45
+ export function imageModifier(path, name) {
46
+ return path.modifiers.find((entry) => entry.name === name.toUpperCase());
47
+ }
48
+ /**
49
+ * Build the Pixi 5x4 colour-matrix for a Wesnoth `CS(r,g,b)` shift.
50
+ *
51
+ * @example
52
+ * ```ts
53
+ * import { colorShiftMatrix } from '@datamoc/mw_games/two-d/render';
54
+ * const matrix = colorShiftMatrix(-20, 0, 30);
55
+ * console.log(matrix.length); // 20
56
+ * ```
57
+ */
58
+ export function colorShiftMatrix(red, green, blue) {
59
+ return [
60
+ 1,
61
+ 0,
62
+ 0,
63
+ 0,
64
+ red / 255,
65
+ 0,
66
+ 1,
67
+ 0,
68
+ 0,
69
+ green / 255,
70
+ 0,
71
+ 0,
72
+ 1,
73
+ 0,
74
+ blue / 255,
75
+ 0,
76
+ 0,
77
+ 0,
78
+ 1,
79
+ 0,
80
+ ];
81
+ }
82
+ /** Apply the renderer-neutral subset supported by the Pixi backend. */
83
+ /**
84
+ * @example
85
+ * ```ts
86
+ * import { Sprite } from 'pixi.js';
87
+ * import { applyImageModifiers, parseImagePath } from '@datamoc/mw_games/two-d/render';
88
+ * applyImageModifiers(new Sprite(), parseImagePath('hero.png~FL(horizontal)'));
89
+ * ```
90
+ */
91
+ export function applyImageModifiers(sprite, parsed, scale = 1) {
92
+ const flip = imageModifier(parsed, 'FL');
93
+ if (flip && (flip.args.length === 0 || flip.args.some((arg) => /horiz|horizontal|x/i.test(arg))))
94
+ sprite.scale.x *= -1;
95
+ if (flip?.args.some((arg) => /vert|vertical|y/i.test(arg)))
96
+ sprite.scale.y *= -1;
97
+ const size = imageModifier(parsed, 'SCALE');
98
+ if (size) {
99
+ const width = Number(size.args[0]);
100
+ const height = Number(size.args[1] ?? size.args[0]);
101
+ if (Number.isFinite(width) && width > 0)
102
+ sprite.width = width * scale;
103
+ if (Number.isFinite(height) && height > 0)
104
+ sprite.height = height * scale;
105
+ }
106
+ if (imageModifier(parsed, 'GS')) {
107
+ const filter = new ColorMatrixFilter();
108
+ filter.greyscale(1, false);
109
+ sprite.filters = [filter];
110
+ }
111
+ const shift = imageModifier(parsed, 'CS');
112
+ if (shift) {
113
+ const values = shift.args.slice(0, 3).map(Number);
114
+ if (values.length === 3 && values.every((value) => Number.isFinite(value))) {
115
+ const filter = new ColorMatrixFilter();
116
+ filter.matrix = colorShiftMatrix(values[0], values[1], values[2]);
117
+ sprite.filters = [...(sprite.filters ?? []), filter];
118
+ }
119
+ }
120
+ }
121
+ /** Make a cropped view without mutating the shared source texture. */
122
+ /**
123
+ * @example
124
+ * ```ts
125
+ * import { Texture } from 'pixi.js';
126
+ * import { croppedTexture, parseImagePath } from '@datamoc/mw_games/two-d/render';
127
+ * const cropped = croppedTexture(Texture.EMPTY, parseImagePath('atlas.png~CROP(0,0,16,16)'));
128
+ * ```
129
+ */
130
+ export function croppedTexture(texture, parsed) {
131
+ const crop = imageModifier(parsed, 'CROP');
132
+ if (!crop || crop.args.length < 4 || !texture.source)
133
+ return texture;
134
+ const values = crop.args.slice(0, 4).map(Number);
135
+ if (values.some((value) => !Number.isFinite(value)) || values[2] <= 0 || values[3] <= 0)
136
+ return texture;
137
+ const x = Math.max(0, values[0]);
138
+ const y = Math.max(0, values[1]);
139
+ const width = Math.min(values[2], Math.max(0, texture.width - x));
140
+ const height = Math.min(values[3], Math.max(0, texture.height - y));
141
+ return width > 0 && height > 0
142
+ ? new Texture({ source: texture.source, frame: new Rectangle(x, y, width, height) })
143
+ : texture;
144
+ }
145
+ //# sourceMappingURL=ImageModifiers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageModifiers.js","sourceRoot":"","sources":["../../../src/two-d/render/ImageModifiers.ts"],"names":[],"mappings":"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"}
@@ -19,7 +19,28 @@ export interface MinimapOptions {
19
19
  heightInCells: number;
20
20
  /** screen pixels per cell; defaults to 2, small enough for an always-on-screen corner HUD */
21
21
  cellSize?: number;
22
+ /** cell topology; hex uses the same flat-top odd-q projection as `TileMap` */
23
+ shape?: 'square' | 'hex';
22
24
  }
25
+ export interface MinimapMarker {
26
+ x: number;
27
+ y: number;
28
+ facing?: number;
29
+ color?: number;
30
+ }
31
+ /**
32
+ * The minimap centre for a cell, useful for overlays that must share its topology.
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * import { minimapCellCenter } from '@datamoc/mw_games/two-d/render';
37
+ * const centre = minimapCellCenter(3, 2, 4, 'hex');
38
+ * ```
39
+ */
40
+ export declare function minimapCellCenter(x: number, y: number, cellSize: number, shape?: 'square' | 'hex'): {
41
+ x: number;
42
+ y: number;
43
+ };
23
44
  /**
24
45
  * A downscaled picture of a level, built from exactly the data `roguelike.FieldOfView`
25
46
  * already tracks - `explored`, distinct from `visible` - plus a per-cell colour a game
@@ -53,6 +74,7 @@ export interface MinimapOptions {
53
74
  export declare class Minimap extends Container {
54
75
  private readonly widthInCells;
55
76
  private readonly cellSize;
77
+ private readonly shape;
56
78
  private renderTexture;
57
79
  private sprite;
58
80
  private drawn;
@@ -69,6 +91,8 @@ export declare class Minimap extends Container {
69
91
  sync(explored: ReadonlySet<number>, colorFor: (x: number, y: number) => number): void;
70
92
  /** positions a marker at a cell, optionally pointing it in a facing direction (radians) */
71
93
  setMarker(x: number, y: number, facing?: number, color?: number): void;
94
+ /** replaces the current overlay with zero or more unit/quest markers */
95
+ setMarkers(markers: readonly MinimapMarker[]): void;
72
96
  /** forgets everything baked, so the next `sync` repaints from a clean texture - a fresh floor */
73
97
  reset(): void;
74
98
  destroy(options?: Parameters<Container['destroy']>[0]): void;
@@ -1 +1 @@
1
- {"version":3,"file":"Minimap.d.ts","sourceRoot":"","sources":["../../../src/two-d/render/Minimap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAmC,MAAM,SAAS,CAAC;AAGrE;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAMxG;AAED,MAAM,WAAW,cAAc;IAC9B,kFAAkF;IAClF,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IAEtB,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,OAAQ,SAAQ,SAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAElC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAqB;IAElC,OAAO,CAAC,MAAM,CAAkB;IAEhC,YAAY,OAAO,EAAE,cAAc,EAalC;IAED,2FAA2F;IAC3F,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED;;;;;OAKG;IACH,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAgBpF;IAED,2FAA2F;IAC3F,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,SAAW,GAAG,IAAI,CAcvE;IAED,iGAAiG;IACjG,KAAK,IAAI,IAAI,CAGZ;IAEQ,OAAO,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAGpE;CACD"}
1
+ {"version":3,"file":"Minimap.d.ts","sourceRoot":"","sources":["../../../src/two-d/render/Minimap.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAmC,MAAM,SAAS,CAAC;AAIrE;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAMxG;AAED,MAAM,WAAW,cAAc;IAC9B,kFAAkF;IAClF,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IAEtB,6FAA6F;IAC7F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC7B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAChC,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,QAAQ,GAAG,KAAgB,GAChC;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAI1B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,OAAQ,SAAQ,SAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmB;IAEzC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAqB;IAElC,OAAO,CAAC,MAAM,CAAkB;IAEhC,YAAY,OAAO,EAAE,cAAc,EAoBlC;IAED,2FAA2F;IAC3F,IAAI,aAAa,IAAI,MAAM,CAE1B;IAED;;;;;OAKG;IACH,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CA2BpF;IAED,2FAA2F;IAC3F,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,SAAW,GAAG,IAAI,CAEvE;IAED,wEAAwE;IACxE,UAAU,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI,CAelD;IAED,iGAAiG;IACjG,KAAK,IAAI,IAAI,CAGZ;IAEQ,OAAO,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAGpE;CACD"}
@@ -1,5 +1,6 @@
1
1
  import { Container, Graphics, RenderTexture, Sprite } from 'pixi.js';
2
2
  import { Game } from '../Game.js';
3
+ import { hexToPixel } from '../../core/Hex.js';
3
4
  /** the cell indices in `explored` not yet baked into a minimap's texture - pure, so it can
4
5
  * be tested without a renderer
5
6
  *
@@ -21,6 +22,20 @@ export function newlyRevealed(explored, alreadyDrawn) {
21
22
  }
22
23
  return result;
23
24
  }
25
+ /**
26
+ * The minimap centre for a cell, useful for overlays that must share its topology.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * import { minimapCellCenter } from '@datamoc/mw_games/two-d/render';
31
+ * const centre = minimapCellCenter(3, 2, 4, 'hex');
32
+ * ```
33
+ */
34
+ export function minimapCellCenter(x, y, cellSize, shape = 'square') {
35
+ return shape === 'hex'
36
+ ? hexToPixel(x, y, cellSize, cellSize)
37
+ : { x: (x + 0.5) * cellSize, y: (y + 0.5) * cellSize };
38
+ }
24
39
  /**
25
40
  * A downscaled picture of a level, built from exactly the data `roguelike.FieldOfView`
26
41
  * already tracks - `explored`, distinct from `visible` - plus a per-cell colour a game
@@ -58,9 +73,14 @@ export class Minimap extends Container {
58
73
  this.marker = new Graphics();
59
74
  this.widthInCells = options.widthInCells;
60
75
  this.cellSize = options.cellSize ?? 2;
76
+ this.shape = options.shape ?? 'square';
61
77
  this.renderTexture = RenderTexture.create({
62
- width: options.widthInCells * this.cellSize,
63
- height: options.heightInCells * this.cellSize,
78
+ width: this.shape === 'hex'
79
+ ? Math.ceil((options.widthInCells - 1) * this.cellSize * 0.75 + this.cellSize)
80
+ : options.widthInCells * this.cellSize,
81
+ height: this.shape === 'hex'
82
+ ? options.heightInCells * this.cellSize + this.cellSize / 2
83
+ : options.heightInCells * this.cellSize,
64
84
  });
65
85
  this.sprite = new Sprite(this.renderTexture);
66
86
  this.addChild(this.sprite);
@@ -85,26 +105,44 @@ export class Minimap extends Container {
85
105
  this.drawn.add(index);
86
106
  const x = index % this.widthInCells;
87
107
  const y = Math.floor(index / this.widthInCells);
88
- patch
89
- .rect(x * this.cellSize, y * this.cellSize, this.cellSize, this.cellSize)
90
- .fill({ color: colorFor(x, y) });
108
+ const center = minimapCellCenter(x, y, this.cellSize, this.shape);
109
+ if (this.shape === 'hex') {
110
+ const radius = this.cellSize / 2;
111
+ const points = [];
112
+ for (let corner = 0; corner < 6; corner++) {
113
+ const angle = (Math.PI / 3) * corner;
114
+ points.push(center.x + Math.cos(angle) * radius, center.y + Math.sin(angle) * radius);
115
+ }
116
+ patch.poly(points).fill({ color: colorFor(x, y) });
117
+ }
118
+ else {
119
+ patch
120
+ .rect(center.x - this.cellSize / 2, center.y - this.cellSize / 2, this.cellSize, this.cellSize)
121
+ .fill({ color: colorFor(x, y) });
122
+ }
91
123
  }
92
124
  Game.current.app.renderer.render({ container: patch, target: this.renderTexture, clear: false });
93
125
  patch.destroy();
94
126
  }
95
127
  /** positions a marker at a cell, optionally pointing it in a facing direction (radians) */
96
128
  setMarker(x, y, facing, color = 0xffffff) {
129
+ this.setMarkers([{ x, y, facing, color }]);
130
+ }
131
+ /** replaces the current overlay with zero or more unit/quest markers */
132
+ setMarkers(markers) {
97
133
  this.marker.clear();
98
- this.marker.circle(0, 0, Math.max(1, this.cellSize)).fill({ color });
99
- if (facing !== undefined) {
100
- const length = this.cellSize * 2;
101
- this.marker
102
- .moveTo(0, 0)
103
- .lineTo(Math.cos(facing) * length, Math.sin(facing) * length)
104
- .stroke({ color, width: 1 });
134
+ for (const marker of markers) {
135
+ const markerColor = marker.color ?? 0xffffff;
136
+ const center = minimapCellCenter(marker.x, marker.y, this.cellSize, this.shape);
137
+ this.marker.circle(center.x, center.y, Math.max(1, this.cellSize)).fill({ color: markerColor });
138
+ if (marker.facing !== undefined) {
139
+ const length = this.cellSize * 2;
140
+ this.marker
141
+ .moveTo(center.x, center.y)
142
+ .lineTo(center.x + Math.cos(marker.facing) * length, center.y + Math.sin(marker.facing) * length)
143
+ .stroke({ color: markerColor, width: 1 });
144
+ }
105
145
  }
106
- this.marker.x = (x + 0.5) * this.cellSize;
107
- this.marker.y = (y + 0.5) * this.cellSize;
108
146
  }
109
147
  /** forgets everything baked, so the next `sync` repaints from a clean texture - a fresh floor */
110
148
  reset() {