@maka/maka-cli 5.386.0 → 5.388.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 +34 -7
- package/bundle/typescript/src/commands/game/sideQuest/engine-version.js +10 -1
- package/bundle/typescript/src/commands/game/sideQuest/utilities/barrier-geometry.js +5 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/combat-exchange.js +17 -3
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.388.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.",
|
|
@@ -6,10 +6,10 @@ import { rollPool, formatRoll, rollD6Total } from '../utilities/dice.js';
|
|
|
6
6
|
import { fuzzyPickName } from '../utilities/fuzzy-match.js';
|
|
7
7
|
import { describeFledTarget } from '../utilities/pursuit.js';
|
|
8
8
|
import { crossPlaneRefusal } from '../utilities/planes.js';
|
|
9
|
-
import { spotOf, ensureAtSpot, furnitureCells, standingOccupants } from '../utilities/spots.js';
|
|
9
|
+
import { spotOf, ensureAtSpot, furnitureCells, standingOccupants, visibleActorsIn, concealedFrom } from '../utilities/spots.js';
|
|
10
10
|
import { key as gridKey, METERS_PER_CELL, distanceMeters, line3D } from '../utilities/room-grid.js';
|
|
11
11
|
import { isSolid, isVoid, roomAtCell, wallsBetween } from '../utilities/district-plane.js';
|
|
12
|
-
import { DIRECTIONAL_WALL_FORCE, DOME_FORCE, forceForWall, buildBarrier, clipFaces, formatCoord, isCoordToken, parseCoord, planDirectionalWall, planDome, planWall, FACE_LABEL, allBreached, damageFace, pierces } from '../utilities/barrier-geometry.js';
|
|
12
|
+
import { DIRECTIONAL_WALL_FORCE, DOME_FORCE, forceForWall, buildBarrier, clipFaces, formatCoord, isCoordToken, parseCoord, planDirectionalWall, planDome, planWall, FACE_LABEL, BARRIER_FOG, allBreached, damageFace, pierces } from '../utilities/barrier-geometry.js';
|
|
13
13
|
import { NPC } from '../models/npc.js';
|
|
14
14
|
import { spellFor, catalogSpells } from '../utilities/catalog.js';
|
|
15
15
|
import { getAspect } from '../aspects.js';
|
|
@@ -91,6 +91,11 @@ const COMBAT_SPELL_FLAVOR = {
|
|
|
91
91
|
* inherited from BypassCommand -- now gated on actually having magic gear,
|
|
92
92
|
* instead of being a free-floating coin flip anyone could attempt.
|
|
93
93
|
*/
|
|
94
|
+
/**
|
|
95
|
+
* BLIND FIRE (p.178): "the same as the Total Darkness modifier", -6 -- the
|
|
96
|
+
* number p.197 gives for a target completely hidden from the attacker.
|
|
97
|
+
*/
|
|
98
|
+
const BLIND_FIRE = -6;
|
|
94
99
|
export class CastCommand extends BypassCommand {
|
|
95
100
|
static verb = 'cast';
|
|
96
101
|
/** Touches another actor: authorized per-act for AI-driven actors
|
|
@@ -99,6 +104,8 @@ export class CastCommand extends BypassCommand {
|
|
|
99
104
|
static description = 'Cast a spell you know, at a FORCE you choose: "cast manabolt 8 at <target>" (default Force = your Magic; drain scales with it, and OVERCASTING past your talent makes the drain PHYSICAL). "spells" lists what you know. Barriers take map coordinates: "cast barrier 3 3,4 3,6" (a wall), "cast barrier 3 5,5" (a dome), "cast barrier east 2". Area spells too: "cast fireball 4 at 12,7" (a point) or "cast fireball 4 at <someone>" -- Force m around it, friend and foe. A ward is "dispel <direction>".';
|
|
100
105
|
deviceKind = 'ward';
|
|
101
106
|
verbName = 'cast';
|
|
107
|
+
/** The bolt in flight is at someone the caster cannot see (castCombatSpell). */
|
|
108
|
+
unseenTarget = false;
|
|
102
109
|
noDeviceMessage = "There's nothing here to cast at.";
|
|
103
110
|
/** Backlash drain when a ward-bypass fails -- resisted with Willpower,
|
|
104
111
|
* like any drain. The nastiest drain in the game: forcing a hostile
|
|
@@ -520,9 +527,10 @@ export class CastCommand extends BypassCommand {
|
|
|
520
527
|
}
|
|
521
528
|
const armor = target.plane === 'meat' ? target.getArmorValue() : 0;
|
|
522
529
|
const modifiedArmor = Math.max(0, armor - force);
|
|
530
|
+
const blind = this.unseenTarget ? BLIND_FIRE : 0;
|
|
523
531
|
return {
|
|
524
|
-
label: label(force) + (weave ? ` -- through a Force ${weave} mana barrier` : ''),
|
|
525
|
-
pool: this.actor.getMagicPool() + this.actor.consumeEdgeBoost(),
|
|
532
|
+
label: label(force) + (weave ? ` -- through a Force ${weave} mana barrier` : '') + (blind ? ` -- thrown BLIND (${blind}, p.178)` : ''),
|
|
533
|
+
pool: Math.max(0, this.actor.getMagicPool() + this.actor.consumeEdgeBoost() + blind),
|
|
526
534
|
damageBase: force,
|
|
527
535
|
soakPool: Math.max(0, target.getSoakPool() - armor + modifiedArmor) + through,
|
|
528
536
|
modifiedArmor: target.plane === 'meat' ? modifiedArmor : undefined,
|
|
@@ -623,6 +631,16 @@ export class CastCommand extends BypassCommand {
|
|
|
623
631
|
if (target.isIncapacitated()) {
|
|
624
632
|
return `${target.name} is already down. The fight is over.`;
|
|
625
633
|
}
|
|
634
|
+
// LINE OF SIGHT (p.281, p.283). Seeing them is the one question
|
|
635
|
+
// visibleActorsIn answers for the map and `look`: on a plane you
|
|
636
|
+
// perceive and not hidden in cover. A DIRECT spell needs it -- it
|
|
637
|
+
// reaches through your own sight, with nothing to aim but that. An
|
|
638
|
+
// INDIRECT one only needs "a clear line of fire": you can throw it
|
|
639
|
+
// blind, at the Blind Fire penalty (p.178; -6, p.197).
|
|
640
|
+
this.unseenTarget = !visibleActorsIn(room, this.actor).includes(target);
|
|
641
|
+
if (this.unseenTarget && spellFor(spellKey)?.soak !== 'armor') {
|
|
642
|
+
return `You can't see ${target.name} -- a ${spellKey} reaches through your own eyes, and they have nothing to fix on (LOS, p.281).${hint(' (An indirect spell can be thrown blind, at -6.)')}`;
|
|
643
|
+
}
|
|
626
644
|
// One exchange at a time per combatant, same as attack.ts: the player
|
|
627
645
|
// is refused cleanly, an NPC caster waits out the exchange and re-runs
|
|
628
646
|
// (see CombatExchange.waitForLull and the rationale in attack.ts).
|
|
@@ -980,13 +998,17 @@ export class CastCommand extends BypassCommand {
|
|
|
980
998
|
this.scene.addWorldEvent(`${actor.name} ${flavor.act} ${aimedAt} in ${room.name}.`);
|
|
981
999
|
const lines = [];
|
|
982
1000
|
const meta = [];
|
|
983
|
-
|
|
1001
|
+
// Seen through a Physical Barrier, the point is in Light Fog: -1 on
|
|
1002
|
+
// the casting test (p.294, p.175). An indirect ball meets the barrier
|
|
1003
|
+
// itself, below, so only a direct one pays this.
|
|
1004
|
+
const fog = spell.soak !== 'armor' && this.scene.barrierOnLine?.(from, aim, 'physical') ? BARRIER_FOG : 0;
|
|
1005
|
+
const pool = Math.max(0, actor.getMagicPool() + actor.consumeEdgeBoost() + fog);
|
|
984
1006
|
const roll = rollPool(pool, force);
|
|
985
1007
|
const header = `${actor.name} casts ${spellKey} at Force ${force}${overcast ? ' (OVERCAST)' : ''}, ${this.castingLabel()}, at ${aimedAt}`;
|
|
986
1008
|
const hurt = [];
|
|
987
1009
|
if (spell.soak !== 'armor') {
|
|
988
1010
|
// ---- DIRECT: one test, opposed by each target it can see. ----
|
|
989
|
-
meta.push(`${header}: casting ${formatRoll(roll)}`);
|
|
1011
|
+
meta.push(`${header}: casting ${formatRoll(roll)}${fog ? ` (${fog} through a force barrier, Light Fog p.175)` : ''}`);
|
|
990
1012
|
lines.push(`${header} -- ${force} m of ${spell.type === 'mana' ? 'raw mana' : 'force'} around ${aimedAt}.`);
|
|
991
1013
|
const caught = everyone.filter(a => {
|
|
992
1014
|
const c = this.scene.worldCellOf?.(a);
|
|
@@ -994,7 +1016,12 @@ export class CastCommand extends BypassCommand {
|
|
|
994
1016
|
return false;
|
|
995
1017
|
// "...that you can see" (p.281): your own body counts; others must
|
|
996
1018
|
// be perceived and in plain sight of you.
|
|
997
|
-
|
|
1019
|
+
// Hidden in cover is not seen either (visibleActorsIn's rule).
|
|
1020
|
+
if (a === actor)
|
|
1021
|
+
return true;
|
|
1022
|
+
if (!actor.canPerceive(a) || !this.inSight(plane, from, c))
|
|
1023
|
+
return false;
|
|
1024
|
+
return a.currentLocation !== room || !concealedFrom(room, actor, a);
|
|
998
1025
|
});
|
|
999
1026
|
if (caught.length === 0)
|
|
1000
1027
|
lines.push(` The spell washes over empty ground.`);
|
|
@@ -3380,5 +3380,14 @@
|
|
|
3380
3380
|
// - A mana barrier between adds its Force to defence/soak; a physical
|
|
3381
3381
|
// barrier stops an indirect spell, which hits the face instead.
|
|
3382
3382
|
// - Fireball drains F - 1 (catalog v36).
|
|
3383
|
-
|
|
3383
|
+
// 1.199.0 (2026-10-05): SPELLS NEED SIGHT (p.281, p.283, p.178). A
|
|
3384
|
+
// direct bolt at someone the caster cannot see -- off their plane or
|
|
3385
|
+
// hidden in cover (visibleActorsIn) -- is refused before any drain; an
|
|
3386
|
+
// indirect one flies blind at -6 (Blind Fire). Direct area spells pass
|
|
3387
|
+
// over anyone hidden in cover inside them.
|
|
3388
|
+
// 1.200.0 (2026-10-05): A PHYSICAL BARRIER IS LIGHT FOG (p.294): a spell
|
|
3389
|
+
// or ranged attack whose line crosses a standing face is at -1, the
|
|
3390
|
+
// Light Fog row of the Environmental Modifiers table (p.175; confirmed
|
|
3391
|
+
// against the rules index and SR5 references online). Melee is exempt.
|
|
3392
|
+
export const ENGINE_VERSION = '1.200.0';
|
|
3384
3393
|
//# sourceMappingURL=engine-version.js.map
|
|
@@ -35,6 +35,11 @@ export function forceForWall(a, b) {
|
|
|
35
35
|
}
|
|
36
36
|
/** The directional wall is three cells across: Force 3. */
|
|
37
37
|
export const DIRECTIONAL_WALL_FORCE = 3;
|
|
38
|
+
/**
|
|
39
|
+
* Seeing through a Physical Barrier: "equivalent to Light Fog" (p.294),
|
|
40
|
+
* the Light Fog row of the Environmental Modifiers table -- -1 (p.175).
|
|
41
|
+
*/
|
|
42
|
+
export const BARRIER_FOG = -1;
|
|
38
43
|
/** A dome with no Force named rises one full 3 m storey: Force 3. */
|
|
39
44
|
export const DOME_FORCE = METERS_PER_LEVEL;
|
|
40
45
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { allBreached, damageFace, penetrationBoxes, pierces } from './barrier-geometry.js';
|
|
1
|
+
import { allBreached, damageFace, penetrationBoxes, pierces, BARRIER_FOG } from './barrier-geometry.js';
|
|
2
2
|
import { recoilCompensation, recoilPenalty } from './recoil.js';
|
|
3
3
|
import { Category } from '../types/shared/item-enum.js';
|
|
4
4
|
import { crashIC } from './ic-actors.js';
|
|
@@ -648,6 +648,20 @@ export class CombatExchange {
|
|
|
648
648
|
const spatial = isMelee ? { meters: 0 } : this.spatialContext(attacker, defender);
|
|
649
649
|
const weaponClass = strike.weaponItem?.isFirearm() ? classifyWeapon(strike.weaponItem) : undefined;
|
|
650
650
|
const range = weaponClass ? rangePenalty(spatial.meters, weaponClass) : 0;
|
|
651
|
+
// THROUGH A PHYSICAL BARRIER (p.294: it is "equivalent to Light Fog"
|
|
652
|
+
// and impedes spellcasting only by visibility): the Light Fog row of
|
|
653
|
+
// the Environmental Modifiers table, -1 (p.175), on a ranged attack or
|
|
654
|
+
// a spell whose line crosses a standing face. Melee never reaches
|
|
655
|
+
// through one. Only the worst condition counts (p.173), and this is
|
|
656
|
+
// the only environmental condition the engine models, so it stands
|
|
657
|
+
// alone.
|
|
658
|
+
let fog = 0;
|
|
659
|
+
if (!isMelee) {
|
|
660
|
+
const a = this.scene.worldCellOf?.(attacker);
|
|
661
|
+
const d = this.scene.worldCellOf?.(defender);
|
|
662
|
+
if (a && d && this.scene.barrierOnLine?.(a, d, 'physical'))
|
|
663
|
+
fog = BARRIER_FOG;
|
|
664
|
+
}
|
|
651
665
|
const rangeMod = range === 'out-of-range' ? -6 : range;
|
|
652
666
|
// p.190 grants the bonus to a defender who "uses a Take Cover
|
|
653
667
|
// action" -- so the dice follow the ACTION, not the address.
|
|
@@ -692,7 +706,7 @@ export class CombatExchange {
|
|
|
692
706
|
// charging, a running defender, surprise, Full Defense and the
|
|
693
707
|
// dodge/block/parry interrupts -- see turnModifiers below.
|
|
694
708
|
const turn = this.turnModifiers(attacker, defender, isMelee, strike, meta);
|
|
695
|
-
const attack = rollPool(strike.pool + directed + rangeMod + attackerFloor + targetDown + turn.attack + recoil, attackLimit);
|
|
709
|
+
const attack = rollPool(strike.pool + directed + rangeMod + attackerFloor + targetDown + turn.attack + recoil + fog, attackLimit);
|
|
696
710
|
// A DIRECT SPELL IS NOT DODGED (p.283): the target opposes the
|
|
697
711
|
// casting test with Body or Willpower, and only counterspelling (and
|
|
698
712
|
// a teammate's inspiration) helps -- not cover, posture, surprise or
|
|
@@ -708,7 +722,7 @@ export class CombatExchange {
|
|
|
708
722
|
const recoilNote = recoil < 0 && strike.weaponItem
|
|
709
723
|
? ` (${recoil} recoil -- round ${attacker.recoilRoundsFired} against RC ${recoilCompensation(attacker, strike.weaponItem)})`
|
|
710
724
|
: '';
|
|
711
|
-
meta.push(` Attack: ${formatRoll(attack)}${directed > 0 ? ` (+${directed} directed)` : ''}${rangeNote}${recoilNote}`);
|
|
725
|
+
meta.push(` Attack: ${formatRoll(attack)}${directed > 0 ? ` (+${directed} directed)` : ''}${rangeNote}${recoilNote}${fog ? ` (${fog} through a force barrier, Light Fog p.175)` : ''}`);
|
|
712
726
|
const coverNote = coverDice > 0 ? ` (+${coverDice} cover -- the ${spotOf(defender)})` : '';
|
|
713
727
|
meta.push(` Defense: ${formatRoll(defense)}${inspired > 0 ? ` (+${inspired} inspired)` : ''}${counterspell > 0 ? ` (+${counterspell} counterspell${counterspellSource ? ` -- ${counterspellSource}'s cover` : ''})` : ''}${coverNote}`);
|
|
714
728
|
const dryWarning = strike.weaponItem?.isFirearm() && strike.weaponItem.ammo === 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.388.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.",
|