feedback5: merge R19E — every enemy finishes its attack clip after a strike before it moves on (PRTS 状态机: ATTACK → MOVE after the attack ends; the 鸭爵 swap's 流泪小子 no longer walks off on its strike tick, community report and GitHub #214), and the 鸭爵 strategy's swapped-in 流泪小子 hides again 1 s after a block ends instead of 0 s (the owner's decision of 2026-10-07, a deliberate deviation for that copy only); golden as R19E's commits (216 scenarios + solo-HARD-2), the merged tree differs from the branch only in R19G's three 蕾缪安 scenarios
This commit is contained in:
commit
11fd65a5a5
19 files changed
+4061
-3741
No files matched your search
+16
-6
@@ -23,7 +23,9 @@
|
||||
// Every enemy attack strikes at its clip's damage frame after its swing starts (attackWindup; a stun before the frame
|
||||
// cuts the swing — enemyAttack, GitHub #187). Unblocked ranged enemies attack allies within their radius and stand for
|
||||
// each attack's clip — through its wind-up and until the clip ends — then walk on (attackStand, GitHub #58;
|
||||
// ATTACK_PAUSE after the strike when no clip is known; 「不停止移动」 attackers never stop); the candidates pass the
|
||||
// ATTACK_PAUSE after the strike when no clip is known; 「不停止移动」 attackers never stop); every enemy whose block ends
|
||||
// after its strike (its blocker stunned by that strike, knocked out, retreated) stands for the rest of the clip too
|
||||
// (PRTS 状态机: an enemy's COMBAT state ends with its attack, not with its blocker); the candidates pass the
|
||||
// enemy's own rule (`e.profile.canTarget`) and are ordered
|
||||
// blocker → taunt → latest deployed (targeting.js sortAllyTargets). An enemy's damage type is its data's unless content
|
||||
// arms it (`e.profile.dmgType`: 转译基底's forms, whose data never attacks). Reaching the final leg's end = leak. A `fear` (恐惧) status suspends the route: the
|
||||
@@ -687,9 +689,11 @@ function advanceRoute(b, e, dt, R, standing = false) {
|
||||
}
|
||||
|
||||
/**
|
||||
* How long an unblocked ranged enemy stands for one attack (GitHub #58 — the owner's decision of 2026-10-04 from
|
||||
* first-hand memory of the official game: a ranged enemy stops for each attack's animation and walks on between
|
||||
* attacks; the handbook names attacking on the move as a special ability, “十字路口”量产型's 「不停止移动的四向攻击」).
|
||||
* How long an enemy stands for one attack: an unblocked ranged enemy for its whole clip (GitHub #58 — the owner's
|
||||
* decision of 2026-10-04 from first-hand memory of the official game: a ranged enemy stops for each attack's animation
|
||||
* and walks on between attacks; the handbook names attacking on the move as a special ability, “十字路口”量产型's
|
||||
* 「不停止移动的四向攻击」), and any enemy for the rest of it after the strike once its block ends meanwhile (enemyAttack;
|
||||
* PRTS 状态机 ATTACK / COMBAT "攻击结束后回退到MOVE状态", 0.2.0).
|
||||
* [ASSUMED] the stand lasts exactly its attack clip — data/enemies.json `attackAnim` { dur, hit }: the clip the client
|
||||
* plays for its attacks and its strike frame (tools/build-data.mjs, from the asset manifest) —, `hit` of it before
|
||||
* the strike (the wind-up, while a target is in range) and the rest after it, both shortened when the attacks come
|
||||
@@ -850,8 +854,14 @@ function enemyAttack(b, e, prevCd) {
|
||||
}
|
||||
if (b._hooks.attack) b.emit('attack', { attacker: e, targets, isSkill: false });
|
||||
e.atkCd = e.s.interval;
|
||||
// stands for the rest of its attack clip (attackStand; the wind-up was stood before the strike)
|
||||
if (!e.blockedBy && radius > 0) e.atkStandUntil = b.time + attackStand(e, STAND).rest;
|
||||
// stands for the rest of its attack clip (attackStand; the wind-up was stood before the strike) — every enemy, blocked
|
||||
// or not: PRTS 状态机, an enemy's ATTACK / COMBAT state "攻击结束后回退到MOVE状态" and checks only 异常状态 every frame
|
||||
// (only a character's COMBAT "检测到不存在阻挡对象时强行切回IDLE状态"), so an enemy whose block ends after its strike — the
|
||||
// strike stunned its blocker (流泪小子: 晕眩 "无法…阻挡"), knocked it out, or the blocker retreated — finishes its clip
|
||||
// before it walks on (until 0.2.0 it walked on at once; community report of 2026-10-07 「哭狗瞬间隐匿没有后摇」). A
|
||||
// blocked enemy never walks, so the stand matters only once it is free; a stun / freeze / sleep / 浮空, hiding or a
|
||||
// displacement (失衡) ends it (updateEnemy, advanceRoute, Battle.displace)
|
||||
e.atkStandUntil = b.time + attackStand(e, STAND).rest;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -139,6 +139,8 @@ export class BattleDisplacement {
|
||||
}
|
||||
if (moved > 0) {
|
||||
this._unblock(e);
|
||||
// 失衡 ends the attack clip it stood for (PRTS 状态机: the states are exclusive — UNBALANCE, then DEFAULT → MOVE)
|
||||
e.atkStandUntil = -Infinity;
|
||||
if (e.route) e.route.pts = null;
|
||||
this.fx('displace', { x: e.x, y: e.y, id: e.id });
|
||||
}
|
||||
|
||||
@@ -373,7 +373,9 @@ K.preparation_start_add_special_goods_every_n_round = (ps) => ({
|
||||
// share taken over that half [ASSUMED: "你和队友遭遇的敌人" — the enemies heading for the player's own protection point;
|
||||
// each official client simulates its own battle, docs/research/08 appendix A]. The originals never exist, so none of
|
||||
// their death / kill / leak effects happen. Reaching the protection point costs 1 LP ("但进入保护目标点将减少1点目标生命值":
|
||||
// the enemies' data lpr, tools/build-data.mjs).
|
||||
// the enemies' data lpr, tools/build-data.mjs). The swaps carry the spawn tag 'duck': their 隐匿 (流泪小子) comes back no
|
||||
// sooner than 1 s after a block ends, not the official 0 s (content/enemies/helpers.js DUCK_STEALTH_RESTORE — the owner's
|
||||
// decision of 2026-10-07, a deliberate deviation).
|
||||
|
||||
export const DUCK_BAND = 'band_ducklord';
|
||||
/** "击倒这些敌人者获得1资金奖励" (the blackboard's `count` is 1 too). */
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
// unbalanced, artsBarrier, husk, statue, bleed, pollution, dmgZone, skill(), blinkForward … and the archetype kits
|
||||
// reused by several keys (kitEp, kitTimes …).
|
||||
|
||||
import { MOVE_SCALE, COLS } from '../../constants.js';
|
||||
import { MOVE_SCALE, COLS, STEALTH_RESTORE } from '../../constants.js';
|
||||
import {
|
||||
HUSK_REBIRTH, BOOM_RADIUS, POLLUTION_INTERVAL, STEALTH_RESTORE_BY_KEY, nthOf, stOf, safe, num, T, elem, hurt,
|
||||
HUSK_REBIRTH, BOOM_RADIUS, POLLUTION_INTERVAL, STEALTH_RESTORE_BY_KEY, DUCK_STEALTH_RESTORE, nthOf, stOf, safe, num, T, elem, hurt,
|
||||
areaAllies, areaAlliesInTiles, zone, spawnChildren, stepToward, setHits, hitCount, setForm, absorbArts, auraBuff,
|
||||
} from './helpers.js';
|
||||
|
||||
@@ -14,11 +14,13 @@ import {
|
||||
|
||||
/**
|
||||
* 隐匿: permanent stealth. Blocked it is lifted; it hides again STEALTH_RESTORE s after the block ends, or after its own
|
||||
* "(解除阻挡N秒后恢复)" (STEALTH_RESTORE_BY_KEY → buff `data.stealthRestore`; Battle._stealthSwitch).
|
||||
* "(解除阻挡N秒后恢复)" (STEALTH_RESTORE_BY_KEY → buff `data.stealthRestore`; Battle._stealthSwitch) — a 鸭爵 swap (tag
|
||||
* 'duck') no sooner than DUCK_STEALTH_RESTORE s (the owner's decision of 2026-10-07, a deliberate deviation).
|
||||
*/
|
||||
const stealth = () => ({
|
||||
spawn(b, e, a, ab) {
|
||||
const n = STEALTH_RESTORE_BY_KEY[ab.key];
|
||||
let n = STEALTH_RESTORE_BY_KEY[ab.key];
|
||||
if (e.tag === 'duck' && (n ?? STEALTH_RESTORE) < DUCK_STEALTH_RESTORE) n = DUCK_STEALTH_RESTORE;
|
||||
b.addBuff(e, { key: 'ab:stealth', flags: { stealth: true }, persist: true, data: n != null ? { stealthRestore: n } : {} });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -44,6 +44,15 @@ const STEALTH_RESTORE_BY_KEY = Object.freeze({
|
||||
enemy_1283_sgkill_2: 1, // 家族暗影灭迹人
|
||||
});
|
||||
|
||||
/**
|
||||
* 隐匿 of the 鸭爵 strategy's swapped-in enemies (spawn tag 'duck': content/bands/meta.js duckReplace) comes back no sooner
|
||||
* than this many seconds after a block ends — in practice 流泪小子 (enemy_2034_sythef_2, the only one of the four with 隐匿;
|
||||
* officially 0 s, STEALTH_RESTORE_BY_KEY). A deliberate deviation from the official game: the owner's decision of
|
||||
* 2026-10-07 「鸭爵策略产生的泪眼汪汪…要不等待个1秒这样再重新隐匿」 (community report of 2026-10-07, GitHub #214). The same
|
||||
* enemy anywhere else — the 流泪小子 of a bounty card (enemy_2034_sythef), a test spawn — keeps the official time.
|
||||
*/
|
||||
const DUCK_STEALTH_RESTORE = 1;
|
||||
|
||||
/** Splash radius (中点判定 around the target) of 烹泉 / 沏虹's attack (PRTS 天赋 "普通攻击对目标对及目标周围半径1.0范围内的所有
|
||||
* 我方单位造成法术普通伤害(无视迷彩,不可对空)") and of 集团军重型火炮's shell (PRTS "对目标半径1.0范围内的所有我方单位造成攻击力
|
||||
* 100%的物理伤害(此弹道会强制击中主目标,碰撞无视迷彩,不可对空)"). */
|
||||
@@ -387,6 +396,6 @@ const onTerrain = (b, u, terrain) => { const [r, c] = tileOf(u); return b.grid.t
|
||||
|
||||
// used by the other enemy modules (content/enemies.js and content/enemies/*.js)
|
||||
export {
|
||||
BOOM_RADIUS, POLLUTION_INTERVAL, STEALTH_RESTORE_BY_KEY, TEA_SPLASH_RADIUS, SHELL_SPLASH_RADIUS, stOf, safe, dispatch,
|
||||
BOOM_RADIUS, POLLUTION_INTERVAL, STEALTH_RESTORE_BY_KEY, DUCK_STEALTH_RESTORE, TEA_SPLASH_RADIUS, SHELL_SPLASH_RADIUS, stOf, safe, dispatch,
|
||||
num, auraBuff, watchDeaths, onTerrain,
|
||||
};
|
||||
Reference in new issue
Block a user