feedback5: merge WE2 — fidelity (data / match / UI): summon hand counts from the tokens' hidden talents (麦哲伦 / 令 3, 白铁 2), 望's 棋子 on any tile, the 一会儿见! marker no hand card, TRY_SEARCH_ALLY_SKILL → SKILL_RANGE + allies, 斩业星熊's normal attack phys, IS / RA-only modules refused (SO-A / SO-B / RA-A), 黑键 S2 at full SP with a free tile, 重构体 / 战术锚点 placement, 塑心's cap per trait, random grants draw 自选 stock, diyBanned shown, AI 托管 buys 自选, 谬因 S2 / 薇薇安娜 S3 area triggers, #148 regression test, tier*.js / diyRecord shims removed; golden: coop2-NORMAL-70-diy, diy-045 / 078 / 114 / 115 / 116 moved (WE2's own), the W4B moves kept
This commit is contained in:
commit
723ee72d38
79 files changed
+1389
-405
No files matched your search
@@ -19,7 +19,8 @@
|
||||
// Targets: bond_by_id / bond_self (own active bonds) / bond_actived_maxstack;
|
||||
// amounts: by_count / by_charcount_samerow / by_charlevel; conditions character_same_row /
|
||||
// character_same_col (≥ check_count incl. self). Gains go through support.gainLayers with
|
||||
// reason 'garrison', source = the trait's owner, cap = max_add_count_per_battle per (instance, bond).
|
||||
// reason 'garrison', source = the trait's owner, cap = max_add_count_per_battle per (instance, bond)
|
||||
// — per instance for bond_actived_maxstack (塑心: the highest bond may change, the cap does not).
|
||||
// act1autochess_gar_event_addition_cnt (魔王) layerGain: a 'garrison' gain whose source stands on the tile in front
|
||||
// of 魔王 (range_id 1-1) — or was knocked out there this instant (幽灵鲨 "被击倒时"; engine ctx.tile)
|
||||
// — gets +extra_cnt per bond (the extra does not count toward the source's per-battle cap).
|
||||
@@ -191,10 +192,20 @@ export function fireGain(battle, it) {
|
||||
if (!conditionMet(battle, it)) return 0;
|
||||
const bonds = targetBonds(battle, it);
|
||||
if (!bonds.length) return 0;
|
||||
const n = amountOf(battle, it);
|
||||
let n = amountOf(battle, it);
|
||||
// bond_actived_maxstack (塑心 garrison_90 "当前已激活且层数最多的盟约层数+1(每场作战至多10层)"): the cap is the instance's
|
||||
// own over the battle, whichever bond is the highest at each gain — per (instance, bond) a change of the highest bond
|
||||
// granted a fresh 10 (PR #178 review). The base amount counts toward it (魔王's extra does not, as in gainLayers).
|
||||
const whole = it.bbStr.bond_type === 'bond_actived_maxstack' && Number.isFinite(it.cap);
|
||||
if (whole) {
|
||||
n = Math.min(Math.floor(n), Math.max(0, Math.floor(it.cap) - (it.wholeUsed ?? 0)));
|
||||
if (!(n > 0)) return 0;
|
||||
}
|
||||
const added = S.gainLayers(battle, {
|
||||
playerId: it.unit.ownerId, bonds, n, requireActive: true, source: it.unit, reason: 'garrison', cap: it.cap, capKey: it.capKey,
|
||||
playerId: it.unit.ownerId, bonds, n, requireActive: true, source: it.unit, reason: 'garrison',
|
||||
cap: whole ? Infinity : it.cap, capKey: whole ? null : it.capKey,
|
||||
});
|
||||
if (whole && added > 0) it.wholeUsed = (it.wholeUsed ?? 0) + n;
|
||||
if (added > 0) S.fxOn(battle, 'garrison', it.unit, `gar:${it.gid}`, it.key, { n: added });
|
||||
return added;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ operators, the self-select 6★ operators, contributions from GitHub issue #136
|
||||
| `shared/tier1.js` | the general kit helpers (blackboard readers, unit predicates, hit hooks, area queries, buffs, zones, free tiles, skill records) and the notes of the tier-1 kits |
|
||||
| `shared/tier2.js` … `tier6.js` | helpers two or more kits of that tier use, and that tier's notes (conventions, simplifications, fx kinds) |
|
||||
| `shared/summoner.js` | the 召唤师 summon deck the 自选 kits of 麦哲伦 / 令 / 电弧 share (`summonDeck`: the holding, a placed piece's return, recalls, the summons leaving with their owner; `holdBuff`, `tokenStat`) |
|
||||
| `tier1.js` … `tier6.js` | re-export shims for the old import paths; removed after the 0.2.0 refactor |
|
||||
|
||||
`content/index.js` takes `KITS` from `index.js`: a unit's kit is `KITS[def.baseId]` (also the exact or the suffix-less
|
||||
id), else the generic kit built from the skill blackboard (`content/generic.js`, docs/SIM.md §7.4). A 补位 stand-in's
|
||||
@@ -184,8 +183,10 @@ pick chooses any of the three, so write every skill under `skills: { [skillId]:
|
||||
both tiers, both forms and both module stages. A 自选 piece has no 特质 and its bonds come from its factions: neither is
|
||||
the kit's business. Summons: `battle.tokenDef(tokenId, unit)` / `battle.spawnToken(unit, …)` resolve the variant of the
|
||||
pick. A **placeable** summon (data/backups.json `tokens[id].placeable`, made by the pick's skill or a talent — the
|
||||
variant's `sources`) is a hand piece like any operator's: when the 自选 piece is deployed in prep its player gets one
|
||||
stack of the variant's `deployLimit` — the picked module's own when its variant has one (望's TRP-X: 7 棋子) —
|
||||
variant's `sources` — and shown by its owner: a skill's own object such as 予愿安洁莉娜's “一会儿见!” is not) is a hand piece like any operator's: when the 自选 piece is deployed in prep its player gets one
|
||||
stack of the variant's `deployLimit` — the picked module's own when its variant has one (望's TRP-X: 7 棋子); it holds
|
||||
the token's own talent additions (max_deploy_count / max_deck_stack_cnt, tools/build-data.mjs `tokenTalentDeckBonus`:
|
||||
麦哲伦 / 令 3, SUM-Y stage 2+ 4, 白铁 2, 夜莺 3), so a deck reads `deployLimit` / `deckStack` as they are —
|
||||
(server/match/player/diy.js `placeableTokens`, PlayerState `grantTokensFor`), places
|
||||
it on a legal tile (the token's position / `ownerRange`), loses it with its owner (sold, merged, moved back), and the
|
||||
match hands every placed piece to the battle as a PlayerBattleInput token unit `{ kind: 'token', tokenId, ownerUid }` —
|
||||
|
||||
@@ -53,9 +53,9 @@
|
||||
// abilities). Off her deployment tile, “一会儿见!” marks it (备注 "每次移动后若不位于初始位置且初始位置不存在标志物…部署一个").
|
||||
// When the skill ends she goes back there by a 【移动】 (the marker leaves) unless she is knocked out.
|
||||
// - “一会儿见!” (its kit below): 无敌, 孤立, 不可选中 by enemies, blocks nothing, no attack (PRTS 备注; token_aglna2_passive).
|
||||
// It is the skill's placeholder (skchr_aglna2_3 `_placeholderTokenKey`), not a player's summon: the data marks it
|
||||
// placeable (the prep hands a piece out with S3 — open question), so a placed piece is used as that marker and never
|
||||
// deploys by itself (`deferDeploy`; its tile stays reserved); without one the skill summons it.
|
||||
// It is the skill's placeholder (skchr_aglna2_3 `_placeholderTokenKey`), not a player's summon: no owner shows it, so the
|
||||
// data does not mark it placeable (tools/build-data.mjs summonRecord, 0.2.0 WE2 — until then the prep handed a piece out
|
||||
// with S3 and this kit used it as the marker) and the skill summons it each time.
|
||||
|
||||
import { num, talentBb, skillRec, batMod, up, installAura, toggleBuff } from '../shared/tier1.js';
|
||||
import { absoluteRangeKeys, canTargetEnemy, sortEnemyTargets } from '../../../targeting.js';
|
||||
@@ -174,13 +174,11 @@ export default {
|
||||
}
|
||||
function placeMarker(battle, unit) {
|
||||
if (!st || (st.marker && st.marker.alive)) return;
|
||||
const piece = battle.allyUnits.find((t) => t.kind === 'token' && t.defId === MARKER && t.ownerUnit === unit && t.uid != null && !t.alive && !t.removed);
|
||||
if (piece && battle.redeploy(piece, { free: true, tile: [st.r0, st.c0] })) { st.marker = piece; return; }
|
||||
st.marker = battle.spawnToken(unit, MARKER, st.r0, st.c0, { kit: markerKit() });
|
||||
}
|
||||
function dropMarker(battle) {
|
||||
const m = st && st.marker;
|
||||
if (m && m.alive) battle.retreat(m, { reason: 'expired', permanent: m.uid == null });
|
||||
if (m && m.alive) battle.retreat(m, { reason: 'expired', permanent: true });
|
||||
if (st) st.marker = null;
|
||||
}
|
||||
/** Her S3 range for where she stands: the 3-9 ∪ x-4 at home; away, the x-4 here + the anchored 3-9 (extra keys). */
|
||||
@@ -297,13 +295,6 @@ export default {
|
||||
if (ctx.target !== unit || !RESIST_STATUSES.has(ctx.status) || (ctx.source && ctx.source.side === 'ally')) return;
|
||||
if (unit.findBuff(CHANT_KEY) || unit.findBuff(MOVE_KEY)) ctx.cancel = true;
|
||||
}, { owner: unit });
|
||||
// a placed “一会儿见!” piece is S3's marker: it never deploys by itself
|
||||
for (const t of battle.allyUnits) {
|
||||
if (t.kind !== 'token' || t.defId !== MARKER || t.ownerUnit !== unit || t.alive || t.deployed) continue;
|
||||
if (t.kit) battle.offOwner(t);
|
||||
battle._setupUnit(t, markerKit());
|
||||
if (sid === S3) t.deferDeploy = true;
|
||||
}
|
||||
if (sid !== S3) return;
|
||||
const dr = num(b3.damage_resistance), slow = -num(b3.move_speed);
|
||||
const aspdB = num(b3['aglna2_s_3[blocked].attack_speed']), aspdU = num(b3['aglna2_s_3[unblocked].attack_speed']);
|
||||
|
||||
@@ -47,8 +47,10 @@
|
||||
// every `interval` (0.5) s, every selectable enemy on her path takes atk_scale × ATK arts (16 hits in the 8 s). The path
|
||||
// meanwhile has no length limit (`_infinite`) and turns at her 中继器 and at every allied
|
||||
// operator it reaches (validators: the piece, allies of profession mask 639 — every operator profession; a 孤立 one too),
|
||||
// to that unit's facing. The data resolves DEFAULT: the unbounded range is no data range (no rangeId), so build-data's
|
||||
// ACTIVE_RANGE rule cannot see it (reported, not hand-coded).
|
||||
// to that unit's facing. Trigger: the data's DEFAULT, plus — the owner's ACTIVE_RANGE rule (2026-10-05: a MANUAL skill
|
||||
// whose running range strictly contains her own casts with an enemy inside that range), which no data range can carry
|
||||
// for an unbounded path that bends — the beam's path as a content trigger range (skill.addTriggerRange: a targetable enemy
|
||||
// on the path she would fire along now, air units too as the beam hits them; 0.2.0 WE2, follow-up #16, O1's report).
|
||||
// - S3 混沌的本质 (MANUAL, data DEFAULT, 20 发弹药 — `ammo`, attack@trigger_time): for charge_time (3) s from the cast 强制缴械
|
||||
// (aphris_sk3[sheltering], abnormal flag disarmed) and aphris_sk3[token-withdraw]: every 中继器 of hers withdrawn and its
|
||||
// redeploy time cleared (ResetTokenRespawnTime — the placed piece is back on its tile at once; the two extra pieces the
|
||||
@@ -285,6 +287,8 @@ export default {
|
||||
battle.on('skillStart', (c) => { if (c.unit === unit) refreshPath(battle, unit); }, { owner: unit });
|
||||
battle.on('skillEnd', (c) => { if (c.unit === unit) refreshPath(battle, unit); }, { owner: unit });
|
||||
battle.on('tick', () => refreshPath(battle, unit), { owner: unit });
|
||||
// S2: the beam's path is its trigger range too (the owner's larger-range rule, see the header)
|
||||
if (unit.skill?.id === S2) unit.skill.addTriggerRange(() => (up(unit) ? [aphrisPath(battle, unit, true).tiles.map(([r, c]) => r * COLS + c)] : []));
|
||||
// S3's 强制缴械 from the cast on: the attack loop goes on to the attack the cast was made for (ai.js updateAlly
|
||||
// checks `disarm` before the cast only) — no target, no attack, no bullet spent
|
||||
battle.on('beforeAttack', (c) => { if (c.attacker === unit && unit.findBuff(CHARGE_KEY)) c.targets = []; }, { owner: unit });
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
// (PRTS "大幅度缩短(*0.2)": batMod's ratio reading), every attack attack@atk_scale × ATK; the energies released meanwhile
|
||||
// use that scale too (备注 "该技能的“每次攻击的攻击倍率”会实时应用在特性积攒的“攻击能量”抛射物上") × the energy scale.
|
||||
// `attack@cnt` (1) has no text [unused].
|
||||
// - S2 荒芜回响 (AUTO, instant, data DEFAULT — an AUTO skill whose effect acts on enemies keeps the data rule): spends every
|
||||
// - S2 荒芜回响 (AUTO, instant): cast at full SP as soon as one placeable tile of his attack range is free — no enemy needed
|
||||
// (PRTS 备注 "攻击范围内不存在可部署位时,技能不会被触发"; the skill prefab's `_trigger._minTileNum` 1 — O20's report; 0.2.0 WE2:
|
||||
// until then the data's DEFAULT waited for an enemy in his range; `trigger: 'NEVER'` + this kit's cast, the pattern of
|
||||
// 伺夜's conditional casts): spends every
|
||||
// stored energy (elite ones first: 备注 "可以消耗并优先消耗第一天赋储存的额外能量") and places energies + 1 旧日残影 on free
|
||||
// placeable tiles of his attack range (部署位置 全部位), in the 备注's order: tiles holding a selectable ground enemy (the
|
||||
// best of them first by his target order — 仇恨值 [ASSUMED: the engine's default operator order]), then the tiles nearest
|
||||
@@ -88,6 +91,15 @@ export const eliteOrLeader = (e) => !!e && (!!e.isBoss || e.def?.rank === 'ELITE
|
||||
/** His stored energies (per deployment): `n` normal, `e` elite, `q` the volleys in flight per target id. */
|
||||
const stateOf = (unit) => unit.trait.ebn ?? (unit.trait.ebn = { n: 0, e: 0, q: new Map() });
|
||||
|
||||
/** A tile 荒芜回响 may place a 旧日残影 on: inside the field, deployable (全部位) and free (Battle.isReservedTile). */
|
||||
const remnantTileOk = (battle, r, c) => battle.grid.inRect(r, c) && battle.grid.canStand(r, c, { ranged: true }) && !battle.isReservedTile(r, c);
|
||||
|
||||
/** Whether `unit`'s attack range holds one tile 荒芜回响 may use (its cast condition; no rng, unlike remnantTiles). */
|
||||
function hasRemnantTile(battle, unit) {
|
||||
for (const k of unit.rangeKeys || []) if (remnantTileOk(battle, Math.floor(k / COLS), k % COLS)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The tiles 荒芜回响 may place 旧日残影 on (PRTS 备注 order): free deployable tiles of `unit`'s attack range — first those
|
||||
* holding a selectable ground enemy, by the best of them in his target order; then by the distance to the nearest
|
||||
@@ -97,8 +109,7 @@ function remnantTiles(battle, unit) {
|
||||
const tiles = [];
|
||||
for (const k of unit.rangeKeys || []) {
|
||||
const r = Math.floor(k / COLS), c = k % COLS;
|
||||
if (!battle.grid.inRect(r, c) || !battle.grid.canStand(r, c, { ranged: true }) || battle.isReservedTile(r, c)) continue;
|
||||
tiles.push([r, c]);
|
||||
if (remnantTileOk(battle, r, c)) tiles.push([r, c]);
|
||||
}
|
||||
if (!tiles.length) return tiles;
|
||||
const foes = battle.enemies.filter((e) => canTargetEnemy(unit, e, GROUND));
|
||||
@@ -231,6 +242,7 @@ export default {
|
||||
},
|
||||
[S2]: {
|
||||
kind: 'instant',
|
||||
trigger: 'NEVER', // the kit's cast (install): full SP and one free placeable tile in his range
|
||||
onStart({ battle, unit, skill }) {
|
||||
const tiles = remnantTiles(battle, unit);
|
||||
if (!tiles.length) { skill.addCharge(1); return; } // "攻击范围内不存在可部署位时,技能不会被触发"
|
||||
@@ -260,6 +272,14 @@ export default {
|
||||
},
|
||||
},
|
||||
install(battle, unit) {
|
||||
// S2 荒芜回响: cast at full SP with one free placeable tile in his range, no enemy needed (see the header)
|
||||
if (unit.skill?.id === S2) {
|
||||
battle.on('tick', () => {
|
||||
const sk = unit.skill;
|
||||
if (!sk || !sk.ready || sk.active || !up(unit) || !unit.canAct || unit.s.flags.silence) return;
|
||||
if (hasRemnantTile(battle, unit)) sk.activate('SP_FULL');
|
||||
}, { owner: unit });
|
||||
}
|
||||
// MSC-Y: ASPD while any energy is stored
|
||||
const aspd = num(hidden.attack_speed);
|
||||
if (aspd) toggleBuff(battle, unit, 'trait:ebnhlz:stored', () => { const v = stateOf(unit); return v.n + v.e > 0; }, { aspd });
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
// 单位反弹伤害,伤害为法术普通伤害"), PRTS 卫戍协议/帮助 技能策略 (her S3 row "于技能持续时间过半后…关闭技能(进入其技能效果的
|
||||
// 临死模式)" added in 上半, REMOVED with 下半; "通常不会自动关闭技能").
|
||||
// - Trait (驭法铁卫) "技能开启时普通攻击会造成法术伤害": physical attacks while no skill runs (the kit's trait dmgType — the
|
||||
// data's 'arts' is the skill-on type) and arts while one runs (every skill's attack override); block 3, ground-only
|
||||
// data's 'phys' too since 0.2.0 WE2 — build-data classifyAttack) and arts while one runs (every skill's attack override); block 3, ground-only
|
||||
// melee on her 1-1. Module AST-X 无迹 adds "且攻击和受到攻击时对目标额外造成10%攻击力的法术伤害" (trait bb atk_scale): every
|
||||
// damage instance of her normal attacks (skill attacks included; not the shield, a counter or the module's own damage)
|
||||
// is followed by atk_scale × ATK arts on that enemy (附加伤害: no dodge), and every enemy attack she takes returns
|
||||
|
||||
@@ -68,8 +68,8 @@
|
||||
// - S3 铁钳号·原型机 (MANUAL, DEFAULT): 30 s, ATK +atk, ASPD +attack_speed; +1 stock at the start.
|
||||
// [ASSUMED] the stock refilled by +cnt on each deployment, capped at cnt + the CRA-X +1; a RechargeToken beyond the cap is lost;
|
||||
// the crab charges only from the attacks that select it (an ally's area skill that picks enemies does not see it); summons
|
||||
// attack it too. Open (data, report): the hand count of his devices is the data's deployLimit 1 — tools/build-data.mjs takes
|
||||
// the attribute frame's maxDeployCount and leaves out the token's hidden talent max_deploy_count +1 (E2 "最多可部署2个").
|
||||
// attack it too. The hand count of his devices is the data's deployLimit 2 (the attribute frame's 1 + the token's hidden talent
|
||||
// max_deploy_count 1 — E2 "最多可部署2个"; tools/build-data.mjs tokenTalentDeckBonus, 0.2.0).
|
||||
|
||||
import { num, talentBb, talentGrid, traitBb, skillRec, up, giveSp } from '../shared/tier1.js';
|
||||
import { absoluteRangeKeys, sortEnemyTargets } from '../../../targeting.js';
|
||||
|
||||
@@ -68,8 +68,8 @@
|
||||
// the move be refused, she comes back on her own tile [ASSUMED; officially she is knocked out].
|
||||
// At the skill's end any anchor of hers on the field is withdrawn and the stock emptied (token_ctrl). The flight's begin
|
||||
// clip and its 生命回复速度 ×0 are not modelled [ASSUMED].
|
||||
// - 战术锚点 (token_10068_kalts2_mtship, placed in prep — "仅可以部署在凯尔希·思衡托攻击范围外的远程位" is the match's
|
||||
// placement rule): 无敌 and never targeted ("不会受到攻击"), no attack, blocks nothing; its range (hers) is only shown.
|
||||
// - 战术锚点 (token_10068_kalts2_mtship, placed in prep on a ranged tile outside her attack range — "仅可以部署在凯尔希·思衡托
|
||||
// 攻击范围外的远程位": the data's `ownerRangeOutside` / `rangedTilesOnly`, enforced by the match, 0.2.0 WE2): 无敌 and never targeted ("不会受到攻击"), no attack, blocks nothing; its range (hers) is only shown.
|
||||
// A placed piece is docked by content/tokens.js (a skill's summon: deployed for free at the battle start, then only when
|
||||
// her S3 gives one and its 70 s redeploy time has passed, on its own tile).
|
||||
|
||||
|
||||
@@ -23,8 +23,9 @@
|
||||
// target her; her summons leave the field with her (shared/summoner.js).
|
||||
// - T1 挑灯问梦 "可以使用5个召唤物(最多同时部署3个),功能随技能选择而改变": the summon of her pick's skill (“清平” / “逍遥” /
|
||||
// “弦惊” — the skills' overrideTokenKey) is the hand piece the player places; the deck of ../shared/summoner.js with charge =
|
||||
// cnt (5), cap = the summon deck (max_deck_stack_cnt 5, +3 with SUM-Y), at most 1 + max_deploy_count (2 ⇒ 3; SUM-Y stage 3:
|
||||
// 4) standing. Every summon holds 禁疗 (PRTS; [ASSUMED: given here — the data's `abnormal` lacks it]) and fights with its
|
||||
// cnt (5), cap = the summon record's deckStack (5: its hidden talent's max_deck_stack_cnt, tools/build-data.mjs
|
||||
// tokenTalentDeckBonus; +3 with SUM-Y), at most its deployLimit standing (3 = 1 + the talent's max_deploy_count 2; SUM-Y
|
||||
// stage 2+: 4) — the hand count too. Every summon holds 禁疗 (PRTS; [ASSUMED: given here — the data's `abnormal` lacks it]) and fights with its
|
||||
// own data: “清平” blocks 1 and strikes in melee (physical) on its 1-1, “逍遥” shoots arts on its 3-1 (air units too), “弦惊”
|
||||
// blocks 2 and strikes every enemy it blocks (physical; its trait "攻击阻挡的所有敌人").
|
||||
// - T2 随付笺咏醉屠苏 "召唤物被击倒/吸收/回收时令额外获得3点技力、攻击力+3%(攻击力加成最多叠加5层)": a summon knocked out,
|
||||
@@ -74,7 +75,6 @@ const TAG_S3 = 'ling:s3';
|
||||
|
||||
const bbOf = (chess, id) => skillRec(chess, id)?.bb ?? {};
|
||||
const skillOn = (u, id) => !!(u && u.skill && u.skill.active && u.skill.id === id);
|
||||
const tokTalent = (def, key) => (def?.talents ?? []).find((t) => t && t.bb && t.bb[key] != null) ?? null;
|
||||
const tileOf = (u) => u.tileR * COLS + u.tileC;
|
||||
|
||||
/** Her S1 / S3 acting on a summon (held from each skill start / end and each summon deployment). */
|
||||
@@ -217,16 +217,13 @@ export default {
|
||||
talents: [
|
||||
{ install(battle, unit) { // 挑灯问梦: the deck of her summon pieces and their kit
|
||||
const cnt = Math.max(0, Math.floor(num(t0.cnt)));
|
||||
const tdef = tokenId ? battle.tokenDef(tokenId, unit) : null;
|
||||
const deckBase = num(tokTalent(tdef, 'max_deck_stack_cnt')?.bb?.max_deck_stack_cnt, cnt);
|
||||
const extra = num(tokTalent(tdef, 'max_deploy_count')?.bb?.max_deploy_count, 0);
|
||||
const sp = num(t1.sp), atk = num(t1.atk), stacks = Math.max(1, Math.floor(num(t1.max_stack_cnt, 1)));
|
||||
summonDeck(battle, unit, {
|
||||
tokenIds: SOULS,
|
||||
charge: cnt,
|
||||
start: sumY,
|
||||
cap: deckBase + num(tokenId ? tokenStat(battle, unit, tokenId, 'deckStack') : 0, 0),
|
||||
maxDeployed: num(tokenId ? tokenStat(battle, unit, tokenId, 'deployLimit') : 1, 1) + extra,
|
||||
cap: num(tokenId ? tokenStat(battle, unit, tokenId, 'deckStack') : cnt, cnt),
|
||||
maxDeployed: num(tokenId ? tokenStat(battle, unit, tokenId, 'deployLimit') : 1, 1),
|
||||
kit: (t) => soulKit(t, unit, opts),
|
||||
// 随付笺咏醉屠苏: knocked out / absorbed / recalled ⇒ +sp SP and a stack of ATK +atk (not when they leave with her)
|
||||
onLeave(t, kind) {
|
||||
|
||||
@@ -23,8 +23,9 @@
|
||||
// PRTS "可对空"), blocks 1, ground enemies target her. Her drones leave the field with her (shared/summoner.js).
|
||||
// - T1 支援无人机·龙腾 "可以使用5个不阻挡敌人的无人机(最多同时部署3个),功能随技能选择而改变": the drone of her pick's skill
|
||||
// (龙腾.F / .L / .A — the skills' overrideTokenKey) is the hand piece the player places; the deck of ../shared/summoner.js
|
||||
// with charge = cnt (5) at each of her deployments, cap = the drone deck (its hidden talent's max_deck_stack_cnt 5, +the
|
||||
// SUM-Y deckStack 3), at most 1 + its max_deploy_count (2 ⇒ 3; SUM-Y stage 3: 3 ⇒ 4) standing. Every drone holds 禁疗
|
||||
// with charge = cnt (5) at each of her deployments, cap = the drone record's deckStack (5: its hidden talent's
|
||||
// max_deck_stack_cnt, tools/build-data.mjs tokenTalentDeckBonus; SUM-Y +3), at most its deployLimit standing (3 = 1 + the
|
||||
// talent's max_deploy_count 2; SUM-Y stage 2+: 4) — the hand count too. Every drone holds 禁疗
|
||||
// (PRTS; the data's `abnormal` lacks it [ASSUMED: given here]), blocks nothing, has no skill of its own here (its 回收 is
|
||||
// her skills' end, below). 龙腾.F (ATK 0) makes no attack (the engine's rule for a 0-ATK summon; a commented-out PRTS
|
||||
// note "缴械,普通攻击不造成伤害" agrees [ASSUMED]); 龙腾.L strikes one ground enemy on its own tile for arts damage; 龙腾.A
|
||||
@@ -135,15 +136,12 @@ export default {
|
||||
talents: [
|
||||
{ install(battle, unit) { // 支援无人机·龙腾: the deck of her drone pieces and their kit
|
||||
const cnt = Math.max(0, Math.floor(num(t0.cnt)));
|
||||
const tdef = tokenId ? battle.tokenDef(tokenId, unit) : null;
|
||||
const deckBase = num(tokTalent(tdef, 'max_deck_stack_cnt')?.bb?.max_deck_stack_cnt, cnt);
|
||||
const extra = num(tokTalent(tdef, 'max_deploy_count')?.bb?.max_deploy_count, 0);
|
||||
summonDeck(battle, unit, {
|
||||
tokenIds: DRONES,
|
||||
charge: cnt,
|
||||
start: sumY,
|
||||
cap: deckBase + num(tokenId ? tokenStat(battle, unit, tokenId, 'deckStack') : 0, 0),
|
||||
maxDeployed: num(tokenId ? tokenStat(battle, unit, tokenId, 'deployLimit') : 1, 1) + extra,
|
||||
cap: num(tokenId ? tokenStat(battle, unit, tokenId, 'deckStack') : cnt, cnt),
|
||||
maxDeployed: num(tokenId ? tokenStat(battle, unit, tokenId, 'deployLimit') : 1, 1),
|
||||
kit: (t) => droneKit(t, unit, opts),
|
||||
});
|
||||
// her skills on the standing drones: at each start / end (a drone deployed meanwhile syncs itself)
|
||||
|
||||
@@ -34,9 +34,9 @@
|
||||
// stage 3: the next attack@chain.extra_cnt jumps after that neither). Her heals pass 禁疗 ("治疗效果无视禁疗"); her selection
|
||||
// takes her own 重构体 through its 禁疗 (`healThrough`), no other 禁疗 unit [ASSUMED: the jumps too].
|
||||
// - Module XAH-X 记忆存档: the trait's 0.85 per jump (traitOverride); stage 3 changes T1 (below); ATK / DEF from the attributes.
|
||||
// - T1 自我修复 (cnt 1): her 重构体 is a hand piece the player places (the talent's summon). "可以在攻击范围内的地面使用" — a
|
||||
// ground tile of her attack range — is not in the token data (`ownerRange` false), so the match does not hold the piece to
|
||||
// it and the battle takes it where it stands [open question]. Its kit: 禁疗 (only her heals reach it), blocks nothing, taunt
|
||||
// - T1 自我修复 (cnt 1): her 重构体 is a hand piece the player places (the talent's summon) on a ground tile of her attack
|
||||
// range — "可以在攻击范围内的地面使用": the data's `ownerRange` from that talent text (tools/build-data.mjs summonRecord,
|
||||
// 0.2.0 WE2) and its MELEE position, enforced by the match. Its kit: 禁疗 (only her heals reach it), blocks nothing, taunt
|
||||
// −1, loses 8 HP every 0.1 s from 1 s after it lands (流失, Battle.loseHp — PRTS "每0.1s流失8生命值"; trait damage_per_second
|
||||
// 80); every ally of its
|
||||
// 3×3 but itself (no 孤立 one) ATK +atk (0.15; XAH-X stage 3 0.25; 同名效果取最高). Gone (destroyed or withdrawn) ⇒ back on its
|
||||
|
||||
@@ -50,7 +50,8 @@
|
||||
// - Modules SO-A 电弧特勤证章 / SO-B 新起点: their trait and talent parts act "在【岁的界园志异】中" only (the client gates them to
|
||||
// the 集成战略 theme rogue_yan / validInGameTag roguelike): N/A here — the module talents the composed record carries
|
||||
// (SO-A's hidden respawn_time / prob / sp, SO-B's attack@max_target and x-5 recall) are ignored; their attributes are in the
|
||||
// stats. (DIY_EXCLUDED_MODULE_TYPE refuses ISW-* only, so a roster may carry them — see the report.)
|
||||
// stats. A player's roster never carries them (shared/diy.js DIY_EXCLUDED_MODULE_TYPE, 0.2.0 WE2); the kit fields them for
|
||||
// its tests.
|
||||
|
||||
import { num, talentBb, skillRec, up } from '../shared/tier1.js';
|
||||
import { summonDeck, holdBuff, tokenStat } from '../shared/summoner.js';
|
||||
@@ -231,8 +232,6 @@ export default {
|
||||
talents: [
|
||||
{ install(battle, unit) { // 卡带里的灵感: the deck of her summon pieces and their kit
|
||||
const cnt = Math.max(0, Math.floor(num(t0.cnt)));
|
||||
const tdef = tokenId ? battle.tokenDef(tokenId, unit) : null;
|
||||
const extra = num(tokTalent(tdef, 'max_deploy_count')?.bb?.max_deploy_count, 0);
|
||||
const ratios = { atk: num(t1.atk), def: num(t1.def), hp: num(t1.max_hp) };
|
||||
const inspireOne = (s) => {
|
||||
if (!up(unit)) return;
|
||||
@@ -250,8 +249,8 @@ export default {
|
||||
summonDeck(battle, unit, {
|
||||
tokenIds: TOWERS,
|
||||
charge: cnt,
|
||||
cap: num(tokenId ? tokenStat(battle, unit, tokenId, 'deckStack') : cnt, cnt) + num(tokTalent(tdef, 'max_deck_stack_cnt')?.bb?.max_deck_stack_cnt, 0),
|
||||
maxDeployed: num(tokenId ? tokenStat(battle, unit, tokenId, 'deployLimit') : 1, 1) + extra,
|
||||
cap: num(tokenId ? tokenStat(battle, unit, tokenId, 'deckStack') : cnt, cnt),
|
||||
maxDeployed: num(tokenId ? tokenStat(battle, unit, tokenId, 'deployLimit') : 1, 1),
|
||||
kit: (t) => towerKit(t, unit, opts),
|
||||
});
|
||||
// her skills on the standing summons at each start / end (a summon deployed meanwhile syncs itself); 鼓舞 again
|
||||
|
||||
@@ -50,9 +50,9 @@
|
||||
// a tile at a Manhattan distance ≥ max_distance from it, it is put back on that tile's centre (unblocked, its route
|
||||
// re-planned from there; waypoints already passed stay passed), stealth or 无敌 notwithstanding; a 消失 (hidden) enemy loses
|
||||
// its mark and every mark ends with the skill (the client attaches it to shu_s_3). No enemy of the mode holds
|
||||
// 免疫传送 (data otherImmunities). Trigger: the data rule is TRY_SEARCH_ALLY_SKILL (her own row, PRTS "技能范围内存在可治疗的
|
||||
// 我方单位时释放技能"), which the engine does not know (an unknown rule acts as DEFAULT): this kit maps it onto the engine's
|
||||
// SKILL_RANGE + `allies` on x-2 (an injured, healable allied unit of the skill range, herself included; 古米 S1's option).
|
||||
// 免疫传送 (data otherImmunities). Trigger: the data's — her own row TRY_SEARCH_ALLY_SKILL (PRTS "技能范围内存在可治疗的我方
|
||||
// 单位时释放技能"), which tools/build-data.mjs (TRIGGER_ALLY_RULES) writes as the engine's SKILL_RANGE + `allies` on the
|
||||
// skill range x-2 (an injured, healable allied unit there, herself included; 古米 S1's option).
|
||||
|
||||
import { num, traitBb, skillRec, batMod, up, giveSp } from '../shared/tier1.js';
|
||||
import { COLS } from '../../../constants.js';
|
||||
@@ -164,7 +164,6 @@ export default {
|
||||
},
|
||||
[S3]: {
|
||||
kind: 'duration',
|
||||
trigger: { rule: 'SKILL_RANGE', grid: g3, allies: true },
|
||||
mods: { atkPct: num(b3.atk) },
|
||||
targeting: { rangeGrid: g3 },
|
||||
onStart({ unit }) { unit.mem.shuMarks = new Map(); },
|
||||
|
||||
@@ -54,8 +54,10 @@
|
||||
// 备注 "每次部署薇薇安娜时,重新计算该技能的使用次数" / "首次技能结束后,本技能的技能范围永久扩大至3-2"; vvana_s_3[check]: a
|
||||
// skill triggered before ⇒ the enhance mark and duration_plus = enhance_duration, vvana_s_3: mode 4 instead of 3): from her
|
||||
// second cast of a deployment the skill runs enhance_duration s with S3_LATER_HITS hits on the 3-2 range (her own grid while
|
||||
// it runs: the card shows it). The trigger stays the data's DEFAULT on her own range for every cast — the later casts'
|
||||
// larger running range is no data range (reported, not changed: kits/README.md checklist 5).
|
||||
// it runs: the card shows it). Trigger: the data's DEFAULT on her own range for the first cast of a deployment; once it
|
||||
// has ended the owner's ACTIVE_RANGE rule (2026-10-05: a MANUAL skill whose running range strictly contains her own casts
|
||||
// with an enemy inside that range) on the 3-2 — no data range carries it, so the kit sets it (skill.setTrigger, 0.2.0 WE2,
|
||||
// follow-up #16; O11's report) — and DEFAULT again at her next deployment.
|
||||
|
||||
import { num, talentBb, traitBb, skillRec, up, batMod } from '../shared/tier1.js';
|
||||
import { isElite, elementDmg, enemiesIn } from '../shared/tier6.js';
|
||||
@@ -168,9 +170,11 @@ export default {
|
||||
skill.timeLeft = Math.max(0.01, num(b3.enhance_duration, skill.duration));
|
||||
setGrid(battle, unit, RANGE_3_2);
|
||||
},
|
||||
onEnd({ battle, unit }) {
|
||||
onEnd({ battle, unit, skill }) {
|
||||
if (unit.mem.vvanaS3Later) setGrid(battle, unit, unit.def?.rangeGrid);
|
||||
unit.mem.vvanaS3Later = false;
|
||||
// its later casts run on the 3-2: from now the owner's ACTIVE_RANGE rule there (see the header)
|
||||
if ((unit.mem.vvanaS3Casts ?? 0) >= 1) skill.setTrigger('ACTIVE_RANGE', RANGE_3_2);
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -258,6 +262,7 @@ export default {
|
||||
unit.mem.vvanaS3Casts = 0;
|
||||
unit.mem.vvanaS3Later = false;
|
||||
setGrid(battle, unit, own);
|
||||
if (unit.skill?.id === S3) unit.skill.setTrigger('DEFAULT', null); // the first cast of a deployment: the data's rule
|
||||
}, { owner: unit });
|
||||
}
|
||||
// AFT-D: 元素伤害 at the output of every damage of hers on an enemy in its 灼燃 爆发冷却 (not on this rider's own)
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
// Sources: the client's battle data (charpack char_248_mgllan / char_2023_ling / char_4195_radian: Talents/1
|
||||
// charge_token[born] — RechargeToken `cnt` at every deployment of the owner; CommonAbilities die_to_kill_token — KillTokens
|
||||
// when the owner leaves; the SUM-Y equips mgllan_equip_2_* / ling_equip_1_*: trigger_charge_token — RechargeToken the
|
||||
// trait's `cnt` once, on the undeployed card (`_attachPassiveBuffsOnDummy`); the token decks: max_deck_stack_cnt /
|
||||
// max_deploy_count of the tokens' hidden talent, deckStack / deployLimit of the token records); PRTS 分支特性信息 召唤师
|
||||
// trait's `cnt` once, on the undeployed card (`_attachPassiveBuffsOnDummy`); the token decks: deckStack / deployLimit of
|
||||
// the token records, which carry the tokens' hidden talent max_deck_stack_cnt / max_deploy_count — tools/build-data.mjs
|
||||
// tokenTalentDeckBonus); PRTS 分支特性信息 召唤师
|
||||
// ("干员离场后,附属的召唤物随之消失"); PRTS 卫戍协议/帮助 §作战阶段 (placed summons: "所有手动部署的召唤物,无视所属干员的持有
|
||||
// 状态,不消耗持有数量,作战开始时立即部署一次", "若战场区初始部署有召唤物,若召唤物在战斗期间退场,将在满足条件后立即原地再部署1个").
|
||||
//
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
// server/sim/content/kits/tier1.js — re-export shim (0.2.0 refactor, R4): the tier-1 kits live one per file in ops/,
|
||||
// registered by index.js; their shared helpers and notes are in shared/tier1.js (README.md). This module keeps the
|
||||
// old imports working until the shims are removed after the 0.2.0 refactor wave.
|
||||
import { TIER_KITS } from './index.js';
|
||||
|
||||
export * from './shared/tier1.js';
|
||||
export { tinmanKit } from './ops/chess_char_1_16-tinman.js';
|
||||
export default TIER_KITS[0];
|
||||
@@ -1,6 +0,0 @@
|
||||
// server/sim/content/kits/tier2.js — re-export shim (0.2.0 refactor, R4): the tier-2 kits live one per file in ops/,
|
||||
// registered by index.js; their shared helpers and notes are in shared/tier2.js (README.md). This module keeps the
|
||||
// old imports working until the shims are removed after the 0.2.0 refactor wave.
|
||||
import { TIER_KITS } from './index.js';
|
||||
|
||||
export default TIER_KITS[1];
|
||||
@@ -1,6 +0,0 @@
|
||||
// server/sim/content/kits/tier3.js — re-export shim (0.2.0 refactor, R4): the tier-3 kits live one per file in ops/,
|
||||
// registered by index.js; their shared helpers and notes are in shared/tier3.js (README.md). This module keeps the
|
||||
// old imports working until the shims are removed after the 0.2.0 refactor wave.
|
||||
import { TIER_KITS } from './index.js';
|
||||
|
||||
export default TIER_KITS[2];
|
||||
@@ -1,6 +0,0 @@
|
||||
// server/sim/content/kits/tier4.js — re-export shim (0.2.0 refactor, R4): the tier-4 kits live one per file in ops/,
|
||||
// registered by index.js; their shared helpers and notes are in shared/tier4.js (README.md). This module keeps the
|
||||
// old imports working until the shims are removed after the 0.2.0 refactor wave.
|
||||
import { TIER_KITS } from './index.js';
|
||||
|
||||
export default Object.freeze({ ...TIER_KITS[3] });
|
||||
@@ -1,6 +0,0 @@
|
||||
// server/sim/content/kits/tier5.js — re-export shim (0.2.0 refactor, R4): the tier-5 kits live one per file in ops/,
|
||||
// registered by index.js; their shared helpers and notes are in shared/tier5.js (README.md). This module keeps the
|
||||
// old imports working until the shims are removed after the 0.2.0 refactor wave.
|
||||
import { TIER_KITS } from './index.js';
|
||||
|
||||
export default TIER_KITS[4];
|
||||
@@ -1,7 +0,0 @@
|
||||
// server/sim/content/kits/tier6.js — re-export shim (0.2.0 refactor, R4): the tier-6 kits live one per file in ops/,
|
||||
// registered by index.js; their shared helpers and notes are in shared/tier6.js (README.md). This module keeps the
|
||||
// old imports working until the shims are removed after the 0.2.0 refactor wave.
|
||||
import { TIER_KITS } from './index.js';
|
||||
|
||||
export { PITHST_ELEMENTS } from './ops/chess_char_1_15-pithst.js';
|
||||
export default TIER_KITS[5];
|
||||
@@ -136,7 +136,8 @@ export function normalizeSkill(rec) {
|
||||
initSp: Math.max(0, num(s.initSp, 0)),
|
||||
maxCharges: Math.max(1, num(s.maxChargeTime ?? s.maxCharges ?? s.charges, 1)),
|
||||
rangeGrid: toArrayOfPairs(s.rangeGrid),
|
||||
trigger: { rule, grid: trigGrid },
|
||||
// `allies` (data: an ally row, tools/build-data.mjs TRIGGER_ALLY_RULES — 黍 S3) — skills.js `trigger.allies`
|
||||
trigger: typeof trig === 'object' && trig.allies === true ? { rule, grid: trigGrid, allies: true } : { rule, grid: trigGrid },
|
||||
kind: s.kind ?? null,
|
||||
bb,
|
||||
description: s.description ?? s.desc ?? '',
|
||||
|
||||
@@ -120,6 +120,19 @@ export class SkillRuntime {
|
||||
this.noSkill = !spec; // unit without any skill spec
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the trigger rule and grid mid-battle — a kit whose skill's running range changes with its own use (薇薇安娜 S3:
|
||||
* "首次技能结束后,本技能的技能范围永久扩大至3-2" — ACTIVE_RANGE on 3-2 from then, DEFAULT again at her next deployment).
|
||||
* `grid`: a facing-RIGHT [dRow, dCol] grid (ACTIVE_RANGE / SKILL_RANGE / CUSTOM_RANGE), or null. (0.2.0 WE2, additive.)
|
||||
* @param {string} rule @param {number[][]|null} [grid]
|
||||
*/
|
||||
setTrigger(rule, grid = null) {
|
||||
this.rule = String(rule ?? 'DEFAULT').toUpperCase();
|
||||
this.triggerGrid = Array.isArray(grid) && grid.length ? grid : null;
|
||||
this._trigKeys = null;
|
||||
this._trigSet = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra DEFAULT-trigger range (海嗣 "攻击范围视为自身攻击范围的延伸", 流形): `fn(battle, unit)` returns a list whose
|
||||
* entries are ally units (their current `rangeKeys` count while they are on the field) or arrays of absolute tile
|
||||
|
||||
Reference in new issue
Block a user