@maka/maka-cli 5.116.0 → 5.118.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundle/typescript/package.json +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +6 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/compile.js +3 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/compile.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/decompile.js +3 -6
- package/bundle/typescript/src/commands/game/sideQuest/commands/decompile.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/download.js +24 -6
- package/bundle/typescript/src/commands/game/sideQuest/commands/download.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/enter-host.d.ts +7 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/enter-host.js +34 -23
- package/bundle/typescript/src/commands/game/sideQuest/commands/enter-host.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.d.ts +7 -7
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +30 -50
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +42 -19
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/hop.d.ts +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/hop.js +95 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/hop.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/jack.js +3 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/jack.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/jackpoint.js +5 -7
- package/bundle/typescript/src/commands/game/sideQuest/commands/jackpoint.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js +40 -8
- package/bundle/typescript/src/commands/game/sideQuest/commands/look.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/map.d.ts +6 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/map.js +21 -74
- package/bundle/typescript/src/commands/game/sideQuest/commands/map.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/mark.js +44 -17
- package/bundle/typescript/src/commands/game/sideQuest/commands/mark.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/move.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/move.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/rest.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/say.js +8 -5
- package/bundle/typescript/src/commands/game/sideQuest/commands/say.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js +2 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js +20 -2
- package/bundle/typescript/src/commands/game/sideQuest/commands/take.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/thread.js +2 -5
- package/bundle/typescript/src/commands/game/sideQuest/commands/thread.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.d.ts +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-chunks.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/factories/scene-factory.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.d.ts +4 -0
- package/bundle/typescript/src/commands/game/sideQuest/game.js +86 -31
- package/bundle/typescript/src/commands/game/sideQuest/game.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/host.d.ts +80 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/host.js +91 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/host.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/player.d.ts +83 -9
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +153 -42
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/room.d.ts +40 -9
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js +73 -12
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.d.ts +3 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js +3 -0
- package/bundle/typescript/src/commands/game/sideQuest/models/scene.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/types/save-file.d.ts +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/room-seed.d.ts +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/types/seed/scene-seed.d.ts +2 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.js +40 -29
- package/bundle/typescript/src/commands/game/sideQuest/utilities/cloud-saves.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +30 -10
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/condition-report.d.ts +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/condition-report.js +142 -56
- package/bundle/typescript/src/commands/game/sideQuest/utilities/condition-report.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-reach.d.ts +58 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-reach.js +107 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-reach.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.d.ts +21 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.js +104 -20
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grid-view.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grids.d.ts +72 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grids.js +100 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/grids.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/ic-actors.d.ts +9 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/ic-actors.js +98 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/ic-actors.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/ic.d.ts +3 -2
- package/bundle/typescript/src/commands/game/sideQuest/utilities/ic.js +26 -6
- package/bundle/typescript/src/commands/game/sideQuest/utilities/ic.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/line-editor.d.ts +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/line-editor.js +5 -5
- package/bundle/typescript/src/commands/game/sideQuest/utilities/line-editor.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/logo-mark.d.ts +13 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/logo-mark.js +47 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/logo-mark.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/matrix-roster.d.ts +62 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/matrix-roster.js +97 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/matrix-roster.js.map +1 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js +11 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/persistence.js.map +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.d.ts +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js +24 -19
- package/bundle/typescript/src/commands/game/sideQuest/utilities/planes.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Room } from './room.js';
|
|
2
|
+
import type { NPC } from './npc.js';
|
|
3
|
+
import type { Door } from './door.js';
|
|
4
|
+
import type { Item } from './item.js';
|
|
5
|
+
import type { Grid } from '../utilities/grids.js';
|
|
6
|
+
/**
|
|
7
|
+
* A HOST (SR5 p.246-247), as its own object. Increment 1 of the Matrix
|
|
8
|
+
* position model (plan 2026-09-02): the host state that used to live as
|
|
9
|
+
* loose fields on Room moves here, and Room keeps delegating accessors
|
|
10
|
+
* so every existing reader and writer compiles and behaves unchanged.
|
|
11
|
+
*
|
|
12
|
+
* `over` is the physical room the host stands over in this engine: the
|
|
13
|
+
* site systems it masters bind through it -- a crack releases that room's
|
|
14
|
+
* maglocks and unseals the matrix items in its inventory, and the room's
|
|
15
|
+
* cameras answer to it (utilities/surveillance.ts). Canon puts no host at
|
|
16
|
+
* a physical address (p.246, "hosts don't have a physical location"); the
|
|
17
|
+
* anchor is the engine's mapping of "the network that runs this place",
|
|
18
|
+
* not a claim about where the host is.
|
|
19
|
+
*
|
|
20
|
+
* `grid` is optional and unset by default: canon says hosts are reached
|
|
21
|
+
* from any grid and pay to be accessible from all of them (p.246, Data
|
|
22
|
+
* Trails p.15); a seed may declare one, and only then does the cross-grid
|
|
23
|
+
* penalty apply to actions against it (increment 3).
|
|
24
|
+
*
|
|
25
|
+
* What persists: `cracked` only (utilities/persistence.ts). Marks, alert,
|
|
26
|
+
* running IC and the patrol clock are session state by design.
|
|
27
|
+
*/
|
|
28
|
+
export declare class Host {
|
|
29
|
+
static readonly DEFAULT_RATING = 3;
|
|
30
|
+
static readonly MIN_RATING = 1;
|
|
31
|
+
static readonly MAX_RATING = 12;
|
|
32
|
+
/** SR5 p.247: Host Rating 1-12; anything else is the default. */
|
|
33
|
+
static clampRating(rating: number | undefined): number;
|
|
34
|
+
readonly over: Room;
|
|
35
|
+
rating: number;
|
|
36
|
+
grid?: Grid;
|
|
37
|
+
/** The seed's word for the grid ('public' | 'local' | 'global:<corp>'); resolved against the scene's local grid by the factory. */
|
|
38
|
+
gridSeed?: string;
|
|
39
|
+
/** The one persisted field. A sanctioned host never remembers a crack. */
|
|
40
|
+
cracked: boolean;
|
|
41
|
+
/** player name -> marks the player holds on this host (max 3, p.236). */
|
|
42
|
+
marksBy: Map<string, number>;
|
|
43
|
+
/** player name -> marks the host holds on that player. */
|
|
44
|
+
marksOn: Map<string, number>;
|
|
45
|
+
/** Walls up: the host holds a mark on an intruder and its ice is hunting. */
|
|
46
|
+
alert: boolean;
|
|
47
|
+
/** IC running now, by kind name. Names, not actors -- until increment 5. */
|
|
48
|
+
runningIC: string[];
|
|
49
|
+
/** The running IC as actors, by kind (utilities/ic-actors.ts). Empty where no scene could host them. */
|
|
50
|
+
iceActors: Map<string, NPC>;
|
|
51
|
+
/** Kinds crashed this turn; the host relaunches them next turn (p.355-356). */
|
|
52
|
+
relaunchQueue: string[];
|
|
53
|
+
patrolCountdown?: number;
|
|
54
|
+
/** A haven (jackpoint.ts): never hunts, shows no rating, forgets cracks. */
|
|
55
|
+
sanctioned: boolean;
|
|
56
|
+
constructor(over: Room, opts?: {
|
|
57
|
+
rating?: number;
|
|
58
|
+
grid?: Grid;
|
|
59
|
+
sanctioned?: boolean;
|
|
60
|
+
});
|
|
61
|
+
/** The host is named for the place it stands over -- every alias the game prints derives from this. */
|
|
62
|
+
get name(): string;
|
|
63
|
+
/** So a Host can stand in for a Room wherever a reader only asks "is there a host here?". */
|
|
64
|
+
get hasNode(): true;
|
|
65
|
+
/**
|
|
66
|
+
* SR5 p.247: a host's four Matrix attributes are Host Rating, +1, +2, +3
|
|
67
|
+
* in some order. This engine's fixed order: Attack +1, Sleaze +0, Data
|
|
68
|
+
* Processing +3, Firewall +2.
|
|
69
|
+
*/
|
|
70
|
+
attributes(): {
|
|
71
|
+
attack: number;
|
|
72
|
+
sleaze: number;
|
|
73
|
+
dataProcessing: number;
|
|
74
|
+
firewall: number;
|
|
75
|
+
};
|
|
76
|
+
/** The matrix-plane items the host keeps, from the room it stands over (until seeds carry host files). */
|
|
77
|
+
files(): Item[];
|
|
78
|
+
/** The locked doors of the room it stands over -- what a crack releases. */
|
|
79
|
+
maglocks(): Door[];
|
|
80
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A HOST (SR5 p.246-247), as its own object. Increment 1 of the Matrix
|
|
3
|
+
* position model (plan 2026-09-02): the host state that used to live as
|
|
4
|
+
* loose fields on Room moves here, and Room keeps delegating accessors
|
|
5
|
+
* so every existing reader and writer compiles and behaves unchanged.
|
|
6
|
+
*
|
|
7
|
+
* `over` is the physical room the host stands over in this engine: the
|
|
8
|
+
* site systems it masters bind through it -- a crack releases that room's
|
|
9
|
+
* maglocks and unseals the matrix items in its inventory, and the room's
|
|
10
|
+
* cameras answer to it (utilities/surveillance.ts). Canon puts no host at
|
|
11
|
+
* a physical address (p.246, "hosts don't have a physical location"); the
|
|
12
|
+
* anchor is the engine's mapping of "the network that runs this place",
|
|
13
|
+
* not a claim about where the host is.
|
|
14
|
+
*
|
|
15
|
+
* `grid` is optional and unset by default: canon says hosts are reached
|
|
16
|
+
* from any grid and pay to be accessible from all of them (p.246, Data
|
|
17
|
+
* Trails p.15); a seed may declare one, and only then does the cross-grid
|
|
18
|
+
* penalty apply to actions against it (increment 3).
|
|
19
|
+
*
|
|
20
|
+
* What persists: `cracked` only (utilities/persistence.ts). Marks, alert,
|
|
21
|
+
* running IC and the patrol clock are session state by design.
|
|
22
|
+
*/
|
|
23
|
+
export class Host {
|
|
24
|
+
static DEFAULT_RATING = 3;
|
|
25
|
+
static MIN_RATING = 1;
|
|
26
|
+
static MAX_RATING = 12;
|
|
27
|
+
/** SR5 p.247: Host Rating 1-12; anything else is the default. */
|
|
28
|
+
static clampRating(rating) {
|
|
29
|
+
if (typeof rating !== 'number' || !Number.isFinite(rating))
|
|
30
|
+
return Host.DEFAULT_RATING;
|
|
31
|
+
return Math.min(Host.MAX_RATING, Math.max(Host.MIN_RATING, Math.round(rating)));
|
|
32
|
+
}
|
|
33
|
+
over;
|
|
34
|
+
rating;
|
|
35
|
+
grid;
|
|
36
|
+
/** The seed's word for the grid ('public' | 'local' | 'global:<corp>'); resolved against the scene's local grid by the factory. */
|
|
37
|
+
gridSeed;
|
|
38
|
+
/** The one persisted field. A sanctioned host never remembers a crack. */
|
|
39
|
+
cracked = false;
|
|
40
|
+
/** player name -> marks the player holds on this host (max 3, p.236). */
|
|
41
|
+
marksBy = new Map();
|
|
42
|
+
/** player name -> marks the host holds on that player. */
|
|
43
|
+
marksOn = new Map();
|
|
44
|
+
/** Walls up: the host holds a mark on an intruder and its ice is hunting. */
|
|
45
|
+
alert = false;
|
|
46
|
+
/** IC running now, by kind name. Names, not actors -- until increment 5. */
|
|
47
|
+
runningIC = [];
|
|
48
|
+
/** The running IC as actors, by kind (utilities/ic-actors.ts). Empty where no scene could host them. */
|
|
49
|
+
iceActors = new Map();
|
|
50
|
+
/** Kinds crashed this turn; the host relaunches them next turn (p.355-356). */
|
|
51
|
+
relaunchQueue = [];
|
|
52
|
+
patrolCountdown;
|
|
53
|
+
/** A haven (jackpoint.ts): never hunts, shows no rating, forgets cracks. */
|
|
54
|
+
sanctioned = false;
|
|
55
|
+
constructor(over, opts = {}) {
|
|
56
|
+
this.over = over;
|
|
57
|
+
this.rating = Host.clampRating(opts.rating);
|
|
58
|
+
this.grid = opts.grid;
|
|
59
|
+
this.sanctioned = opts.sanctioned ?? false;
|
|
60
|
+
}
|
|
61
|
+
/** The host is named for the place it stands over -- every alias the game prints derives from this. */
|
|
62
|
+
get name() {
|
|
63
|
+
return this.over.name;
|
|
64
|
+
}
|
|
65
|
+
/** So a Host can stand in for a Room wherever a reader only asks "is there a host here?". */
|
|
66
|
+
get hasNode() {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* SR5 p.247: a host's four Matrix attributes are Host Rating, +1, +2, +3
|
|
71
|
+
* in some order. This engine's fixed order: Attack +1, Sleaze +0, Data
|
|
72
|
+
* Processing +3, Firewall +2.
|
|
73
|
+
*/
|
|
74
|
+
attributes() {
|
|
75
|
+
const hr = this.rating;
|
|
76
|
+
return { attack: hr + 1, sleaze: hr, dataProcessing: hr + 3, firewall: hr + 2 };
|
|
77
|
+
}
|
|
78
|
+
/** The matrix-plane items the host keeps, from the room it stands over (until seeds carry host files). */
|
|
79
|
+
files() {
|
|
80
|
+
return this.over.inventory.getAllItems().filter(i => i.plane === 'matrix');
|
|
81
|
+
}
|
|
82
|
+
/** The locked doors of the room it stands over -- what a crack releases. */
|
|
83
|
+
maglocks() {
|
|
84
|
+
return Array.from(this.over.exits.values())
|
|
85
|
+
.filter((exit) => isDoor(exit) && exit.checkIfLocked());
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function isDoor(exit) {
|
|
89
|
+
return typeof exit === 'object' && exit !== null && typeof exit.checkIfLocked === 'function';
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=host.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../../../../../../../src/commands/game/sideQuest/models/host.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,OAAO,IAAI;IACf,MAAM,CAAU,cAAc,GAAG,CAAC,CAAC;IACnC,MAAM,CAAU,UAAU,GAAG,CAAC,CAAC;IAC/B,MAAM,CAAU,UAAU,GAAG,EAAE,CAAC;IAEhC,iEAAiE;IACjE,MAAM,CAAC,WAAW,CAAC,MAA0B;QAC3C,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC,cAAc,CAAC;QACvF,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAClF,CAAC;IAEQ,IAAI,CAAO;IACpB,MAAM,CAAS;IACf,IAAI,CAAQ;IACZ,mIAAmI;IACnI,QAAQ,CAAU;IAClB,0EAA0E;IAC1E,OAAO,GAAG,KAAK,CAAC;IAChB,yEAAyE;IACzE,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IACpC,0DAA0D;IAC1D,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IACpC,6EAA6E;IAC7E,KAAK,GAAG,KAAK,CAAC;IACd,4EAA4E;IAC5E,SAAS,GAAa,EAAE,CAAC;IACzB,wGAAwG;IACxG,SAAS,GAAG,IAAI,GAAG,EAAe,CAAC;IACnC,+EAA+E;IAC/E,aAAa,GAAa,EAAE,CAAC;IAC7B,eAAe,CAAU;IACzB,4EAA4E;IAC5E,UAAU,GAAG,KAAK,CAAC;IAEnB,YAAY,IAAU,EAAE,OAA+D,EAAE;QACvF,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,KAAK,CAAC;IAC7C,CAAC;IAED,uGAAuG;IACvG,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IACxB,CAAC;IAED,6FAA6F;IAC7F,IAAI,OAAO;QACT,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;OAIG;IACH,UAAU;QACR,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC;QACvB,OAAO,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,cAAc,EAAE,EAAE,GAAG,CAAC,EAAE,QAAQ,EAAE,EAAE,GAAG,CAAC,EAAE,CAAC;IAClF,CAAC;IAED,0GAA0G;IAC1G,KAAK;QACH,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC;IAC7E,CAAC;IAED,4EAA4E;IAC5E,QAAQ;QACN,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;aACxC,MAAM,CAAC,CAAC,IAAI,EAAgB,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;IAC1E,CAAC;;AAGH,SAAS,MAAM,CAAC,IAAa;IAC3B,OAAO,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,OAAQ,IAAa,CAAC,aAAa,KAAK,UAAU,CAAC;AACzG,CAAC"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { PlayerEquipment } from '../types/player-equipment.js';
|
|
2
|
+
import type { Grid } from '../utilities/grids.js';
|
|
3
|
+
import type { Host } from './host.js';
|
|
2
4
|
import { type Aspect } from '../aspects.js';
|
|
3
5
|
import { LifestyleChoices } from '../types/seed/lifestyle-seed.js';
|
|
4
6
|
import { Logger } from '../utilities/logger.js';
|
|
@@ -51,6 +53,19 @@ export declare function isPhysicalPlane(plane: Plane): boolean;
|
|
|
51
53
|
* actors out of a Matrix target list; this function answers one question
|
|
52
54
|
* and is not a second plane check.
|
|
53
55
|
*/
|
|
56
|
+
/**
|
|
57
|
+
* A persona's place in the Matrix (SR5 p.239, p.246): out on a grid, or
|
|
58
|
+
* inside a host it entered from one -- and never on a street. See
|
|
59
|
+
* Player.matrixPosition.
|
|
60
|
+
*/
|
|
61
|
+
export type MatrixPosition = {
|
|
62
|
+
kind: 'grid';
|
|
63
|
+
grid?: Grid;
|
|
64
|
+
} | {
|
|
65
|
+
kind: 'host';
|
|
66
|
+
host: Host;
|
|
67
|
+
from?: Grid;
|
|
68
|
+
};
|
|
54
69
|
export declare function sameHostSide(actor: Player, other: Player): boolean;
|
|
55
70
|
export type SimMode = 'cold' | 'hot';
|
|
56
71
|
/**
|
|
@@ -435,7 +450,32 @@ export declare class Player extends AbstractPlayer {
|
|
|
435
450
|
* Session state like hostAlert and linkLockedBy: leaving the Matrix
|
|
436
451
|
* ends it, so nothing here is persisted.
|
|
437
452
|
*/
|
|
438
|
-
|
|
453
|
+
/**
|
|
454
|
+
* WHERE THE PERSONA IS (increment 2 of the Matrix position model): on a
|
|
455
|
+
* grid, or inside a host it entered from one. Session-only, like
|
|
456
|
+
* bodyRoom. The body stays in currentLocation on every plane -- the
|
|
457
|
+
* persona never writes it. The setter fires the live-sheet beat, which
|
|
458
|
+
* is the beat the currentLocation setter used to fire on enter/exit.
|
|
459
|
+
* `grid` may be unknown (fixtures, and increment 2 itself): unknown is
|
|
460
|
+
* not "another grid", so it costs nothing.
|
|
461
|
+
*/
|
|
462
|
+
private _matrixPosition?;
|
|
463
|
+
get matrixPosition(): MatrixPosition | undefined;
|
|
464
|
+
set matrixPosition(v: MatrixPosition | undefined);
|
|
465
|
+
/** The grid the persona rides, when it is on one. */
|
|
466
|
+
get currentGrid(): Grid | undefined;
|
|
467
|
+
/** The host the persona is inside, when it is inside one. */
|
|
468
|
+
get hostInside(): Host | undefined;
|
|
469
|
+
/**
|
|
470
|
+
* @deprecated compat through increment 4 -- read hostInside / write
|
|
471
|
+
* matrixPosition. The room a host stands over, when inside one. The
|
|
472
|
+
* setter still builds the position so every old writer keeps working.
|
|
473
|
+
*/
|
|
474
|
+
private _compatInsideRoom?;
|
|
475
|
+
get insideHost(): Room | undefined;
|
|
476
|
+
set insideHost(room: Room | undefined);
|
|
477
|
+
/** The grid last stood on, so an exit built through the old setter returns there. */
|
|
478
|
+
private get lastGrid();
|
|
439
479
|
/**
|
|
440
480
|
* MATRIX ATTRIBUTES BURNED OFF BY IC (p.248): Acid eats Firewall,
|
|
441
481
|
* Binder eats Data Processing, Jammer eats Attack, Marker eats Sleaze.
|
|
@@ -571,6 +611,10 @@ export declare class Player extends AbstractPlayer {
|
|
|
571
611
|
private static readonly ATTR_EATER;
|
|
572
612
|
private static readonly ATTR_NAME;
|
|
573
613
|
/** The undamaged array, before any IC reduction. */
|
|
614
|
+
/** Set on an IC actor (utilities/ic-actors.ts): "IC uses the Matrix attributes of its host" (p.247). */
|
|
615
|
+
icHost?: Host;
|
|
616
|
+
/** The IC kind an IC actor was launched as -- the name the host's running list carries. */
|
|
617
|
+
icKind?: string;
|
|
574
618
|
baseMatrixAttribute(kind: 'attack' | 'sleaze' | 'dataProcessing' | 'firewall'): number;
|
|
575
619
|
/**
|
|
576
620
|
* THE LIVE LINK -- what makes this runner reachable and lit (p.223,
|
|
@@ -1330,40 +1374,70 @@ export declare class Player extends AbstractPlayer {
|
|
|
1330
1374
|
*/
|
|
1331
1375
|
sustainedNames(): string[];
|
|
1332
1376
|
releaseSustainedWorkings(): number;
|
|
1333
|
-
|
|
1334
|
-
|
|
1377
|
+
private _sustainedInvisibility;
|
|
1378
|
+
get sustainedInvisibility(): boolean;
|
|
1379
|
+
set sustainedInvisibility(v: boolean);
|
|
1380
|
+
private _sustainedReflexes;
|
|
1381
|
+
get sustainedReflexes(): boolean;
|
|
1382
|
+
set sustainedReflexes(v: boolean);
|
|
1335
1383
|
/** The sustained PHYSICAL BARRIER spell (ruling 2026-08-24): seals
|
|
1336
1384
|
* one exit of one room while held -- go.ts and move refuse passage
|
|
1337
1385
|
* for everyone (the caster included; walls don't play favorites).
|
|
1338
1386
|
* Session-only, like every sustain. */
|
|
1339
|
-
|
|
1387
|
+
private _sustainedBarrier?;
|
|
1388
|
+
get sustainedBarrier(): {
|
|
1340
1389
|
roomName: string;
|
|
1341
1390
|
direction: string;
|
|
1342
1391
|
force: number;
|
|
1343
|
-
};
|
|
1392
|
+
} | undefined;
|
|
1393
|
+
set sustainedBarrier(v: {
|
|
1394
|
+
roomName: string;
|
|
1395
|
+
direction: string;
|
|
1396
|
+
force: number;
|
|
1397
|
+
} | undefined);
|
|
1344
1398
|
/** The MANA BARRIER twin (canon p.294, RAG-checked): blocks SPIRITS
|
|
1345
1399
|
* and astral forms at the sealed exit; living mundanes and physical
|
|
1346
1400
|
* objects pass freely. Same lifecycle as sustainedBarrier. */
|
|
1347
|
-
|
|
1401
|
+
private _sustainedManaBarrier?;
|
|
1402
|
+
get sustainedManaBarrier(): {
|
|
1348
1403
|
roomName: string;
|
|
1349
1404
|
direction: string;
|
|
1350
1405
|
force: number;
|
|
1351
|
-
};
|
|
1406
|
+
} | undefined;
|
|
1407
|
+
set sustainedManaBarrier(v: {
|
|
1408
|
+
roomName: string;
|
|
1409
|
+
direction: string;
|
|
1410
|
+
force: number;
|
|
1411
|
+
} | undefined);
|
|
1352
1412
|
/** `hits` is the original threading test's hits -- the number Kill
|
|
1353
1413
|
* Complex Form (p.250) eats away at. Optional so a form woven by
|
|
1354
1414
|
* anything that predates that bookkeeping still reads sanely;
|
|
1355
1415
|
* unravel.ts falls back to the Level. */
|
|
1356
|
-
|
|
1416
|
+
private _sustainedForms;
|
|
1417
|
+
/** Sustained complex forms. Writers push/splice it directly (sustain.ts,
|
|
1418
|
+
* thread.ts, game.ts), so the array itself is observed: every element
|
|
1419
|
+
* write fires the beat, and assigning a fresh array re-wraps it. */
|
|
1420
|
+
get sustainedForms(): {
|
|
1357
1421
|
key: string;
|
|
1358
1422
|
level: number;
|
|
1359
1423
|
hits?: number;
|
|
1360
1424
|
}[];
|
|
1425
|
+
set sustainedForms(v: {
|
|
1426
|
+
key: string;
|
|
1427
|
+
level: number;
|
|
1428
|
+
hits?: number;
|
|
1429
|
+
}[]);
|
|
1430
|
+
private observedForms;
|
|
1361
1431
|
/** Diagnostics: +2 to matrix action pools while sustained. */
|
|
1362
1432
|
get formMatrixBonus(): number;
|
|
1363
1433
|
/** Static Veil: +2 to matrix defense while sustained. */
|
|
1364
1434
|
get formDefenseBonus(): number;
|
|
1365
1435
|
get sustainingPenalty(): number;
|
|
1366
|
-
|
|
1436
|
+
private _astralPerceiving;
|
|
1437
|
+
/** Assensing (SR5 p.312): the live sheet shows it, so a change is a
|
|
1438
|
+
* condition beat. Accessor pair -- every writer compiles unchanged. */
|
|
1439
|
+
get astralPerceiving(): boolean;
|
|
1440
|
+
set astralPerceiving(v: boolean);
|
|
1367
1441
|
sneaking: boolean;
|
|
1368
1442
|
riggedDrone?: Item;
|
|
1369
1443
|
/** Whether the ridden drone carries a weapon mount (name/description duck-typing). */
|
|
@@ -16,40 +16,11 @@ export const STANCE_CYCLE = ['manual', 'guarded', 'aggressive'];
|
|
|
16
16
|
export function isPhysicalPlane(plane) {
|
|
17
17
|
return plane === 'meat' || plane === 'drone';
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
20
|
-
* ARE THESE TWO STANDING ON THE SAME SIDE OF A HOST'S WALL?
|
|
21
|
-
*
|
|
22
|
-
* The companion to fileReach, and it exists for the same reason: the
|
|
23
|
-
* boundary was being asked about in more than one place and the copies
|
|
24
|
-
* disagreed. fileReach settled it for FILES; nothing settled it for
|
|
25
|
-
* ICONS, so `look` honoured insideHost while every verb that picks a
|
|
26
|
-
* target read the room's raw actor list and saw straight through the
|
|
27
|
-
* wall. A technomancer standing inside a host was told "Empty
|
|
28
|
-
* architecture -- turning geometry and nothing in it worth the trip" by
|
|
29
|
-
* one surface and offered those same two IC as spike targets by the
|
|
30
|
-
* next.
|
|
31
|
-
*
|
|
32
|
-
* CANON MAKES THE PANEL BINDING, not the target list. Inside a host you
|
|
33
|
-
* see and interact with the icons inside it and not the ones outside,
|
|
34
|
-
* and from the grid the inside is unreachable (p.246) -- and threading
|
|
35
|
-
* adds its own half of the same rule: "You can only use complex forms
|
|
36
|
-
* on icons you've spotted" (p.251). An icon the room refuses to name is
|
|
37
|
-
* an icon nobody has spotted, so targeting it was never legal.
|
|
38
|
-
*
|
|
39
|
-
* BOTH IN THE SAME ROOM IS THE PRECONDITION, and every caller has it --
|
|
40
|
-
* these are room-scoped target lists. Given that, comparing insideHost
|
|
41
|
-
* directly is the whole rule: both undefined means both are out on the
|
|
42
|
-
* open grid, both equal to the room means both are inside its
|
|
43
|
-
* architecture. There is deliberately no third state.
|
|
44
|
-
*
|
|
45
|
-
* MEAT AND ASTRAL ACTORS PASS TRIVIALLY, which is correct and not an
|
|
46
|
-
* oversight: insideHost is persona state, so a body in the room has
|
|
47
|
-
* none. The plane filters the callers already apply are what keep those
|
|
48
|
-
* actors out of a Matrix target list; this function answers one question
|
|
49
|
-
* and is not a second plane check.
|
|
50
|
-
*/
|
|
51
19
|
export function sameHostSide(actor, other) {
|
|
52
|
-
|
|
20
|
+
// A real Player answers with its Host; a bare fixture ({ insideHost })
|
|
21
|
+
// answers with whatever it carries. Both sides read the same way.
|
|
22
|
+
const sideOf = (p) => p.hostInside ?? p.insideHost;
|
|
23
|
+
return sideOf(actor) === sideOf(other);
|
|
53
24
|
}
|
|
54
25
|
export const SKILL_NAMES = [
|
|
55
26
|
'blades', 'clubs', 'unarmed', 'archery', 'free-fall',
|
|
@@ -426,7 +397,68 @@ export class Player extends AbstractPlayer {
|
|
|
426
397
|
* Session state like hostAlert and linkLockedBy: leaving the Matrix
|
|
427
398
|
* ends it, so nothing here is persisted.
|
|
428
399
|
*/
|
|
429
|
-
|
|
400
|
+
/**
|
|
401
|
+
* WHERE THE PERSONA IS (increment 2 of the Matrix position model): on a
|
|
402
|
+
* grid, or inside a host it entered from one. Session-only, like
|
|
403
|
+
* bodyRoom. The body stays in currentLocation on every plane -- the
|
|
404
|
+
* persona never writes it. The setter fires the live-sheet beat, which
|
|
405
|
+
* is the beat the currentLocation setter used to fire on enter/exit.
|
|
406
|
+
* `grid` may be unknown (fixtures, and increment 2 itself): unknown is
|
|
407
|
+
* not "another grid", so it costs nothing.
|
|
408
|
+
*/
|
|
409
|
+
_matrixPosition;
|
|
410
|
+
get matrixPosition() { return this._matrixPosition; }
|
|
411
|
+
set matrixPosition(v) {
|
|
412
|
+
const same = v === this._matrixPosition
|
|
413
|
+
|| (v !== undefined && this._matrixPosition !== undefined && v.kind === this._matrixPosition.kind
|
|
414
|
+
&& (v.kind === 'host' ? v.host === this._matrixPosition.host
|
|
415
|
+
: v.grid?.key === this._matrixPosition.grid?.key));
|
|
416
|
+
if (same)
|
|
417
|
+
return;
|
|
418
|
+
this._matrixPosition = v;
|
|
419
|
+
this.noteConditionChanged();
|
|
420
|
+
}
|
|
421
|
+
/** The grid the persona rides, when it is on one. */
|
|
422
|
+
get currentGrid() {
|
|
423
|
+
return this._matrixPosition?.kind === 'grid' ? this._matrixPosition.grid : undefined;
|
|
424
|
+
}
|
|
425
|
+
/** The host the persona is inside, when it is inside one. */
|
|
426
|
+
get hostInside() {
|
|
427
|
+
return this._matrixPosition?.kind === 'host' ? this._matrixPosition.host : undefined;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* @deprecated compat through increment 4 -- read hostInside / write
|
|
431
|
+
* matrixPosition. The room a host stands over, when inside one. The
|
|
432
|
+
* setter still builds the position so every old writer keeps working.
|
|
433
|
+
*/
|
|
434
|
+
_compatInsideRoom;
|
|
435
|
+
get insideHost() { return this.hostInside?.over ?? this._compatInsideRoom; }
|
|
436
|
+
set insideHost(room) {
|
|
437
|
+
if (room) {
|
|
438
|
+
// A room you are inside of IS a host. Fixtures (and older callers)
|
|
439
|
+
// set insideHost on a plain Room; install the Host they meant.
|
|
440
|
+
if (!room.host)
|
|
441
|
+
room.hasNode = true;
|
|
442
|
+
const host = room.host;
|
|
443
|
+
if (!host) {
|
|
444
|
+
// Not a real Room (a fixture stub): remember it so the getter and
|
|
445
|
+
// sameHostSide still answer, without a position to stand on.
|
|
446
|
+
this._compatInsideRoom = room;
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
this._compatInsideRoom = undefined;
|
|
450
|
+
this.matrixPosition = { kind: 'host', host, from: this.currentGrid ?? this.lastGrid };
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
this._compatInsideRoom = undefined;
|
|
454
|
+
if (this._matrixPosition?.kind === 'host')
|
|
455
|
+
this.matrixPosition = { kind: 'grid', grid: this._matrixPosition.from };
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/** The grid last stood on, so an exit built through the old setter returns there. */
|
|
459
|
+
get lastGrid() {
|
|
460
|
+
return this._matrixPosition?.kind === 'host' ? this._matrixPosition.from : this._matrixPosition?.grid;
|
|
461
|
+
}
|
|
430
462
|
/**
|
|
431
463
|
* MATRIX ATTRIBUTES BURNED OFF BY IC (p.248): Acid eats Firewall,
|
|
432
464
|
* Binder eats Data Processing, Jammer eats Attack, Marker eats Sleaze.
|
|
@@ -647,7 +679,13 @@ export class Player extends AbstractPlayer {
|
|
|
647
679
|
dataProcessing: 'Data Processing', firewall: 'Firewall',
|
|
648
680
|
};
|
|
649
681
|
/** The undamaged array, before any IC reduction. */
|
|
682
|
+
/** Set on an IC actor (utilities/ic-actors.ts): "IC uses the Matrix attributes of its host" (p.247). */
|
|
683
|
+
icHost;
|
|
684
|
+
/** The IC kind an IC actor was launched as -- the name the host's running list carries. */
|
|
685
|
+
icKind;
|
|
650
686
|
baseMatrixAttribute(kind) {
|
|
687
|
+
if (this.icHost)
|
|
688
|
+
return this.icHost.attributes()[kind];
|
|
651
689
|
if (this.isTechnomancer()) {
|
|
652
690
|
switch (kind) {
|
|
653
691
|
case 'attack': return this.charisma;
|
|
@@ -922,7 +960,13 @@ export class Player extends AbstractPlayer {
|
|
|
922
960
|
/** Routes Matrix combat damage into the jacked-in deck's hardware track. */
|
|
923
961
|
takeMatrixDamage(boxes) {
|
|
924
962
|
const deck = this.getAnyCyberdeck();
|
|
925
|
-
|
|
963
|
+
const taken = deck ? deck.takeDeckDamage(boxes) : 0;
|
|
964
|
+
// DECK DAMAGE BEATS (web report 2026-09-02: "the damage to the deck is not moving in realtime"):
|
|
965
|
+
// the Item has no path to its owner, so the owner fires here, and the
|
|
966
|
+
// exchange and rest fire on their own deck writes.
|
|
967
|
+
if (taken > 0)
|
|
968
|
+
this.noteConditionChanged();
|
|
969
|
+
return taken;
|
|
926
970
|
}
|
|
927
971
|
get matrixCrashed() {
|
|
928
972
|
return this.getAnyCyberdeck()?.isBricked ?? false;
|
|
@@ -2681,7 +2725,14 @@ export class Player extends AbstractPlayer {
|
|
|
2681
2725
|
return this._sustainedArmor;
|
|
2682
2726
|
}
|
|
2683
2727
|
setSustainedArmor(bonus) {
|
|
2684
|
-
|
|
2728
|
+
const next = Math.max(0, Math.floor(bonus));
|
|
2729
|
+
if (next === this._sustainedArmor)
|
|
2730
|
+
return;
|
|
2731
|
+
this._sustainedArmor = next;
|
|
2732
|
+
// SUSTAINS BEAT ON FLIP (web report 2026-09-02: "I cast armor on myself
|
|
2733
|
+
// and don't see it in the sustained list"): the sheet only caught up on
|
|
2734
|
+
// the next unrelated beat. Every sustain mutator fires the hook now.
|
|
2735
|
+
this.noteConditionChanged();
|
|
2685
2736
|
}
|
|
2686
2737
|
/**
|
|
2687
2738
|
* REST BREAKS CONCENTRATION (yZSAdPAvwMpzjnTaM; SR5 p.251: "You
|
|
@@ -2734,17 +2785,45 @@ export class Player extends AbstractPlayer {
|
|
|
2734
2785
|
// light around the bearer (+3 on sneak contests, npc.ts), increase
|
|
2735
2786
|
// reflexes runs the nerves hot (+2 initiative). Each holds the standard
|
|
2736
2787
|
// -2 sustaining burden; cast again to release (cast.ts).
|
|
2737
|
-
|
|
2738
|
-
|
|
2788
|
+
_sustainedInvisibility = false;
|
|
2789
|
+
get sustainedInvisibility() { return this._sustainedInvisibility; }
|
|
2790
|
+
set sustainedInvisibility(v) {
|
|
2791
|
+
if (this._sustainedInvisibility === v)
|
|
2792
|
+
return;
|
|
2793
|
+
this._sustainedInvisibility = v;
|
|
2794
|
+
this.noteConditionChanged();
|
|
2795
|
+
}
|
|
2796
|
+
_sustainedReflexes = false;
|
|
2797
|
+
get sustainedReflexes() { return this._sustainedReflexes; }
|
|
2798
|
+
set sustainedReflexes(v) {
|
|
2799
|
+
if (this._sustainedReflexes === v)
|
|
2800
|
+
return;
|
|
2801
|
+
this._sustainedReflexes = v;
|
|
2802
|
+
this.noteConditionChanged();
|
|
2803
|
+
}
|
|
2739
2804
|
/** The sustained PHYSICAL BARRIER spell (ruling 2026-08-24): seals
|
|
2740
2805
|
* one exit of one room while held -- go.ts and move refuse passage
|
|
2741
2806
|
* for everyone (the caster included; walls don't play favorites).
|
|
2742
2807
|
* Session-only, like every sustain. */
|
|
2743
|
-
|
|
2808
|
+
_sustainedBarrier;
|
|
2809
|
+
get sustainedBarrier() { return this._sustainedBarrier; }
|
|
2810
|
+
set sustainedBarrier(v) {
|
|
2811
|
+
if (this._sustainedBarrier === v)
|
|
2812
|
+
return;
|
|
2813
|
+
this._sustainedBarrier = v;
|
|
2814
|
+
this.noteConditionChanged();
|
|
2815
|
+
}
|
|
2744
2816
|
/** The MANA BARRIER twin (canon p.294, RAG-checked): blocks SPIRITS
|
|
2745
2817
|
* and astral forms at the sealed exit; living mundanes and physical
|
|
2746
2818
|
* objects pass freely. Same lifecycle as sustainedBarrier. */
|
|
2747
|
-
|
|
2819
|
+
_sustainedManaBarrier;
|
|
2820
|
+
get sustainedManaBarrier() { return this._sustainedManaBarrier; }
|
|
2821
|
+
set sustainedManaBarrier(v) {
|
|
2822
|
+
if (this._sustainedManaBarrier === v)
|
|
2823
|
+
return;
|
|
2824
|
+
this._sustainedManaBarrier = v;
|
|
2825
|
+
this.noteConditionChanged();
|
|
2826
|
+
}
|
|
2748
2827
|
// THREADED COMPLEX FORMS (M4.15, see commands/thread.ts): the
|
|
2749
2828
|
// technomancer's sustained workings ride the same -2 rail as spells.
|
|
2750
2829
|
// Instant forms never land here.
|
|
@@ -2752,7 +2831,30 @@ export class Player extends AbstractPlayer {
|
|
|
2752
2831
|
* Complex Form (p.250) eats away at. Optional so a form woven by
|
|
2753
2832
|
* anything that predates that bookkeeping still reads sanely;
|
|
2754
2833
|
* unravel.ts falls back to the Level. */
|
|
2755
|
-
|
|
2834
|
+
_sustainedForms = this.observedForms([]);
|
|
2835
|
+
/** Sustained complex forms. Writers push/splice it directly (sustain.ts,
|
|
2836
|
+
* thread.ts, game.ts), so the array itself is observed: every element
|
|
2837
|
+
* write fires the beat, and assigning a fresh array re-wraps it. */
|
|
2838
|
+
get sustainedForms() { return this._sustainedForms; }
|
|
2839
|
+
set sustainedForms(v) {
|
|
2840
|
+
this._sustainedForms = this.observedForms([...v]);
|
|
2841
|
+
this.noteConditionChanged();
|
|
2842
|
+
}
|
|
2843
|
+
observedForms(arr) {
|
|
2844
|
+
return new Proxy(arr, {
|
|
2845
|
+
set: (target, key, value) => {
|
|
2846
|
+
target[key] = value;
|
|
2847
|
+
if (key !== 'length')
|
|
2848
|
+
this.noteConditionChanged();
|
|
2849
|
+
return true;
|
|
2850
|
+
},
|
|
2851
|
+
deleteProperty: (target, key) => {
|
|
2852
|
+
delete target[key];
|
|
2853
|
+
this.noteConditionChanged();
|
|
2854
|
+
return true;
|
|
2855
|
+
},
|
|
2856
|
+
});
|
|
2857
|
+
}
|
|
2756
2858
|
/** Diagnostics: +2 to matrix action pools while sustained. */
|
|
2757
2859
|
get formMatrixBonus() {
|
|
2758
2860
|
return this.sustainedForms.some(f => f.key === 'diagnostics') ? 2 : 0;
|
|
@@ -2782,7 +2884,16 @@ export class Player extends AbstractPlayer {
|
|
|
2782
2884
|
// sustained state -- passive aura reads, at -2 dice to everything else.
|
|
2783
2885
|
// Meat-plane only; projecting IS full astral senses (cleared on
|
|
2784
2886
|
// project), and the Matrix has no auras at all.
|
|
2785
|
-
|
|
2887
|
+
_astralPerceiving = false;
|
|
2888
|
+
/** Assensing (SR5 p.312): the live sheet shows it, so a change is a
|
|
2889
|
+
* condition beat. Accessor pair -- every writer compiles unchanged. */
|
|
2890
|
+
get astralPerceiving() { return this._astralPerceiving; }
|
|
2891
|
+
set astralPerceiving(v) {
|
|
2892
|
+
if (this._astralPerceiving === v)
|
|
2893
|
+
return;
|
|
2894
|
+
this._astralPerceiving = v;
|
|
2895
|
+
this.noteConditionChanged();
|
|
2896
|
+
}
|
|
2786
2897
|
// Sneak-walking (see commands/sneak.ts): room entries become opposed
|
|
2787
2898
|
// Sneaking-vs-Perception rolls per witness (NPC.see). Broken by any
|
|
2788
2899
|
// loud act -- attacking, casting, speaking, brandishing, calling.
|