@maka/maka-cli 5.129.0 → 5.130.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/cast.js +28 -15
- package/bundle/typescript/src/commands/game/sideQuest/commands/sheet.js +1 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/spells.js +12 -1
- package/bundle/typescript/src/commands/game/sideQuest/game.js +18 -4
- package/bundle/typescript/src/commands/game/sideQuest/models/npc.js +2 -2
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +106 -13
- package/bundle/typescript/src/commands/game/sideQuest/utilities/catalog.js +15 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.130.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"summary": "A command line tool for scaffolding Meteor 3.x applications using either React.",
|
|
6
6
|
"description": "A command line tool for scaffolding Meteor 3.x applications using React.",
|
|
@@ -777,15 +777,20 @@ export class CastCommand extends BypassCommand {
|
|
|
777
777
|
return error ?? `You don't see them here.`;
|
|
778
778
|
const self = target === this.actor;
|
|
779
779
|
// Sustained toggle per bearer: casting at them again unravels it, no
|
|
780
|
-
// drain to let go.
|
|
781
|
-
//
|
|
780
|
+
// drain to let go. The caster is the only one who can release it
|
|
781
|
+
// (p.282: the caster sustains) -- a weave somebody ELSE is holding
|
|
782
|
+
// on them is that caster's to drop, not yours to overwrite.
|
|
782
783
|
if (target.sustainedArmor > 0) {
|
|
784
|
+
const holder = target.wornSpellCaster('armor');
|
|
785
|
+
if (holder && holder !== this.actor) {
|
|
786
|
+
return `${holder.name} is holding an armor weave on ${self ? 'you' : target.name} already -- only ${holder.name} can let it go.`;
|
|
787
|
+
}
|
|
783
788
|
target.setSustainedArmor(0);
|
|
784
789
|
this.scene.updateStatus();
|
|
785
790
|
this.actor.performAction('releases armor spell', self ? '' : `on ${target.name}`);
|
|
786
791
|
return this.finishSpell([self
|
|
787
792
|
? `You let the armor spell go. The air stops humming, and the sustaining penalty lifts.`
|
|
788
|
-
: `You unravel the armor weave around ${target.name}. The shimmer fades, and
|
|
793
|
+
: `You unravel the armor weave around ${target.name}. The shimmer fades, and the burden of holding it lifts from you.`]);
|
|
789
794
|
}
|
|
790
795
|
if (target.isIncapacitated()) {
|
|
791
796
|
return `${target.name} is down -- armor won't help now.`;
|
|
@@ -795,19 +800,20 @@ export class CastCommand extends BypassCommand {
|
|
|
795
800
|
const focus = this.actor.getBestFocus();
|
|
796
801
|
const bonus = Math.ceil(force / 3) + (focus?.ratingValue ?? 0); // the focus's Force (catalog v13)
|
|
797
802
|
const spell = spellFor('armor');
|
|
798
|
-
// The
|
|
799
|
-
//
|
|
803
|
+
// The BEARER wears the weave (the soak is theirs); the CASTER
|
|
804
|
+
// sustains it and carries the -2 (p.282), on top of the drain.
|
|
800
805
|
target.setSustainedArmor(bonus);
|
|
801
806
|
target.noteSustainedForce('armor', force);
|
|
807
|
+
target.sustainedBy('armor', this.actor);
|
|
802
808
|
this.actor.performAction('casts armor', self ? '' : `on ${target.name}`);
|
|
803
809
|
this.scene.addWorldEvent(`${this.actor.name} wrapped ${self ? 'themselves' : target.name} in a shimmering armor spell.`);
|
|
804
810
|
this.scene.updateStatus();
|
|
805
|
-
// The burden is the
|
|
811
|
+
// The burden is the CASTER's ledger's, not a restated -2: a Focused
|
|
806
812
|
// Concentration holder is told "no penalty" the moment it applies.
|
|
807
|
-
const burden = sustainBurden(
|
|
813
|
+
const burden = sustainBurden(this.actor.sustainedLedger().find(w => w.key === 'armor' && (self ? w.bearer === undefined : w.bearer === target.name)), ' to everything else you do');
|
|
808
814
|
const lines = [
|
|
809
815
|
`${this.actor.name} casts armor at Force ${force}${self ? '' : ` on ${target.name}`}, ${this.castingLabel()}:`,
|
|
810
|
-
` The air hardens around ${self ? 'you' : target.name} -- +${bonus} soak dice while sustained, ${burden}.`,
|
|
816
|
+
` The air hardens around ${self ? 'you' : target.name} -- +${bonus} soak dice while sustained${self ? '' : '; you hold the weave up'}, ${burden}.`,
|
|
811
817
|
hint(` (Cast armor${self ? '' : ` on ${target.name}`} again to release it.)`),
|
|
812
818
|
...this.applyDrain(spell, force, overcast),
|
|
813
819
|
];
|
|
@@ -882,8 +888,8 @@ export class CastCommand extends BypassCommand {
|
|
|
882
888
|
}
|
|
883
889
|
/**
|
|
884
890
|
* The sustained utility pair (invisibility / increase reflexes), same
|
|
885
|
-
* toggle shape as the armor spell: the BEARER
|
|
886
|
-
*
|
|
891
|
+
* toggle shape as the armor spell: the BEARER wears the effect, the
|
|
892
|
+
* CASTER sustains it (-2, p.282) and pays the drain, and casting at
|
|
887
893
|
* the same bearer again releases it for free.
|
|
888
894
|
*/
|
|
889
895
|
async castSustainedUtility(spellKey, force, overcast, rest = []) {
|
|
@@ -894,12 +900,16 @@ export class CastCommand extends BypassCommand {
|
|
|
894
900
|
const flag = spellKey === 'invisibility' ? 'sustainedInvisibility' : 'sustainedReflexes';
|
|
895
901
|
const label = spellKey === 'invisibility' ? 'invisibility' : 'increase reflexes';
|
|
896
902
|
if (target[flag]) {
|
|
903
|
+
const holder = target.wornSpellCaster(spellKey);
|
|
904
|
+
if (holder && holder !== this.actor) {
|
|
905
|
+
return `${holder.name} is holding ${label} on ${self ? 'you' : target.name} already -- only ${holder.name} can let it go.`;
|
|
906
|
+
}
|
|
897
907
|
target[flag] = false;
|
|
898
908
|
this.scene.updateStatus();
|
|
899
909
|
this.actor.performAction(`releases ${label}`, self ? '' : `on ${target.name}`);
|
|
900
910
|
return this.finishSpell([spellKey === 'invisibility'
|
|
901
|
-
? `${self ? 'You shimmer back into the world' : `${target.name} shimmers back into the world`} -- the light remembers ${self ? 'you' : 'them'}, and the sustaining burden lifts.`
|
|
902
|
-
: `${self ? 'Your' : `${target.name}'s`} nerves cool back to human speed. The sustaining burden lifts.`]);
|
|
911
|
+
? `${self ? 'You shimmer back into the world' : `${target.name} shimmers back into the world`} -- the light remembers ${self ? 'you' : 'them'}, and the sustaining burden lifts${self ? '' : ' from you'}.`
|
|
912
|
+
: `${self ? 'Your' : `${target.name}'s`} nerves cool back to human speed. The sustaining burden lifts${self ? '' : ' from you'}.`]);
|
|
903
913
|
}
|
|
904
914
|
if (target.isIncapacitated()) {
|
|
905
915
|
return `${target.name} is down -- ${label} won't help now.`;
|
|
@@ -911,15 +921,18 @@ export class CastCommand extends BypassCommand {
|
|
|
911
921
|
const spell = spellFor(spellKey);
|
|
912
922
|
target[flag] = true;
|
|
913
923
|
target.noteSustainedForce(spellKey, force);
|
|
924
|
+
target.sustainedBy(spellKey, this.actor);
|
|
914
925
|
this.actor.performAction(`casts ${label}`, self ? '' : `on ${target.name}`);
|
|
915
926
|
this.scene.addWorldEvent(spellKey === 'invisibility'
|
|
916
927
|
? `${this.actor.name} wove an invisibility spell${self ? '' : ` over ${target.name}`}.`
|
|
917
928
|
: `${this.actor.name} cast increase reflexes${self ? '' : ` on ${target.name}`}.`);
|
|
918
929
|
this.scene.updateStatus();
|
|
919
|
-
|
|
930
|
+
// The CASTER's ledger: they hold it, they pay (p.282).
|
|
931
|
+
const burden = sustainBurden(this.actor.sustainedLedger().find(w => w.key === spellKey && (self ? w.bearer === undefined : w.bearer === target.name)), ' to everything else you do');
|
|
932
|
+
const holding = self ? '' : '; you hold it up';
|
|
920
933
|
const effectLine = spellKey === 'invisibility'
|
|
921
|
-
? ` The light slides around ${self ? 'you' : target.name} like water around a stone -- +3 dice on sneak contests while sustained, ${burden}. Loud acts still announce ${self ? 'you' : 'them'}.`
|
|
922
|
-
: ` ${self ? 'Your' : `${target.name}'s`} nerves run HOT -- +2 initiative while sustained, ${burden}.`;
|
|
934
|
+
? ` The light slides around ${self ? 'you' : target.name} like water around a stone -- +3 dice on sneak contests while sustained${holding}, ${burden}. Loud acts still announce ${self ? 'you' : 'them'}.`
|
|
935
|
+
: ` ${self ? 'Your' : `${target.name}'s`} nerves run HOT -- +2 initiative while sustained${holding}, ${burden}.`;
|
|
923
936
|
const lines = [
|
|
924
937
|
`${this.actor.name} casts ${label} at Force ${force}${self ? '' : ` on ${target.name}`}, ${this.castingLabel()}:`,
|
|
925
938
|
effectLine,
|
|
@@ -173,7 +173,7 @@ export class SheetCommand extends Command {
|
|
|
173
173
|
...(a.initiationGrade > 0 ? [row(a.isTechnomancer() ? 'Submersion' : 'Initiation', `grade ${a.initiationGrade} -- ${catalogMetamagics().filter(m => a.hasMetamagic(m.key)).map(m => m.name).join(', ')}`)] : []),
|
|
174
174
|
...(a.lockedFatigue > 0 ? [row('Fatigue', `${a.lockedFatigue} stun LOCKED until the need is met`)] : []),
|
|
175
175
|
...(a.woundModifier < 0 ? [row('Wounds', `${a.woundModifier} dice on everything`)] : []),
|
|
176
|
-
...(a.sustainedArmor > 0 ? [row('Sustained', `armor +${a.sustainedArmor} soak (${sustainBurden(a.sustainedLedger().find(w => w.key === 'armor'))})`)] : []),
|
|
176
|
+
...(a.sustainedArmor > 0 ? [row('Sustained', `armor +${a.sustainedArmor} soak (${a.wornSpellCaster('armor') ? `held by ${a.wornSpellCaster('armor').name}` : sustainBurden(a.sustainedLedger().find(w => w.key === 'armor' && w.bearer === undefined))})`)] : []),
|
|
177
177
|
];
|
|
178
178
|
return lines.join('\n');
|
|
179
179
|
}
|
|
@@ -70,14 +70,25 @@ export class SpellsCommand extends Command {
|
|
|
70
70
|
// The cost per working is the engine's (Player.sustainedLedger): a
|
|
71
71
|
// Focused Concentration holder reads "no penalty" here, not a
|
|
72
72
|
// restated -2.
|
|
73
|
+
// The CASTER pays (p.282): a spell somebody else holds on you shows
|
|
74
|
+
// as worn, with the holder named; what you hold on others is listed
|
|
75
|
+
// after, with its bearer.
|
|
73
76
|
const ledger = actor.sustainedLedger();
|
|
74
|
-
const burden = (key) =>
|
|
77
|
+
const burden = (key) => {
|
|
78
|
+
const holder = actor.wornSpellCaster(key);
|
|
79
|
+
return holder ? `held by ${holder.name} -- their burden` : sustainBurden(ledger.find(w => w.key === key && w.bearer === undefined), ' to everything else');
|
|
80
|
+
};
|
|
75
81
|
if (actor.sustainedArmor > 0) {
|
|
76
82
|
lines.push(`Sustained: armor spell, +${actor.sustainedArmor} soak (${burden('armor')}).${hint(' Cast armor again to release.')}`);
|
|
77
83
|
}
|
|
78
84
|
if (actor.sustainedInvisibility) {
|
|
79
85
|
lines.push(`Sustained: invisibility, +3 on sneak contests (${burden('invisibility')}).${hint(' Cast invisibility again to release.')}`);
|
|
80
86
|
}
|
|
87
|
+
for (const w of ledger) {
|
|
88
|
+
if (w.bearer === undefined)
|
|
89
|
+
continue;
|
|
90
|
+
lines.push(`Sustaining on ${w.bearer}: ${w.key === 'reflexes' ? 'increase reflexes' : w.key} (${sustainBurden(w, ' to everything else')}).${hint(` Cast ${w.key} on ${w.bearer} again to release.`)}`);
|
|
91
|
+
}
|
|
81
92
|
if (actor.sustainedBarrier) {
|
|
82
93
|
lines.push(`Sustained: force barrier sealing ${actor.sustainedBarrier.direction} in ${actor.sustainedBarrier.roomName}, Force ${actor.sustainedBarrier.force} (${burden('barrier')}).${hint(' Cast barrier again to release.')}`);
|
|
83
94
|
}
|
|
@@ -5281,9 +5281,16 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
5281
5281
|
// literal -2 until 2026-09-06, while sustainingPenalty was already
|
|
5282
5282
|
// waiving a Focused Concentration holder's spell -- the HUD told the
|
|
5283
5283
|
// player they were paying for what the pools were not charging.
|
|
5284
|
+
//
|
|
5285
|
+
// THE CASTER PAYS (p.282, 2026-09-06). A spell somebody else holds
|
|
5286
|
+
// on this character is on their sheet as WORN -- soak and all --
|
|
5287
|
+
// with the holder named and no cost; what this character holds on
|
|
5288
|
+
// others rides the ledger below with its bearer named.
|
|
5284
5289
|
const ledger = player.sustainedLedger();
|
|
5290
|
+
const heldBy = (key) => player.wornSpellCaster(key)?.name;
|
|
5285
5291
|
if (player.sustainedArmor > 0) {
|
|
5286
|
-
|
|
5292
|
+
const holder = heldBy('armor');
|
|
5293
|
+
lines.push(`{magenta-fg}Sustained: armor +${player.sustainedArmor} soak (${holder ? `held by ${holder} -- their burden` : sustainBurden(ledger.find(w => w.key === 'armor' && w.bearer === undefined), ' to everything else')}){/magenta-fg}`);
|
|
5287
5294
|
}
|
|
5288
5295
|
// THE MAGE'S HALF OF THE SAME QUESTION (TNf9WrZGxCmCzqA4L, scope
|
|
5289
5296
|
// completed by ruling).
|
|
@@ -5309,10 +5316,11 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
5309
5316
|
// Naming it belongs with the condition monitors if it belongs
|
|
5310
5317
|
// anywhere; that is a separate call and not this item's.
|
|
5311
5318
|
const heldSpells = [];
|
|
5319
|
+
const worn = (label, key) => heldSpells.push(heldBy(key) ? `${label} (held by ${heldBy(key)})` : label);
|
|
5312
5320
|
if (player.sustainedInvisibility)
|
|
5313
|
-
|
|
5321
|
+
worn('invisibility', 'invisibility');
|
|
5314
5322
|
if (player.sustainedReflexes)
|
|
5315
|
-
|
|
5323
|
+
worn('increased reflexes', 'reflexes');
|
|
5316
5324
|
// The barriers say WHERE, because a wall you forgot you put up is
|
|
5317
5325
|
// the one that strands the team -- and both of them seal an exit
|
|
5318
5326
|
// that the caster cannot pass either.
|
|
@@ -5322,8 +5330,14 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
5322
5330
|
if (player.sustainedManaBarrier) {
|
|
5323
5331
|
heldSpells.push(`mana barrier (${player.sustainedManaBarrier.roomName} ${player.sustainedManaBarrier.direction})`);
|
|
5324
5332
|
}
|
|
5333
|
+
// What this character holds on OTHERS -- the caster's burden, named
|
|
5334
|
+
// by bearer so the mage knows whose armor is costing them two dice.
|
|
5335
|
+
for (const w of ledger) {
|
|
5336
|
+
if (w.bearer !== undefined)
|
|
5337
|
+
heldSpells.push(`${w.key === 'reflexes' ? 'increased reflexes' : w.key} on ${w.bearer}`);
|
|
5338
|
+
}
|
|
5325
5339
|
if (heldSpells.length > 0) {
|
|
5326
|
-
const these = ledger.filter(w => w.kind === 'spell' && w.key
|
|
5340
|
+
const these = ledger.filter(w => w.kind === 'spell' && !(w.key === 'armor' && w.bearer === undefined));
|
|
5327
5341
|
lines.push(`{magenta-fg}✴ Sustained: ${heldSpells.join(', ')} (${sustainTotal(these)}){/magenta-fg}`);
|
|
5328
5342
|
}
|
|
5329
5343
|
// ACTIVE THREADS, WITHOUT ASKING (TNf9WrZGxCmCzqA4L: "instead of
|
|
@@ -824,8 +824,8 @@ Available in-game commands (use with [COMMAND] prefix):
|
|
|
824
824
|
"manabolt" (combat damage resisted by body, not armor), "heal [on <ally>]" (heal
|
|
825
825
|
physical wounds -- your own by default, or an ally's in your room; healing a
|
|
826
826
|
wounded ally is a strong support move), "armor [on <ally>]" (sustained protection
|
|
827
|
-
on yourself or an ally; its bearer gets +soak
|
|
828
|
-
again to release). EVERY cast costs drain -- stun damage to YOU the caster; fill
|
|
827
|
+
on yourself or an ally; its bearer gets +soak, and YOU carry -2 dice to all else
|
|
828
|
+
while you hold it; cast at them again to release). EVERY cast costs drain -- stun damage to YOU the caster; fill
|
|
829
829
|
your stun track and you're out cold. Never cast if you're mundane with no magic gear.
|
|
830
830
|
- cast — bare "cast" attempts to magically bypass the ward/barrier in this room
|
|
831
831
|
without knowing the answer (requires the same talent or gear; failing burns the
|
|
@@ -3631,7 +3631,7 @@ export class Player extends AbstractPlayer {
|
|
|
3631
3631
|
return;
|
|
3632
3632
|
this._sustainedArmor = next;
|
|
3633
3633
|
if (next === 0)
|
|
3634
|
-
this.
|
|
3634
|
+
this.onSustainDropped('armor');
|
|
3635
3635
|
// SUSTAINS BEAT ON FLIP (web report 2026-09-02: "I cast armor on myself
|
|
3636
3636
|
// and don't see it in the sustained list"): the sheet only caught up on
|
|
3637
3637
|
// the next unrelated beat. Every sustain mutator fires the hook now.
|
|
@@ -3682,8 +3682,27 @@ export class Player extends AbstractPlayer {
|
|
|
3682
3682
|
* spells in `sustainedForces`.
|
|
3683
3683
|
*/
|
|
3684
3684
|
sustainedWorkings() {
|
|
3685
|
+
const mine = this.wornWorkings().filter(w => w.caster === undefined).map(({ caster: _c, ...w }) => w);
|
|
3686
|
+
const onOthers = [];
|
|
3687
|
+
for (const bearer of this.sustainedBearers) {
|
|
3688
|
+
const held = bearer.wornWorkings().filter(w => w.caster === this);
|
|
3689
|
+
if (held.length === 0) {
|
|
3690
|
+
this.sustainedBearers.delete(bearer);
|
|
3691
|
+
continue;
|
|
3692
|
+
}
|
|
3693
|
+
for (const { caster: _c, ...w } of held)
|
|
3694
|
+
onOthers.push({ ...w, bearer: bearer.name });
|
|
3695
|
+
}
|
|
3696
|
+
return [...mine, ...onOthers];
|
|
3697
|
+
}
|
|
3698
|
+
/**
|
|
3699
|
+
* Every working whose STATE is on this character, with who holds it
|
|
3700
|
+
* up when that is somebody else. The bearer's view; sustainedWorkings
|
|
3701
|
+
* is the holder's.
|
|
3702
|
+
*/
|
|
3703
|
+
wornWorkings() {
|
|
3685
3704
|
const spell = (key, force) => ({
|
|
3686
|
-
key, kind: 'spell', force, category: spellFor(key)?.category.toLowerCase(),
|
|
3705
|
+
key, kind: 'spell', force, category: spellFor(key)?.category.toLowerCase(), caster: this.sustainedCasters.get(key),
|
|
3687
3706
|
});
|
|
3688
3707
|
return [
|
|
3689
3708
|
...this.sustainedForms.map((f) => ({ key: f.key, kind: 'form', force: f.level })),
|
|
@@ -3749,15 +3768,22 @@ export class Player extends AbstractPlayer {
|
|
|
3749
3768
|
}
|
|
3750
3769
|
}
|
|
3751
3770
|
releaseSustainedWorkings() {
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
this
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3771
|
+
// What this character HOLDS -- their own, and what they hold on
|
|
3772
|
+
// others (which ends on the bearer). A spell somebody else holds on
|
|
3773
|
+
// this character is that caster's concentration, and sleeping in
|
|
3774
|
+
// it does not break it.
|
|
3775
|
+
const held = this.sustainedWorkings();
|
|
3776
|
+
for (const w of held) {
|
|
3777
|
+
if (w.bearer === undefined) {
|
|
3778
|
+
this.dropSustainedWorking(w.key);
|
|
3779
|
+
continue;
|
|
3780
|
+
}
|
|
3781
|
+
for (const bearer of this.sustainedBearers) {
|
|
3782
|
+
if (bearer.name === w.bearer)
|
|
3783
|
+
bearer.dropSustainedWorking(w.key);
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
return held.length;
|
|
3761
3787
|
}
|
|
3762
3788
|
// The other sustained spells (RAW gear/spell pass): invisibility bends
|
|
3763
3789
|
// light around the bearer (+3 on sneak contests, npc.ts), increase
|
|
@@ -3770,7 +3796,7 @@ export class Player extends AbstractPlayer {
|
|
|
3770
3796
|
return;
|
|
3771
3797
|
this._sustainedInvisibility = v;
|
|
3772
3798
|
if (!v)
|
|
3773
|
-
this.
|
|
3799
|
+
this.onSustainDropped('invisibility');
|
|
3774
3800
|
this.noteConditionChanged();
|
|
3775
3801
|
}
|
|
3776
3802
|
_sustainedReflexes = false;
|
|
@@ -3780,9 +3806,72 @@ export class Player extends AbstractPlayer {
|
|
|
3780
3806
|
return;
|
|
3781
3807
|
this._sustainedReflexes = v;
|
|
3782
3808
|
if (!v)
|
|
3783
|
-
this.
|
|
3809
|
+
this.onSustainDropped('reflexes');
|
|
3784
3810
|
this.noteConditionChanged();
|
|
3785
3811
|
}
|
|
3812
|
+
/**
|
|
3813
|
+
* WHO HOLDS A SPELL THAT IS ON THIS CHARACTER, when it is not them
|
|
3814
|
+
* (2026-09-06: "bill the caster, not the bearer"). SR5 p.282 puts the
|
|
3815
|
+
* -2 on whoever SUSTAINS the spell; the subject only wears it. Until
|
|
3816
|
+
* this the engine taxed the bearer -- a mundane street sam with an
|
|
3817
|
+
* ally's armor on them lost two dice off every pool, and the mage
|
|
3818
|
+
* holding it up paid nothing.
|
|
3819
|
+
*
|
|
3820
|
+
* The effect state stays on the bearer (that is what grants the
|
|
3821
|
+
* soak); this map is the other half, and the caster's ledger reads
|
|
3822
|
+
* it through `sustainedBearers`. Session-only, like every sustain.
|
|
3823
|
+
*/
|
|
3824
|
+
sustainedCasters = new Map();
|
|
3825
|
+
/** The characters this one holds a spell on. Pruned at read time. */
|
|
3826
|
+
sustainedBearers = new Set();
|
|
3827
|
+
/** Record that `caster` is the one holding `key` up on this character. */
|
|
3828
|
+
sustainedBy(key, caster) {
|
|
3829
|
+
if (caster === this) {
|
|
3830
|
+
this.sustainedCasters.delete(key);
|
|
3831
|
+
return;
|
|
3832
|
+
}
|
|
3833
|
+
this.sustainedCasters.set(key, caster);
|
|
3834
|
+
caster.sustainedBearers.add(this);
|
|
3835
|
+
caster.noteConditionChanged();
|
|
3836
|
+
}
|
|
3837
|
+
/** The caster holding `key` on this character, when it is somebody else. */
|
|
3838
|
+
wornSpellCaster(key) {
|
|
3839
|
+
return this.sustainedCasters.get(key);
|
|
3840
|
+
}
|
|
3841
|
+
/** Bookkeeping when a spell on this character ends, whoever ended it. */
|
|
3842
|
+
onSustainDropped(key) {
|
|
3843
|
+
this.sustainedForces.delete(key);
|
|
3844
|
+
const caster = this.sustainedCasters.get(key);
|
|
3845
|
+
if (caster) {
|
|
3846
|
+
this.sustainedCasters.delete(key);
|
|
3847
|
+
caster.noteConditionChanged();
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
/** End one working by key, whatever kind it is. */
|
|
3851
|
+
dropSustainedWorking(key) {
|
|
3852
|
+
switch (key) {
|
|
3853
|
+
case 'armor':
|
|
3854
|
+
this.setSustainedArmor(0);
|
|
3855
|
+
return;
|
|
3856
|
+
case 'invisibility':
|
|
3857
|
+
this.sustainedInvisibility = false;
|
|
3858
|
+
return;
|
|
3859
|
+
case 'reflexes':
|
|
3860
|
+
this.sustainedReflexes = false;
|
|
3861
|
+
return;
|
|
3862
|
+
case 'barrier':
|
|
3863
|
+
this.sustainedBarrier = undefined;
|
|
3864
|
+
return;
|
|
3865
|
+
case 'manabarrier':
|
|
3866
|
+
this.sustainedManaBarrier = undefined;
|
|
3867
|
+
return;
|
|
3868
|
+
default: {
|
|
3869
|
+
const i = this.sustainedForms.findIndex(f => f.key === key);
|
|
3870
|
+
if (i >= 0)
|
|
3871
|
+
this.sustainedForms.splice(i, 1);
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3786
3875
|
/** The sustained PHYSICAL BARRIER spell (ruling 2026-08-24): seals
|
|
3787
3876
|
* one exit of one room while held -- go.ts and move refuse passage
|
|
3788
3877
|
* for everyone (the caster included; walls don't play favorites).
|
|
@@ -3793,6 +3882,8 @@ export class Player extends AbstractPlayer {
|
|
|
3793
3882
|
if (this._sustainedBarrier === v)
|
|
3794
3883
|
return;
|
|
3795
3884
|
this._sustainedBarrier = v;
|
|
3885
|
+
if (!v)
|
|
3886
|
+
this.onSustainDropped('barrier');
|
|
3796
3887
|
this.noteConditionChanged();
|
|
3797
3888
|
}
|
|
3798
3889
|
/** The MANA BARRIER twin (canon p.294, RAG-checked): blocks SPIRITS
|
|
@@ -3804,6 +3895,8 @@ export class Player extends AbstractPlayer {
|
|
|
3804
3895
|
if (this._sustainedManaBarrier === v)
|
|
3805
3896
|
return;
|
|
3806
3897
|
this._sustainedManaBarrier = v;
|
|
3898
|
+
if (!v)
|
|
3899
|
+
this.onSustainDropped('manabarrier');
|
|
3807
3900
|
this.noteConditionChanged();
|
|
3808
3901
|
}
|
|
3809
3902
|
// THREADED COMPLEX FORMS (M4.15, see commands/thread.ts): the
|
|
@@ -105,7 +105,18 @@ export function parseCatalogVersion(raw) {
|
|
|
105
105
|
const n = Number(raw);
|
|
106
106
|
return Number.isFinite(n) ? n : undefined;
|
|
107
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Set by resetCatalogForTest and never cleared: a suite that has reset
|
|
110
|
+
* the catalog wants ONLY what it applies. Found 2026-09-06, the day the
|
|
111
|
+
* developer's cache moved to v17 -- every fixture pinned at 16 lost the
|
|
112
|
+
* "highest version wins" contest to a file in the home directory, and
|
|
113
|
+
* eleven accessor tests went red on one machine and green on every
|
|
114
|
+
* other. Exactly the failure the note above says a test must not have.
|
|
115
|
+
*/
|
|
116
|
+
let diskCacheDisabled = false;
|
|
108
117
|
function readCache() {
|
|
118
|
+
if (diskCacheDisabled)
|
|
119
|
+
return undefined;
|
|
109
120
|
try {
|
|
110
121
|
const parsed = JSON.parse(fs.readFileSync(cachePath(), 'utf8'));
|
|
111
122
|
if (Array.isArray(parsed?.items) && parsed.items.length > 0)
|
|
@@ -571,6 +582,10 @@ export function resetCatalogForTest() {
|
|
|
571
582
|
liveSource = undefined;
|
|
572
583
|
resolved = undefined;
|
|
573
584
|
fetchAttempted = false;
|
|
585
|
+
// The machine's cache is out of the contest from here on: a fixture
|
|
586
|
+
// applied after this is the whole catalog, whatever version it says.
|
|
587
|
+
// A test that wants a cache passes its own reader (CacheReader).
|
|
588
|
+
diskCacheDisabled = true;
|
|
574
589
|
resetCatalogIndexes();
|
|
575
590
|
}
|
|
576
591
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.130.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"summary": "A command line tool for scaffolding Meteor 3.x applications using either React.",
|
|
6
6
|
"description": "A command line tool for scaffolding Meteor 3.x applications using React.",
|