feedback2 WI: a dollkeeper knocked out as its 替身 lies as the 替身 facing UP and on a model rebuilt while down (the view keeps the form it died in); Back clip lists and form comments corrected, the unused knockout fx removed (review of #44)
This commit is contained in:
1 parent
0ac7dc7dd2
commit
29784db4d9
5 files changed
+138
-24
No files matched your search
+5
-2
@@ -239,8 +239,11 @@ stealth bit only while its 隐匿 is on:
|
|||||||
- the 傀儡师 operators' <替身> (form `doll` of the sim's 'substitute' / 'swap' / 'dollEnd' fx, DESIGN §22.11; the `*_B`
|
- the 傀儡师 operators' <替身> (form `doll` of the sim's 'substitute' / 'swap' / 'dollEnd' fx, DESIGN §22.11; the `*_B`
|
||||||
clips draw the 替身's own slots and hide the 本体's): 归溟幽灵鲨 `Start_B` (it fades in), `Idle_B`, `Die_B` over its last
|
clips draw the 替身's own slots and hide the 本体's): 归溟幽灵鲨 `Start_B` (it fades in), `Idle_B`, `Die_B` over its last
|
||||||
second (it breaks apart and fades), `Die_B_2` when it is knocked out (it collapses), and the 本体 back on `Start_2` (a
|
second (it breaks apart and fades), `Die_B_2` when it is knocked out (it collapses), and the 本体 back on `Start_2` (a
|
||||||
form's `leave` clip); 风丸 `Start_B`, `Idle_B` / `Attack_B` / `Die_B`, the 本体 back on `Start`. Their Back skeletons
|
form's `leave` clip); 风丸 `Start_B`, `Idle_B` / `Attack_B` / `Die_B`, the 本体 back on `Start`. Facing up, 归溟幽灵鲨's
|
||||||
(facing up) only have `Idle_B` (归溟幽灵鲨 also `Start_2`): the missing switch clips are skipped.
|
Back skeleton has only `Idle_B` and `Start_2`, 风丸's `Start_B`, `Idle_B` and `Attack_B`: the missing clips are skipped.
|
||||||
|
Neither Back skeleton has the 替身's death clip, so a 替身 knocked out lies on the Front model (like every knocked-out
|
||||||
|
operator facing up, DESIGN §22.1) with its `Die_B_2` / `Die_B`: the view keeps the form it died in for that model and
|
||||||
|
for one rebuilt while it lies down, although the sim resets the form right after the knock-out.
|
||||||
|
|
||||||
Not mapped (clip names ambiguous): “自在”, “巨大的丑东西”, 主角阵营角色 and “余音” (`*_A` / `*_B`: which of its two forms is A
|
Not mapped (clip names ambiguous): “自在”, “巨大的丑东西”, 主角阵营角色 and “余音” (`*_A` / `*_B`: which of its two forms is A
|
||||||
is not known) keep their manifest clips.
|
is not known) keep their manifest clips.
|
||||||
|
|||||||
@@ -258,8 +258,9 @@ export const showsDeathFx = (info, consumed = false, reason = null) => !consumed
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The views' info of a battle unit from its UnitInfo (m.field / fieldMeta `units`, a 'spawn' event; snapshot.js
|
* The views' info of a battle unit from its UnitInfo (m.field / fieldMeta `units`, a 'spawn' event; snapshot.js
|
||||||
* unitInfo), sanitised; null for a malformed entry. `form` — an enemy's current model form (content/enemies.js setForm:
|
* unitInfo), sanitised; null for a malformed entry. `form` — the unit's current model form (an enemy's, content/enemies.js
|
||||||
* 转译基底·α's forms, a 逐火 余烬, a leader after its 重生, 掠海漂移体's crawl) — makes a view built mid-battle (a teammate's
|
* setForm: 转译基底·α's forms, a 逐火 余烬, a leader after its 重生, 掠海漂移体's crawl; a 傀儡师 fighting as its 替身, sim
|
||||||
|
* professions.js) — makes a view built mid-battle (a teammate's
|
||||||
* field watched later, 联防 observers, a reconnect, server-run watchers: no fx of the change is replayed) start on that
|
* field watched later, 联防 observers, a reconnect, server-run watchers: no fx of the change is replayed) start on that
|
||||||
* clip set (render/units.js FORMS); it used to be dropped here, so such views drew the first form (player report #5).
|
* clip set (render/units.js FORMS); it used to be dropped here, so such views drew the first form (player report #5).
|
||||||
*/
|
*/
|
||||||
@@ -273,7 +274,7 @@ export function renderInfo(u) {
|
|||||||
// deploy direction of allies (UnitInfo.dir, DESIGN §3): the model (Back for UP, mirrored for LEFT) and the
|
// deploy direction of allies (UnitInfo.dir, DESIGN §3): the model (Back for UP, mirrored for LEFT) and the
|
||||||
// ground wedge follow it; absent = unknown (legacy frames) → derived from `facing`, no wedge
|
// ground wedge follow it; absent = unknown (legacy frames) → derived from `facing`, no wedge
|
||||||
dir: typeof u.dir === 'string' ? u.dir : undefined,
|
dir: typeof u.dir === 'string' ? u.dir : undefined,
|
||||||
// an enemy's current model form (UnitInfo.form): the view starts in it (UnitView reads info.form)
|
// the unit's current model form (UnitInfo.form: an enemy's mode, a 傀儡师's 替身): the view starts in it (UnitView reads info.form)
|
||||||
form: typeof u.form === 'string' ? u.form : undefined,
|
form: typeof u.form === 'string' ? u.form : undefined,
|
||||||
// DESIGN §16 loadout of an ally (UnitInfo.skillIndex / moduleId): the Spine actor plays that skill's clip, and a
|
// DESIGN §16 loadout of an ally (UnitInfo.skillIndex / moduleId): the Spine actor plays that skill's clip, and a
|
||||||
// tap hands them to the detail card (a teammate's unit shows its owner's skill / module)
|
// tap hands them to the detail card (a teammate's unit shows its owner's skill / module)
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ export const FX_KINDS = Object.freeze({
|
|||||||
frostNova: { a: 'blast', c: 0x9fe6ff, smoke: 0x1c2630 }, sunBurst: { a: 'blast', c: 0xffe28a }, meltdown: { a: 'blast', c: 0xff5a2a, r: 1.5, heavy: true },
|
frostNova: { a: 'blast', c: 0x9fe6ff, smoke: 0x1c2630 }, sunBurst: { a: 'blast', c: 0xffe28a }, meltdown: { a: 'blast', c: 0xff5a2a, r: 1.5, heavy: true },
|
||||||
iceSpike: { a: 'blast', c: 0xbfeeff, smoke: 0x1c2630 }, rockfall: { a: 'blast', c: 0xc8a878, smoke: 0x4a3f33 }, rockslide: { a: 'blast', c: 0xc8a878, smoke: 0x4a3f33 },
|
iceSpike: { a: 'blast', c: 0xbfeeff, smoke: 0x1c2630 }, rockfall: { a: 'blast', c: 0xc8a878, smoke: 0x4a3f33 }, rockslide: { a: 'blast', c: 0xc8a878, smoke: 0x4a3f33 },
|
||||||
finale: { a: 'blast', c: 0xffd45a, r: 1.5 }, swordStorm: { a: 'blast', c: 0xdfe8ff }, swordRain: { a: 'blast', c: 0xdfe8ff }, liberate: { a: 'blast', c: 0xffffff },
|
finale: { a: 'blast', c: 0xffd45a, r: 1.5 }, swordStorm: { a: 'blast', c: 0xdfe8ff }, swordRain: { a: 'blast', c: 0xdfe8ff }, liberate: { a: 'blast', c: 0xffffff },
|
||||||
knockout: { a: 'crit', c: 0xffc27a }, quadShot: { a: 'volley', c: 0xfff2d0 }, featherArrow: { a: 'counter', c: 0xfff2d0 },
|
quadShot: { a: 'volley', c: 0xfff2d0 }, featherArrow: { a: 'counter', c: 0xfff2d0 },
|
||||||
burst: { a: 'element', c: 0xd0a0ff },
|
burst: { a: 'element', c: 0xd0a0ff },
|
||||||
// areas
|
// areas
|
||||||
zone: { a: 'zone', c: 0xffb35c, dur: 3 }, healField: { a: 'zone', c: 0x62f08a, dur: 4 }, firewall: { a: 'wall', c: 0xff6a2a, dur: 4 },
|
zone: { a: 'zone', c: 0xffb35c, dur: 3 }, healField: { a: 'zone', c: 0x62f08a, dur: 4 }, firewall: { a: 'wall', c: 0xff6a2a, dur: 4 },
|
||||||
|
|||||||
@@ -36,8 +36,9 @@
|
|||||||
// Enemy modes (the `form` of a sim fx — shared/protocol.js fxForm — → `setForm(form, fx)`): 掠海漂移体 dropping to 爬行模式
|
// Enemy modes (the `form` of a sim fx — shared/protocol.js fxForm — → `setForm(form, fx)`): 掠海漂移体 dropping to 爬行模式
|
||||||
// (user playtest #5 item 1) plays its skeleton's 'Change' clip once, then the crawl set (*_02); 暴鸰 flies on without its
|
// (user playtest #5 item 1) plays its skeleton's 'Change' clip once, then the crawl set (*_02); 暴鸰 flies on without its
|
||||||
// bomb (*_2) after the drop (feedback D4); 转译基底's forms, the 逐火 embers, 再生's puppet, the leaders' 重生 and 守墓石像
|
// bomb (*_2) after the drop (feedback D4); 转译基底's forms, the 逐火 embers, 再生's puppet, the leaders' 重生 and 守墓石像
|
||||||
// likewise (user report after 0.1.0) — FORMS. A view built later (`info.form` = UnitInfo `form`, the sim's current form,
|
// likewise (user report after 0.1.0) — FORMS; an operator's form is a 傀儡师's 替身 (GitHub issue #44). A view built later
|
||||||
// through render/app.js renderInfo) starts in the mode.
|
// (`info.form` = UnitInfo `form`, the sim's current form, through render/app.js renderInfo) starts in the mode; a dead
|
||||||
|
// view keeps the form it died in (`_dieForm`) for a model built while it lies down.
|
||||||
// Element gauges (b.snap `elem` → sample `el` / `elFill` / `elUntil` / `elDur`), the official form (PRTS 元素: "模型
|
// Element gauges (b.snap `elem` → sample `el` / `elFill` / `elUntil` / `elDur`), the official form (PRTS 元素: "模型
|
||||||
// 下部会显示对应的元素图标,并以白条显示剩余的元素值"; enemies "小尺寸图标(不显示元素图标,仅根据元素种类改变背景色)"): a row
|
// 下部会显示对应的元素图标,并以白条显示剩余的元素值"; enemies "小尺寸图标(不显示元素图标,仅根据元素种类改变背景色)"): a row
|
||||||
// right under the unit's own HP / SP bars and inside their span — the element's disc at the left (operators with its
|
// right under the unit's own HP / SP bars and inside their span — the element's disc at the left (operators with its
|
||||||
@@ -162,8 +163,11 @@ export const EL_BAR = Object.freeze({ icon: 0.15, min: 8, max: 15, enemy: 0.8, g
|
|||||||
* 本体's are hidden). 归溟幽灵鲨: Start_B fades it in (the 1 s switch), Idle_B (it never attacks), Die_B breaks it
|
* 本体's are hidden). 归溟幽灵鲨: Start_B fades it in (the 1 s switch), Idle_B (it never attacks), Die_B breaks it
|
||||||
* apart over its last second (`end`, timed from the 'substitute' fx's `dur`), the 本体 comes back on Start_2 (`leave`:
|
* apart over its last second (`end`, timed from the 'substitute' fx's `dur`), the 本体 comes back on Start_2 (`leave`:
|
||||||
* played when the form ends); knocked out as the 替身 it collapses on Die_B_2 and stays down so. 风丸: Start_B, then
|
* played when the form ends); knocked out as the 替身 it collapses on Die_B_2 and stays down so. 风丸: Start_B, then
|
||||||
* Idle_B / Attack_B (her 替身 attacks), Die_B; the 本体 comes back on Start. A Back skeleton (facing UP) only has
|
* Idle_B / Attack_B (her 替身 attacks), Die_B; the 本体 comes back on Start. Facing UP: 归溟幽灵鲨's Back skeleton has
|
||||||
* Idle_B (归溟幽灵鲨 also Start_2): the switch clips it lacks are skipped.
|
* only Idle_B and Start_2, 风丸's Start_B, Idle_B and Attack_B — the clips a Back skeleton lacks are skipped; neither has
|
||||||
|
* the 替身's death clip, so a 替身 knocked out lies on the Front model like every knocked-out operator facing UP
|
||||||
|
* (_wantsBack). The sim resets the form right after the 'die' event; the form it died in (`_dieForm`) gives the model
|
||||||
|
* built for the knock-out — and one rebuilt while it is down — the 替身's death clip; it stands up as the 本体.
|
||||||
* A kind without a clip set of this skeleton (barriers, charges, …) changes nothing. 吉兆飞鳞's 晕眩模式 is its Stun clip.
|
* A kind without a clip set of this skeleton (barriers, charges, …) changes nothing. 吉兆飞鳞's 晕眩模式 is its Stun clip.
|
||||||
*/
|
*/
|
||||||
const loop = (name, via = null) => Object.freeze(via ? { begin: null, loop: name, end: null, via } : { begin: null, loop: name, end: null });
|
const loop = (name, via = null) => Object.freeze(via ? { begin: null, loop: name, end: null, via } : { begin: null, loop: name, end: null });
|
||||||
@@ -328,7 +332,8 @@ export class UnitView {
|
|||||||
this.shake = 0;
|
this.shake = 0;
|
||||||
this.screen = { x: 0, y: 0, s: 1, top: 0 };
|
this.screen = { x: 0, y: 0, s: 1, top: 0 };
|
||||||
this.destroyed = false;
|
this.destroyed = false;
|
||||||
this.form = typeof info.form === 'string' ? info.form : null; // an enemy's mode (setForm, FORMS)
|
this.form = typeof info.form === 'string' ? info.form : null; // the unit's model form: an enemy's mode, a 傀儡师's 替身 (setForm, FORMS)
|
||||||
|
this._dieForm = null; // the form it died in (die): a model built while it lies down shows that form's death
|
||||||
|
|
||||||
// --- display objects
|
// --- display objects
|
||||||
this.shadow = new P.Sprite(ctx.shadowTex || shadowTexture());
|
this.shadow = new P.Sprite(ctx.shadowTex || shadowTexture());
|
||||||
@@ -473,8 +478,10 @@ export class UnitView {
|
|||||||
this.spineReady = true;
|
this.spineReady = true;
|
||||||
this.swapT = swap ? 1 : 0;
|
this.swapT = swap ? 1 : 0;
|
||||||
this.actor.spine.alpha = swap ? 1 : 0;
|
this.actor.spine.alpha = swap ? 1 : 0;
|
||||||
// a mode the unit is already in (a model built or rebuilt after the change): its clip set, no change clip
|
// a mode the unit is already in (a model built or rebuilt after the change): its clip set, no change clip — a dead
|
||||||
const f = this._formSpec();
|
// one lies in the form it died in (a 替身 knocked out: the sim resets the form at once, but the Front model it lies
|
||||||
|
// down with facing UP, §22.1, is built a frame later; a model rebuilt while it is down)
|
||||||
|
const f = this._formSpec() || (this.alive ? null : this._dieForm);
|
||||||
if (f) this.actor.setForm(f.roles);
|
if (f) this.actor.setForm(f.roles);
|
||||||
// replay current state (a dead model resumes its Die clip where it would be — a knocked-down one holds its end)
|
// replay current state (a dead model resumes its Die clip where it would be — a knocked-down one holds its end)
|
||||||
if (!this.alive) {
|
if (!this.alive) {
|
||||||
@@ -763,12 +770,15 @@ export class UnitView {
|
|||||||
* An operator facing UP whose Back model has no Die clip (GitHub issue #25) falls with its Front model: the swap is
|
* An operator facing UP whose Back model has no Die clip (GitHub issue #25) falls with its Front model: the swap is
|
||||||
* made by this frame's update (`_modelDirty` → _syncModel, after every event and snapshot of the frame, so a death
|
* made by this frame's update (`_modelDirty` → _syncModel, after every event and snapshot of the frame, so a death
|
||||||
* and a redeploy in one frame — a 突袭 jump, a 不屈 revive, a backlog after a hidden tab — load nothing); meanwhile the
|
* and a redeploy in one frame — a 突袭 jump, a 不屈 revive, a backlog after a hidden tab — load nothing); meanwhile the
|
||||||
* Back model holds still (SpineActor.die), and the Front model's Die clip times the fade.
|
* Back model holds still (SpineActor.die), and the Front model's Die clip times the fade. The form it dies in
|
||||||
|
* (`_dieForm`: a 傀儡师's 替身, which the sim resets right after the 'die' event) gives that model — and any built while
|
||||||
|
* it lies down — its death clip, until it stands up again.
|
||||||
*/
|
*/
|
||||||
die(instant = false) {
|
die(instant = false) {
|
||||||
if (!this.alive) return;
|
if (!this.alive) return;
|
||||||
this.alive = false;
|
this.alive = false;
|
||||||
this._modelDirty = true;
|
this._modelDirty = true;
|
||||||
|
this._dieForm = this._formSpec();
|
||||||
let d = this.actor ? this.actor.die() : 0;
|
let d = this.actor ? this.actor.die() : 0;
|
||||||
if (this.actor && !d) d = this._fallDur();
|
if (this.actor && !d) d = this._fallDur();
|
||||||
const rate = this.ctx.animRate?.() || 1;
|
const rate = this.ctx.animRate?.() || 1;
|
||||||
@@ -789,6 +799,9 @@ export class UnitView {
|
|||||||
revive() {
|
revive() {
|
||||||
this.alive = true;
|
this.alive = true;
|
||||||
this._modelDirty = true;
|
this._modelDirty = true;
|
||||||
|
// a model built while it lay in a form's death pose (_dieForm) stands up on the unit's own clips
|
||||||
|
if (this._dieForm && this.actor && !this._formSpec()) this.actor.setForm(null);
|
||||||
|
this._dieForm = null;
|
||||||
this.dying = 0; this.remove = false; this.alpha = 1;
|
this.dying = 0; this.remove = false; this.alpha = 1;
|
||||||
this.down = null;
|
this.down = null;
|
||||||
this.hp = this.maxHp; this.ghostHp = this.hp;
|
this.hp = this.maxHp; this.ghostHp = this.hp;
|
||||||
|
|||||||
@@ -2,8 +2,10 @@
|
|||||||
// its <替身> is in the model form 'doll' (sim professions.js: fx 'substitute' { form: 'doll', dur } … 'swap' / 'dollEnd'
|
// its <替身> is in the model form 'doll' (sim professions.js: fx 'substitute' { form: 'doll', dur } … 'swap' / 'dollEnd'
|
||||||
// { form: null }, UnitInfo `form`), drawn with the skeleton's *_B clips (render/units.js FORMS): 归溟幽灵鲨's Start_B fades
|
// { form: null }, UnitInfo `form`), drawn with the skeleton's *_B clips (render/units.js FORMS): 归溟幽灵鲨's Start_B fades
|
||||||
// the 替身 in, Idle_B, Die_B breaks it apart over its last second, the 本体 comes back on Start_2 (the form's `leave`
|
// the 替身 in, Idle_B, Die_B breaks it apart over its last second, the 本体 comes back on Start_2 (the form's `leave`
|
||||||
// clip); knocked out as the 替身 she collapses on Die_B_2 and stays so. 0.1.1 drew the 本体 all along. Headless fake PIXI
|
// clip); knocked out as the 替身 she collapses on Die_B_2 and stays so — facing UP too, on the Front model a knocked-out
|
||||||
// (test/render/fakepixi.js); the clip meanings are checked on the real skeleton when the assets are fetched.
|
// operator lies with (§22.1), and on any model built while she lies down. 0.1.1 drew the 本体 all along. Headless fake
|
||||||
|
// PIXI (test/render/fakepixi.js) on the real manifest and asset helpers; the clip meanings are checked on the real skeleton
|
||||||
|
// when the assets are fetched.
|
||||||
|
|
||||||
import { test, describe, before, after } from 'node:test';
|
import { test, describe, before, after } from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
@@ -15,6 +17,7 @@ import { installFakePixi, fakeViewCtx } from './fakepixi.js';
|
|||||||
import { presetCamera } from '../../public/js/render/projection.js';
|
import { presetCamera } from '../../public/js/render/projection.js';
|
||||||
import { statusIconKey } from '../../public/js/render/style.js';
|
import { statusIconKey } from '../../public/js/render/style.js';
|
||||||
import { STATUS_KEYS } from '../../public/js/render/textures.js';
|
import { STATUS_KEYS } from '../../public/js/render/textures.js';
|
||||||
|
import { spineEntry, hasBackSpine } from '../../public/js/assets.js';
|
||||||
import { fxForm } from '../../shared/protocol.js';
|
import { fxForm } from '../../shared/protocol.js';
|
||||||
import { makeBattle, enemyRec } from '../helpers/battleHarness.js';
|
import { makeBattle, enemyRec } from '../helpers/battleHarness.js';
|
||||||
import { unitInfo } from '../../server/sim/snapshot.js';
|
import { unitInfo } from '../../server/sim/snapshot.js';
|
||||||
@@ -34,19 +37,20 @@ after(() => fake.restore());
|
|||||||
|
|
||||||
const tick = () => new Promise((r) => setImmediate(r));
|
const tick = () => new Promise((r) => setImmediate(r));
|
||||||
const cam = () => presetCamera('normal', { width: 1280, height: 720 });
|
const cam = () => presetCamera('normal', { width: 1280, height: 720 });
|
||||||
/** Asset store with the operator's real Front / Back manifest entries and their animation names. */
|
/** Asset store on the real manifest (Front / Back entries, their animation names) and asset helpers; loads counted. */
|
||||||
function store(id) {
|
function store() {
|
||||||
const sp = assets.chars[id].spine;
|
const s = {
|
||||||
return {
|
loads: 0,
|
||||||
picture: () => null,
|
picture: () => null,
|
||||||
image: async () => null,
|
image: async () => null,
|
||||||
hasBack: () => !!sp.back,
|
hasBack: (id) => hasBackSpine(assets, id),
|
||||||
spineEntry: (_, o = {}) => (o.back ? sp.back : sp.front),
|
spineEntry: (id, o) => spineEntry(assets, id, o),
|
||||||
spine: { acquire: async (entry) => ({ animations: Object.keys(entry.animations || {}).map((name) => ({ name })) }), release() {} },
|
spine: { acquire: async (entry) => { s.loads++; return { animations: Object.keys(entry.animations || {}).map((name) => ({ name })) }; }, release() {} },
|
||||||
};
|
};
|
||||||
|
return s;
|
||||||
}
|
}
|
||||||
async function op(id, info = {}) {
|
async function op(id, info = {}) {
|
||||||
const ctx = fakeViewCtx(fake.P, { assets: store(id), cam });
|
const ctx = fakeViewCtx(fake.P, { assets: store(), cam });
|
||||||
const v = new UnitView(ctx, { id: 1, side: 'ally', kind: 'op', defId: id, spine: id, tier: 5, x: 5, y: 9, maxHp: 2657, dir: 'RIGHT', skillIndex: 1, ...info });
|
const v = new UnitView(ctx, { id: 1, side: 'ally', kind: 'op', defId: id, spine: id, tier: 5, x: 5, y: 9, maxHp: 2657, dir: 'RIGHT', skillIndex: 1, ...info });
|
||||||
await tick(); await tick();
|
await tick(); await tick();
|
||||||
assert.ok(v.actor, 'Spine model built');
|
assert.ok(v.actor, 'Spine model built');
|
||||||
@@ -134,6 +138,99 @@ describe('归溟幽灵鲨\'s 替身 (FORMS doll)', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Knocked out as the 替身 with the sim's order in one frame: 'die', then fx 'dollEnd' { form: null } (the form back to the
|
||||||
|
// 本体 for the redeploy), then the frame's update. Facing UP the view lies down on the Front model (§22.1: the Back
|
||||||
|
// skeleton has no fall), loaded after the reset: it still lies as the 替身 (the form it died in, `_dieForm`), and so does
|
||||||
|
// any model built while she is down; the redeploy is the 本体 on the Back model.
|
||||||
|
describe('knocked out as the 替身: facing UP, models built while down, the redeploy (§22.1 × §22.11)', () => {
|
||||||
|
const DOWN = [1, 70, 70, 0, 9, 5];
|
||||||
|
const settle = async () => { await tick(); await tick(); };
|
||||||
|
async function doll(id, dir) {
|
||||||
|
const ctx = fakeViewCtx(fake.P, { assets: store(), cam });
|
||||||
|
const v = new UnitView(ctx, { id: 1, side: 'ally', kind: 'op', defId: id, spine: id, tier: 5, x: 5, y: 9, maxHp: 2657, dir, skillIndex: 1 });
|
||||||
|
await settle();
|
||||||
|
v.setForm('doll', { id: 1, form: 'doll', dur: 21 });
|
||||||
|
secs(v, 2);
|
||||||
|
assert.equal(clip(v), 'Idle_B');
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
const knockOut = (v) => { v.die(); v.setForm(null, { id: 1, form: null }); v.setDown(DOWN, 0); };
|
||||||
|
|
||||||
|
test('归溟幽灵鲨 facing UP: the Front model shows the 替身 collapsing (Die_B_2), with the reset in the same frame or the next', async () => {
|
||||||
|
for (const late of [false, true]) {
|
||||||
|
const v = await doll(GHOST, 'UP');
|
||||||
|
assert.ok(v.entryBack, 'standing: the Back model');
|
||||||
|
v.die();
|
||||||
|
if (!late) v.setForm(null, { id: 1, form: null });
|
||||||
|
v.setDown(DOWN, 0);
|
||||||
|
frames(v, 1);
|
||||||
|
if (late) v.setForm(null, { id: 1, form: null });
|
||||||
|
await settle(); secs(v, 0.5);
|
||||||
|
assert.ok(!v.entryBack, 'down: the Front model (its Back skeleton has no fall)');
|
||||||
|
assert.equal(clip(v), 'Die_B_2', `the 替身 collapses (reset ${late ? 'a frame later' : 'in the same frame'})`);
|
||||||
|
assert.equal(v.form, null, 'the form is the 本体\'s again');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('a model built while she lies down keeps the 替身\'s pose (facing RIGHT: dropped and loaded again)', async () => {
|
||||||
|
const v = await doll(GHOST, 'RIGHT');
|
||||||
|
knockOut(v);
|
||||||
|
frames(v, 1); await settle(); secs(v, 0.5);
|
||||||
|
assert.equal(clip(v), 'Die_B_2');
|
||||||
|
v._dropActor(); v.retryAssets(); await settle(); frames(v, 5);
|
||||||
|
assert.equal(clip(v), 'Die_B_2', 'rebuilt: still the 替身');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the redeploy is the 本体: facing UP back on the Back model, its deploy clip, then its idle', async () => {
|
||||||
|
const v = await doll(GHOST, 'UP');
|
||||||
|
knockOut(v);
|
||||||
|
frames(v, 1); await settle(); secs(v, 2);
|
||||||
|
v.setDown(null); v.onDeploy();
|
||||||
|
frames(v, 1); await settle(); frames(v, 2);
|
||||||
|
assert.ok(v.entryBack, 'the Back model again');
|
||||||
|
assert.equal(clip(v), 'Start');
|
||||||
|
secs(v, 1.2);
|
||||||
|
assert.equal(clip(v), 'Idle', 'the 本体\'s idle, not Idle_B');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('revived in the same frame (阿戈尔: die → deploy → dollEnd): the 本体 comes back on Start_2, no model is loaded', async () => {
|
||||||
|
for (const dir of ['UP', 'RIGHT']) {
|
||||||
|
const v = await doll(GHOST, dir);
|
||||||
|
const loads = v.ctx.assets.loads;
|
||||||
|
v.die(); v.onDeploy(); v.setForm(null, { id: 1, form: null });
|
||||||
|
frames(v, 1); await settle(); frames(v, 2);
|
||||||
|
assert.equal(clip(v), 'Start_2', dir);
|
||||||
|
assert.equal(v.ctx.assets.loads, loads, `${dir}: no model load`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('风丸 facing UP: her Back skeleton\'s Start_B and Attack_B; knocked out as the 替身, the Front model\'s Die_B', async () => {
|
||||||
|
const ctx = fakeViewCtx(fake.P, { assets: store(), cam });
|
||||||
|
const v = new UnitView(ctx, { id: 1, side: 'ally', kind: 'op', defId: KAZEMA, spine: KAZEMA, tier: 2, x: 5, y: 9, maxHp: 1816, dir: 'UP', skillIndex: 0 });
|
||||||
|
await settle();
|
||||||
|
assert.ok(v.entryBack);
|
||||||
|
v.setForm('doll', { id: 1, form: 'doll', dur: 21 });
|
||||||
|
assert.equal(clip(v), 'Start_B', 'its Back skeleton has the switch clip');
|
||||||
|
secs(v, 1.1);
|
||||||
|
v.onAttack(null, 2);
|
||||||
|
assert.equal(clip(v), 'Attack_B', '…and the 替身\'s attack');
|
||||||
|
secs(v, 2);
|
||||||
|
knockOut(v);
|
||||||
|
frames(v, 1); await settle(); secs(v, 0.5);
|
||||||
|
assert.ok(!v.entryBack);
|
||||||
|
assert.equal(clip(v), 'Die_B');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('[ASSUMED] a view built from UnitInfo while she lies down (no form: the sim reset it) shows the 本体\'s fall', async () => {
|
||||||
|
const ctx = fakeViewCtx(fake.P, { assets: store(), cam });
|
||||||
|
const v = new UnitView(ctx, { id: 1, side: 'ally', kind: 'op', defId: GHOST, spine: GHOST, tier: 5, x: 5, y: 9, maxHp: 2657, dir: 'UP', skillIndex: 1 });
|
||||||
|
await settle();
|
||||||
|
v.setDown(DOWN, 0, true);
|
||||||
|
frames(v, 1); await settle(); frames(v, 2);
|
||||||
|
assert.equal(clip(v), 'Die');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('风丸\'s 替身: Start_B, Idle_B / Attack_B (it attacks), Die_B; the 本体 back on Start', async () => {
|
test('风丸\'s 替身: Start_B, Idle_B / Attack_B (it attacks), Die_B; the 本体 back on Start', async () => {
|
||||||
const anims = assets.chars[KAZEMA].spine.front.animations;
|
const anims = assets.chars[KAZEMA].spine.front.animations;
|
||||||
for (const name of ['Start_B', 'Idle_B', 'Attack_B', 'Die_B', 'Start']) assert.ok(name in anims, name);
|
for (const name of ['Start_B', 'Idle_B', 'Attack_B', 'Die_B', 'Start']) assert.ok(name in anims, name);
|
||||||
|
|||||||
Reference in new issue
Block a user