feedback5 WE2: 重构体 only on the ground inside Mon3tr's range, 战术锚点 only on a 高台 outside 凯尔希·思衡托's (#26)
Mon3tr T1 "可以在攻击范围内的地面使用…重构体"; PRTS 战术锚点 特性 "仅可以部署在凯尔希·思衡托攻击 范围外的远程位". tools/build-data.mjs summonRecord: ownerRange also from an owner talent naming the token, ownerRangeOutside / rangedTilesOnly from the token text. Regenerated offline: 3 paths (重构体 ownerRange true; 战术锚点 ownerRangeOutside, rangedTilesOnly). Match: board.js placement class 'high'; PlayerState.summonExcluded in _legal, the re-orientation lift (+ toast), _roomForOutOfRange, the invariants and the bot; the client mirror (piecePosition HIGH, summonExcluded, reasons; English strings added). Golden: no scenario moved.
This commit is contained in:
1 parent
708719f382
commit
53ee0a1c6f
15 files changed
+236
-45
No files matched your search
@@ -577,6 +577,8 @@
|
||||
"请将装备拖拽至干员身上": "Drag Equipment onto an Operator",
|
||||
"近战单位只能部署在地面": "Melee units can only be deployed on ground tiles",
|
||||
"只能部署在召唤者攻击范围内": "Can only be deployed within the summoner's Attack Range",
|
||||
"只能部署在召唤者攻击范围外": "Can only be deployed outside the summoner's Attack Range",
|
||||
"只能部署在远程位": "Can only be deployed on a Ranged tile",
|
||||
"该位置已有单位": "This tile is already occupied",
|
||||
"召唤者尚未部署": "The summoner is not deployed yet",
|
||||
"装备只能配发给干员": "Equipment can only be equipped on Operators",
|
||||
@@ -842,6 +844,8 @@
|
||||
"地形变化:{names}无法停留在原位置,已撤回整备区": "Terrain change: {names} can no longer stay in place and returned to the Bench",
|
||||
"{names}只能部署在召唤者攻击范围内,已退回整备区": "{names} can only be deployed within the summoner's attack range and returned to the Bench",
|
||||
"{names}只能部署在召唤者攻击范围内,整备区已满,下回合返还": "{names} can only be deployed within the summoner's attack range; the Bench is full, so they come back next round",
|
||||
"{names}只能部署在召唤者攻击范围外,已退回整备区": "{names} can only be deployed outside the summoner's attack range and returned to the Bench",
|
||||
"{names}只能部署在召唤者攻击范围外,整备区已满,下回合返还": "{names} can only be deployed outside the summoner's attack range; the Bench is full, so they come back next round",
|
||||
"【{label}】层数达成,下回合开始时获得{gain}资金": "[{label}] stack milestone reached: +{gain} Funds at the start of the next round",
|
||||
"【{label}】层数达成,获得{gain}资金": "[{label}] stack milestone reached: +{gain} Funds",
|
||||
"【奇迹】下次刷新不消耗资金": "[Marvel] Your next refresh is free",
|
||||
|
||||
@@ -33,7 +33,7 @@ export { sortedPlayers } from './gameLogic/shared.js';
|
||||
export { ownFieldId, homeFieldId, cycleField, watchTarget, switcherLabel, fieldLabel, activeBubbles } from './gameLogic/watch.js';
|
||||
export { sortBonds, bondTier, nextThreshold, grantedBonds, pieceBondIds, morphPairings, HARMONY_BOND, harmonyMembers, bondMembers, memberHeadCount, bannedPerBond, disabledBondSets, briefingBondTip, modeOffBonds, bandOffBonds, bandOffLine } from './gameLogic/bonds.js';
|
||||
export { priceTone, mergeProgress, mergeTarget, handFull, completesMerge, offerHeader, shopBlockReason, readyFundsPrompt } from './gameLogic/shop.js';
|
||||
export { deploySets, stageOverrides, deployMap, effectiveStage, indexPieces, placementContext, piecePosition, tileAllows, summonRange, canPlace, itemAttaches, equipMerges, boardTargets, dropIntent, dropFailureReason } from './gameLogic/placement.js';
|
||||
export { deploySets, stageOverrides, deployMap, effectiveStage, indexPieces, placementContext, piecePosition, tileAllows, summonRange, summonExcluded, canPlace, itemAttaches, equipMerges, boardTargets, dropIntent, dropFailureReason } from './gameLogic/placement.js';
|
||||
export { normalizeDraft, normalizeSp } from './gameLogic/draft.js';
|
||||
export { groupEnemies, PEN, penZoneTiles, penPlacement, previewEnemyKey, factionTypes } from './gameLogic/enemies.js';
|
||||
export { snapHud, bossFrac, bossPctText, hasFlag, UF, attackInterval, fmtNum, rangeGridBox } from './gameLogic/format.js';
|
||||
|
||||
@@ -227,8 +227,13 @@ export function piecePosition(ctx, piece) {
|
||||
if (meleeOnHighGround(rec)) return 'ALL';
|
||||
return rec?.position === 'MELEE' ? 'MELEE' : 'RANGED';
|
||||
}
|
||||
// tokens: MELEE → ground only; RANGED / ALL → any deployable tile
|
||||
if (piece.kind === 'token') return ctx.getToken(piece.id)?.position === 'MELEE' ? 'MELEE' : 'RANGED';
|
||||
// tokens: MELEE → ground only; RANGED / ALL → any deployable tile; `rangedTilesOnly` (战术锚点 "仅可以部署在…远程位")
|
||||
// → 'HIGH', the ranged tiles only (server/match/board.js positionClass 'high')
|
||||
if (piece.kind === 'token') {
|
||||
const t = ctx.getToken(piece.id);
|
||||
if (t?.rangedTilesOnly === true) return 'HIGH';
|
||||
return t?.position === 'MELEE' ? 'MELEE' : 'RANGED';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -237,6 +242,7 @@ export function tileAllows(ctx, piece, row, col) {
|
||||
const pos = piecePosition(ctx, piece);
|
||||
if (!pos) return false;
|
||||
const k = tileKey(row, col);
|
||||
if (pos === 'HIGH') return ctx.deploy.ranged.has(k) && !ctx.deploy.melee.has(k);
|
||||
return pos === 'MELEE' ? ctx.deploy.melee.has(k) : ctx.deploy.ranged.has(k);
|
||||
}
|
||||
|
||||
@@ -251,6 +257,23 @@ export function tileAllows(ctx, piece, row, col) {
|
||||
*/
|
||||
export function summonRange(ctx, piece, owner = null) {
|
||||
if (!isObj(piece) || piece.kind !== 'token' || ctx?.getToken?.(piece.id)?.ownerRange !== true) return null;
|
||||
return ownerRangeOf(ctx, piece, owner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Board tiles ('r,c') an outside-bound summon may NOT stand on — its owner's attack range (tokens.json
|
||||
* `ownerRangeOutside`: 凯尔希·思衡托's 战术锚点 "仅可以部署在凯尔希·思衡托攻击范围外的远程位") — or null. Mirror of
|
||||
* server/match/player/placement.js summonExcluded.
|
||||
* @param {ReturnType<typeof placementContext>} ctx
|
||||
* @returns {Set<string> | null}
|
||||
*/
|
||||
export function summonExcluded(ctx, piece, owner = null) {
|
||||
if (!isObj(piece) || piece.kind !== 'token' || ctx?.getToken?.(piece.id)?.ownerRangeOutside !== true) return null;
|
||||
return ownerRangeOf(ctx, piece, owner);
|
||||
}
|
||||
|
||||
/** The attack-range tiles of a summon piece's owner on the board (summonRange / summonExcluded), or null. */
|
||||
function ownerRangeOf(ctx, piece, owner = null) {
|
||||
let at = owner;
|
||||
if (!at) for (const e of ctx.boardAt.values()) if (e.piece.uid === piece.ownerUid && e.piece.kind === 'chess') { at = e; break; }
|
||||
const rec = at && ctx.getChess(at.piece.id);
|
||||
@@ -260,11 +283,14 @@ export function summonRange(ctx, piece, owner = null) {
|
||||
return new Set(rangeTiles(grid || rec.rangeGrid, at.row, at.col, pieceDir(at.piece)).map(([r, c]) => tileKey(r, c)));
|
||||
}
|
||||
|
||||
/** tileAllows plus the owner-range rule of a range-bound summon (summonRange). */
|
||||
/** tileAllows plus the owner-range rules of a range-bound summon (summonRange, summonExcluded). */
|
||||
function unitAllowed(ctx, piece, row, col, owner = null) {
|
||||
if (!tileAllows(ctx, piece, row, col)) return false;
|
||||
const k = tileKey(row, col);
|
||||
const range = summonRange(ctx, piece, owner);
|
||||
return !range || range.has(tileKey(row, col));
|
||||
if (range && !range.has(k)) return false;
|
||||
const out = summonExcluded(ctx, piece, owner);
|
||||
return !out || !out.has(k);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -329,13 +355,16 @@ export function canPlace(ctx, uid, target) {
|
||||
if (src.area === 'board' && src.row === row && src.col === col) return { ok: true, action: 'orient' };
|
||||
if (!tileAllows(ctx, piece, row, col)) {
|
||||
const deployable = ctx.deploy.ranged.has(tileKey(row, col));
|
||||
return no('BAD_TILE', deployable && piecePosition(ctx, piece) === 'MELEE' ? '近战单位只能部署在地面' : '无法部署在该位置');
|
||||
const pos = piecePosition(ctx, piece);
|
||||
return no('BAD_TILE', deployable && pos === 'MELEE' ? '近战单位只能部署在地面' : deployable && pos === 'HIGH' ? '只能部署在远程位' : '无法部署在该位置');
|
||||
}
|
||||
// a range-bound summon (战术点): inside its owner's attack range — seen from the summon's old tile when it is
|
||||
// dropped onto its own owner (the two swap)
|
||||
// dropped onto its own owner (the two swap); an outside-bound one (战术锚点) outside it
|
||||
const ownerSwap = src.area === 'board' && occ && occ.piece.uid === piece.ownerUid ? { row: src.row, col: src.col, piece: occ.piece } : null;
|
||||
const range = summonRange(ctx, piece, ownerSwap);
|
||||
if (range && !range.has(tileKey(row, col))) return no('BAD_TILE', '只能部署在召唤者攻击范围内');
|
||||
const out = summonExcluded(ctx, piece, ownerSwap);
|
||||
if (out && out.has(tileKey(row, col))) return no('BAD_TILE', '只能部署在召唤者攻击范围外');
|
||||
if (src.area === 'board') {
|
||||
// board → board: move or swap (the occupant must be legal on the source tile — the mover's own summon excepted:
|
||||
// a moved operator's summons go back to the hand anyway)
|
||||
|
||||
Reference in new issue
Block a user