@maka/maka-cli 5.167.0 → 5.170.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/archetypes.js +238 -146
- package/bundle/typescript/src/commands/game/sideQuest/commands/attack.js +27 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/climb.js +21 -1
- package/bundle/typescript/src/commands/game/sideQuest/commands/go.js +7 -0
- package/bundle/typescript/src/commands/game/sideQuest/commands/hack.js +54 -11
- package/bundle/typescript/src/commands/game/sideQuest/commands/move.js +381 -255
- package/bundle/typescript/src/commands/game/sideQuest/game.js +10 -0
- package/bundle/typescript/src/commands/game/sideQuest/headless.js +8 -4
- package/bundle/typescript/src/commands/game/sideQuest/models/player.js +7 -1
- package/bundle/typescript/src/commands/game/sideQuest/models/room.js +34 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/climb-state.js +51 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/matrix-intrusion.js +16 -0
- package/bundle/typescript/src/commands/game/sideQuest/utilities/npc-combat-brain.js +10 -6
- package/bundle/typescript/src/commands/game/sideQuest/utilities/pregen-worksheet.js +41 -10
- package/bundle/typescript/src/commands/game/sideQuest/utilities/spots.js +31 -0
- package/package.json +1 -1
|
@@ -2394,6 +2394,16 @@ ${client.name} won't be remembering anything. Dead Johnsons pay nothing -- and t
|
|
|
2394
2394
|
// never report; the anonymous/bench gate is evaluated at fire time
|
|
2395
2395
|
// because _anonymous is decided after construction.
|
|
2396
2396
|
this.player.onConditionChanged = () => {
|
|
2397
|
+
// THE TERMINAL'S ROOM PANE RIDES THE SAME SIGNAL (q6tBaFn5EDmGMoJFh):
|
|
2398
|
+
// the room now reports every occupant's move to the humans in it
|
|
2399
|
+
// (Room.noteOccupancyChanged), and this is the one hook a human's
|
|
2400
|
+
// report fires -- so a body stepping across the room while the
|
|
2401
|
+
// player watches repaints here at once, not on the next keystroke
|
|
2402
|
+
// or the 2-second map heartbeat. Same call a typed move makes;
|
|
2403
|
+
// updateExits is a no-op without a screen, and combat repaints
|
|
2404
|
+
// itself blow by blow (see mapHeartbeatTick).
|
|
2405
|
+
if (this._mapBox && !this.player.inExchange)
|
|
2406
|
+
this.updateExits(this.player.currentLocation);
|
|
2397
2407
|
if (this._anonymous || this._bench)
|
|
2398
2408
|
return;
|
|
2399
2409
|
// Checked HERE too, before the import -- a dynamic import after a
|
|
@@ -391,10 +391,14 @@ export async function createHeadlessSession(opts) {
|
|
|
391
391
|
// THE WEB MAP FOLLOWS THE ROOM, NOT JUST YOUR OWN BODY
|
|
392
392
|
// (oHhjxjqKuptDGMHLD: a looted body stayed on the map "until I
|
|
393
393
|
// move"; 9aEMA4APbB6rYtWnT: a killed NPC's dot never changed).
|
|
394
|
-
// The condition beat is what the browser draws actors from
|
|
395
|
-
//
|
|
396
|
-
//
|
|
397
|
-
//
|
|
394
|
+
// The condition beat is what the browser draws actors from. A
|
|
395
|
+
// kill or a loot mutates the room, not the killer, so this
|
|
396
|
+
// nudge -- one per command, coalesced -- still covers those.
|
|
397
|
+
// MOVEMENT NO LONGER WAITS FOR IT (q6tBaFn5EDmGMoJFh): the room
|
|
398
|
+
// publishes every occupant's position change to the humans in
|
|
399
|
+
// it the moment it happens (Room.noteOccupancyChanged), so an
|
|
400
|
+
// NPC stepping on its own reflect chain between commands
|
|
401
|
+
// reaches the browser without anyone typing.
|
|
398
402
|
game.player.noteConditionChanged();
|
|
399
403
|
return res;
|
|
400
404
|
})),
|
|
@@ -2258,7 +2258,10 @@ export class Player extends AbstractPlayer {
|
|
|
2258
2258
|
// gets no cover) -- the compat default for everything un-migrated.
|
|
2259
2259
|
// Accessor (live-sheet, 2026-09-01, user: "let's get that streaming
|
|
2260
2260
|
// in too"): in-room movement walks the sheet's dot map, so the write
|
|
2261
|
-
// fires the report hook on CHANGE. Source-compatible;
|
|
2261
|
+
// fires the report hook on CHANGE. Source-compatible; an NPC's own
|
|
2262
|
+
// hook is a no-op -- which is why the ROOM is told as well
|
|
2263
|
+
// (Room.noteOccupancyChanged, q6tBaFn5EDmGMoJFh): the humans standing
|
|
2264
|
+
// in it are the ones whose maps draw this body, and they report.
|
|
2262
2265
|
_atSpot;
|
|
2263
2266
|
get atSpot() { return this._atSpot; }
|
|
2264
2267
|
set atSpot(v) {
|
|
@@ -2266,6 +2269,7 @@ export class Player extends AbstractPlayer {
|
|
|
2266
2269
|
return;
|
|
2267
2270
|
this._atSpot = v;
|
|
2268
2271
|
this.noteConditionChanged();
|
|
2272
|
+
this._currentLocation?.noteOccupancyChanged(this);
|
|
2269
2273
|
}
|
|
2270
2274
|
/**
|
|
2271
2275
|
* WHICH PAPER YOU CARRY (player request 2026-08-25, twice: "I should
|
|
@@ -2310,6 +2314,8 @@ export class Player extends AbstractPlayer {
|
|
|
2310
2314
|
return;
|
|
2311
2315
|
this._atCell = v;
|
|
2312
2316
|
this.noteConditionChanged();
|
|
2317
|
+
// The witnesses' maps move too -- see the atSpot accessor above.
|
|
2318
|
+
this._currentLocation?.noteOccupancyChanged(this);
|
|
2313
2319
|
}
|
|
2314
2320
|
/** The spot this actor was SEEDED at -- i.e. the fixture they work
|
|
2315
2321
|
* (a bartender's bar, a shopkeeper's counter). Seating puts them on
|
|
@@ -619,6 +619,7 @@ Keep it concise and dramatic.`;
|
|
|
619
619
|
this.logger.write(`${other.name} will observe ${actor.name}`);
|
|
620
620
|
}
|
|
621
621
|
}
|
|
622
|
+
this.noteOccupancyChanged(actor);
|
|
622
623
|
}
|
|
623
624
|
}
|
|
624
625
|
removeActor(actor) {
|
|
@@ -633,6 +634,39 @@ Keep it concise and dramatic.`;
|
|
|
633
634
|
actor.off('action', other._onSeenAction);
|
|
634
635
|
}
|
|
635
636
|
}
|
|
637
|
+
this.noteOccupancyChanged(actor);
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* THE ROOM PUBLISHES ITS OWN OCCUPANCY (q6tBaFn5EDmGMoJFh, 2026-09-11:
|
|
641
|
+
* "I still can't see the NPCs moving unless I move").
|
|
642
|
+
*
|
|
643
|
+
* The browser draws every dot on the room map from a human's condition
|
|
644
|
+
* report (condition-report.ts buildConditionReport reads THIS room's
|
|
645
|
+
* actors at fire time), and that report is fired by
|
|
646
|
+
* Player.noteConditionChanged -- which every position setter calls on
|
|
647
|
+
* the actor that MOVED. An NPC has no reporter of its own (game.ts sets
|
|
648
|
+
* the hook on the primary human only, on purpose), so an NPC stepping
|
|
649
|
+
* across the room on its own reflect chain, a companion answering the
|
|
650
|
+
* pursuit heartbeat, or a hostile taking its Action Phase changed the
|
|
651
|
+
* room's truth and told nobody. The only thing that ever refreshed the
|
|
652
|
+
* dots was the once-per-command nudge in headless.ts -- which is
|
|
653
|
+
* exactly "they move when I move".
|
|
654
|
+
*
|
|
655
|
+
* So the room tells everyone standing in it. NPCs and crew shells
|
|
656
|
+
* no-op (no hook); each human present fires its own report, and the
|
|
657
|
+
* report layer coalesces a burst (pendingByPlayer, 250 ms) so a mob of
|
|
658
|
+
* four stepping at once is one beat per watching human. DDP carries it
|
|
659
|
+
* from there -- no timer, no polling, no beat of its own (user ruling:
|
|
660
|
+
* "this is a pub/sub mechanic that doesn't need beats").
|
|
661
|
+
*
|
|
662
|
+
* `cause` is left out: the mover already reported itself through its
|
|
663
|
+
* own setter, and a second report for the same change is just noise.
|
|
664
|
+
*/
|
|
665
|
+
noteOccupancyChanged(cause) {
|
|
666
|
+
for (const other of this.actors) {
|
|
667
|
+
if (other !== cause)
|
|
668
|
+
other.noteConditionChanged();
|
|
669
|
+
}
|
|
636
670
|
}
|
|
637
671
|
getActors() {
|
|
638
672
|
return Array.from(this.actors);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { hint } from './hints.js';
|
|
2
|
+
import { spotOf, spotClimbSteps } from './spots.js';
|
|
3
|
+
import { METERS_PER_LEVEL } from './room-grid.js';
|
|
4
|
+
export function onTheWall(actor) {
|
|
5
|
+
const progress = actor.climbProgress;
|
|
6
|
+
if (!progress)
|
|
7
|
+
return undefined;
|
|
8
|
+
const room = actor.currentLocation;
|
|
9
|
+
const grid = room.ensureGrid();
|
|
10
|
+
const from = spotOf(actor);
|
|
11
|
+
if (!grid || from === undefined)
|
|
12
|
+
return undefined;
|
|
13
|
+
const fromZ = grid.spotCells.get(from)?.z;
|
|
14
|
+
const targetZ = grid.spotCells.get(progress.target)?.z;
|
|
15
|
+
// A bank toward a spot this grid cannot place, or one on the actor's
|
|
16
|
+
// own level, is stale -- it is not a wall anyone is on.
|
|
17
|
+
if (fromZ === undefined || targetZ === undefined || targetZ === fromZ)
|
|
18
|
+
return undefined;
|
|
19
|
+
const steps = spotClimbSteps(room, from, progress.target) ?? Math.abs(targetZ - fromZ);
|
|
20
|
+
const wallHeight = Math.max(steps, 1) * METERS_PER_LEVEL;
|
|
21
|
+
const travelled = Math.min(progress.meters, wallHeight);
|
|
22
|
+
const up = targetZ > fromZ;
|
|
23
|
+
return {
|
|
24
|
+
target: progress.target, from, travelled, wallHeight, up,
|
|
25
|
+
// Climbing UP, what you have travelled is your height. Climbing
|
|
26
|
+
// DOWN, travel is measured from the top, so height is what is left.
|
|
27
|
+
height: up ? travelled : wallHeight - travelled,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** "about 1m" / "about a body length" -- the same rounding the climb
|
|
31
|
+
* ticker and the elevation bar use, so no two readouts disagree. */
|
|
32
|
+
export function heightPhrase(meters) {
|
|
33
|
+
return meters < 1 ? 'about a body length' : `about ${Math.round(meters)}m`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The refusal every horizontal verb gives a runner who is part-way up
|
|
37
|
+
* a wall. Not a rule -- canon says nothing about acting mid-climb, and
|
|
38
|
+
* this invents nothing -- but a fact about where the body is: you do
|
|
39
|
+
* not cross a floor you are not standing on. Names both ways off the
|
|
40
|
+
* wall, because "you can't" with no way out is the false-impression
|
|
41
|
+
* shape this whole family of reports has been about.
|
|
42
|
+
*/
|
|
43
|
+
export function midClimbRefusal(actor) {
|
|
44
|
+
const wall = onTheWall(actor);
|
|
45
|
+
if (!wall)
|
|
46
|
+
return undefined;
|
|
47
|
+
return wall.up
|
|
48
|
+
? `You're partway up the wall toward the ${wall.target}, ${heightPhrase(wall.height)} off the ground -- not going anywhere sideways from here.${hint(` ("climb" keeps going; "descend" gets you back down to the ${wall.from} first.)`)}`
|
|
49
|
+
: `You're partway down the wall from the ${wall.from}, ${heightPhrase(wall.height)} above the ${wall.target} -- not going anywhere sideways from here.${hint(` ("descend" keeps going; "climb" gets you back up onto the ${wall.from} first.)`)}`;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=climb-state.js.map
|
|
@@ -92,6 +92,22 @@ export function parseMarkDeclaration(words) {
|
|
|
92
92
|
export function deviceDefensePool(device) {
|
|
93
93
|
return { label: 'Device Rating + Firewall', pool: Math.max(0, device.rating * 2) };
|
|
94
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* A SLAVE ATTACKED THROUGH ITS MASTER (SR5 p.233, PANs and WANs):
|
|
97
|
+
* "Whenever a slaved device is called on to make a defense test, it
|
|
98
|
+
* uses either its own or its master's rating for each rating in the
|
|
99
|
+
* test ... whichever is higher in each instance." Per rating, not
|
|
100
|
+
* wholesale: the device's own Rating against the host's Rating, its
|
|
101
|
+
* own Firewall (which this engine reads as its rating, see above)
|
|
102
|
+
* against the host's Firewall. Only from the grid -- inside the host
|
|
103
|
+
* you are directly connected and the slave stands alone (p.233; Data
|
|
104
|
+
* Trails p.87-88), which is deviceDefensePool.
|
|
105
|
+
*/
|
|
106
|
+
export function wanDefensePool(device, host) {
|
|
107
|
+
const rating = Math.max(device.rating, host.rating);
|
|
108
|
+
const firewall = Math.max(device.rating, host.attributes().firewall);
|
|
109
|
+
return { label: "Device Rating + Firewall (the master's where higher, p.233)", pool: Math.max(0, rating + firewall) };
|
|
110
|
+
}
|
|
95
111
|
/**
|
|
96
112
|
* A HOST'S OWN DEFENCE IS AN ENGINE RULING, NOT A CITATION, and this
|
|
97
113
|
* comment is the label on it. Asked directly (2026-09-03), the rulebooks
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Category } from '../types/shared/item-enum.js';
|
|
2
|
-
import {
|
|
2
|
+
import { inMeleeReach, isInCover, coverAvailableFor, actorDistanceMeters } from './spots.js';
|
|
3
3
|
import { Logger } from './logger.js';
|
|
4
4
|
/**
|
|
5
5
|
* AN NPC'S ACTION PHASE, PLAYED BY RULE (SR5 p.163-167).
|
|
@@ -82,11 +82,15 @@ export async function runNpcActionPhase(enc, npc) {
|
|
|
82
82
|
}
|
|
83
83
|
const melee = !firearm || dry || weapon.jammed;
|
|
84
84
|
if (melee) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
// ARM'S REACH, NOT SPOT NAME (HBssKNQahWxGR95wB: "Ellis is able to
|
|
86
|
+
// attack me, however his position is several squares away"). The
|
|
87
|
+
// open floor is one spot across most of a room, so canReach said
|
|
88
|
+
// yes from five squares off and the swing landed. inMeleeReach asks
|
|
89
|
+
// the cells; "move to <person>" is the walk that closes them.
|
|
90
|
+
if (!inMeleeReach(npc, target)) {
|
|
91
|
+
const r = await npc.actInCombat(`move to ${target.name}`);
|
|
92
|
+
logger.write(`Brain: ${npc.name} move to ${target.name} -> ${r}`);
|
|
93
|
+
if (!inMeleeReach(npc, target)) {
|
|
90
94
|
// Couldn't close this turn: get behind something and wait.
|
|
91
95
|
tell(`${npc.name} closes on ${target.name} but can't cover the ground this turn.`);
|
|
92
96
|
if (!isInCover(npc) && coverAvailableFor(npc) && (enc.budgetOf(npc)?.simple ?? 0) > 0) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { archetypeGearDefs } from '../archetypes.js';
|
|
2
2
|
import { siteRequest } from './cloud-saves.js';
|
|
3
3
|
import { getAspect, ASPECTED_PRIORITY, MAGICAL_GROUP_SKILLS } from '../aspects.js';
|
|
4
|
+
import { skillsInGroup } from '../skill-groups.js';
|
|
4
5
|
/**
|
|
5
6
|
* A PREGEN GOES TO THE WEB TO BE BOUND (user, 2026-09-03): "if they choose
|
|
6
7
|
* to bind the pre-gen character, I'd like the chargen sheet to open the
|
|
@@ -11,16 +12,25 @@ import { getAspect, ASPECTED_PRIORITY, MAGICAL_GROUP_SKILLS } from '../aspects.j
|
|
|
11
12
|
* So binding a pregen no longer writes a bound save. The CLI creates a
|
|
12
13
|
* chargen WORKSHEET through the site's own API (the same collection the
|
|
13
14
|
* Forge wizard edits), fills it from the archetype -- attributes as spend
|
|
14
|
-
* over the human minimums, the skills
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* The runner is born when the
|
|
18
|
-
* web-built one is, and launch
|
|
15
|
+
* over the human minimums, the skills and skill groups, the qualities
|
|
16
|
+
* with their ratings and picks, the chrome at standard grade, the catalog
|
|
17
|
+
* slugs as gear lines, the spellbook and powers, the name and gender --
|
|
18
|
+
* and opens the wizard on it at Review. The runner is born when the
|
|
19
|
+
* player finalizes there, exactly as a web-built one is, and launch
|
|
20
|
+
* sources her from the cloud afterwards.
|
|
19
21
|
*
|
|
20
|
-
* A
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
22
|
+
* A PREGEN ARRIVES COMPLETE (user, 2026-09-11: "when a user chooses a
|
|
23
|
+
* premade archetype, they shouldn't need to figure out what's wrong
|
|
24
|
+
* with it -- it should just work"). Every archetype is a priority build
|
|
25
|
+
* (IArchetype.priorities) with every pool spent to the column and
|
|
26
|
+
* nothing the validator refuses; archetype-priorities.test.ts holds the
|
|
27
|
+
* CLI half of that contract and the site's chargen-archetypes suite runs
|
|
28
|
+
* the real validator over every one. Review is where the player may
|
|
29
|
+
* ADJUST, not where they finish someone else's homework. Until this
|
|
30
|
+
* date the patch dropped three fields the wizard reads -- `picks`,
|
|
31
|
+
* `qualityRatings`, `skillGroups` -- so the Decker arrived with an
|
|
32
|
+
* unanswered Codeslinger, every rated quality priced at rating 1, and
|
|
33
|
+
* no archetype could spend a single group point.
|
|
24
34
|
*
|
|
25
35
|
* The URL shape and the `open` query are CO-OWNED with the web side
|
|
26
36
|
* (cli-login.tsx allowedNext, character-generator.tsx open(id)) -- do not
|
|
@@ -133,6 +143,24 @@ export function pregenWorksheetPatch(archetype, name, gender) {
|
|
|
133
143
|
for (const member of MAGICAL_GROUP_SKILLS[aspect.group] ?? [])
|
|
134
144
|
delete skills[member];
|
|
135
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* BOUGHT GROUPS, THE SAME WAY (SR5 p.88, the skills column's second
|
|
148
|
+
* pool): a group the archetype declares is sent as a group, and its
|
|
149
|
+
* members come OUT of `skills` -- a group cannot be broken by rating a
|
|
150
|
+
* member individually on top, and the validator refuses exactly that
|
|
151
|
+
* ("covered by the X group"). The archetype's `combat.skills` keeps the
|
|
152
|
+
* expanded per-skill ratings for the local engine; the group is what
|
|
153
|
+
* the worksheet says about how they were paid for. Membership is read
|
|
154
|
+
* from skill-groups.ts itself, never copied here.
|
|
155
|
+
*/
|
|
156
|
+
const skillGroups = {};
|
|
157
|
+
for (const [group, rating] of Object.entries(archetype.skillGroups ?? {})) {
|
|
158
|
+
if (!(rating > 0))
|
|
159
|
+
continue;
|
|
160
|
+
skillGroups[group] = rating;
|
|
161
|
+
for (const member of skillsInGroup(group))
|
|
162
|
+
delete skills[member];
|
|
163
|
+
}
|
|
136
164
|
const gearSeen = new Set();
|
|
137
165
|
const gear = [];
|
|
138
166
|
for (const slug of archetype.gearSlugs ?? []) {
|
|
@@ -156,7 +184,10 @@ export function pregenWorksheetPatch(archetype, name, gender) {
|
|
|
156
184
|
attributeSpend,
|
|
157
185
|
specialSpend: { edge: spend(c.edge, HUMAN_EDGE_MIN), magic: magicPath === 'technomancer' || magicPath === 'mundane' ? 0 : Math.max(0, (c.magic ?? 0) - magicOffer), resonance: magicPath === 'technomancer' ? Math.max(0, (c.resonance ?? 0) - magicOffer) : 0 },
|
|
158
186
|
skills,
|
|
187
|
+
...(Object.keys(skillGroups).length ? { skillGroups } : {}),
|
|
159
188
|
qualities: [...(archetype.qualities ?? [])],
|
|
189
|
+
...(archetype.qualityRatings && Object.keys(archetype.qualityRatings).length ? { qualityRatings: { ...archetype.qualityRatings } } : {}),
|
|
190
|
+
...(archetype.qualityPicks && Object.keys(archetype.qualityPicks).length ? { picks: { ...archetype.qualityPicks } } : {}),
|
|
160
191
|
augmentations: (archetype.augs ?? []).map(key => ({ key, grade: 'standard' })),
|
|
161
192
|
gear,
|
|
162
193
|
lifestyle: 'Street',
|
|
@@ -203,7 +234,7 @@ export function pregenHandoffLines(url, name) {
|
|
|
203
234
|
return {
|
|
204
235
|
notice: `${name}'s sheet is waiting in the character generator: ${url}`,
|
|
205
236
|
lines: [
|
|
206
|
-
`
|
|
237
|
+
`It arrives complete and legal -- look it over there, adjust anything you like, then finalize.`,
|
|
207
238
|
`When she is born, run "maka play" and she will be in your runners.`,
|
|
208
239
|
],
|
|
209
240
|
};
|
|
@@ -803,6 +803,37 @@ export function actorCell(room, actor, seats) {
|
|
|
803
803
|
* words for the test are "next to me in the room map".
|
|
804
804
|
*/
|
|
805
805
|
export const ADJACENT_METERS = METERS_PER_CELL * 1.5;
|
|
806
|
+
/**
|
|
807
|
+
* WITHIN ARM'S REACH -- the melee question, asked of CELLS and not of
|
|
808
|
+
* spots (HBssKNQahWxGR95wB: "Ellis is able to attack me, however his
|
|
809
|
+
* position is several squares away from me, not in melee range").
|
|
810
|
+
*
|
|
811
|
+
* canReach answers by SPOT NAME, and that was right until bodies got
|
|
812
|
+
* their own cells (seatingIn, 2026-08-25): the open floor is one spot
|
|
813
|
+
* that covers most of a room, so two people "on the open floor" can
|
|
814
|
+
* be five squares apart and canReach still says yes. Every melee gate
|
|
815
|
+
* -- the player's attack, the NPC brain's "can I swing this phase" --
|
|
816
|
+
* read that yes and let the blow land across the room. The map drew
|
|
817
|
+
* the truth and the dice ignored it.
|
|
818
|
+
*
|
|
819
|
+
* Reach here is the engine's own atom: one cell, orthogonal or diagonal
|
|
820
|
+
* (ADJACENT_METERS -- see the note above on why that is not dressed as
|
|
821
|
+
* a canon number). Falls back to canReach wherever there is no cell to
|
|
822
|
+
* measure from, so a spotless or ungridded room keeps the compat rule.
|
|
823
|
+
*/
|
|
824
|
+
export function inMeleeReach(actor, target) {
|
|
825
|
+
if (!canReach(actor, spotOf(target)))
|
|
826
|
+
return false;
|
|
827
|
+
const room = actor.currentLocation;
|
|
828
|
+
if (!room.ensureGrid() || target.currentLocation !== room)
|
|
829
|
+
return true;
|
|
830
|
+
const seats = seatingIn(room);
|
|
831
|
+
const a = actorCell(room, actor, seats);
|
|
832
|
+
const t = actorCell(room, target, seats);
|
|
833
|
+
if (!a || !t)
|
|
834
|
+
return true;
|
|
835
|
+
return distanceMeters(a, t) <= ADJACENT_METERS;
|
|
836
|
+
}
|
|
806
837
|
/** Everything you have to walk AROUND rather than through: other
|
|
807
838
|
* people's bodies and the furniture itself. findPath treats these as a
|
|
808
839
|
* soft block (see its pass order) so they steer a route without ever
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maka/maka-cli",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.170.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.",
|