feedback2: merge WI — 归溟幽灵鲨's 替身 is drawn (form + status icon), makes no attack and casts nothing, S2's end switches her (GitHub #44)
This commit is contained in:
commit
0ac7dc7dd2
17 files changed
+572
-67
No files matched your search
@@ -184,6 +184,9 @@ export const FX_KINDS = Object.freeze({
|
||||
appear: { a: 'summon', c: 0xb36bff }, copy: { a: 'summon', c: 0xd8b0ff }, manifoldCopy: { a: 'summon', c: 0xd8b0ff }, split: { a: 'summon', c: 0xff9a6a },
|
||||
disappear: { a: 'vanish', c: 0xb36bff }, stealth: { a: 'vanish', c: 0x8fa0b0 }, camouflage: { a: 'vanish', c: 0x8fb08f }, phase: { a: 'vanish', c: 0xb36bff },
|
||||
substitute: { a: 'vanish', c: 0xd8b0ff }, swap: { a: 'blink', c: 0xd8b0ff }, blink: { a: 'blink', c: 0xb36bff }, teleport: { a: 'blink', c: 0xb36bff },
|
||||
// a dollkeeper knocked out as its <替身> (sim professions.js): only its model form goes back to the 本体 for the
|
||||
// redeploy — the 替身's death clip already shows the knock-out, nothing is drawn
|
||||
dollEnd: { a: 'none', c: 0xd8b0ff },
|
||||
ulpiaReturn: { a: 'blink', c: 0x9ff0dc }, manifoldSplit: { a: 'blink', c: 0xd8b0ff },
|
||||
// displacement
|
||||
pull: { a: 'move', c: 0x9fd4ff }, push: { a: 'move', c: 0xffd9a0 }, displace: { a: 'move', c: 0xd0c0a0 }, lure: { a: 'move', c: 0xffb3ec },
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// die() die clip once (callers fade out afterwards); a skeleton without one holds its
|
||||
// idle clip's first frame (GitHub issue #25: the attack loop went on)
|
||||
// stunned (setBase('stun')) stun clip, or the current track frozen at timeScale 0
|
||||
// setForm(roles, change, end) another clip set of the skeleton (an enemy's mode), after a change clip
|
||||
// setForm(roles, change, end) another clip set of the skeleton (an enemy's mode, a 傀儡师's 替身), after a change clip
|
||||
// (no attack cuts the change clip short); `end` = { clip, in, roles? }: a
|
||||
// closing clip timed to end `in` s from now (a 重生's last clip ends with the
|
||||
// 重生), landing in `roles`
|
||||
@@ -122,18 +122,22 @@ export class SpineActor {
|
||||
setSkillIndex(index) {
|
||||
const anims = this.entry?.anims || {};
|
||||
const clip = Number.isInteger(index) && anims.skills ? anims.skills[String(index)] : null;
|
||||
this.roles = clip ? { ...anims, skill: clip } : anims;
|
||||
this.roles = this.baseRoles = clip ? { ...anims, skill: clip } : anims;
|
||||
}
|
||||
|
||||
/** The unit's own roles: the manifest's with its equipped skill's clip (setSkillIndex) — what a form ends in. */
|
||||
_baseRoles() { return this.baseRoles || this.entry?.anims || {}; }
|
||||
|
||||
/**
|
||||
* Another clip set of the same skeleton — an enemy's mode (render/units.js FORMS: 掠海漂移体's 爬行模式 plays its *_02
|
||||
* clips): `roles` override the manifest roles (null = back to them); `change` = a transition clip played once first
|
||||
* (also while stunned: the pose it ends in is the one a stun then holds). `end` = { clip, in, roles? } (game s): a
|
||||
* closing clip played the same way so that it ends `in` s from now, landing in `roles` — a leader's 重生 ends on its
|
||||
* last clip while the sim still holds it, and the next form starts on its own clips.
|
||||
* clips), a 傀儡师's 替身: `roles` override the unit's own roles (null = back to them — the equipped skill's clip
|
||||
* included); `change` = a transition clip played once first (also while stunned: the pose it ends in is the one a stun
|
||||
* then holds). `end` = { clip, in, roles? } (game s): a closing clip played the same way so that it ends `in` s from
|
||||
* now, landing in `roles` — a leader's 重生 ends on its last clip while the sim still holds it, and the next form
|
||||
* starts on its own clips.
|
||||
*/
|
||||
setForm(roles, change = null, end = null) {
|
||||
const anims = this.entry?.anims || {};
|
||||
const anims = this._baseRoles();
|
||||
this.roles = roles ? { ...anims, ...roles } : anims;
|
||||
this.endClip = null;
|
||||
if (this.dead) return;
|
||||
@@ -391,7 +395,7 @@ export class SpineActor {
|
||||
if (this.endClip && this.clock >= this.endAt) {
|
||||
const clip = this.endClip;
|
||||
this.endClip = null;
|
||||
if (this.endRoles) this.roles = { ...(this.entry?.anims || {}), ...this.endRoles };
|
||||
if (this.endRoles) this.roles = { ...this._baseRoles(), ...this.endRoles };
|
||||
if (!this.dead) this._change(clip);
|
||||
}
|
||||
if (this.windUntil != null && this.clock >= this.windUntil) {
|
||||
|
||||
@@ -125,6 +125,8 @@ export const STATUS_ICON = Object.freeze({
|
||||
silence: 'silence', slow: 'slow', sluggish: 'slow', bind: 'bind', fear: 'fear', tremble: 'fear', weaken: 'weaken',
|
||||
levitate: 'levitate', taunt: 'taunt', defDown: 'weaken', resDown: 'weaken', aspdDown: 'slow', disarm: 'silence',
|
||||
burn: 'burn', burnBurst: 'burn', neural: 'neural', neuralBurst: 'neural', necrosis: 'necrosis', apoptosis: 'necrosis',
|
||||
// a 傀儡师 fighting as its <替身> (sim professions.js buff 'trait:substitute', the 20 s form)
|
||||
substitute: 'doll',
|
||||
});
|
||||
|
||||
/** Keyword fallbacks for namespaced / content status keys ('ab:frost', 'reed2:scorch', 'skill:shotst_shred' …). */
|
||||
|
||||
@@ -835,7 +835,7 @@ export function fxAtlas() {
|
||||
return _fx;
|
||||
}
|
||||
|
||||
export const STATUS_KEYS = ['stun', 'freeze', 'cold', 'stealth', 'shield', 'fragile', 'sleep', 'invuln', 'silence', 'slow', 'bind', 'fear', 'weaken', 'levitate', 'taunt', 'burn', 'neural', 'necrosis', 'blocked', 'skill'];
|
||||
export const STATUS_KEYS = ['stun', 'freeze', 'cold', 'stealth', 'shield', 'fragile', 'sleep', 'invuln', 'silence', 'slow', 'bind', 'fear', 'weaken', 'levitate', 'taunt', 'burn', 'neural', 'necrosis', 'blocked', 'skill', 'doll'];
|
||||
|
||||
function drawStatusIcon(c, key, x, y, s) {
|
||||
const cx = x + s / 2, cy = y + s / 2;
|
||||
@@ -930,6 +930,17 @@ function drawStatusIcon(c, key, x, y, s) {
|
||||
c.fillStyle = '#ffe066'; star(c, cx, cy, 10, 4.5);
|
||||
break;
|
||||
}
|
||||
case 'doll': {
|
||||
// a 傀儡师's <替身>: a puppet hanging from two strings
|
||||
disc('rgba(40,25,55,0.92)', '#d8b0ff');
|
||||
c.strokeStyle = '#f0dcff'; c.lineWidth = 1.2;
|
||||
c.beginPath(); c.moveTo(cx - 6, cy - 12); c.lineTo(cx - 6, cy - 1); c.moveTo(cx + 6, cy - 12); c.lineTo(cx + 6, cy - 1); c.stroke();
|
||||
c.fillStyle = '#f0dcff';
|
||||
c.beginPath(); c.arc(cx, cy - 5, 3.2, 0, Math.PI * 2); c.fill();
|
||||
c.beginPath(); c.moveTo(cx - 6, cy - 1); c.lineTo(cx + 6, cy - 1); c.lineTo(cx + 3, cy + 5); c.lineTo(cx + 5, cy + 11); c.lineTo(cx + 1.5, cy + 11);
|
||||
c.lineTo(cx, cy + 6); c.lineTo(cx - 1.5, cy + 11); c.lineTo(cx - 5, cy + 11); c.lineTo(cx - 3, cy + 5); c.closePath(); c.fill();
|
||||
break;
|
||||
}
|
||||
default: disc('rgba(30,30,30,0.9)', '#ccc');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +157,13 @@ export const EL_BAR = Object.freeze({ icon: 0.15, min: 8, max: 15, enemy: 0.8, g
|
||||
* clip (`end`) is timed from the 重生's `dur` (the 'telegraph' fx) to end with it, so the second form walks and
|
||||
* attacks on its own clips at once (a view that missed the timing — built mid-重生 — skips the closing clip);
|
||||
* - 守墓石像 (forms 'stone' → 'fly'): the statue on Sleep [ASSUMED by name], then the flyer's *_2 clips.
|
||||
* - the 傀儡师 operators' <替身> (sim professions.js installDollkeeper: form 'doll' from the start of the switch to it
|
||||
* until the switch back starts, GitHub issue #44): the skeletons draw it on their *_B clips (their own slots — the
|
||||
* 本体'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`:
|
||||
* 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 (归溟幽灵鲨 also Start_2): the switch clips it lacks are skipped.
|
||||
* 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 });
|
||||
@@ -180,7 +187,17 @@ const STATUE = Object.freeze({
|
||||
fly: Object.freeze({ change: null, roles: clipSet('Idle_2', 'Move_2', 'Die_2', 'Attack_2') }),
|
||||
});
|
||||
const JAKILL2 = clipSet('C2_Idle', 'C2_Move', 'C2_Die', 'C2_Attack');
|
||||
/** A 傀儡师's 替身 roles: idle `idle`, death `die`, attack `attack` (null: none), no skill clip of its own. */
|
||||
const dollRoles = (idle, die, attack = null) => Object.freeze({
|
||||
idle, deploy: idle, die, attack: attack ? Object.freeze({ begin: null, loop: attack, end: null }) : null, attackDown: null, skill: null,
|
||||
});
|
||||
export const FORMS = Object.freeze({
|
||||
char_1023_ghost2: Object.freeze({
|
||||
doll: Object.freeze({ change: 'Start_B', end: 'Die_B', leave: 'Start_2', roles: dollRoles('Idle_B', 'Die_B_2') }),
|
||||
}),
|
||||
char_4016_kazema: Object.freeze({
|
||||
doll: Object.freeze({ change: 'Start_B', leave: 'Start', roles: dollRoles('Idle_B', 'Die_B', 'Attack_B') }),
|
||||
}),
|
||||
enemy_1040_bombd: Object.freeze({
|
||||
bombed: Object.freeze({
|
||||
roles: Object.freeze({
|
||||
@@ -487,14 +504,15 @@ export class UnitView {
|
||||
* The unit changed mode (the `form` of a sim fx — shared/protocol.js fxForm; `fx` = that fx's extra): the mode's clip
|
||||
* set (FORMS) after its change clip, and its closing clip (`end`, landing in the `next` form's clips) timed to end
|
||||
* `fx.dur` game s later — the unit is still in this mode while it plays (an ember can be beaten in its last second),
|
||||
* so this mode's death clip stays until the next mode's fx; null goes back to the manifest clips; a kind this skeleton
|
||||
* has no clip set for (an arts barrier, a broken charge …) changes nothing. Kept for a model built later.
|
||||
* so this mode's death clip stays until the next mode's fx; null goes back to the manifest clips (through the old
|
||||
* mode's `leave` clip when it has one: a 替身's 本体 coming back); a kind this skeleton has no clip set for (an arts
|
||||
* barrier, a broken charge …) changes nothing. Kept for a model built later.
|
||||
*/
|
||||
setForm(kind, fx = null) {
|
||||
const k = typeof kind === 'string' ? kind : null;
|
||||
if (k === this.form) return;
|
||||
if (k && !FORMS[this.info.spine || this.info.defId]?.[k]) return;
|
||||
const had = !!this._formSpec();
|
||||
const prev = this._formSpec();
|
||||
this.form = k;
|
||||
this.info.form = k;
|
||||
const f = this._formSpec();
|
||||
@@ -506,7 +524,7 @@ export class UnitView {
|
||||
const late = fx && Number(fx.late) > 0 ? Number(fx.late) : 0;
|
||||
const change = f && f.change && !(late > 0 && late >= (this.actor.dur?.(f.change) ?? Infinity)) ? f.change : null;
|
||||
if (f) this.actor.setForm(f.roles, change, f.end && dur > 0 ? { clip: f.end, in: dur, roles: next } : null);
|
||||
else if (had) this.actor.setForm(null);
|
||||
else if (prev) this.actor.setForm(null, prev.leave && !(late > 0 && late >= (this.actor.dur?.(prev.leave) ?? Infinity)) ? prev.leave : null);
|
||||
}
|
||||
|
||||
// ---- HUD -------------------------------------------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user