feedback5: merge W5AA — community reports of 2026-10-06: the 孤岛风云 prisoners leave the gate confined (grey) and change model as their prefabs do, the 4th attack hits freed; a bounty enemy that dies with no operator behind it pays in 联防 too (the helper whose half it fell on); 忍冬's trigger and 枯朽之种's bounty were already fixed (pinned by tests); DESIGN §25.14 after round 18's §25.13; data/enemies.json regenerated offline on the merged code (round 18's model quirks + the prisoners' clips); golden: vs feedback5 exactly W5AA's moves (26 battles, solo-ABYSS-2, coop2-NORMAL-3, coop3-ABYSS-5, solo-HARD-9-boosted), vs W5AA exactly round 18's boss-pool moves
This commit is contained in:
commit
714af994fc
26 files changed
+1081
-366
No files matched your search
@@ -139,10 +139,7 @@ export class BattleDeploy {
|
||||
if (pp) pp.killed++;
|
||||
}
|
||||
if (killer) killer.stats.kills++;
|
||||
if (unit.bounty && unit.bounty.coins > 0) {
|
||||
const payee = killer && killer.side === 'ally' && killer.ownerId != null ? killer.ownerId : (unit.bounty.ownerPlayerId ?? unit.ownerId);
|
||||
this.addCoins(payee, unit.bounty.coins);
|
||||
}
|
||||
if (unit.bounty && unit.bounty.coins > 0) this.addCoins(this._bountyPayee(unit, killer), unit.bounty.coins);
|
||||
}
|
||||
}
|
||||
// the reason ('killed' | 'retreat' | 'expired' | …) lets the client keep the knock-down sound for real knock-outs
|
||||
|
||||
@@ -48,6 +48,24 @@ export class BattleEconomy {
|
||||
return u.side === 'ally' ? [u.tileR, u.tileC] : [Math.round(u.y), Math.round(u.x)];
|
||||
}
|
||||
|
||||
/**
|
||||
* The player a kill bounty (`unit.bounty`: a 悬赏 card's enemy, a 鸭爵 swap) pays when its enemy dies: the bounty pays
|
||||
* whenever the body dies (the owner's decision, relayed 2026-10-06). An operator or summon of a player of this battle
|
||||
* that dealt the blow pays its player ("将其击倒者获得N资金" — a 联防 helper included); any other death — 无来源 damage
|
||||
* (terrain, an uncredited tick), its own drain, another enemy, an ownerless unit — pays the card's owner when that
|
||||
* player fights here (own field, Final Assault / Hidden Core), else (联防, where the owner is the leaker) the player
|
||||
* whose half of the field it fell on: PRTS 卫戍协议:盟约 决策 "该敌人于对应玩家所属区域倒下时,使相应玩家获得额外资金"
|
||||
* [ASSUMED for 联防: no source names the payee of a death nobody caused there]. Until 0.2.0 that fallback was the
|
||||
* leaker, who has no entry in a 联防 battle, and addCoins dropped the coins (community report of 2026-10-06
|
||||
* 「被源石地板烫死的悬赏没给赏金」: until 0.1.3 the 联防 field was the round's map, 战场#04's 活性源石 included).
|
||||
*/
|
||||
_bountyPayee(unit, killer = null) {
|
||||
if (killer && killer.side === 'ally' && killer.ownerId != null && this._pp(killer.ownerId)) return killer.ownerId;
|
||||
const owner = unit.bounty?.ownerPlayerId ?? unit.ownerId;
|
||||
if (owner != null && this._pp(owner)) return owner;
|
||||
return this._ownerForTile([Math.round(unit.y), Math.round(unit.x)]);
|
||||
}
|
||||
|
||||
addCoins(playerId, n) {
|
||||
const pp = this._pp(playerId);
|
||||
if (!pp || !(n > 0) || !Number.isFinite(n)) return 0;
|
||||
|
||||
@@ -85,8 +85,8 @@
|
||||
// kind, tiles?, id?, form?} (delayed strikes, charges, 'reborn' with form 'reborn') · 'beam' {from, to, kind} ·
|
||||
// 'summon' {id, key} · 'ember' {id, hits, dur, form: 'husk'} · 'revive' {id, kind?, form: 'form2' | 'revived' | 'fly'}
|
||||
// · 'stone' {id, dur, form: 'stone'} · 'blink' {id, fx, fy} · 'charge' {id, tx, ty} · 'expose' {id} · 'shieldBreak'
|
||||
// {id} · 'liberate' {id} · 'phase' {id, kind, dur?, form?} (form changes — 掠海漂移体 'crawl', 暴鸰 'bombed',
|
||||
// translator_* — and barrier / charge states) · 'lpLoss' {value, reason} · 'steal' · 'ignite'. Forms go through
|
||||
// {id} · 'liberate' {id, form: 'liberty'} · 'phase' {id, kind, dur?, form?} (form changes — 掠海漂移体 'crawl', 暴鸰
|
||||
// 'bombed', translator_*, a prisoner's 'warning' — and barrier / charge states) · 'lpLoss' {value, reason} · 'steal' · 'ignite'. Forms go through
|
||||
// setForm(): the unit keeps it (`e.form`, UnitInfo `form`) and the fx's `form` is the model's clip set from then on
|
||||
// (render/app.js → UnitView.setForm, units.js FORMS); the client keeps every fx with a `form` through catch-ups and
|
||||
// hidden tabs (shared/protocol.js fxForm).
|
||||
|
||||
@@ -232,16 +232,28 @@ const selfFear = (ab) => ({
|
||||
},
|
||||
});
|
||||
|
||||
/** Prisoners (禁锢 → 解放). */
|
||||
/**
|
||||
* Prisoners (孤岛风云: 禁锢 → 解放). The official battle prefabs (local client battle/enm_pfb_*.ab) have three modes and the
|
||||
* buff templates (buff_template_data enemy_confinement, enemy_confinement[atk_cnt], enemy_liberty_listener[warning]) move
|
||||
* between them: the 禁锢终端 starts with `confinement.times` charges; before each attack (ON_BEFORE_ATTACK — here the
|
||||
* `before` hook: an attack 麻痹 interrupts never gets there, PRTS 拳师囚犯 "在持有麻痹的情况下,攻击不计数") one is spent, and
|
||||
* the one that leaves a single charge switches it to mode R, the warning — still confined, its collar light blinking
|
||||
* orange; before the next attack it switches to mode L, 【解放】, so the times-th attack already hits freed (PRTS
|
||||
* "进行第4次攻击前,切换至解放状态"; until 0.2.0 it was freed after that attack's damage). The model follows the mode
|
||||
* (setForm: UnitInfo `form`, render/units.js FORMS): the manifest's clips are the grey confined set, 'warning' the orange
|
||||
* set, 'liberty' the red one — the 'liberate' fx carries it (a community report of 2026-10-06: they came out of the gate
|
||||
* drawn freed and never changed).
|
||||
*/
|
||||
function prisoner(ab, { freeAll = false } = {}) {
|
||||
const t = (k) => T(ab, k) ?? 0;
|
||||
const times = Math.max(1, Math.round(T(ab, 'confinement.times') ?? 4));
|
||||
const liberate = (b, e, a) => {
|
||||
if (a.free || !e.alive) return;
|
||||
a.free = true;
|
||||
b.removeBuff(e, 'ab:confined');
|
||||
b.addBuff(e, { key: 'ab:liberty', persist: true, visible: true, mods: {
|
||||
atkPct: t('liberty.atk'), defIgnorePct: t('liberty.def_penetrate'), resFlat: t('liberty.magic_resistance'), hpRegen: t('liberty.hp_recovery_per_sec') } });
|
||||
b.fx('liberate', { x: e.x, y: e.y, id: e.id });
|
||||
setForm(b, e, 'liberty', 'liberate');
|
||||
if (freeAll && !stOf(b).freedAll) {
|
||||
// "第一次解放时同时解放全场敌人"
|
||||
stOf(b).freedAll = true;
|
||||
@@ -254,7 +266,11 @@ function prisoner(ab, { freeAll = false } = {}) {
|
||||
a.n = 0; a.free = false;
|
||||
b.addBuff(e, { key: 'ab:confined', persist: true, visible: true, mods: { aspd: t('confinement.attack_speed'), defFlat: t('confinement.def') } });
|
||||
},
|
||||
attack(c, b, e, a) { if (!a.free && ++a.n >= (T(ab, 'confinement.times') ?? 4)) liberate(b, e, a); },
|
||||
before(c, b, e, a) {
|
||||
if (a.free) return;
|
||||
if (++a.n >= times) liberate(b, e, a);
|
||||
else if (a.n === times - 1) setForm(b, e, 'warning');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user