Mesin mode pasca-ultimate generik: aura, buff damage/kecepatan, skill dan ultimate pengganti, bentuk (atlas) kedua opsional
Setiap karakter data-driven boleh punya blok mode: nama, durasi, pengali damage/kecepatan, skill 1 dan 2 pengganti dengan cooldown sendiri, ultimate pengganti sekali pakai yang mengakhiri mode, HUD (nama dan ikon), cut-in tersendiri, dan opsi form: atlas sprite kedua selama mode.
This commit is contained in:
parent
db8f74c1bd
commit
60f70d805b
84
game.js
84
game.js
|
|
@ -677,7 +677,7 @@
|
||||||
// along the floor (ground projectiles with `tall`, so a jump does not clear them), 16 each, 48 total, fading after
|
// along the floor (ground projectiles with `tall`, so a jump does not clear them), 16 each, 48 total, fading after
|
||||||
// SO.roar.range px. SOLAN is free after the 0.5 s cast; a rival farther away than the range is safe.
|
// SO.roar.range px. SOLAN is free after the 0.5 s cast; a rival farther away than the range is safe.
|
||||||
function startSunroar(actor = hero) {
|
function startSunroar(actor = hero) {
|
||||||
const group = { t: 0, owner: actor === hero ? 'player' : 'enemy', facing: actor.facing, roars: 0, phase: 'cutin', kind: actor.bwRage ? 'tornado' : 'forest', cf: CF[actor === hero ? selectedCharacter : opponentCharacter] ? (actor === hero ? selectedCharacter : opponentCharacter) : null };
|
const group = { t: 0, owner: actor === hero ? 'player' : 'enemy', facing: actor.facing, roars: 0, phase: 'cutin', kind: actor.bwRage ? (CF[actor === hero ? selectedCharacter : opponentCharacter] ? 'mode' : 'tornado') : 'forest', cf: CF[actor === hero ? selectedCharacter : opponentCharacter] ? (actor === hero ? selectedCharacter : opponentCharacter) : null };
|
||||||
if (actor === hero) sunroar = group; else enemySunroar = group;
|
if (actor === hero) sunroar = group; else enemySunroar = group;
|
||||||
cinematic = .9; announceTimer = 0;
|
cinematic = .9; announceTimer = 0;
|
||||||
{ const vid = actor === hero ? selectedCharacter : opponentCharacter; startUltimateVoice(CF[vid] || vid === 'browo' ? vid : 'solan', actor); }
|
{ const vid = actor === hero ? selectedCharacter : opponentCharacter; startUltimateVoice(CF[vid] || vid === 'browo' ? vid : 'solan', actor); }
|
||||||
|
|
@ -705,10 +705,10 @@
|
||||||
// dives onto the caster under a dark screen, the rival is held, the beats land, then an optional finisher. See work/WORKFLOW.md.
|
// dives onto the caster under a dark screen, the rival is held, the beats land, then an optional finisher. See work/WORKFLOW.md.
|
||||||
const cfx = { ents: [], hazards: [], marks: [] };
|
const cfx = { ents: [], hazards: [], marks: [] };
|
||||||
function cfKit(d) {
|
function cfKit(d) {
|
||||||
const move = (name, index = 1) => {
|
const move = (name, index = 1, mode = false) => {
|
||||||
if (name === 'attack') return { name: `attack${index}`, type: 'attack', index, ...d.combo[index - 1] };
|
if (name === 'attack') return { name: `attack${index}`, type: 'attack', index, ...d.combo[index - 1] };
|
||||||
if (name === 'ultimate') return { name: 'ultimate', type: 'ultimate', duration: d.balance.castTime, damage: 0, hitAt: 1 };
|
if (name === 'ultimate') return { name: 'ultimate', type: 'ultimate', duration: d.balance.castTime, damage: 0, hitAt: 1 };
|
||||||
const k = d.skills[name], m = { name, type: name, knockback: 160, duration: .6, ...k, hitAt: k.hitAt ?? .5 };
|
const k = (mode && d.mode?.skills?.[name]) || d.skills[name], m = { name, type: name, knockback: 160, duration: .6, ...k, hitAt: k.hitAt ?? .5 };
|
||||||
if (k.kind === 'projectile' || k.kind === 'wave' || k.kind === 'homing' || k.kind === 'lob') { m.projectile = true; m.range = k.range || 560; m.reach = m.range; }
|
if (k.kind === 'projectile' || k.kind === 'wave' || k.kind === 'homing' || k.kind === 'lob') { m.projectile = true; m.range = k.range || 560; m.reach = m.range; }
|
||||||
if (k.kind === 'dash') m.dash = k.dash;
|
if (k.kind === 'dash') m.dash = k.dash;
|
||||||
if (k.kind === 'area' || k.kind === 'pillar') { m.area = true; m.reach = k.reach || ((k.ahead || 0) + (k.radius || 0)); }
|
if (k.kind === 'area' || k.kind === 'pillar') { m.area = true; m.reach = k.reach || ((k.ahead || 0) + (k.radius || 0)); }
|
||||||
|
|
@ -717,8 +717,9 @@
|
||||||
const start = (a, name, index = 1) => {
|
const start = (a, name, index = 1) => {
|
||||||
if (a.action || a.state === 'hurt' || a.state === 'down' || a.state === 'recover') return false;
|
if (a.action || a.state === 'hurt' || a.state === 'down' || a.state === 'recover') return false;
|
||||||
if (name !== 'attack' && a.cooldowns[name] > 0) return false;
|
if (name !== 'attack' && a.cooldowns[name] > 0) return false;
|
||||||
if (name !== 'attack') a.cooldowns[name] = d.balance.cooldowns[name];
|
const rg = !!a.bwRage && !!d.mode && name !== 'attack';
|
||||||
a.action = { ...move(name, index), t: 0, fired: false, queued: 0 }; a.state = a.action.name; a.stateTime = 0; return true;
|
if (name !== 'attack') a.cooldowns[name] = rg ? (name === 'ultimate' ? 999 : (d.mode.cooldowns?.[name] ?? 6)) : d.balance.cooldowns[name];
|
||||||
|
a.action = { ...move(name, index, rg), t: 0, fired: false, queued: 0 }; a.state = a.action.name; a.stateTime = 0; return true;
|
||||||
};
|
};
|
||||||
const refund = a => { for (const n of Object.keys(d.balance.cooldowns)) a.cooldowns[n] = Math.max(0, a.cooldowns[n] - d.balance.cooldowns[n] * (d.balance.basicRefund ?? .05)); };
|
const refund = a => { for (const n of Object.keys(d.balance.cooldowns)) a.cooldowns[n] = Math.max(0, a.cooldowns[n] - d.balance.cooldowns[n] * (d.balance.basicRefund ?? .05)); };
|
||||||
return { balance: d.balance, names: d.names, combo: d.combo, move, start, refund, def: d };
|
return { balance: d.balance, names: d.names, combo: d.combo, move, start, refund, def: d };
|
||||||
|
|
@ -727,6 +728,8 @@
|
||||||
const enemy = actor === dummy, target = enemy ? hero : dummy, id = def.id, em = def.metrics?.emitters?.[move.name], col = def.color || '#ffd36a';
|
const enemy = actor === dummy, target = enemy ? hero : dummy, id = def.id, em = def.metrics?.emitters?.[move.name], col = def.color || '#ffd36a';
|
||||||
const hy = clamp(actor.y + (em?.y ?? -95), actor.y - 190, actor.y - 40), owner = enemy ? 'enemy' : 'player', key = a => id + '-' + a;
|
const hy = clamp(actor.y + (em?.y ?? -95), actor.y - 190, actor.y - 40), owner = enemy ? 'enemy' : 'player', key = a => id + '-' + a;
|
||||||
const hit = (dmg, kb, x, y, knockdown = false) => enemy ? window.__game.receiveHit(dmg) : hitDummy(dmg, kb, col, x, y, knockdown, true);
|
const hit = (dmg, kb, x, y, knockdown = false) => enemy ? window.__game.receiveHit(dmg) : hitDummy(dmg, kb, col, x, y, knockdown, true);
|
||||||
|
const raging = !!actor.bwRage && !!def.mode, mul = raging ? (def.mode.damageMul || 1.3) : 1, radd = raging ? (def.mode.reachAdd || 15) : 0;
|
||||||
|
move = { ...move, damage: Math.round((move.damage || 0) * mul), reach: (move.reach || 0) + radd };
|
||||||
const k = move.kind;
|
const k = move.kind;
|
||||||
if (k === 'projectile' || k === 'wave' || k === 'homing') {
|
if (k === 'projectile' || k === 'wave' || k === 'homing') {
|
||||||
const n = move.count || 1, ox = actor.x + actor.facing * Math.round((em?.x ?? 100) * .7);
|
const n = move.count || 1, ox = actor.x + actor.facing * Math.round((em?.x ?? 100) * .7);
|
||||||
|
|
@ -813,7 +816,7 @@
|
||||||
}
|
}
|
||||||
// The ultimate director. s.t counts from the cast; beats start after `hold` seconds of camera dive.
|
// The ultimate director. s.t counts from the cast; beats start after `hold` seconds of camera dive.
|
||||||
function updateCfUltimate(dt, s) {
|
function updateCfUltimate(dt, s) {
|
||||||
const def = CF[s.cf], U = def.ultimate, a = s.owner === 'enemy' ? dummy : hero, target = s.owner === 'enemy' ? hero : dummy, tk = s.owner === 'enemy' ? 'player' : 'enemy';
|
const def = CF[s.cf], U = s.kind === 'mode' ? def.mode.ultimate : def.ultimate, a = s.owner === 'enemy' ? dummy : hero, target = s.owner === 'enemy' ? hero : dummy, tk = s.owner === 'enemy' ? 'player' : 'enemy';
|
||||||
const hold = U.hold ?? 1.1, gap = U.gap ?? .12, count = U.count ?? 10, endAct = hold + (count - 1) * gap + (U.settle ?? .5), col = def.color || '#ffd36a', id = def.id;
|
const hold = U.hold ?? 1.1, gap = U.gap ?? .12, count = U.count ?? 10, endAct = hold + (count - 1) * gap + (U.settle ?? .5), col = def.color || '#ffd36a', id = def.id;
|
||||||
s.t += dt; const mx = (a.x + target.x) / 2, dist = Math.abs(target.x - a.x);
|
s.t += dt; const mx = (a.x + target.x) / 2, dist = Math.abs(target.x - a.x);
|
||||||
if (s.t < hold) { bwx.zoomT = U.zoom ?? 1.7; bwx.dimT = U.dim ?? .8; bwx.cx = a.x; bwx.cy = a.y - 90; if (s.t > .15) trauma = Math.min(.35, trauma + dt * .5); }
|
if (s.t < hold) { bwx.zoomT = U.zoom ?? 1.7; bwx.dimT = U.dim ?? .8; bwx.cx = a.x; bwx.cy = a.y - 90; if (s.t > .15) trauma = Math.min(.35, trauma + dt * .5); }
|
||||||
|
|
@ -850,16 +853,20 @@
|
||||||
if (bwAlive(target)) { if (target === dummy) { if (!hitDummy(F2.damage, 360, col, a.x, CONFIG.groundY - 100, true, true, 2)) dotDamage(dummy, F2.damage, col); } else if (!window.__game.receiveHit(F2.damage, { shake: 2 })) dotDamage(hero, F2.damage, col); }
|
if (bwAlive(target)) { if (target === dummy) { if (!hitDummy(F2.damage, 360, col, a.x, CONFIG.groundY - 100, true, true, 2)) dotDamage(dummy, F2.damage, col); } else if (!window.__game.receiveHit(F2.damage, { shake: 2 })) dotDamage(hero, F2.damage, col); }
|
||||||
}
|
}
|
||||||
s.phase = s.t < .78 ? 'cutin' : 'roaring';
|
s.phase = s.t < .78 ? 'cutin' : 'roaring';
|
||||||
if (s.t >= endAct + (U.tail ?? .5)) { if (s.owner === 'enemy') enemySunroar = null; else sunroar = null; }
|
if (s.t >= endAct + (U.tail ?? .5)) {
|
||||||
|
const ok = s.owner === 'enemy' ? 'enemy' : 'player';
|
||||||
|
if (s.kind === 'mode') bwExitRage(ok); else if (def.mode) { bwEnterRage(ok); announce(def.mode.announce || 'MODE!', 1.3); effects.push({ type: 'cf-fx', asset: s.cf + '-burst', x: a.x, y: a.y - 95, facing: 1, life: .6, maxLife: .6, size: 380 }); }
|
||||||
|
if (s.owner === 'enemy') enemySunroar = null; else sunroar = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
function cfCutin(key, owner) {
|
function cfCutin(key, owner) {
|
||||||
const el = document.querySelector('.cutin'); if (!el) return; const f = CF[key];
|
const el = document.querySelector('.cutin'); if (!el) return; const isMode = key.endsWith('~mode'), f = CF[key.replace('~mode', '')];
|
||||||
if (!f) { el.style.background = ''; el.style.borderColor = ''; el.querySelector('.cutin-kicker').style.color = ''; el.querySelector('.cutin-caption em') && (el.querySelector('.cutin-caption em').style.color = ''); return; }
|
if (!f) { el.style.background = ''; el.style.borderColor = ''; el.querySelector('.cutin-kicker').style.color = ''; const e0 = el.querySelector('.cutin-caption em'); if (e0) e0.style.color = ''; return; }
|
||||||
const c = f.cutin || {};
|
const c = (isMode ? f.mode?.cutin : f.cutin) || {}, nm = isMode ? f.mode.names[3] : f.names[3];
|
||||||
el.style.background = `linear-gradient(100deg,${c.a || '#222'},${c.b || '#555'} 58%,${c.a || '#222'})`; el.style.borderColor = f.color || '#fff';
|
el.style.background = `linear-gradient(100deg,${c.a || '#222'},${c.b || '#555'} 58%,${c.a || '#222'})`; el.style.borderColor = (isMode && f.mode.aura) || f.color || '#fff';
|
||||||
el.querySelector('.cutin-kicker').textContent = f.name + ' / ' + owner; el.querySelector('.cutin-kicker').style.color = c.kicker || '#fff';
|
el.querySelector('.cutin-kicker').textContent = f.name + ' / ' + owner; el.querySelector('.cutin-kicker').style.color = c.kicker || '#fff';
|
||||||
el.querySelector('strong').innerHTML = c.title || f.names[3]; el.querySelector('.cutin-detail').textContent = c.detail || '';
|
el.querySelector('strong').innerHTML = c.title || nm; el.querySelector('.cutin-detail').textContent = c.detail || '';
|
||||||
el.setAttribute('aria-label', f.name + ' ultimate: ' + f.names[3]);
|
el.setAttribute('aria-label', f.name + ' ultimate: ' + nm);
|
||||||
const em = el.querySelector('.cutin-caption em'); if (em) em.style.color = f.color || '';
|
const em = el.querySelector('.cutin-caption em'); if (em) em.style.color = f.color || '';
|
||||||
}
|
}
|
||||||
function drawCfBack() {
|
function drawCfBack() {
|
||||||
|
|
@ -892,20 +899,32 @@
|
||||||
bwx.zoom = bwx.zoomT = 1; bwx.dim = bwx.dimT = 0; bwx.rage.player = bwx.rage.enemy = 0; bwx.poison.player = bwx.poison.enemy = null; bwx.lock.player = bwx.lock.enemy = 0; bwx.dance.length = 0; bwx.giant = null;
|
bwx.zoom = bwx.zoomT = 1; bwx.dim = bwx.dimT = 0; bwx.rage.player = bwx.rage.enemy = 0; bwx.poison.player = bwx.poison.enemy = null; bwx.lock.player = bwx.lock.enemy = 0; bwx.dance.length = 0; bwx.giant = null;
|
||||||
bwx.fires.length = 0; bwx.burn.player = bwx.burn.enemy = 0; bwx.burn.shown.player = bwx.burn.shown.enemy = 0; bwx.tornado = null; cfx.ents.length = cfx.hazards.length = cfx.marks.length = 0;
|
bwx.fires.length = 0; bwx.burn.player = bwx.burn.enemy = 0; bwx.burn.shown.player = bwx.burn.shown.enemy = 0; bwx.tornado = null; cfx.ents.length = cfx.hazards.length = cfx.marks.length = 0;
|
||||||
hero.bwRage = dummy.bwRage = false; hero.bwSaved = dummy.bwSaved = null;
|
hero.bwRage = dummy.bwRage = false; hero.bwSaved = dummy.bwSaved = null;
|
||||||
if (BW && selectedCharacter === 'browo') Object.assign(cooldownMax, BW.balance.cooldowns);
|
{ const b = bwBaseCooldowns(selectedCharacter); if (b) Object.assign(cooldownMax, b); if (CF[selectedCharacter]?.mode?.form) syncPlayerForm(); }
|
||||||
}
|
}
|
||||||
// Rage swaps the specials: skills 1/2 and the ultimate start fresh (their own cooldowns, see BW.rageBalance); the normal
|
// Rage swaps the specials: skills 1/2 and the ultimate start fresh (their own cooldowns, see BW.rageBalance); the normal
|
||||||
// cooldowns are put back when the rampage ends.
|
// cooldowns are put back when the rampage ends.
|
||||||
|
// The post-ultimate "mode": BRO WO's Mengamuk (bespoke) or CF[id].mode (data). Same machinery for both: timer in bwx.rage[side],
|
||||||
|
// a.bwRage tells the kit to use the mode's skills, cooldowns swap in and out, and an optional `form` swaps the whole atlas.
|
||||||
|
function modeOf(id) {
|
||||||
|
if (id === 'browo') return BW ? { duration: BW.rage.duration, aura: '#ff6e28', label: 'MENGAMUK', announce: 'MENGAMUK!', names: BW.rageNames, icons: ['basic', 'rage-skill1', 'rage-skill2', 'rage-ultimate'], cooldowns: BW.rageBalance.cooldowns, speedMul: BW.rage.speedMul } : null;
|
||||||
|
const m = CF[id]?.mode; if (!m) return null;
|
||||||
|
return { duration: m.duration || 10, aura: m.aura || '#ff5a3c', label: m.label || 'MODE', announce: m.announce || 'MODE!', names: m.names, icons: ['basic', 'mode-skill1', 'mode-skill2', 'mode-ultimate'], cooldowns: { skill1: m.cooldowns?.skill1 ?? 5, skill2: m.cooldowns?.skill2 ?? 8, ultimate: 999 }, speedMul: m.speedMul || 1.15 };
|
||||||
|
}
|
||||||
|
function cfFormOn(id, side) { return !!(CF[id]?.mode?.form && bwx.rage[side] > 0); }
|
||||||
|
function bwBaseCooldowns(id) { return id === 'browo' ? BW.balance.cooldowns : CF[id] ? CF[id].balance.cooldowns : null; }
|
||||||
function bwEnterRage(key) {
|
function bwEnterRage(key) {
|
||||||
const a = key === 'player' ? hero : dummy; bwx.rage[key] = BW.rage.duration;
|
const a = key === 'player' ? hero : dummy, id = key === 'player' ? selectedCharacter : opponentCharacter, M = modeOf(id); if (!M) return;
|
||||||
|
bwx.rage[key] = M.duration;
|
||||||
a.bwSaved = { skill1: a.cooldowns.skill1, skill2: a.cooldowns.skill2, ultimate: a.cooldowns.ultimate };
|
a.bwSaved = { skill1: a.cooldowns.skill1, skill2: a.cooldowns.skill2, ultimate: a.cooldowns.ultimate };
|
||||||
a.cooldowns.skill1 = a.cooldowns.skill2 = a.cooldowns.ultimate = 0; a.bwRage = true;
|
a.cooldowns.skill1 = a.cooldowns.skill2 = a.cooldowns.ultimate = 0; a.bwRage = true;
|
||||||
if (key === 'player') Object.assign(cooldownMax, BW.rageBalance.cooldowns);
|
if (key === 'player') { if (CF[id]?.mode?.form) syncPlayerForm(); Object.assign(cooldownMax, M.cooldowns); }
|
||||||
|
hudIdentity = '';
|
||||||
}
|
}
|
||||||
function bwExitRage(key) {
|
function bwExitRage(key) {
|
||||||
const a = key === 'player' ? hero : dummy; bwx.rage[key] = 0; a.bwRage = false;
|
const a = key === 'player' ? hero : dummy, id = key === 'player' ? selectedCharacter : opponentCharacter; bwx.rage[key] = 0; a.bwRage = false;
|
||||||
if (a.bwSaved) { for (const n of ['skill1', 'skill2', 'ultimate']) a.cooldowns[n] = a.bwSaved[n]; a.bwSaved = null; }
|
if (a.bwSaved) { for (const n of ['skill1', 'skill2', 'ultimate']) a.cooldowns[n] = a.bwSaved[n]; a.bwSaved = null; }
|
||||||
if (key === 'player' && BW) Object.assign(cooldownMax, BW.balance.cooldowns);
|
if (key === 'player') { if (CF[id]?.mode?.form) syncPlayerForm(); const b = bwBaseCooldowns(id); if (b) Object.assign(cooldownMax, b); }
|
||||||
|
hudIdentity = '';
|
||||||
}
|
}
|
||||||
function bwAlive(target) { return target === dummy ? !dummy.ko && dummy.state !== 'down' && dummy.state !== 'recover' : hero.hp > 0; }
|
function bwAlive(target) { return target === dummy ? !dummy.ko && dummy.state !== 'down' && dummy.state !== 'recover' : hero.hp > 0; }
|
||||||
// Damage over time / scripted damage: no hurt animation, no knockback, no cooldown refunds; K.O. still applies.
|
// Damage over time / scripted damage: no hurt animation, no knockback, no cooldown refunds; K.O. still applies.
|
||||||
|
|
@ -926,7 +945,7 @@
|
||||||
function bwPoison(key) { const D = BW.dance, p = bwx.poison[key]; if (p) p.t = D.poisonTime; else bwx.poison[key] = { t: D.poisonTime, acc: 0, shown: 0 }; }
|
function bwPoison(key) { const D = BW.dance, p = bwx.poison[key]; if (p) p.t = D.poisonTime; else bwx.poison[key] = { t: D.poisonTime, acc: 0, shown: 0 }; }
|
||||||
function updateBrowo(dt) {
|
function updateBrowo(dt) {
|
||||||
const D = BW ? BW.dance : null; if (!D) return;
|
const D = BW ? BW.dance : null; if (!D) return;
|
||||||
hero.bwRage = selectedCharacter === 'browo' && bwx.rage.player > 0; dummy.bwRage = opponentCharacter === 'browo' && bwx.rage.enemy > 0;
|
hero.bwRage = !!modeOf(selectedCharacter) && bwx.rage.player > 0; dummy.bwRage = !!modeOf(opponentCharacter) && bwx.rage.enemy > 0;
|
||||||
// Dance bursts: each burst that reaches the rival stuns them, deals damage and refreshes the poison.
|
// Dance bursts: each burst that reaches the rival stuns them, deals damage and refreshes the poison.
|
||||||
for (let n = bwx.dance.length - 1; n >= 0; n--) {
|
for (let n = bwx.dance.length - 1; n >= 0; n--) {
|
||||||
const d = bwx.dance[n], caster = d.owner === 'enemy' ? dummy : hero, target = d.owner === 'enemy' ? hero : dummy, tk = d.owner === 'enemy' ? 'player' : 'enemy';
|
const d = bwx.dance[n], caster = d.owner === 'enemy' ? dummy : hero, target = d.owner === 'enemy' ? hero : dummy, tk = d.owner === 'enemy' ? 'player' : 'enemy';
|
||||||
|
|
@ -1167,7 +1186,7 @@
|
||||||
if (!hero.action && hero.hurtTime <= 0 && hero.hp > 0) {
|
if (!hero.action && hero.hurtTime <= 0 && hero.hp > 0) {
|
||||||
if (dir && hero.doublePose <= 0) hero.facing = dir;
|
if (dir && hero.doublePose <= 0) hero.facing = dir;
|
||||||
if (hero.runDirection && dir !== hero.runDirection) hero.runDirection = 0;
|
if (hero.runDirection && dir !== hero.runDirection) hero.runDirection = 0;
|
||||||
const speed = (hero.runDirection === dir && dir ? CONFIG.runSpeed : CONFIG.walkSpeed)*(selectedCharacter==='fenr'&&hero.form==='wolf'?1.08:1)*(selectedCharacter==='browo'&&bwx.rage.player>0?BW.rage.speedMul:1);
|
const speed = (hero.runDirection === dir && dir ? CONFIG.runSpeed : CONFIG.walkSpeed)*(selectedCharacter==='fenr'&&hero.form==='wolf'?1.08:1)*(bwx.rage.player>0?(modeOf(selectedCharacter)?.speedMul||1):1);
|
||||||
hero.vx = approach(hero.vx, crouching ? 0 : dir * speed, dt * (hero.grounded ? 2800 : 1600));
|
hero.vx = approach(hero.vx, crouching ? 0 : dir * speed, dt * (hero.grounded ? 2800 : 1600));
|
||||||
if (hero.grounded) state(crouching ? 'crouch' : dir ? hero.runDirection ? 'run' : 'walk' : 'idle');
|
if (hero.grounded) state(crouching ? 'crouch' : dir ? hero.runDirection ? 'run' : 'walk' : 'idle');
|
||||||
else state(hero.doublePose > 0 ? 'doublejump' : 'jump');
|
else state(hero.doublePose > 0 ? 'doublejump' : 'jump');
|
||||||
|
|
@ -1263,7 +1282,7 @@
|
||||||
else if(selectedCharacter==='haldor') {manifest=window.HALDOR_MANIFEST;metrics=window.HALDOR_METRICS;images.hero=images.haldor;Object.assign(cooldownMax,HD.balance.cooldowns);}
|
else if(selectedCharacter==='haldor') {manifest=window.HALDOR_MANIFEST;metrics=window.HALDOR_METRICS;images.hero=images.haldor;Object.assign(cooldownMax,HD.balance.cooldowns);}
|
||||||
else if(selectedCharacter==='edda') {manifest=window.EDDA_MANIFEST;metrics=window.EDDA_METRICS;images.hero=images.edda;Object.assign(cooldownMax,ED.balance.cooldowns);}
|
else if(selectedCharacter==='edda') {manifest=window.EDDA_MANIFEST;metrics=window.EDDA_METRICS;images.hero=images.edda;Object.assign(cooldownMax,ED.balance.cooldowns);}
|
||||||
else if(selectedCharacter==='nib') {manifest=window.NIB_MANIFEST;metrics=window.NIB_METRICS;images.hero=images.nib;Object.assign(cooldownMax,NB.balance.cooldowns);}
|
else if(selectedCharacter==='nib') {manifest=window.NIB_MANIFEST;metrics=window.NIB_METRICS;images.hero=images.nib;Object.assign(cooldownMax,NB.balance.cooldowns);}
|
||||||
else if(CF[selectedCharacter]) {manifest=CF[selectedCharacter].manifest;metrics=CF[selectedCharacter].metrics;images.hero=images[selectedCharacter];Object.assign(cooldownMax,CF[selectedCharacter].balance.cooldowns);}
|
else if(CF[selectedCharacter]) {const fo=cfFormOn(selectedCharacter,'player'),F0=CF[selectedCharacter];manifest=fo?F0.mode.form.manifest:F0.manifest;metrics=fo?F0.mode.form.metrics:F0.metrics;images.hero=images[fo?selectedCharacter+'-form':selectedCharacter];Object.assign(cooldownMax,CF[selectedCharacter].balance.cooldowns);}
|
||||||
else if(selectedCharacter==='browo') {manifest=window.BROWO_MANIFEST;metrics=window.BROWO_METRICS;images.hero=images.browo;Object.assign(cooldownMax,BW.balance.cooldowns);}
|
else if(selectedCharacter==='browo') {manifest=window.BROWO_MANIFEST;metrics=window.BROWO_METRICS;images.hero=images.browo;Object.assign(cooldownMax,BW.balance.cooldowns);}
|
||||||
else if(selectedCharacter==='solan') {manifest=window.SOLAN_MANIFEST;metrics=window.SOLAN_METRICS;images.hero=images.solan;Object.assign(cooldownMax,SO.balance.cooldowns);}
|
else if(selectedCharacter==='solan') {manifest=window.SOLAN_MANIFEST;metrics=window.SOLAN_METRICS;images.hero=images.solan;Object.assign(cooldownMax,SO.balance.cooldowns);}
|
||||||
else if(selectedCharacter==='rhea') {manifest=window.RHEA_MANIFEST;metrics=window.RHEA_METRICS;images.hero=images.rhea;Object.assign(cooldownMax,RH.balance.cooldowns);}
|
else if(selectedCharacter==='rhea') {manifest=window.RHEA_MANIFEST;metrics=window.RHEA_METRICS;images.hero=images.rhea;Object.assign(cooldownMax,RH.balance.cooldowns);}
|
||||||
|
|
@ -1800,7 +1819,7 @@
|
||||||
dummy.aiThink = pick || dist < 260 ? Math.min(P.reaction, 1 / 60) : P.reaction * .5;
|
dummy.aiThink = pick || dist < 260 ? Math.min(P.reaction, 1 / 60) : P.reaction * .5;
|
||||||
}
|
}
|
||||||
function opponentPose() {
|
function opponentPose() {
|
||||||
const m=CF[opponentCharacter]?CF[opponentCharacter].manifest:opponentCharacter==='arco'?window.MECHA_MANIFEST:opponentCharacter==='mira'?window.MIRA_MANIFEST:opponentCharacter==='cora'?window.CORA_MANIFEST:opponentCharacter==='naja'?window.NAJA_MANIFEST:opponentCharacter==='haldor'?window.HALDOR_MANIFEST:opponentCharacter==='zanni'?window.ZANNI_MANIFEST:opponentCharacter==='isolde'?window.ISOLDE_MANIFEST:opponentCharacter==='rhea'?window.RHEA_MANIFEST:opponentCharacter==='solan'?window.SOLAN_MANIFEST:opponentCharacter==='nib'?window.NIB_MANIFEST:opponentCharacter==='edda'?window.EDDA_MANIFEST:opponentCharacter==='browo'?window.BROWO_MANIFEST:dummy.form==='wolf'?window.FENR_WOLF_MANIFEST:window.FENR_HUMAN_MANIFEST;
|
const m=CF[opponentCharacter]?(cfFormOn(opponentCharacter,'enemy')?CF[opponentCharacter].mode.form.manifest:CF[opponentCharacter].manifest):opponentCharacter==='arco'?window.MECHA_MANIFEST:opponentCharacter==='mira'?window.MIRA_MANIFEST:opponentCharacter==='cora'?window.CORA_MANIFEST:opponentCharacter==='naja'?window.NAJA_MANIFEST:opponentCharacter==='haldor'?window.HALDOR_MANIFEST:opponentCharacter==='zanni'?window.ZANNI_MANIFEST:opponentCharacter==='isolde'?window.ISOLDE_MANIFEST:opponentCharacter==='rhea'?window.RHEA_MANIFEST:opponentCharacter==='solan'?window.SOLAN_MANIFEST:opponentCharacter==='nib'?window.NIB_MANIFEST:opponentCharacter==='edda'?window.EDDA_MANIFEST:opponentCharacter==='browo'?window.BROWO_MANIFEST:dummy.form==='wolf'?window.FENR_WOLF_MANIFEST:window.FENR_HUMAN_MANIFEST;
|
||||||
const name=m?.frame_layout?.rows?.[dummy.state]?dummy.state:'idle',list=m?.frame_layout?.rows?.[name]||[];
|
const name=m?.frame_layout?.rows?.[dummy.state]?dummy.state:'idle',list=m?.frame_layout?.rows?.[name]||[];
|
||||||
let index=0;
|
let index=0;
|
||||||
// Projectile casts that release at 50% show wind-up -> thrust -> full extension, like the player's pose.
|
// Projectile casts that release at 50% show wind-up -> thrust -> full extension, like the player's pose.
|
||||||
|
|
@ -1880,7 +1899,7 @@
|
||||||
ctx.drawImage(images.hero, r.x, r.y, r.w, r.h, -anchorX, -anchorY, r.w, r.h); ctx.restore();
|
ctx.drawImage(images.hero, r.x, r.y, r.w, r.h, -anchorX, -anchorY, r.w, r.h); ctx.restore();
|
||||||
}
|
}
|
||||||
function drawDummy() {
|
function drawDummy() {
|
||||||
const pose=opponentPose(),m=pose.manifest,img=images[opponentCharacter==='fenr'?(dummy.form==='wolf'?'fenrWolf':'fenrHuman'):opponentCharacter];
|
const pose=opponentPose(),m=pose.manifest,img=images[opponentCharacter==='fenr'?(dummy.form==='wolf'?'fenrWolf':'fenrHuman'):(cfFormOn(opponentCharacter,'enemy')?opponentCharacter+'-form':opponentCharacter)];
|
||||||
shadow(dummy.x,CONFIG.groundY,dummy.state==='down'?65:opponentCharacter==='mira'?61:opponentCharacter==='haldor'?55:43,.20);
|
shadow(dummy.x,CONFIG.groundY,dummy.state==='down'?65:opponentCharacter==='mira'?61:opponentCharacter==='haldor'?55:43,.20);
|
||||||
if(!m||!img)return;
|
if(!m||!img)return;
|
||||||
const r=m.frame_layout.rows[pose.state][pose.frame];
|
const r=m.frame_layout.rows[pose.state][pose.frame];
|
||||||
|
|
@ -1925,8 +1944,9 @@
|
||||||
for (const key of ['player', 'enemy']) {
|
for (const key of ['player', 'enemy']) {
|
||||||
if (bwx.rage[key] <= 0) continue;
|
if (bwx.rage[key] <= 0) continue;
|
||||||
const a = key === 'player' ? hero : dummy; if (!bwAlive(a)) continue;
|
const a = key === 'player' ? hero : dummy; if (!bwAlive(a)) continue;
|
||||||
|
const aura = modeOf(key === 'player' ? selectedCharacter : opponentCharacter)?.aura || '#ff6e28', rgb = [1, 3, 5].map(k => parseInt(aura.slice(k, k + 2), 16)).join(',');
|
||||||
const pulse = .75 + Math.sin(realTime * 9) * .25, g = ctx.createRadialGradient(a.x, a.y - 80, 8, a.x, a.y - 80, 125);
|
const pulse = .75 + Math.sin(realTime * 9) * .25, g = ctx.createRadialGradient(a.x, a.y - 80, 8, a.x, a.y - 80, 125);
|
||||||
g.addColorStop(0, `rgba(255,110,40,${.42 * pulse})`); g.addColorStop(1, 'rgba(255,60,20,0)'); ctx.fillStyle = g; ctx.beginPath(); ctx.arc(a.x, a.y - 80, 125, 0, 7); ctx.fill();
|
g.addColorStop(0, `rgba(${rgb},${.42 * pulse})`); g.addColorStop(1, `rgba(${rgb},0)`); ctx.fillStyle = g; ctx.beginPath(); ctx.arc(a.x, a.y - 80, 125, 0, 7); ctx.fill();
|
||||||
if (Math.sin(realTime * 40) > .6) spawnParticles(a.x + (random() - .5) * 50, a.y - 40 - random() * 80, '#ff8a3a', 1, 80);
|
if (Math.sin(realTime * 40) > .6) spawnParticles(a.x + (random() - .5) * 50, a.y - 40 - random() * 80, '#ff8a3a', 1, 80);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2099,10 +2119,10 @@
|
||||||
}
|
}
|
||||||
function updateFighterIdentity() {
|
function updateFighterIdentity() {
|
||||||
if(!F)return;
|
if(!F)return;
|
||||||
const identity=selectedCharacter+(selectedCharacter==='browo'&&bwx.rage.player>0?'*rage':'')+':'+hero.form+':'+opponentCharacter+':'+dummy.form;
|
const identity=selectedCharacter+(bwx.rage.player>0&&modeOf(selectedCharacter)?'*mode':'')+':'+hero.form+':'+opponentCharacter+':'+dummy.form;
|
||||||
if(identity!==hudIdentity) {
|
if(identity!==hudIdentity) {
|
||||||
hudIdentity=identity;const me=fighterInfo(selectedCharacter,hero.form),rival=fighterInfo(opponentCharacter,dummy.form);
|
hudIdentity=identity;const me=fighterInfo(selectedCharacter,hero.form),rival=fighterInfo(opponentCharacter,dummy.form);
|
||||||
if(selectedCharacter==='browo'&&bwx.rage.player>0){me.names=BW.rageNames;me.icons=['basic','rage-skill1','rage-skill2','rage-ultimate'].map(n=>'assets/browo/ui/icon-'+n+'.webp');}
|
if(bwx.rage.player>0&&modeOf(selectedCharacter)){const M=modeOf(selectedCharacter);me.names=M.names;me.icons=M.icons.map(n=>'assets/'+selectedCharacter+'/ui/icon-'+n+'.webp');}
|
||||||
$('#player-name').textContent=me.name;$('#player-class').textContent=me.cls;$('#deck-character').textContent=me.deck;
|
$('#player-name').textContent=me.name;$('#player-class').textContent=me.cls;$('#deck-character').textContent=me.deck;
|
||||||
$('#player-title').textContent=me.title;
|
$('#player-title').textContent=me.title;
|
||||||
$('#player-portrait').src=me.portrait;$('#player-portrait').alt='Portrait '+me.name;
|
$('#player-portrait').src=me.portrait;$('#player-portrait').alt='Portrait '+me.name;
|
||||||
|
|
@ -2111,7 +2131,7 @@
|
||||||
$('.fighter-two').setAttribute('aria-label','Status lawan '+opponentCharacter.toUpperCase());
|
$('.fighter-two').setAttribute('aria-label','Status lawan '+opponentCharacter.toUpperCase());
|
||||||
document.querySelectorAll('.skill-card').forEach((card,i)=>{card.querySelector('.skill-name').textContent=me.names[i];card.querySelector('img').src=me.icons[i];card.setAttribute('aria-label',me.names[i]+', '+['Spasi','I','O','P'][i]);});
|
document.querySelectorAll('.skill-card').forEach((card,i)=>{card.querySelector('.skill-name').textContent=me.names[i];card.querySelector('img').src=me.icons[i];card.setAttribute('aria-label',me.names[i]+', '+['Spasi','I','O','P'][i]);});
|
||||||
}
|
}
|
||||||
$('#player-status').textContent=selectedCharacter==='fenr'?(hero.form==='wolf'?'FERAL ACTIVE':'RANGER READY'):selectedCharacter==='mira'?'PILOT READY':selectedCharacter==='cora'?'WINGS READY':selectedCharacter==='naja'?'SANDS READY':selectedCharacter==='haldor'?'FURNACE HOT':selectedCharacter==='zanni'?'SHOWTIME':selectedCharacter==='isolde'?'LANCE READY':selectedCharacter==='rhea'?'ORBITS SET':selectedCharacter==='solan'?'PRIDE READY':selectedCharacter==='nib'?'ON THE ROUTE':selectedCharacter==='edda'?'STEADY AS STONE':CF[selectedCharacter]?CF[selectedCharacter].status:selectedCharacter==='browo'?(bwx.rage.player>0?'MENGAMUK '+Math.ceil(bwx.rage.player)+'s':'SAFARI READY'):'CORE ACTIVE';
|
$('#player-status').textContent=selectedCharacter==='fenr'?(hero.form==='wolf'?'FERAL ACTIVE':'RANGER READY'):selectedCharacter==='mira'?'PILOT READY':selectedCharacter==='cora'?'WINGS READY':selectedCharacter==='naja'?'SANDS READY':selectedCharacter==='haldor'?'FURNACE HOT':selectedCharacter==='zanni'?'SHOWTIME':selectedCharacter==='isolde'?'LANCE READY':selectedCharacter==='rhea'?'ORBITS SET':selectedCharacter==='solan'?'PRIDE READY':selectedCharacter==='nib'?'ON THE ROUTE':selectedCharacter==='edda'?'STEADY AS STONE':CF[selectedCharacter]?(bwx.rage.player>0&&modeOf(selectedCharacter)?modeOf(selectedCharacter).label+' '+Math.ceil(bwx.rage.player)+'s':CF[selectedCharacter].status):selectedCharacter==='browo'?(bwx.rage.player>0?'MENGAMUK '+Math.ceil(bwx.rage.player)+'s':'SAFARI READY'):'CORE ACTIVE';
|
||||||
for(const [actor,id] of [[hero,'player-form-timer'],[dummy,'enemy-form-timer']]){const el=$('#'+id),active=actor.form==='wolf';el.hidden=!active;el.textContent=active?'FERAL '+Math.max(0,actor.formTime).toFixed(1)+' s':'';el.style.setProperty?.('--remaining',String(actor.formTime/F.balance.duration));}
|
for(const [actor,id] of [[hero,'player-form-timer'],[dummy,'enemy-form-timer']]){const el=$('#'+id),active=actor.form==='wolf';el.hidden=!active;el.textContent=active?'FERAL '+Math.max(0,actor.formTime).toFixed(1)+' s':'';el.style.setProperty?.('--remaining',String(actor.formTime/F.balance.duration));}
|
||||||
}
|
}
|
||||||
function updateHud() {
|
function updateHud() {
|
||||||
|
|
@ -2161,10 +2181,10 @@
|
||||||
// One banner at a time: FENR transformation, then ARCO, then a MIRA/CORA/NAJA/HALDOR summon still inside its cut-in window.
|
// One banner at a time: FENR transformation, then ARCO, then a MIRA/CORA/NAJA/HALDOR summon still inside its cut-in window.
|
||||||
const cutin=$('#ultimate-cutin'),live=g=>g&&g.t<SQUAD.cutinDuration?g:null;
|
const cutin=$('#ultimate-cutin'),live=g=>g&&g.t<SQUAD.cutinDuration?g:null;
|
||||||
const arcoGroup=live(enemySquad)||live(squad),summon=live(enemyParade)||live(parade)||live(enemyFlock)||live(flock)||live(enemySerpent)||live(serpent)||live(enemyQuake)||live(quake)||live(enemyFinale)||live(finale)||live(enemySkyfall)||live(skyfall)||live(enemyOrrery)||live(orrery)||live(enemySunroar)||live(sunroar)||live(enemyDelivery)||live(delivery)||live(enemyTortoise)||live(tortoise);
|
const arcoGroup=live(enemySquad)||live(squad),summon=live(enemyParade)||live(parade)||live(enemyFlock)||live(flock)||live(enemySerpent)||live(serpent)||live(enemyQuake)||live(quake)||live(enemyFinale)||live(finale)||live(enemySkyfall)||live(skyfall)||live(enemyOrrery)||live(orrery)||live(enemySunroar)||live(sunroar)||live(enemyDelivery)||live(delivery)||live(enemyTortoise)||live(tortoise);
|
||||||
const cutinKey=fenrCutin?'fenr':arcoGroup?'arco':summon?(summon===parade||summon===enemyParade?'mira':summon===serpent||summon===enemySerpent?'naja':summon===quake||summon===enemyQuake?'haldor':summon===finale||summon===enemyFinale?'zanni':summon===skyfall||summon===enemySkyfall?'isolde':summon===orrery||summon===enemyOrrery?'rhea':summon===sunroar||summon===enemySunroar?(summon.cf?summon.cf:(summon===enemySunroar?opponentCharacter:selectedCharacter)==='browo'?(summon.kind==='tornado'?'browofire':'browo'):'solan'):summon===delivery||summon===enemyDelivery?'nib':summon===tortoise||summon===enemyTortoise?'edda':'cora'):'arco';
|
const cutinKey=fenrCutin?'fenr':arcoGroup?'arco':summon?(summon===parade||summon===enemyParade?'mira':summon===serpent||summon===enemySerpent?'naja':summon===quake||summon===enemyQuake?'haldor':summon===finale||summon===enemyFinale?'zanni':summon===skyfall||summon===enemySkyfall?'isolde':summon===orrery||summon===enemyOrrery?'rhea':summon===sunroar||summon===enemySunroar?(summon.cf?(summon.kind==='mode'?summon.cf+'~mode':summon.cf):(summon===enemySunroar?opponentCharacter:selectedCharacter)==='browo'?(summon.kind==='tornado'?'browofire':'browo'):'solan'):summon===delivery||summon===enemyDelivery?'nib':summon===tortoise||summon===enemyTortoise?'edda':'cora'):'arco';
|
||||||
const ct=fenrCutin?.t ?? (arcoGroup||summon)?.t ?? 100;
|
const ct=fenrCutin?.t ?? (arcoGroup||summon)?.t ?? 100;
|
||||||
cutin.classList.toggle('fenr-cutin',cutinKey==='fenr');cutin.classList.toggle('mira-cutin',cutinKey==='mira');cutin.classList.toggle('cora-cutin',cutinKey==='cora');cutin.classList.toggle('naja-cutin',cutinKey==='naja');cutin.classList.toggle('haldor-cutin',cutinKey==='haldor');cutin.classList.toggle('zanni-cutin',cutinKey==='zanni');cutin.classList.toggle('isolde-cutin',cutinKey==='isolde');cutin.classList.toggle('rhea-cutin',cutinKey==='rhea');cutin.classList.toggle('solan-cutin',cutinKey==='solan');cutin.classList.toggle('nib-cutin',cutinKey==='nib');cutin.classList.toggle('edda-cutin',cutinKey==='edda');cutin.classList.toggle('browo-cutin',cutinKey==='browo'||cutinKey==='browofire');
|
cutin.classList.toggle('fenr-cutin',cutinKey==='fenr');cutin.classList.toggle('mira-cutin',cutinKey==='mira');cutin.classList.toggle('cora-cutin',cutinKey==='cora');cutin.classList.toggle('naja-cutin',cutinKey==='naja');cutin.classList.toggle('haldor-cutin',cutinKey==='haldor');cutin.classList.toggle('zanni-cutin',cutinKey==='zanni');cutin.classList.toggle('isolde-cutin',cutinKey==='isolde');cutin.classList.toggle('rhea-cutin',cutinKey==='rhea');cutin.classList.toggle('solan-cutin',cutinKey==='solan');cutin.classList.toggle('nib-cutin',cutinKey==='nib');cutin.classList.toggle('edda-cutin',cutinKey==='edda');cutin.classList.toggle('browo-cutin',cutinKey==='browo'||cutinKey==='browofire');
|
||||||
const cutinArt=cutin.querySelector('.cutin-art'),cutinOwner=cutinKey==='fenr'?fenrCutin.owner:cutinKey==='mira'||cutinKey==='cora'||cutinKey==='naja'||cutinKey==='haldor'||cutinKey==='zanni'||cutinKey==='isolde'||cutinKey==='rhea'||CF[cutinKey]||cutinKey==='solan'||cutinKey==='browo'||cutinKey==='browofire'||cutinKey==='nib'||cutinKey==='edda'?(summon.owner==='enemy'?'RIVAL':'PLAYER'):'';
|
const cutinArt=cutin.querySelector('.cutin-art'),cutinOwner=cutinKey==='fenr'?fenrCutin.owner:cutinKey==='mira'||cutinKey==='cora'||cutinKey==='naja'||cutinKey==='haldor'||cutinKey==='zanni'||cutinKey==='isolde'||cutinKey==='rhea'||CF[cutinKey.replace('~mode','')]||cutinKey==='solan'||cutinKey==='browo'||cutinKey==='browofire'||cutinKey==='nib'||cutinKey==='edda'?(summon.owner==='enemy'?'RIVAL':'PLAYER'):'';
|
||||||
if(cutin.dataset.identity!==cutinKey+cutinOwner){cutin.dataset.identity=cutinKey+cutinOwner;cutinArt.src=CUTIN_ART[cutinKey];if(cutinArt.complete)cutinArt.style.visibility='';else{cutinArt.style.visibility='hidden';cutinArt.onload=()=>{cutinArt.style.visibility='';};}cutin.querySelector('.cutin-kicker').textContent={fenr:'FENR / '+cutinOwner,mira:'MIRA / '+cutinOwner,cora:'CORA / '+cutinOwner,naja:'NAJA / '+cutinOwner,haldor:'HALDOR / '+cutinOwner,zanni:'ZANNI / '+cutinOwner,isolde:'ISOLDE / '+cutinOwner,rhea:'RHEA / '+cutinOwner,solan:'SOLAN / '+cutinOwner,browo:'BRO WO / '+cutinOwner,browofire:'BRO WO / '+cutinOwner,nib:'NIB / '+cutinOwner,edda:'EDDA / '+cutinOwner,arco:'ARCO / AETHER COMMAND'}[cutinKey];cutin.querySelector('strong').innerHTML={fenr:'FERAL<br><em>AWAKENING</em>',mira:'ROCKET<br><em>PARADE</em>',cora:'NIGHT<br><em>MURMURATION</em>',naja:'DUNE<br><em>SERPENT</em>',haldor:'FORGE<br><em>QUAKE</em>',zanni:'GRAND<br><em>FINALE</em>',isolde:'SKYFALL<br><em>LANCES</em>',rhea:'GRAND<br><em>ORRERY</em>',solan:'SUNMANE<br><em>ROAR</em>',browo:'HUTAN<br><em>SAWIT</em>',browofire:'TORNADO<br><em>API SAWIT</em>',nib:'SPECIAL<br><em>DELIVERY</em>',edda:'ELDER<br><em>TORTOISE</em>',arco:'HELIOS<br><em>SQUADRON</em>'}[cutinKey];cutin.querySelector('.cutin-detail').textContent={fenr:'THE BEAST WITHIN',mira:'TWELVE-ROCKET SALVO',cora:'THREE-PASS RAVEN STORM',naja:'THREE-STRIKE SAND COBRA',haldor:'THREE-SLAM MOLTEN SHOCKWAVE',zanni:'THREE-RING BLADE BOOMERANG',isolde:'THREE-LANCE ICE DIVE',rhea:'THREE-PLANET ORBIT',solan:'THREE-ROAR SHOCKWAVE',browo:'TEN-PALM RUN + GIANT SLAM',browofire:'FIRE TORNADO FINISHER',nib:'THREE-PARCEL HOMING RUN',edda:'THREE-STOMP SPIRIT WALK',arco:'ORBITAL LASER STRIKE'}[cutinKey];cutin.setAttribute('aria-label',{fenr:'FENR ultimate: Feral Awakening',mira:'MIRA ultimate: Rocket Parade',cora:'CORA ultimate: Night Murmuration',naja:'NAJA ultimate: Dune Serpent',haldor:'HALDOR ultimate: Forge Quake',zanni:'ZANNI ultimate: Grand Finale',isolde:'ISOLDE ultimate: Skyfall Lances',rhea:'RHEA ultimate: Grand Orrery',solan:'SOLAN ultimate: Sunmane Roar',browo:'BRO WO ultimate: Hutan Sawit',browofire:'BRO WO rage ultimate: Tornado Api Sawit',nib:'NIB ultimate: Special Delivery',edda:'EDDA ultimate: Elder Tortoise',arco:'ARCO ultimate: Helios Squadron'}[cutinKey]);cfCutin(cutinKey,cutinOwner);}
|
if(cutin.dataset.identity!==cutinKey+cutinOwner){cutin.dataset.identity=cutinKey+cutinOwner;cutinArt.src=CUTIN_ART[cutinKey];if(cutinArt.complete)cutinArt.style.visibility='';else{cutinArt.style.visibility='hidden';cutinArt.onload=()=>{cutinArt.style.visibility='';};}cutin.querySelector('.cutin-kicker').textContent={fenr:'FENR / '+cutinOwner,mira:'MIRA / '+cutinOwner,cora:'CORA / '+cutinOwner,naja:'NAJA / '+cutinOwner,haldor:'HALDOR / '+cutinOwner,zanni:'ZANNI / '+cutinOwner,isolde:'ISOLDE / '+cutinOwner,rhea:'RHEA / '+cutinOwner,solan:'SOLAN / '+cutinOwner,browo:'BRO WO / '+cutinOwner,browofire:'BRO WO / '+cutinOwner,nib:'NIB / '+cutinOwner,edda:'EDDA / '+cutinOwner,arco:'ARCO / AETHER COMMAND'}[cutinKey];cutin.querySelector('strong').innerHTML={fenr:'FERAL<br><em>AWAKENING</em>',mira:'ROCKET<br><em>PARADE</em>',cora:'NIGHT<br><em>MURMURATION</em>',naja:'DUNE<br><em>SERPENT</em>',haldor:'FORGE<br><em>QUAKE</em>',zanni:'GRAND<br><em>FINALE</em>',isolde:'SKYFALL<br><em>LANCES</em>',rhea:'GRAND<br><em>ORRERY</em>',solan:'SUNMANE<br><em>ROAR</em>',browo:'HUTAN<br><em>SAWIT</em>',browofire:'TORNADO<br><em>API SAWIT</em>',nib:'SPECIAL<br><em>DELIVERY</em>',edda:'ELDER<br><em>TORTOISE</em>',arco:'HELIOS<br><em>SQUADRON</em>'}[cutinKey];cutin.querySelector('.cutin-detail').textContent={fenr:'THE BEAST WITHIN',mira:'TWELVE-ROCKET SALVO',cora:'THREE-PASS RAVEN STORM',naja:'THREE-STRIKE SAND COBRA',haldor:'THREE-SLAM MOLTEN SHOCKWAVE',zanni:'THREE-RING BLADE BOOMERANG',isolde:'THREE-LANCE ICE DIVE',rhea:'THREE-PLANET ORBIT',solan:'THREE-ROAR SHOCKWAVE',browo:'TEN-PALM RUN + GIANT SLAM',browofire:'FIRE TORNADO FINISHER',nib:'THREE-PARCEL HOMING RUN',edda:'THREE-STOMP SPIRIT WALK',arco:'ORBITAL LASER STRIKE'}[cutinKey];cutin.setAttribute('aria-label',{fenr:'FENR ultimate: Feral Awakening',mira:'MIRA ultimate: Rocket Parade',cora:'CORA ultimate: Night Murmuration',naja:'NAJA ultimate: Dune Serpent',haldor:'HALDOR ultimate: Forge Quake',zanni:'ZANNI ultimate: Grand Finale',isolde:'ISOLDE ultimate: Skyfall Lances',rhea:'RHEA ultimate: Grand Orrery',solan:'SOLAN ultimate: Sunmane Roar',browo:'BRO WO ultimate: Hutan Sawit',browofire:'BRO WO rage ultimate: Tornado Api Sawit',nib:'NIB ultimate: Special Delivery',edda:'EDDA ultimate: Elder Tortoise',arco:'ARCO ultimate: Helios Squadron'}[cutinKey]);cfCutin(cutinKey,cutinOwner);}
|
||||||
const visible = ct < SQUAD.cutinDuration;
|
const visible = ct < SQUAD.cutinDuration;
|
||||||
cutin.classList.toggle('visible', visible); cutin.setAttribute('aria-hidden', String(!visible));
|
cutin.classList.toggle('visible', visible); cutin.setAttribute('aria-hidden', String(!visible));
|
||||||
|
|
@ -2176,9 +2196,9 @@
|
||||||
// Cut-in art per fighter. Only the two fighters of the current fight are warmed (at start/select), so a banner never waits
|
// Cut-in art per fighter. Only the two fighters of the current fight are warmed (at start/select), so a banner never waits
|
||||||
// for its image; until the right image is decoded the art stays hidden (see updateHud) instead of showing the last one.
|
// for its image; until the right image is decoded the art stays hidden (see updateHud) instead of showing the last one.
|
||||||
const CUTIN_ART={fenr:'assets/fenr/ui/cutin.webp',mira:'assets/mira/ui/cutin.webp',cora:'assets/cora/ui/cutin.webp',naja:'assets/naja/ui/cutin.webp',haldor:'assets/haldor/ui/cutin.webp',zanni:'assets/zanni/ui/cutin.webp',isolde:'assets/isolde/ui/cutin.webp',rhea:'assets/rhea/ui/cutin.webp',solan:'assets/solan/ui/cutin.webp',browo:'assets/browo/ui/cutin.webp',browofire:'assets/browo/ui/cutin-fire.webp',nib:'assets/nib/ui/cutin.webp',edda:'assets/edda/ui/cutin.webp',arco:'assets/ui/ultimate-cutin.webp'};
|
const CUTIN_ART={fenr:'assets/fenr/ui/cutin.webp',mira:'assets/mira/ui/cutin.webp',cora:'assets/cora/ui/cutin.webp',naja:'assets/naja/ui/cutin.webp',haldor:'assets/haldor/ui/cutin.webp',zanni:'assets/zanni/ui/cutin.webp',isolde:'assets/isolde/ui/cutin.webp',rhea:'assets/rhea/ui/cutin.webp',solan:'assets/solan/ui/cutin.webp',browo:'assets/browo/ui/cutin.webp',browofire:'assets/browo/ui/cutin-fire.webp',nib:'assets/nib/ui/cutin.webp',edda:'assets/edda/ui/cutin.webp',arco:'assets/ui/ultimate-cutin.webp'};
|
||||||
for(const id of Object.keys(CF))CUTIN_ART[id]='assets/'+id+'/ui/cutin.webp';
|
for(const id of Object.keys(CF)){CUTIN_ART[id]='assets/'+id+'/ui/cutin.webp';if(CF[id].mode)CUTIN_ART[id+'~mode']='assets/'+id+'/ui/cutin-mode.webp';}
|
||||||
let cutinWarm=[];
|
let cutinWarm=[];
|
||||||
function warmCutins(){cutinWarm=[...new Set([selectedCharacter,opponentCharacter])].flatMap(id=>id==='browo'?[CUTIN_ART.browo,CUTIN_ART.browofire]:[CUTIN_ART[id]]).filter(Boolean).map(src=>{const im=new Image();im.decoding='async';im.src=src;return im;});}
|
function warmCutins(){cutinWarm=[...new Set([selectedCharacter,opponentCharacter])].flatMap(id=>id==='browo'?[CUTIN_ART.browo,CUTIN_ART.browofire]:[CUTIN_ART[id],CUTIN_ART[id+'~mode']]).filter(Boolean).map(src=>{const im=new Image();im.decoding='async';im.src=src;return im;});}
|
||||||
function setPaused(value) { paused = value; clearInput(); syncUltimateVoice(); $('#pause-btn').textContent = value ? '▶' : 'Ⅱ'; const panel = $('#pause-panel'); if (value && !panel.open) panel.showModal(); if (!value && panel.open) panel.close(); if (!value) canvas.focus({ preventScroll: true }); }
|
function setPaused(value) { paused = value; clearInput(); syncUltimateVoice(); $('#pause-btn').textContent = value ? '▶' : 'Ⅱ'; const panel = $('#pause-panel'); if (value && !panel.open) panel.showModal(); if (!value && panel.open) panel.close(); if (!value) canvas.focus({ preventScroll: true }); }
|
||||||
function openSettings() { settingsOpen = true; clearInput(); syncUltimateVoice(); window.FrontEnd?.syncBackground(); $('#close-settings').innerHTML=(menuOpen?'Kembali ke menu':'Kembali ke arena')+' <span>↗</span>'; $('#settings-panel').showModal(); }
|
function openSettings() { settingsOpen = true; clearInput(); syncUltimateVoice(); window.FrontEnd?.syncBackground(); $('#close-settings').innerHTML=(menuOpen?'Kembali ke menu':'Kembali ke arena')+' <span>↗</span>'; $('#settings-panel').showModal(); }
|
||||||
function closeSettings() { settingsOpen = false; $('#settings-panel').close(); syncUltimateVoice(); window.FrontEnd?.syncBackground(); canvas.focus({ preventScroll: true }); }
|
function closeSettings() { settingsOpen = false; $('#settings-panel').close(); syncUltimateVoice(); window.FrontEnd?.syncBackground(); canvas.focus({ preventScroll: true }); }
|
||||||
|
|
@ -2219,7 +2239,7 @@
|
||||||
if(window.FRONTEND_ENABLED)setMenuOpen(true);
|
if(window.FRONTEND_ENABLED)setMenuOpen(true);
|
||||||
resize(); requestAnimationFrame(tick);
|
resize(); requestAnimationFrame(tick);
|
||||||
// Wait for the first-visit download (preload.js) so these requests are served from the cache, not fetched twice.
|
// Wait for the first-visit download (preload.js) so these requests are served from the cache, not fetched twice.
|
||||||
Promise.resolve(window.AETHER_PRELOAD).then(() => Promise.all([loadImage('hero', 'assets/mecha/run/sprite-sheet-alpha.webp', true), loadImage('stage', 'assets/stage.webp', true), ...(Rules?Object.entries(Rules.stages).filter(([id])=>id!=='bellora').map(([id,s])=>loadImage('stage-'+id,s.image,true)):[]), ...(F?[loadImage('fenrHuman','assets/fenr/human/run/sprite-sheet-alpha.webp',true),loadImage('fenrWolf','assets/fenr/wolf/run/sprite-sheet-alpha.webp',true),...['claw','gale','rush','bite','howl','transform'].map(name=>loadImage('fx-'+name,'assets/fenr/ui/fx-'+name+'.webp',true))]:[]), ...(M?[loadImage('mira','assets/mira/run/sprite-sheet-alpha.webp',true),...['star','rocket','burst','crash'].map(name=>loadImage('fx-mira-'+name,'assets/mira/ui/fx-'+name+'.webp',true))]:[]), ...(C?[loadImage('cora','assets/cora/run/sprite-sheet-alpha.webp',true),...['feather','gust','raven'].map(name=>loadImage('fx-cora-'+name,'assets/cora/ui/fx-'+name+'.webp',true))]:[]), ...(N?[loadImage('naja','assets/naja/run/sprite-sheet-alpha.webp',true),...['sandwave','cyclone','serpent','ripple'].map(name=>loadImage('fx-naja-'+name,'assets/naja/ui/fx-'+name+'.webp',true))]:[]), ...(HD?[loadImage('haldor','assets/haldor/run/sprite-sheet-alpha.webp',true),...['slag','splash','steam','quake'].map(name=>loadImage('fx-haldor-'+name,'assets/haldor/ui/fx-'+name+'.webp',true))]:[]), ...(Z?[loadImage('zanni','assets/zanni/run/sprite-sheet-alpha.webp',true),...['ring','bigring','snatch','confetti'].map(name=>loadImage('fx-zanni-'+name,'assets/zanni/ui/fx-'+name+'.webp',true))]:[]), ...(IS?[loadImage('isolde','assets/isolde/run/sprite-sheet-alpha.webp',true),...['piercer','skyfall','shatter','frost'].map(name=>loadImage('fx-isolde-'+name,'assets/isolde/ui/fx-'+name+'.webp',true))]:[]), ...(RH?[loadImage('rhea','assets/rhea/run/sprite-sheet-alpha.webp',true),...['drift','planet','well','burst'].map(name=>loadImage('fx-rhea-'+name,'assets/rhea/ui/fx-'+name+'.webp',true))]:[]), ...(SO?[loadImage('solan','assets/solan/run/sprite-sheet-alpha.webp',true),...['crescent','roar','impact','sunburst'].map(name=>loadImage('fx-solan-'+name,'assets/solan/ui/fx-'+name+'.webp',true))]:[]), ...(NB?[loadImage('nib','assets/nib/run/sprite-sheet-alpha.webp',true),...['letter','plane','slip','stamp'].map(name=>loadImage('fx-nib-'+name,'assets/nib/ui/fx-'+name+'.webp',true))]:[]), ...(BW?[loadImage('browo','assets/browo/run/sprite-sheet-alpha.webp',true),...['sawit','impact','sunburst','truck','burntree','tornado'].map(name=>loadImage('fx-browo-'+name,'assets/browo/ui/fx-'+name+'.webp',true))]:[]), ...Object.values(CF).flatMap(f=>[loadImage(f.id,'assets/'+f.id+'/run/sprite-sheet-alpha.webp',true),...f.fx.map(n=>loadImage('fx-'+f.id+'-'+n,'assets/'+f.id+'/ui/fx-'+n+'.webp',true))]), ...(ED?[loadImage('edda','assets/edda/run/sprite-sheet-alpha.webp',true),...['stone','ripple','shell','tortoise','stomp'].map(name=>loadImage('fx-edda-'+name,'assets/edda/ui/fx-'+name+'.webp',true))]:[]), loadImage('drone', 'assets/ui/drone.png', true), loadImage('cutin', 'assets/ui/ultimate-cutin.webp', true)])).then(results => {
|
Promise.resolve(window.AETHER_PRELOAD).then(() => Promise.all([loadImage('hero', 'assets/mecha/run/sprite-sheet-alpha.webp', true), loadImage('stage', 'assets/stage.webp', true), ...(Rules?Object.entries(Rules.stages).filter(([id])=>id!=='bellora').map(([id,s])=>loadImage('stage-'+id,s.image,true)):[]), ...(F?[loadImage('fenrHuman','assets/fenr/human/run/sprite-sheet-alpha.webp',true),loadImage('fenrWolf','assets/fenr/wolf/run/sprite-sheet-alpha.webp',true),...['claw','gale','rush','bite','howl','transform'].map(name=>loadImage('fx-'+name,'assets/fenr/ui/fx-'+name+'.webp',true))]:[]), ...(M?[loadImage('mira','assets/mira/run/sprite-sheet-alpha.webp',true),...['star','rocket','burst','crash'].map(name=>loadImage('fx-mira-'+name,'assets/mira/ui/fx-'+name+'.webp',true))]:[]), ...(C?[loadImage('cora','assets/cora/run/sprite-sheet-alpha.webp',true),...['feather','gust','raven'].map(name=>loadImage('fx-cora-'+name,'assets/cora/ui/fx-'+name+'.webp',true))]:[]), ...(N?[loadImage('naja','assets/naja/run/sprite-sheet-alpha.webp',true),...['sandwave','cyclone','serpent','ripple'].map(name=>loadImage('fx-naja-'+name,'assets/naja/ui/fx-'+name+'.webp',true))]:[]), ...(HD?[loadImage('haldor','assets/haldor/run/sprite-sheet-alpha.webp',true),...['slag','splash','steam','quake'].map(name=>loadImage('fx-haldor-'+name,'assets/haldor/ui/fx-'+name+'.webp',true))]:[]), ...(Z?[loadImage('zanni','assets/zanni/run/sprite-sheet-alpha.webp',true),...['ring','bigring','snatch','confetti'].map(name=>loadImage('fx-zanni-'+name,'assets/zanni/ui/fx-'+name+'.webp',true))]:[]), ...(IS?[loadImage('isolde','assets/isolde/run/sprite-sheet-alpha.webp',true),...['piercer','skyfall','shatter','frost'].map(name=>loadImage('fx-isolde-'+name,'assets/isolde/ui/fx-'+name+'.webp',true))]:[]), ...(RH?[loadImage('rhea','assets/rhea/run/sprite-sheet-alpha.webp',true),...['drift','planet','well','burst'].map(name=>loadImage('fx-rhea-'+name,'assets/rhea/ui/fx-'+name+'.webp',true))]:[]), ...(SO?[loadImage('solan','assets/solan/run/sprite-sheet-alpha.webp',true),...['crescent','roar','impact','sunburst'].map(name=>loadImage('fx-solan-'+name,'assets/solan/ui/fx-'+name+'.webp',true))]:[]), ...(NB?[loadImage('nib','assets/nib/run/sprite-sheet-alpha.webp',true),...['letter','plane','slip','stamp'].map(name=>loadImage('fx-nib-'+name,'assets/nib/ui/fx-'+name+'.webp',true))]:[]), ...(BW?[loadImage('browo','assets/browo/run/sprite-sheet-alpha.webp',true),...['sawit','impact','sunburst','truck','burntree','tornado'].map(name=>loadImage('fx-browo-'+name,'assets/browo/ui/fx-'+name+'.webp',true))]:[]), ...Object.values(CF).flatMap(f=>[loadImage(f.id,'assets/'+f.id+'/run/sprite-sheet-alpha.webp',true),...(f.mode?.form?[loadImage(f.id+'-form','assets/'+f.id+'/form/run/sprite-sheet-alpha.webp',true)]:[]),...f.fx.map(n=>loadImage('fx-'+f.id+'-'+n,'assets/'+f.id+'/ui/fx-'+n+'.webp',true))]), ...(ED?[loadImage('edda','assets/edda/run/sprite-sheet-alpha.webp',true),...['stone','ripple','shell','tortoise','stomp'].map(name=>loadImage('fx-edda-'+name,'assets/edda/ui/fx-'+name+'.webp',true))]:[]), loadImage('drone', 'assets/ui/drone.png', true), loadImage('cutin', 'assets/ui/ultimate-cutin.webp', true)])).then(results => {
|
||||||
images['stage-bellora']=images.stage;images.arco=images.hero;if(F)syncPlayerForm();
|
images['stage-bellora']=images.stage;images.arco=images.hero;if(F)syncPlayerForm();
|
||||||
ready = results.every(Boolean) && (!F || (!!window.FENR_HUMAN_MANIFEST && !!window.FENR_WOLF_MANIFEST)) && !!manifest && frames('idle').length > 0;
|
ready = results.every(Boolean) && (!F || (!!window.FENR_HUMAN_MANIFEST && !!window.FENR_WOLF_MANIFEST)) && !!manifest && frames('idle').length > 0;
|
||||||
if (ready) { $('#load-state').classList.add('hidden'); announce('TRAINING READY', 1.65); }
|
if (ready) { $('#load-state').classList.add('hidden'); announce('TRAINING READY', 1.65); }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue