feedback5: merge W5V — the owner's decisions of 2026-10-06: 望's placed 棋子 take and return her stock (her skills cycle all battle), skill triggers through summon ranges (麦哲伦 S1, 令 S3, 电弧 S2 / S3; summoner.js summonTriggerArea), a melee 突袭 member prefers a tile where it can block (GitHub #148); from the sources: 泥岩 S3 闭锁 releases and stops blocking (PRTS 异常效果 不可阻挡), 忍冬 / 深靛 / 阿 pinned as they are, the 自选 kits' 庇护 copies use holdProtect; DESIGN §24.2's #148 note points to §25; golden: roster-040, boss-boss_2 / 3 / 4 / 6 / 7-pair, hidden-boss_10-pair moved (W5V's own; the sleep-port moves kept)
This commit is contained in:
commit
73b810ec45
27 files changed
+746
-334
No files matched your search
@@ -11,7 +11,8 @@ export default {
|
||||
// 柔光缚目: attacks bind the target `duration` s with `prob`; bound enemies are never chosen as targets.
|
||||
// Alternate S1 灯塔守卫者: for `duration` s the skill range (4-1), attack interval ×(1 + base_attack_time) (PRTS:
|
||||
// "大幅度缩短(-80%)" ⇒ ×0.2 — a flat −0.8 s on 3 s would be less than S2's "略微缩短 ×0.7"), every attack hits for
|
||||
// attack@atk_scale × ATK arts. Elite module MSC-X (store 4) is the mystic profile of the module trait (TUNE.mystic);
|
||||
// attack@atk_scale × ATK arts — its stored energies too (PRTS S1 备注 "该技能的"每次攻击的攻击倍率"会实时应用在特性积攒的"攻击
|
||||
// 能量"抛射物上"; the client's ChargeAttackS1 reads the same atk_scale), not the branch's general 100 %. Elite module MSC-X (store 4) is the mystic profile of the module trait (TUNE.mystic);
|
||||
// the store itself is the shared 秘术师 profile (professions.js installMystic: at the attack check, PRTS 分支特性信息).
|
||||
chess_char_1_17_a: (bb, chess, def) => {
|
||||
const t = talentBb(chess, 0);
|
||||
|
||||
@@ -20,7 +20,9 @@ export default {
|
||||
// regen; 精锐 module SOL-X: ATK/DEF + while blocking
|
||||
// S1 小施惩戒 (charges): next attack + extra_damage_ratio × ATK arts and +cost DP; S2 坠刃拷问 (charges, the data rule
|
||||
// SKILL_RANGE: fires once an enemy is inside its own range 3-12, charges 3 s apart): +cost DP, ≤ max_target enemies of that range take atk_scale arts and
|
||||
// 停顿 — the ones already 停顿 are also stunned. (SOL-Y "首次部署时部署费用-4": the initial deployment is free.)
|
||||
// 停顿 — the ones already 停顿 are also stunned; its targets come from the 3-12 only (PRTS S2 备注 "可对空;无法选中被自身
|
||||
// 阻挡而没有位于技能范围内的敌人" — an S2 note, not S3's: S3 隐狐之艺 has none, its client selector is the standard
|
||||
// 「同时攻击阻挡的所有敌人」 one). (SOL-Y "首次部署时部署费用-4": the initial deployment is free.)
|
||||
chess_char_3_18_a: (bb, chess, def) => {
|
||||
const d = defOf(chess, def);
|
||||
const t0 = talentBb(d, 0), t1 = talentBb(d, 1);
|
||||
|
||||
@@ -8,10 +8,12 @@ import {
|
||||
|
||||
export default withDefaults({
|
||||
// ===== 泥岩 (unyield) S2 岩崩锤 — next attack: heal 4 %, 190 % phys to all ground enemies around, 30 % stun; talents
|
||||
// S1 防御力强化·γ型 (TAKE_DAMAGE); S3 秽壤的血脉 (10 s dormant — cannot act, takes no damage, keeps blocking —
|
||||
// with enemies around −60 % speed; then ground enemies around stunned 3 s/3.5 s and for the rest of the skill
|
||||
// BAT −0.3 s, ATK/DEF up, attacks every blocked enemy); module UNY-Y (沃土的愿景): ATK/DEF +8 % with no ally
|
||||
// on the 8 tiles around
|
||||
// S1 防御力强化·γ型 (TAKE_DAMAGE); S3 秽壤的血脉 (10 s dormant — PRTS 备注 "实际将会进入闭锁状态": 闭锁 = 强制缴械 +
|
||||
// 无敌 + 不可阻挡 (PRTS 异常效果: SHELTERING; 不可阻挡 BLOCK_FREE "无法阻挡/被阻挡,自动解除阻挡") — she makes no attack,
|
||||
// takes no damage and blocks nobody: the enemies she held walk on (flag noBlock — "keeps blocking" until 0.2.0
|
||||
// WV); the 备注's 眩晕 / 冻结 / 沉默 反制 are not modelled — with enemies around −60 % speed; then ground
|
||||
// enemies around stunned 3 s/3.5 s and for the rest of the skill BAT −0.3 s, ATK/DEF up, attacks every blocked
|
||||
// enemy); module UNY-Y (沃土的愿景): ATK/DEF +8 % with no ally on the 8 tiles around
|
||||
chess_char_4_18_a: (bb, chess, def) => {
|
||||
const t0 = tbb(def, 0), t1 = tbb(def, 1);
|
||||
const tb = def.traitBb || {};
|
||||
@@ -25,7 +27,8 @@ export default withDefaults({
|
||||
attack: { hitAllBlocked: true },
|
||||
onStart({ battle, unit }) {
|
||||
unit.mem.mudS3 = { awake: false, t: 0 };
|
||||
battle.addBuff(unit, { key: 'mudrok:dormant', duration: sleep + 1, flags: { invulnerable: true, disarm: true }, visible: true });
|
||||
// 闭锁: 强制缴械 + 无敌 + 不可阻挡 (her blocked enemies are released: ai.js lets go of a noBlock blocker)
|
||||
battle.addBuff(unit, { key: 'mudrok:dormant', duration: sleep + 1, flags: { invulnerable: true, disarm: true, noBlock: true }, visible: true });
|
||||
battle.fx('stone', { x: unit.x, y: unit.y, id: unit.id });
|
||||
},
|
||||
onTick({ battle, unit, dt }) {
|
||||
|
||||
@@ -43,7 +43,9 @@
|
||||
// +attack_speed (S3) for the skill's duration (per 阿 on the target: independentCharacterSource). [ASSUMED] the volley
|
||||
// lands at once (its 0.42 s of shots and flight are not modelled); with no ally in his range the cast does nothing (the
|
||||
// client's sequence stops when its attack finds no target) — his skill still runs and spends its SP: the data's DEFAULT
|
||||
// trigger needs an enemy, not an ally (reported as an open question).
|
||||
// trigger needs an enemy, not an ally — settled by the sources in 0.2.0 WV: no skillTriggerDataList row names 阿 or 怪杰
|
||||
// (the basic strategy), the skill prefabs cast with no target and keep the SP spent (skchr_haak_2 / _3 `_allowNoTarget`
|
||||
// 1, `_checkHasTargetBeforeDoCast` 0, `_recoverSpIfNoTarget` 0) and PRTS 阿 has no note on it.
|
||||
|
||||
import { num, talentBb, moduleBb, moduleOn, skillRec, statBuff, toggleBuff, up } from '../shared/tier1.js';
|
||||
import { toLocal } from '../../../dir.js';
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
// invulnerable / untargetable one) and while it stays levitated takes atk_scale × her ATK arts damage per second (the
|
||||
// first after 0.5 s, then every `interval` s; 持续伤害, not dodgeable; 无来源 at her last ATK once she left).
|
||||
|
||||
import { num, up, traitBb, talentBb, skillRec, batMod, giveSp } from '../shared/tier1.js';
|
||||
import { num, up, traitBb, talentBb, skillRec, batMod, giveSp, protectMods } from '../shared/tier1.js';
|
||||
import { absoluteRangeKeys } from '../../../targeting.js';
|
||||
import { COLS } from '../../../constants.js';
|
||||
|
||||
@@ -58,7 +58,6 @@ const S3 = 'skchr_haruka_3';
|
||||
export const BUBBLE = 'haruka:bubble';
|
||||
/** The S3 levitation damage (haruka_s_3_debuff). */
|
||||
export const FLOAT_DOT = 'haruka:float';
|
||||
const protectMods = (v) => ({ physTakenMul: 1 - v, artsTakenMul: 1 - v });
|
||||
/** 浮光泡影: the break delay when the talent carries no `interval`. */
|
||||
const BREAK_DELAY = 0.5;
|
||||
/** S3's levitation damage: the first tick (haruka_s_3_debuff firstTriggerInterval). */
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
// - S3 披荆斩棘 (MANUAL, hurt SP; data DEFAULT, rawRule TAKE_DAMAGE): at once barrier +hp_ratio × max HP (topping T1's,
|
||||
// capped), then `duration` s ATK +atk, attack interval +base_attack_time s (a flat +0.9 on 1.6 s), taunt +taunt_level.
|
||||
|
||||
import { num, traitBb, talentBb, skillRec, toggleBuff, batMod, up, enemiesInGrid } from '../shared/tier1.js';
|
||||
import { num, traitBb, talentBb, skillRec, toggleBuff, batMod, up, enemiesInGrid, holdProtect, PROTECT } from '../shared/tier1.js';
|
||||
import { isHpLoss } from '../../../damage.js';
|
||||
|
||||
const S1 = 'skchr_judge_1';
|
||||
@@ -60,9 +60,6 @@ const X4 = Object.freeze([[1, -1], [1, 0], [1, 1], [0, -1], [0, 0], [0, 1], [-1,
|
||||
/** judge_s_2[aoe]: firstTriggerInterval 0.9 s, then triggerInterval 1 s. */
|
||||
const S2_FIRST = 0.9;
|
||||
const S2_EVERY = 1;
|
||||
/** 庇护 (ba.protect): one "同名效果取最高" effect per unit, whoever grants it (battle.applyStrongest). */
|
||||
const PROTECT = 'protect';
|
||||
const protectMods = (v) => ({ physTakenMul: 1 - v, artsTakenMul: 1 - v });
|
||||
|
||||
const bbOf = (chess, id) => skillRec(chess, id)?.bb ?? {};
|
||||
const tagged = (d, tag) => !!(d && Array.isArray(d.tags) && d.tags.includes(tag));
|
||||
@@ -79,7 +76,7 @@ export default {
|
||||
/** S2's 庇护 (damage_resistance[inf] for the skill's time). */
|
||||
const protect = (battle, unit, skill) => {
|
||||
const dr = num(b2.damage_resistance);
|
||||
if (dr > 0) battle.applyStrongest(unit, PROTECT, { duration: Math.max(0.05, skill.timeLeft), value: dr, mods: protectMods, source: unit });
|
||||
holdProtect(battle, unit, dr, Math.max(0.05, skill.timeLeft), unit); // 庇护: the shared effect (同名效果取最高)
|
||||
};
|
||||
/** judge_t_1's shield_scale: 1, + S2's shield_scale while S2 runs. */
|
||||
const shieldScale = (unit) => 1 + (unit.skill?.active && unit.skill.id === S2 ? num(b2.shield_scale) : 0);
|
||||
|
||||
@@ -42,7 +42,9 @@
|
||||
// The 备注's cast with no target cannot happen here: the data's DEFAULT casts it on an attack.
|
||||
// - S3 宁作吾 (MANUAL, data DEFAULT, 30 s): she and her summons ATK +atk, DEF +def; every `interval` (0.5) s from the moment
|
||||
// it covers a summon, every ground enemy on that summon's x-5 takes atk_scale × 令's ATK arts (溅射: not dodgeable, her
|
||||
// damage — PRTS); +cnt held when it ends (not when she leaves). Passive: the 弦惊 merge — at a 弦惊's deployment, another
|
||||
// damage — PRTS); +cnt held when it ends (not when she leaves). Its cast: the data's DEFAULT, and — the owner's
|
||||
// larger-range rule of 2026-10-06 (its pulses act on each summon's x-5) — also a ground enemy on a standing summon's x-5
|
||||
// (shared/summoner.js summonTriggerArea, checked every tick). Passive: the 弦惊 merge — at a 弦惊's deployment, another
|
||||
// basic 弦惊 of hers on its range ⇒ that one turns 高级形态 and the new one is absorbed (withdrawn); else, the new one
|
||||
// standing on the range of one or more basic ones ⇒ it turns 高级形态 and the latest deployed of them is absorbed. 高级形态
|
||||
// (until it leaves the field): its skill's 2.max_hp / 2.atk / 2.def (直接乘算), RES ×(1 + 2.magic_resistance), attack
|
||||
@@ -53,7 +55,7 @@
|
||||
// stats). Its attributes are in the stats.
|
||||
|
||||
import { num, talentBb, traitBb, moduleOn, skillRec, batMod, giveSp, up } from '../shared/tier1.js';
|
||||
import { summonDeck, holdBuff, tokenStat } from '../shared/summoner.js';
|
||||
import { summonDeck, holdBuff, tokenStat, summonTriggerArea } from '../shared/summoner.js';
|
||||
import { absoluteRangeKeys } from '../../../targeting.js';
|
||||
import { acquireTargets } from '../../../ai.js';
|
||||
import { COLS, PROJECTILE_SPEEDS } from '../../../constants.js';
|
||||
@@ -72,6 +74,8 @@ export const RECYCLE_DELAY = 1.2;
|
||||
const X5 = Object.freeze([[1, 0], [0, -1], [0, 0], [0, 1], [-1, 0]]);
|
||||
const TAG_S2 = 'ling:s2';
|
||||
const TAG_S3 = 'ling:s3';
|
||||
/** S3's pulses: ground enemies only ("不可对空"). */
|
||||
const GROUND = Object.freeze({ canHitFly: false });
|
||||
|
||||
const bbOf = (chess, id) => skillRec(chess, id)?.bb ?? {};
|
||||
const skillOn = (u, id) => !!(u && u.skill && u.skill.active && u.skill.id === id);
|
||||
@@ -127,7 +131,7 @@ function soulKit(t, owner, opts) {
|
||||
if (battle.time + 1e-9 < s.mem.lingAoeAt) return;
|
||||
s.mem.lingAoeAt += iv;
|
||||
const keys = absoluteRangeKeys(X5, s.tileR, s.tileC, s.dir, 0);
|
||||
for (const e of battle.enemiesInKeys(keys, s, { canHitFly: false })) {
|
||||
for (const e of battle.enemiesInKeys(keys, s, GROUND)) {
|
||||
if (!e.alive) continue;
|
||||
battle.dealDamage(owner, e, { amount: owner.s.atk * num(opts.b3.atk_scale), type: 'arts', isSkill: true, isSplash: true, canDodge: false, tags: ['skill', TAG_S3] });
|
||||
}
|
||||
@@ -241,6 +245,10 @@ export default {
|
||||
} },
|
||||
// 随付笺咏醉屠苏 lives in the deck's onLeave above
|
||||
],
|
||||
install(battle, unit) {
|
||||
// S3's cast: a ground enemy on a standing summon's x-5 too (the owner's larger-range rule, 2026-10-06)
|
||||
summonTriggerArea(battle, unit, S3, (t) => ({ keys: absoluteRangeKeys(X5, t.tileR, t.tileC, t.dir, 0), profile: GROUND }));
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -38,7 +38,9 @@
|
||||
// - S1 高效制冷模块 (MANUAL, data DEFAULT, 15 s): passive (while S1 is picked and not running) every attack@interval (3) s from
|
||||
// her deployment / the skill's end, every enemy (air units too) on her attack range and on each of her standing drones'
|
||||
// ranges (龙腾.F: x-4) takes 停顿 attack@sluggish s; while it runs, every 3 s from the cast, 束缚 attack@frozen_duration s
|
||||
// instead. It acts while she is stunned or silenced (the buff is neither — isStunnable / isSilenceable 0).
|
||||
// instead. It acts while she is stunned or silenced (the buff is neither — isStunnable / isSilenceable 0). Its cast:
|
||||
// the data's DEFAULT, and — the owner's larger-range rule of 2026-10-06 (its 束缚 acts through the drones' ranges) —
|
||||
// also an enemy (air units too) on a standing drone's range (shared/summoner.js summonTriggerArea, checked every tick).
|
||||
// - S2 激光开采模块 (MANUAL, data DEFAULT, 15 s): she and her drones ASPD +attack_speed; a 龙腾.L strikes every enemy on its tile
|
||||
// (群体) while it runs. S3 武装打击模块 (MANUAL, data DEFAULT, 15 s): she and her drones ATK +atk; 龙腾.A's blast radius
|
||||
// BLAST_S3 (1.25). Passive parts ("无人机可以部署在近战位 / 远程位"): placement — the piece's position, nothing in battle.
|
||||
@@ -51,10 +53,12 @@
|
||||
// drones' lower costs (the module variant); stage 2+ T1 "最多同时部署4个", stage 3 "属性更强的" (the module variant's stats).
|
||||
|
||||
import { num, talentBb, traitBb, moduleOn, skillRec, up } from '../shared/tier1.js';
|
||||
import { summonDeck, holdBuff, tokenStat } from '../shared/summoner.js';
|
||||
import { summonDeck, holdBuff, tokenStat, summonTriggerArea } from '../shared/summoner.js';
|
||||
import { absoluteRangeKeys } from '../../../targeting.js';
|
||||
import { COLS } from '../../../constants.js';
|
||||
|
||||
/** S1's selection on her range and her drones' (air units too). */
|
||||
const ANY = Object.freeze({ canHitFly: true });
|
||||
const S1 = 'skchr_mgllan_1';
|
||||
const S2 = 'skchr_mgllan_2';
|
||||
const S3 = 'skchr_mgllan_3';
|
||||
@@ -171,6 +175,8 @@ export default {
|
||||
install(battle, unit) {
|
||||
// S1 高效制冷模块: every attack@interval s, 停顿 (passive) / 束缚 (running) on her range and her drones' ranges
|
||||
if (picked !== S1) return;
|
||||
// its cast: an enemy on a standing drone's range too (the owner's larger-range rule, 2026-10-06)
|
||||
summonTriggerArea(battle, unit, S1, (d) => ({ keys: d.rangeKeys, profile: ANY }));
|
||||
const iv = num(b1['attack@interval'], 3);
|
||||
if (!(iv > 0)) return;
|
||||
const restart = (c) => { if (c.unit === unit) unit.mem.mgllanPulseAt = battle.time + iv; };
|
||||
@@ -186,7 +192,7 @@ export default {
|
||||
if (!(duration > 0)) return;
|
||||
const done = new Set();
|
||||
for (const src of [unit, ...(unit.mem.summonDeck?.standing() ?? [])]) {
|
||||
for (const e of battle.enemiesInKeys(src.rangeKeys, src, { canHitFly: true })) {
|
||||
for (const e of battle.enemiesInKeys(src.rangeKeys, src, ANY)) {
|
||||
if (done.has(e)) continue;
|
||||
done.add(e);
|
||||
battle.applyStatus(e, key, { duration, source: src });
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
// [ASSUMED] (beyond op-ironmn.js's): the 监工专员 loses its 1 HP per pulse with or without an operator in range (dec_blood is a
|
||||
// passive buff of the ability); the combined range one step deep; the 小工程师's 1 s heal / SP timer runs from its deployment.
|
||||
|
||||
import { num, talentBb, traitBb, skillRec, up, giveSp } from '../shared/tier1.js';
|
||||
import { num, talentBb, traitBb, skillRec, up, giveSp, holdProtect } from '../shared/tier1.js';
|
||||
import { absoluteRangeKeys } from '../../../targeting.js';
|
||||
import { COLS } from '../../../constants.js';
|
||||
import { frontOf } from '../../../dir.js';
|
||||
@@ -86,9 +86,6 @@ export const NSTBLD = 'token_10061_nasti_nstbld';
|
||||
const DEVICES = new Set([NSTDEF, NSTCHR, NSTBLD]);
|
||||
/** A device's 1-1 when its def carries none. */
|
||||
const FRONT1 = Object.freeze([[0, 0], [0, 1]]);
|
||||
/** 庇护 (ba.protect): one "同名效果取最高" effect per unit, whoever grants it (battle.applyStrongest; op-judge.js, op-zumama.js). */
|
||||
const PROTECT = 'protect';
|
||||
const protectMods = (v) => ({ physTakenMul: 1 - v, artsTakenMul: 1 - v });
|
||||
/** 注意安全's 庇护 refresh: a little longer than the check period, so it never lapses while it holds. */
|
||||
const T2_PERIOD = 0.25;
|
||||
const PROTECT_HOLD = T2_PERIOD + 0.05;
|
||||
@@ -402,13 +399,13 @@ export default {
|
||||
if (!up(unit)) { triggered = false; timers.clear(); return; }
|
||||
if (!triggered) triggered = battle.enemies.some((e) => e.alive && !e.hidden && RANGED_WAYS.has(String(e.def?.applyWay ?? '').toUpperCase()));
|
||||
if (!triggered) {
|
||||
if (resBase > 0) battle.applyStrongest(unit, PROTECT, { duration: PROTECT_HOLD, value: resBase, mods: protectMods, source: unit });
|
||||
holdProtect(battle, unit, resBase, PROTECT_HOLD, unit); // 庇护: the shared effect (同名效果取最高)
|
||||
return;
|
||||
}
|
||||
const who = [unit, ...battle.allyUnits.filter((a) => a !== unit && up(a) && !a.hidden && a.kind === 'op' && RANGED_POS.has(String(a.def?.position ?? '').toUpperCase()))];
|
||||
for (const a of [...timers.keys()]) if (!who.includes(a)) timers.delete(a);
|
||||
for (const a of who) {
|
||||
if (resPlus > 0) battle.applyStrongest(a, PROTECT, { duration: PROTECT_HOLD, value: resPlus, mods: protectMods, source: unit });
|
||||
holdProtect(battle, a, resPlus, PROTECT_HOLD, unit);
|
||||
if (!(spEvery > 0)) continue;
|
||||
if (!timers.has(a)) { timers.set(a, battle.time + spEvery); continue; }
|
||||
if (battle.time + 1e-9 < timers.get(a)) continue;
|
||||
|
||||
@@ -43,10 +43,14 @@
|
||||
// - S2 环形鳞地 (MANUAL, data DEFAULT, 25 s): +cnt held; she and her summons ATK +atk (base_attack_time 0: no change); 赛柯's
|
||||
// attacks fire BULLET_OFFSETS (three bullets: its centre, 0.2 tile left-up and right-down — fixed, PRTS) that fly its token
|
||||
// skill's attack@projectile_life_time s (0.6 at rank 4, 0.8 at rank 7: "子弹飞行距离+1"); its 3-2 range is unchanged at ranks
|
||||
// 4 / 7 (PRTS). Passive "会持续向前方发射子弹": 赛柯's data.
|
||||
// 4 / 7 (PRTS). Passive "会持续向前方发射子弹": 赛柯's data. Its cast: the data's DEFAULT, and — the owner's larger-range
|
||||
// rule of 2026-10-06 (it acts through 赛柯's shots) — also a ground enemy on a standing summon's range (its 3-2: the
|
||||
// bullets reach no air unit; shared/summoner.js summonTriggerArea, checked every tick).
|
||||
// - S3 手牵手 (MANUAL, data DEFAULT, 30 s): +cnt held; she and her summons ATK +atk; before each damage of her attacks and of
|
||||
// any damage of her summons on an enemy: 停顿 `sluggish` s and 法术脆弱 (artsFragile damage_scale − 1, "同名效果取最高") for
|
||||
// weak[magic][limit] s (a dodged hit still carries them [ASSUMED]). Passive "召唤物可部署在远程位…协同攻击": 桑特拉 (above).
|
||||
// Its cast: the data's DEFAULT, and — the owner's larger-range rule of 2026-10-06 (it acts through her summons' damage)
|
||||
// — also an enemy (air units too: S3 备注 "召唤物可对空") on a standing summon's range (summonTriggerArea).
|
||||
// - 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
|
||||
@@ -54,7 +58,7 @@
|
||||
// its tests.
|
||||
|
||||
import { num, talentBb, skillRec, up } from '../shared/tier1.js';
|
||||
import { summonDeck, holdBuff, tokenStat } from '../shared/summoner.js';
|
||||
import { summonDeck, holdBuff, tokenStat, summonTriggerArea } from '../shared/summoner.js';
|
||||
import { acquireTargets } from '../../../ai.js';
|
||||
import { aggregateMods } from '../../../buffs.js';
|
||||
import { dirVec } from '../../../dir.js';
|
||||
@@ -79,6 +83,9 @@ const BULLET_OFFSETS = Object.freeze([[0, 0], [-0.2, 0.2], [0.2, -0.2]]);
|
||||
const LINK_SPEED = 50;
|
||||
const SHIELD_TYPES = Object.freeze(['phys', 'arts', 'true']);
|
||||
const KEY_BARRIER = 'radian:s1:barrier';
|
||||
/** The summons' areas a skill casts on (summonTriggerArea): 赛柯's bullets hit ground enemies only, S3's summons air too. */
|
||||
const GROUND = Object.freeze({ canHitFly: false });
|
||||
const ANY = Object.freeze({ canHitFly: true });
|
||||
const TAG_BULLET = 'radian:bullet';
|
||||
const TAG_LINK = 'radian:link';
|
||||
|
||||
@@ -268,6 +275,9 @@ export default {
|
||||
// 加油~ lives in the deck's install above (its 鼓舞 at each deployment and every second)
|
||||
],
|
||||
install(battle, unit) {
|
||||
// S2 / S3's cast: an enemy on a standing summon's range too (the owner's larger-range rule, 2026-10-06)
|
||||
summonTriggerArea(battle, unit, S2, (t) => ({ keys: t.rangeKeys, profile: GROUND }));
|
||||
summonTriggerArea(battle, unit, S3, (t) => ({ keys: t.rangeKeys, profile: ANY }));
|
||||
// S3 手牵手: before each damage of her attacks / of her summons' damage on an enemy, 停顿 + 法术脆弱
|
||||
if (picked !== S3) return;
|
||||
const slug = num(b3.sluggish), dur = num(b3['weak[magic][limit]']), frag = num(b3.damage_scale, 1) - 1;
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
// 单位时释放技能"), 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 { num, traitBb, skillRec, batMod, up, giveSp, holdProtect } from '../shared/tier1.js';
|
||||
import { COLS } from '../../../constants.js';
|
||||
import { effectiveProfile, acquireTargets, performAttack } from '../../../ai.js';
|
||||
|
||||
@@ -80,9 +80,6 @@ const PROF_NEEDED = 3;
|
||||
const SUI_NEEDED = 4;
|
||||
/** 【岁】: character_table groupId 'sui' (data/backups.json diy.operators powers carry it for the 自选 picks). */
|
||||
const SUI = new Set(['char_2014_nian', 'char_2015_dusk', 'char_2023_ling', 'char_2024_chyue', 'char_2025_shu', 'char_2026_yu', 'char_2027_wang']);
|
||||
/** 庇护 (ba.protect): one "同名效果取最高" effect per unit, whoever grants it (battle.applyStrongest). */
|
||||
const PROTECT = 'protect';
|
||||
const protectMods = (v) => ({ physTakenMul: 1 - v, artsTakenMul: 1 - v });
|
||||
const FARM = 'talent:shu:farm';
|
||||
const HARVEST = 'skill:shu:harvest';
|
||||
|
||||
@@ -122,7 +119,7 @@ export default {
|
||||
if (hp > 0 && !(cur && cur.source !== unit && (cur.data?.v ?? 0) > hp && cur.timeLeft > 0.05)) {
|
||||
battle.addBuff(a, { key: FARM, duration: FARM_HOLD, mods: { hpRegen: hp }, source: unit, data: { v: hp }, tags: ['talent'] });
|
||||
}
|
||||
if (dr > 0) battle.applyStrongest(a, PROTECT, { duration: FARM_HOLD, value: dr, mods: protectMods, source: unit });
|
||||
holdProtect(battle, a, dr, FARM_HOLD, unit); // 庇护: the shared effect (同名效果取最高)
|
||||
}
|
||||
};
|
||||
/** Sow the tiles of `grid` around (r, c) — only tiles that can be deployed on or walked. Returns the new ones. */
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
// held like the hammer's (PRIO_UNDYING_HELD −99).
|
||||
// Statuses: none applied to enemies. Damage: arts normal attacks only.
|
||||
|
||||
import { num, up, traitBb, moduleBb, talentBb, skillRec, once } from '../shared/tier1.js';
|
||||
import { num, up, traitBb, moduleBb, talentBb, skillRec, once, holdProtect } from '../shared/tier1.js';
|
||||
import { releaseSkillSummon } from '../../tokens.js';
|
||||
import { holdsUndying, PRIO_UNDYING_HELD, PRIO_REVIVE, PRIO_RESPAWN } from '../../items/battle.js';
|
||||
import { absoluteRangeKeys } from '../../../targeting.js';
|
||||
@@ -69,9 +69,6 @@ const S1 = 'skchr_slent2_1';
|
||||
const S2 = 'skchr_slent2_2';
|
||||
const S3 = 'skchr_slent2_3';
|
||||
export const LAMP = 'token_10029_slent2_protrb';
|
||||
/** 庇护 (ba.protect, the client's damage_resistance[inf]): one "同名效果取最高" effect per unit (battle.applyStrongest). */
|
||||
export const PROTECT = 'protect';
|
||||
const protectMods = (v) => ({ physTakenMul: 1 - v, artsTakenMul: 1 - v });
|
||||
/** The client's trigger period of slent2_t_1 / slent2_t_2 (talent abilities, triggerInterval 0.1). */
|
||||
export const TALENT_IV = 0.1;
|
||||
/** Lifetime of a refreshed effect: a little longer than the refresh period, so it never lapses while it holds. */
|
||||
@@ -136,7 +133,7 @@ function lampKit(t, owner, t1) {
|
||||
const keys = new Set(absoluteRangeKeys(grid, lamp.tileR, lamp.tileC, lamp.dir, 0));
|
||||
for (const a of alliesOn(battle, lamp, keys)) {
|
||||
const v = shelterOf(tb, a, scale);
|
||||
if (v > 0) battle.applyStrongest(a, PROTECT, { duration: HOLD, value: v, mods: protectMods, source: lamp });
|
||||
holdProtect(battle, a, v, HOLD, lamp); // 庇护 (the client's damage_resistance[inf]): the shared effect
|
||||
}
|
||||
}, { owner: lamp, immediate: true });
|
||||
},
|
||||
@@ -194,7 +191,7 @@ export default {
|
||||
const now = new Set(allies);
|
||||
for (const a of allies) {
|
||||
const v = shelterOf(t1, a, scale);
|
||||
if (v > 0) battle.applyStrongest(a, PROTECT, { duration: HOLD, value: v, mods: protectMods, source: unit });
|
||||
holdProtect(battle, a, v, HOLD, unit);
|
||||
const mult = isRhine(a) ? 2 : 1;
|
||||
if (maxHp > 0) battle.applyStrongest(a, MAXHP_KEY, { duration: HOLD, value: maxHp * mult, mods: (x) => ({ hpPct: x }), source: unit });
|
||||
if (regenRatio > 0 && a.hpRatio < regenBelow - 1e-9) {
|
||||
|
||||
@@ -22,12 +22,19 @@
|
||||
// may). Module TRP-X "部署费用更低(-1)…可同时部署的陷阱数量提升(+1)": the 棋子 variant's cost 2 (the DP of its returns) and
|
||||
// deploy limit 7 (the hand count — the match's); its attributes in the stats.
|
||||
// - T1 铸子 (cnt / attack@max_spawn_cnt; "最多拥有7枚"): the 棋子 are hand pieces the player places; they deploy with the board
|
||||
// for free and without using her stock (stock = cnt at the start, at most STOCK_CAP; the skills add to it). 棋子 / 跟子
|
||||
// for free. Her stock (持有库存: the 棋子 in her hand — cnt at the start, at most STOCK_CAP; the skills add to it) counts
|
||||
// them — the owner's decision of 2026-10-06, a deliberate deviation from PRTS 卫戍协议/帮助 (placed summons "无视所属干员的
|
||||
// 持有状态,不消耗持有数量", which left her stock at cnt: one S1 / S2 cast reached the cap and 阻回 held her SP for the rest
|
||||
// of the battle): every deployment of a 棋子 (the battle start's and each return) takes one from it, like one she deployed
|
||||
// herself (never below 0 [ASSUMED: a 7th placed piece of TRP-X's deploy limit takes none]), and a 棋子 that leaves the
|
||||
// field (set off, or gone with her) goes back into it at once by the official summon rule — its card back in her hand
|
||||
// while its redeploy time runs (as shared/summoner.js's recall) —, at most the cap [ASSUMED: one over the cap is lost].
|
||||
// So "立即获得两枚棋子" and the 阻回 at the cap keep cycling through the battle. 棋子 / 跟子
|
||||
// that touch on a side (上下 / 左右) activate each other and stay active once activated [ASSUMED from the S2 备注]; an
|
||||
// enemy (air units too) on an active one's tile sets it off: its effect, then it is used up (a 棋子 leaves the field; a
|
||||
// 跟子 is gone) [ASSUMED: a trap is spent by its trigger]. A spent 棋子 comes back on its tile ("原地再部署") after its
|
||||
// redeploy time (2 s), paying its cost, while 望 is on the field — its own card returns to the stock as it leaves, so the
|
||||
// stock does not change [ASSUMED]. That return is the mode's deployment of her card: it counts as a 手动部署 for 铸子 —
|
||||
// redeploy time (2 s), paying its cost and one of her stock, while 望 is on the field. That return is the mode's
|
||||
// deployment of her card: it counts as a 手动部署 for 铸子 —
|
||||
// one 跟子 on a tile next to it (the PRTS order above), at most attack@max_spawn_cnt on the field [ASSUMED: the battle-start
|
||||
// deployment does not count]. When 望 leaves the field her 棋子 and 跟子 vanish (PRTS 分支特性信息); the 棋子 come back
|
||||
// with her.
|
||||
@@ -195,7 +202,7 @@ export default {
|
||||
const at = battle.time + Math.max(0, num(stone.base.respawnTime));
|
||||
battle.every(RETRY, (b, sched) => {
|
||||
if (stone.alive || stone.removed || b.finished) { sched.cancel(); return; }
|
||||
if (b.time + 1e-9 < at || !up(unit)) return;
|
||||
if (b.time + 1e-9 < at || !up(unit) || unit.mem.wang.stock < 1) return; // (one of her stock: the deployment takes it)
|
||||
const r = stone.homeR, c = stone.homeC;
|
||||
const s3on = unit.skill?.id === S3 && unit.skill.active && inHerRange(unit, r, c);
|
||||
if (groundFoeOn(b, r, c) && !s3on) return; // "陷阱无法放置于敌人已在的格子中"
|
||||
@@ -219,12 +226,16 @@ export default {
|
||||
battle.addBuff(unit, { key: 'trait:wangStone', flags: { untargetable: true }, persist: true, allowDead: true });
|
||||
battle.on('deploy', (ctx) => {
|
||||
if (ctx.unit !== unit) return;
|
||||
unit.mem.wangNode = { seq: ++owner.mem.wang.seq, act: false, h: false, v: false, stacks: 0 };
|
||||
const w = owner.mem.wang;
|
||||
unit.mem.wangNode = { seq: ++w.seq, act: false, h: false, v: false, stacks: 0 };
|
||||
w.stock = Math.max(0, w.stock - 1); // it occupies her stock (the owner's decision of 2026-10-06)
|
||||
}, { owner: unit });
|
||||
battle.on('death', (ctx) => {
|
||||
if (ctx.unit !== unit || battle.finished) return;
|
||||
unit.removed = false; // the piece is kept (Battle.redeploy accepts it; its hooks stay)
|
||||
unit.mem.wangNode = null;
|
||||
const w = owner.mem.wang;
|
||||
w.stock = Math.min(stockCap, w.stock + 1); // its card goes back into her stock (the official summon rule)
|
||||
scheduleReturn(battle, owner, unit);
|
||||
}, { owner: unit, priority: -10 });
|
||||
},
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
// she is stunned `stun` s (zumama_s_3 ON_BUFF_FINISH 'stun'; a stunned operator blocks nobody).
|
||||
// - Melee physical, ground-only (data canHitFly false), block 1, range 1-1; ground enemies target her (no 起飞 / 迷彩).
|
||||
|
||||
import { num, traitBb, skillRec, toggleBuff, batMod, up } from '../shared/tier1.js';
|
||||
import { num, traitBb, skillRec, toggleBuff, batMod, up, holdProtect } from '../shared/tier1.js';
|
||||
|
||||
const S1 = 'skchr_zumama_1';
|
||||
const S2 = 'skchr_zumama_2';
|
||||
@@ -51,9 +51,6 @@ const S3 = 'skchr_zumama_3';
|
||||
const T1_PERIOD = 0.1;
|
||||
/** 庇护 refresh: a little longer than the check period, so it never lapses while it holds. */
|
||||
const PROTECT_HOLD = T1_PERIOD + 0.05;
|
||||
/** 庇护 (ba.protect): one "同名效果取最高" effect per unit, whoever grants it (battle.applyStrongest). */
|
||||
const PROTECT = 'protect';
|
||||
const protectMods = (v) => ({ physTakenMul: 1 - v, artsTakenMul: 1 - v });
|
||||
/** S2's blockee stun, renewed every tick while it holds (three ticks long). */
|
||||
const STUN_PULSE = 0.1;
|
||||
const VALOR = 'talent:zumama:valor';
|
||||
@@ -101,7 +98,7 @@ export default {
|
||||
const has = unit.findBuff(VALOR);
|
||||
if (high && !has && scale !== 1) battle.addBuff(unit, { key: VALOR, mods: { atkScaleMul: scale }, tags: ['talent'] });
|
||||
else if (!high && has) battle.removeBuff(unit, VALOR);
|
||||
if (up(unit) && !high && dr > 0) battle.applyStrongest(unit, PROTECT, { duration: PROTECT_HOLD, value: dr, mods: protectMods, source: unit });
|
||||
if (up(unit) && !high) holdProtect(battle, unit, dr, PROTECT_HOLD, unit); // 庇护: the shared effect
|
||||
};
|
||||
battle.every(T1_PERIOD, check, { owner: unit, immediate: true });
|
||||
battle.on('deploy', (ctx) => { if (ctx.unit === unit) check(); }, { owner: unit });
|
||||
|
||||
Reference in new issue
Block a user