@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.
- package/CHANGELOG.md +33 -0
- package/README.md +1 -1
- package/dist/board/FogOfWar.d.ts +6 -0
- package/dist/board/FogOfWar.d.ts.map +1 -1
- package/dist/board/FogOfWar.js +15 -0
- package/dist/board/FogOfWar.js.map +1 -1
- package/dist/core/ActionJournal.d.ts +36 -0
- package/dist/core/ActionJournal.d.ts.map +1 -0
- package/dist/core/ActionJournal.js +60 -0
- package/dist/core/ActionJournal.js.map +1 -0
- package/dist/core/Pathfinding.d.ts +41 -0
- package/dist/core/Pathfinding.d.ts.map +1 -0
- package/dist/core/Pathfinding.js +86 -0
- package/dist/core/Pathfinding.js.map +1 -0
- package/dist/core/State.d.ts +59 -0
- package/dist/core/State.d.ts.map +1 -0
- package/dist/core/State.js +100 -0
- package/dist/core/State.js.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -0
- package/dist/core/index.js.map +1 -1
- package/dist/mw_games.global.js +97 -97
- package/dist/mwl/hooks.d.ts +3 -2
- package/dist/mwl/hooks.d.ts.map +1 -1
- package/dist/mwl/hooks.js.map +1 -1
- package/dist/mwl/index.d.ts +3 -3
- package/dist/mwl/index.d.ts.map +1 -1
- package/dist/mwl/index.js +1 -1
- package/dist/mwl/index.js.map +1 -1
- package/dist/mwl/report.d.ts +17 -1
- package/dist/mwl/report.d.ts.map +1 -1
- package/dist/mwl/report.js +33 -0
- package/dist/mwl/report.js.map +1 -1
- package/dist/mwl/runtime.d.ts +46 -1
- package/dist/mwl/runtime.d.ts.map +1 -1
- package/dist/mwl/runtime.js +237 -19
- package/dist/mwl/runtime.js.map +1 -1
- package/dist/mwl/schema.d.ts.map +1 -1
- package/dist/mwl/schema.js +136 -1
- package/dist/mwl/schema.js.map +1 -1
- package/dist/roguelike/FieldOfView.d.ts +3 -2
- package/dist/roguelike/FieldOfView.d.ts.map +1 -1
- package/dist/roguelike/FieldOfView.js +3 -2
- package/dist/roguelike/FieldOfView.js.map +1 -1
- package/dist/roguelike/Level.d.ts +9 -1
- package/dist/roguelike/Level.d.ts.map +1 -1
- package/dist/roguelike/Level.js +6 -2
- package/dist/roguelike/Level.js.map +1 -1
- package/dist/roguelike/MultiTurnBeam.d.ts +70 -0
- package/dist/roguelike/MultiTurnBeam.d.ts.map +1 -0
- package/dist/roguelike/MultiTurnBeam.js +117 -0
- package/dist/roguelike/MultiTurnBeam.js.map +1 -0
- package/dist/roguelike/Scheduler.d.ts +5 -1
- package/dist/roguelike/Scheduler.d.ts.map +1 -1
- package/dist/roguelike/Scheduler.js +10 -3
- package/dist/roguelike/Scheduler.js.map +1 -1
- package/dist/roguelike/Targeting.d.ts +51 -0
- package/dist/roguelike/Targeting.d.ts.map +1 -1
- package/dist/roguelike/Targeting.js +87 -2
- package/dist/roguelike/Targeting.js.map +1 -1
- package/dist/roguelike/index.d.ts +4 -2
- package/dist/roguelike/index.d.ts.map +1 -1
- package/dist/roguelike/index.js +2 -1
- package/dist/roguelike/index.js.map +1 -1
- package/dist/rpg/automap.d.ts +4 -0
- package/dist/rpg/automap.d.ts.map +1 -1
- package/dist/rpg/automap.js +5 -2
- package/dist/rpg/automap.js.map +1 -1
- package/dist/simulation/Campaign.d.ts +60 -0
- package/dist/simulation/Campaign.d.ts.map +1 -0
- package/dist/simulation/Campaign.js +85 -0
- package/dist/simulation/Campaign.js.map +1 -0
- package/dist/simulation/Harness.d.ts +26 -0
- package/dist/simulation/Harness.d.ts.map +1 -0
- package/dist/simulation/Harness.js +25 -0
- package/dist/simulation/Harness.js.map +1 -0
- package/dist/simulation/index.d.ts +4 -0
- package/dist/simulation/index.d.ts.map +1 -1
- package/dist/simulation/index.js +2 -0
- package/dist/simulation/index.js.map +1 -1
- package/dist/two-d/render/ImageModifiers.d.ts +58 -0
- package/dist/two-d/render/ImageModifiers.d.ts.map +1 -0
- package/dist/two-d/render/ImageModifiers.js +145 -0
- package/dist/two-d/render/ImageModifiers.js.map +1 -0
- package/dist/two-d/render/Minimap.d.ts +24 -0
- package/dist/two-d/render/Minimap.d.ts.map +1 -1
- package/dist/two-d/render/Minimap.js +52 -14
- package/dist/two-d/render/Minimap.js.map +1 -1
- package/dist/two-d/render/Particles.d.ts +16 -0
- package/dist/two-d/render/Particles.d.ts.map +1 -1
- package/dist/two-d/render/Particles.js +15 -2
- package/dist/two-d/render/Particles.js.map +1 -1
- package/dist/two-d/render/index.d.ts +4 -2
- package/dist/two-d/render/index.d.ts.map +1 -1
- package/dist/two-d/render/index.js +2 -1
- package/dist/two-d/render/index.js.map +1 -1
- package/dist/two-d/ui/Bar.d.ts +21 -2
- package/dist/two-d/ui/Bar.d.ts.map +1 -1
- package/dist/two-d/ui/Bar.js +27 -5
- package/dist/two-d/ui/Bar.js.map +1 -1
- package/dist/two-d/ui/FloatingText.d.ts +42 -1
- package/dist/two-d/ui/FloatingText.d.ts.map +1 -1
- package/dist/two-d/ui/FloatingText.js +52 -4
- package/dist/two-d/ui/FloatingText.js.map +1 -1
- package/dist/two-d/ui/FloatingTextStack.d.ts +77 -0
- package/dist/two-d/ui/FloatingTextStack.d.ts.map +1 -0
- package/dist/two-d/ui/FloatingTextStack.js +99 -0
- package/dist/two-d/ui/FloatingTextStack.js.map +1 -0
- package/dist/two-d/ui/index.d.ts +3 -1
- package/dist/two-d/ui/index.d.ts.map +1 -1
- package/dist/two-d/ui/index.js +2 -1
- package/dist/two-d/ui/index.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- 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"}
|
package/dist/simulation/index.js
CHANGED
|
@@ -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;
|
|
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:
|
|
63
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
this.marker
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
.
|
|
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() {
|