403 lines
15 KiB
TypeScript
403 lines
15 KiB
TypeScript
import type { ChoreoContext } from './types'
|
|||
|
|
import { generateSpriteSheet, MAX_FRAMES, TOTAL_ROWS, type SpriteCustomization } from '../sprites'
|
||
|
|
import { spriteAnims } from './constants'
|
||
|
|
import { announceHype } from '../audio'
|
||
|
|
|
||
|
|
interface BotConfig {
|
||
|
|
name: string; seed: string; tier: number; archetype?: string; customization?: SpriteCustomization; wins?: number; losses?: number
|
||
|
|
}
|
||
|
|
|
||
|
|
interface MorphDeps {
|
||
|
|
botA: BotConfig
|
||
|
|
botB: BotConfig
|
||
|
|
colorsA: { primary: string; secondary: string }
|
||
|
|
colorsB: { primary: string; secondary: string }
|
||
|
|
isHumanA: boolean
|
||
|
|
isHumanB: boolean
|
||
|
|
loadSpriteWithTimeout: (name: string, src: string, opts: any) => Promise<void>
|
||
|
|
}
|
||
|
|
|
||
|
|
export function createMorphSystem(ctx: ChoreoContext, deps: MorphDeps) {
|
||
|
|
const { k, safeColor, screenFlash, spawnShockwave, glitchRGB, sfxZoomWhoosh, sfxSpecial, theme } = ctx
|
||
|
|
const { botA, botB, colorsA, colorsB, isHumanA, isHumanB, loadSpriteWithTimeout } = deps
|
||
|
|
|
||
|
|
interface MorphOverlay { obj: any; type: string }
|
||
|
|
let activeMorphs: MorphOverlay[] = []
|
||
|
|
|
||
|
|
function destroyMorphOverlays() {
|
||
|
|
activeMorphs.forEach(m => { if (m.obj.exists()) m.obj.destroy() })
|
||
|
|
activeMorphs = []
|
||
|
|
}
|
||
|
|
|
||
|
|
// Morph 0: ELEMENTAL — fire/ice/electric aura wraps around the fighter
|
||
|
|
function applyElementalMorph(fighter: any, side: 'a' | 'b') {
|
||
|
|
const seed = side === 'a' ? botA.seed : botB.seed
|
||
|
|
let h = 0
|
||
|
|
for (let i = 0; i < seed.length; i++) h = ((h << 5) - h + seed.charCodeAt(i)) | 0
|
||
|
|
const element = Math.abs(h) % 3 // 0=fire, 1=ice, 2=electric
|
||
|
|
|
||
|
|
const colors = [
|
||
|
|
['#ff4400', '#ff8800', '#ffcc00'], // fire
|
||
|
|
['#44ccff', '#88eeff', '#ffffff'], // ice
|
||
|
|
['#ffff00', '#88ff00', '#00ffff'], // electric
|
||
|
|
][element]
|
||
|
|
|
||
|
|
// Aura glow behind fighter
|
||
|
|
const aura = k.add([
|
||
|
|
k.circle(45 + Math.random() * 10),
|
||
|
|
k.pos(fighter.pos.x, fighter.pos.y - 20),
|
||
|
|
k.color(safeColor(colors[0])),
|
||
|
|
k.opacity(0.15),
|
||
|
|
k.z(fighter.z - 1),
|
||
|
|
k.anchor('center'),
|
||
|
|
k.scale(1),
|
||
|
|
])
|
||
|
|
aura.onUpdate(() => {
|
||
|
|
aura.pos.x = fighter.pos.x
|
||
|
|
aura.pos.y = fighter.pos.y - 20
|
||
|
|
aura.opacity = 0.1 + Math.sin(k.time() * 8) * 0.08
|
||
|
|
const s = 1 + Math.sin(k.time() * 6) * 0.15
|
||
|
|
aura.scale.x = s; aura.scale.y = s
|
||
|
|
})
|
||
|
|
activeMorphs.push({ obj: aura, type: 'elemental' })
|
||
|
|
|
||
|
|
// Orbiting particles
|
||
|
|
for (let i = 0; i < 6; i++) {
|
||
|
|
const p = k.add([
|
||
|
|
k.circle(2 + Math.random() * 2),
|
||
|
|
k.pos(fighter.pos.x, fighter.pos.y),
|
||
|
|
k.color(safeColor(colors[1 + (i % 2)])),
|
||
|
|
k.opacity(0.6),
|
||
|
|
k.z(fighter.z + 1),
|
||
|
|
k.anchor('center'),
|
||
|
|
])
|
||
|
|
const angle = (i / 6) * Math.PI * 2
|
||
|
|
const radius = 30 + Math.random() * 15
|
||
|
|
p.onUpdate(() => {
|
||
|
|
const a = angle + k.time() * (3 + i * 0.5)
|
||
|
|
p.pos.x = fighter.pos.x + Math.cos(a) * radius
|
||
|
|
p.pos.y = fighter.pos.y - 20 + Math.sin(a) * radius * 0.6
|
||
|
|
p.opacity = 0.4 + Math.sin(k.time() * 10 + i) * 0.3
|
||
|
|
})
|
||
|
|
activeMorphs.push({ obj: p, type: 'elemental' })
|
||
|
|
}
|
||
|
|
|
||
|
|
// Color tint the fighter
|
||
|
|
fighter.color = safeColor(colors[0])
|
||
|
|
fighter.opacity = 0.9
|
||
|
|
}
|
||
|
|
|
||
|
|
// Morph 1: MECH — armor plates, wings/jets, metallic overlay
|
||
|
|
function applyMechMorph(fighter: any, _side: 'a' | 'b') {
|
||
|
|
const dir = fighter.scale.x > 0 ? 1 : -1
|
||
|
|
|
||
|
|
// Shoulder armor plates
|
||
|
|
for (let s = 0; s < 2; s++) {
|
||
|
|
const sDir = s === 0 ? -1 : 1
|
||
|
|
const shoulder = k.add([
|
||
|
|
k.rect(12, 8), k.pos(fighter.pos.x + sDir * 25 * dir, fighter.pos.y - 30),
|
||
|
|
k.color(safeColor('#888899')), k.opacity(0.85), k.z(fighter.z + 1), k.anchor('center'),
|
||
|
|
])
|
||
|
|
shoulder.onUpdate(() => {
|
||
|
|
shoulder.pos.x = fighter.pos.x + sDir * 25 * dir
|
||
|
|
shoulder.pos.y = fighter.pos.y - 30 + Math.sin(k.time() * 4) * 2
|
||
|
|
})
|
||
|
|
activeMorphs.push({ obj: shoulder, type: 'mech' })
|
||
|
|
}
|
||
|
|
|
||
|
|
// Jet wings + flame boosters
|
||
|
|
for (let w = 0; w < 2; w++) {
|
||
|
|
const wingDir = w === 0 ? -1 : 1
|
||
|
|
const wing = k.add([
|
||
|
|
k.rect(6, 20 + w * 5), k.pos(fighter.pos.x - 30 * dir * wingDir, fighter.pos.y - 15),
|
||
|
|
k.color(safeColor('#5566aa')), k.opacity(0.7), k.z(fighter.z - 1), k.anchor('center'),
|
||
|
|
k.rotate(wingDir * 15 * dir),
|
||
|
|
])
|
||
|
|
wing.onUpdate(() => {
|
||
|
|
wing.pos.x = fighter.pos.x - 30 * dir * wingDir
|
||
|
|
wing.pos.y = fighter.pos.y - 15
|
||
|
|
})
|
||
|
|
activeMorphs.push({ obj: wing, type: 'mech' })
|
||
|
|
|
||
|
|
const flame = k.add([
|
||
|
|
k.rect(4, 8 + Math.random() * 6),
|
||
|
|
k.pos(fighter.pos.x - 30 * dir * wingDir, fighter.pos.y + 5),
|
||
|
|
k.color(safeColor('#ff6600')),
|
||
|
|
k.opacity(0.6), k.z(fighter.z - 2), k.anchor('center'),
|
||
|
|
])
|
||
|
|
flame.onUpdate(() => {
|
||
|
|
flame.pos.x = fighter.pos.x - 30 * dir * wingDir + (Math.random() - 0.5) * 3
|
||
|
|
flame.pos.y = fighter.pos.y + 5 + Math.random() * 4
|
||
|
|
flame.opacity = 0.3 + Math.random() * 0.4
|
||
|
|
flame.color = safeColor(Math.random() > 0.5 ? '#ff6600' : '#ffcc00')
|
||
|
|
})
|
||
|
|
activeMorphs.push({ obj: flame, type: 'mech' })
|
||
|
|
}
|
||
|
|
|
||
|
|
// Visor glow
|
||
|
|
const visor = k.add([
|
||
|
|
k.rect(20, 4), k.pos(fighter.pos.x + 5 * dir, fighter.pos.y - 35),
|
||
|
|
k.color(safeColor('#00ffaa')), k.opacity(0.7), k.z(fighter.z + 2), k.anchor('center'),
|
||
|
|
])
|
||
|
|
visor.onUpdate(() => {
|
||
|
|
visor.pos.x = fighter.pos.x + 5 * dir
|
||
|
|
visor.pos.y = fighter.pos.y - 35
|
||
|
|
visor.opacity = 0.5 + Math.sin(k.time() * 12) * 0.3
|
||
|
|
})
|
||
|
|
activeMorphs.push({ obj: visor, type: 'mech' })
|
||
|
|
|
||
|
|
// Metallic tint
|
||
|
|
fighter.color = safeColor('#aabbcc')
|
||
|
|
fighter.opacity = 0.95
|
||
|
|
}
|
||
|
|
|
||
|
|
// Morph 2: BEAST — grows horns/tail/claws, goes wild
|
||
|
|
function applyBeastMorph(fighter: any, side: 'a' | 'b') {
|
||
|
|
const dir = fighter.scale.x > 0 ? 1 : -1
|
||
|
|
const seed = side === 'a' ? botA.seed : botB.seed
|
||
|
|
let h = 0
|
||
|
|
for (let i = 0; i < seed.length; i++) h = ((h << 5) - h + seed.charCodeAt(i)) | 0
|
||
|
|
const beastType = Math.abs(h >> 4) % 3 // 0=demon, 1=wolf, 2=dragon
|
||
|
|
|
||
|
|
const beastColors = [
|
||
|
|
['#cc2222', '#ff4444', '#880000'], // demon
|
||
|
|
['#666688', '#aaaacc', '#444466'], // wolf
|
||
|
|
['#228844', '#44cc66', '#115533'], // dragon
|
||
|
|
][beastType]
|
||
|
|
|
||
|
|
// Horns
|
||
|
|
for (let horn = 0; horn < 2; horn++) {
|
||
|
|
const hDir = horn === 0 ? -1 : 1
|
||
|
|
const h1 = k.add([
|
||
|
|
k.rect(3, 12 + Math.random() * 6),
|
||
|
|
k.pos(fighter.pos.x + hDir * 10, fighter.pos.y - 45),
|
||
|
|
k.color(safeColor(beastColors[0])),
|
||
|
|
k.opacity(0.9), k.z(fighter.z + 2), k.anchor('bot'),
|
||
|
|
k.rotate(hDir * (20 + Math.random() * 15)),
|
||
|
|
])
|
||
|
|
h1.onUpdate(() => {
|
||
|
|
h1.pos.x = fighter.pos.x + hDir * 10
|
||
|
|
h1.pos.y = fighter.pos.y - 45
|
||
|
|
})
|
||
|
|
activeMorphs.push({ obj: h1, type: 'beast' })
|
||
|
|
}
|
||
|
|
|
||
|
|
// Tail
|
||
|
|
for (let s = 0; s < 5; s++) {
|
||
|
|
const seg = k.add([
|
||
|
|
k.circle(4 - s * 0.5),
|
||
|
|
k.pos(fighter.pos.x - dir * (20 + s * 8), fighter.pos.y - 5 + s * 2),
|
||
|
|
k.color(safeColor(beastColors[1])),
|
||
|
|
k.opacity(0.8), k.z(fighter.z - 1), k.anchor('center'),
|
||
|
|
])
|
||
|
|
seg.onUpdate(() => {
|
||
|
|
const wave = Math.sin(k.time() * 5 + s * 0.8) * (4 + s * 2)
|
||
|
|
seg.pos.x = fighter.pos.x - dir * (20 + s * 8)
|
||
|
|
seg.pos.y = fighter.pos.y - 5 + s * 2 + wave
|
||
|
|
})
|
||
|
|
activeMorphs.push({ obj: seg, type: 'beast' })
|
||
|
|
}
|
||
|
|
|
||
|
|
// Claws on hands
|
||
|
|
for (let c = 0; c < 2; c++) {
|
||
|
|
const cDir = c === 0 ? -1 : 1
|
||
|
|
for (let cl = 0; cl < 3; cl++) {
|
||
|
|
const claw = k.add([
|
||
|
|
k.rect(2, 6), k.pos(fighter.pos.x + cDir * 22, fighter.pos.y - 18 + cl * 3),
|
||
|
|
k.color(safeColor(beastColors[2])),
|
||
|
|
k.opacity(0.8), k.z(fighter.z + 1), k.anchor('center'),
|
||
|
|
k.rotate(cDir * (30 + cl * 10)),
|
||
|
|
])
|
||
|
|
claw.onUpdate(() => {
|
||
|
|
claw.pos.x = fighter.pos.x + cDir * 22
|
||
|
|
claw.pos.y = fighter.pos.y - 18 + cl * 3
|
||
|
|
})
|
||
|
|
activeMorphs.push({ obj: claw, type: 'beast' })
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Wild eye glow
|
||
|
|
const eyeGlow = k.add([
|
||
|
|
k.circle(4), k.pos(fighter.pos.x + 5 * dir, fighter.pos.y - 36),
|
||
|
|
k.color(safeColor('#ff0000')), k.opacity(0.6), k.z(fighter.z + 2), k.anchor('center'),
|
||
|
|
])
|
||
|
|
eyeGlow.onUpdate(() => {
|
||
|
|
eyeGlow.pos.x = fighter.pos.x + 5 * dir
|
||
|
|
eyeGlow.pos.y = fighter.pos.y - 36
|
||
|
|
eyeGlow.opacity = 0.4 + Math.sin(k.time() * 15) * 0.4
|
||
|
|
})
|
||
|
|
activeMorphs.push({ obj: eyeGlow, type: 'beast' })
|
||
|
|
|
||
|
|
// Size increase + color tint
|
||
|
|
fighter.color = safeColor(beastColors[0])
|
||
|
|
fighter.opacity = 0.9
|
||
|
|
}
|
||
|
|
|
||
|
|
const morphAppliers = [applyElementalMorph, applyMechMorph, applyBeastMorph]
|
||
|
|
const morphNames = ['ELEMENTAL FORM', 'MECH ARMOR', 'BEAST MODE']
|
||
|
|
|
||
|
|
// === THE CREATOR: OMNI-MORPH ===
|
||
|
|
// Instead of 3 morph types, the creator morphs into a random archetype each time
|
||
|
|
const OMNI_MORPH_ARCHETYPES = [
|
||
|
|
'lobster', 'sheep', 'cyborg', 'blob', 'tank', 'dog', 'cat', 'cactus', 'pizza',
|
||
|
|
'mushroom', 'shark', 'penguin', 'octopus', 'skeleton', 'ghost', 'alien', 'dinosaur',
|
||
|
|
'pirate', 'ninja', 'cowboy', 'wizard', 'bee', 'frog', 'snail',
|
||
|
|
'robot', 'android', 'toaster', 'mech', 'minotaur', 'unicorn', 'phoenix', 'dragon',
|
||
|
|
'mermaid', 'griffin', 'cyclops', 'gargoyle', 'golem', 'vampire', 'werewolf', 'zombie',
|
||
|
|
'witch', 'demon', 'chef', 'firefighter', 'astronaut', 'clown', 'detective',
|
||
|
|
'lumberjack', 'scientist', 'wrestler', 'boxer', 'gladiator', 'samurai', 'viking', 'knight',
|
||
|
|
'elephant', 'giraffe', 'hippo', 'lion', 'monkey', 'parrot', 'raccoon', 'snake',
|
||
|
|
'turtle', 'whale', 'crocodile', 'flamingo', 'hedgehog', 'panda', 'hamster',
|
||
|
|
'sock_puppet', 'traffic_cone', 'toilet_man', 'potato', 'cloud_man', 'rock_man',
|
||
|
|
'balloon_man', 'trash_can', 'rubber_duck', 'snowman', 'scarecrow', 'jack_o_lantern',
|
||
|
|
'garden_gnome', 'lamp_post', 'broom_man',
|
||
|
|
]
|
||
|
|
|
||
|
|
let omniMorphCounter = 0
|
||
|
|
|
||
|
|
async function applyCreatorOmniMorph(fighter: any, side: 'a' | 'b'): Promise<string> {
|
||
|
|
const pick = OMNI_MORPH_ARCHETYPES[Math.floor(Math.random() * OMNI_MORPH_ARCHETYPES.length)]
|
||
|
|
|
||
|
|
// Generate and load a sprite sheet for the picked archetype
|
||
|
|
const bot = side === 'a' ? botA : botB
|
||
|
|
const colors = side === 'a' ? colorsA : colorsB
|
||
|
|
const morphKey = `omniMorph_${side}_${omniMorphCounter++}`
|
||
|
|
try {
|
||
|
|
const morphSheet = generateSpriteSheet(bot.seed, bot.tier, colors.primary, colors.secondary, pick)
|
||
|
|
await loadSpriteWithTimeout(morphKey, morphSheet, { sliceX: MAX_FRAMES, sliceY: TOTAL_ROWS, anims: spriteAnims })
|
||
|
|
// Swap the fighter's sprite to the morphed archetype
|
||
|
|
fighter.use(k.sprite(morphKey, { anim: fighter.curAnim() || 'idle' }))
|
||
|
|
} catch (err) {
|
||
|
|
console.warn('[FightScene] omni-morph sprite failed:', err)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Golden aura unique to creator morph
|
||
|
|
const aura = k.add([
|
||
|
|
k.circle(55),
|
||
|
|
k.pos(fighter.pos.x, fighter.pos.y - 20),
|
||
|
|
k.color(safeColor('#c8a000')),
|
||
|
|
k.opacity(0.2),
|
||
|
|
k.z(fighter.z - 1),
|
||
|
|
k.anchor('center'),
|
||
|
|
k.scale(1),
|
||
|
|
])
|
||
|
|
aura.onUpdate(() => {
|
||
|
|
aura.pos.x = fighter.pos.x
|
||
|
|
aura.pos.y = fighter.pos.y - 20
|
||
|
|
aura.opacity = 0.12 + Math.sin(k.time() * 6) * 0.1
|
||
|
|
const s = 1 + Math.sin(k.time() * 4) * 0.2
|
||
|
|
aura.scale.x = s; aura.scale.y = s
|
||
|
|
})
|
||
|
|
activeMorphs.push({ obj: aura, type: 'omni' })
|
||
|
|
|
||
|
|
// Orbiting golden ₿ symbols (8 in dual rings, varied sizes)
|
||
|
|
for (let i = 0; i < 8; i++) {
|
||
|
|
const ring = i < 5 ? 0 : 1 // inner ring (5) + outer ring (3)
|
||
|
|
const ringCount = ring === 0 ? 5 : 3
|
||
|
|
const ringIdx = ring === 0 ? i : i - 5
|
||
|
|
const sz = ring === 0 ? 10 + Math.random() * 4 : 14 + Math.random() * 4
|
||
|
|
const rad = ring === 0 ? 40 : 60
|
||
|
|
const spd = ring === 0 ? 3 : -2 // counter-rotate outer ring
|
||
|
|
const btc = k.add([
|
||
|
|
k.text('₿', { size: sz }),
|
||
|
|
k.pos(fighter.pos.x, fighter.pos.y - 20),
|
||
|
|
k.color(safeColor(i % 3 === 0 ? '#ffd700' : i % 3 === 1 ? '#ffee88' : '#ff8c00')),
|
||
|
|
k.opacity(0.8),
|
||
|
|
k.z(fighter.z + 2),
|
||
|
|
k.anchor('center'),
|
||
|
|
k.rotate(0),
|
||
|
|
])
|
||
|
|
const baseAngle = (ringIdx / ringCount) * Math.PI * 2
|
||
|
|
btc.onUpdate(() => {
|
||
|
|
const a = baseAngle + k.time() * spd
|
||
|
|
btc.pos.x = fighter.pos.x + Math.cos(a) * rad
|
||
|
|
btc.pos.y = fighter.pos.y - 20 + Math.sin(a) * rad * 0.5
|
||
|
|
btc.opacity = 0.7 + Math.sin(k.time() * 6 + i * 1.3) * 0.3
|
||
|
|
btc.angle = Math.sin(k.time() * 4 + i) * 20
|
||
|
|
})
|
||
|
|
activeMorphs.push({ obj: btc, type: 'omni' })
|
||
|
|
}
|
||
|
|
|
||
|
|
// Apply archetype-specific color tint based on pick category
|
||
|
|
const tints: Record<string, string> = {
|
||
|
|
fire: '#ff4400', ice: '#44ccff', nature: '#44cc44', dark: '#8844cc',
|
||
|
|
metal: '#aabbcc', electric: '#ffff00', beast: '#cc6622', cosmic: '#cc44ff',
|
||
|
|
}
|
||
|
|
const category =
|
||
|
|
['phoenix', 'dragon', 'demon'].includes(pick) ? 'fire' :
|
||
|
|
['penguin', 'snowman', 'yeti'].includes(pick) ? 'ice' :
|
||
|
|
['cactus', 'mushroom', 'frog', 'snail', 'turtle'].includes(pick) ? 'nature' :
|
||
|
|
['skeleton', 'ghost', 'vampire', 'zombie', 'witch'].includes(pick) ? 'dark' :
|
||
|
|
['robot', 'android', 'mech', 'toaster', 'knight'].includes(pick) ? 'metal' :
|
||
|
|
['alien', 'unicorn', 'mermaid'].includes(pick) ? 'cosmic' :
|
||
|
|
['werewolf', 'minotaur', 'lion', 'shark', 'crocodile'].includes(pick) ? 'beast' :
|
||
|
|
'electric'
|
||
|
|
fighter.color = safeColor(tints[category] || '#ffd700')
|
||
|
|
fighter.opacity = 0.9
|
||
|
|
|
||
|
|
return pick
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get morph order for a bot (deterministic by seed)
|
||
|
|
function getMorphOrder(seed: string): number[] {
|
||
|
|
let h = 0
|
||
|
|
for (let i = 0; i < seed.length; i++) h = ((h << 5) - h + seed.charCodeAt(i)) | 0
|
||
|
|
const orders = [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]]
|
||
|
|
return orders[Math.abs(h) % 6]
|
||
|
|
}
|
||
|
|
|
||
|
|
async function playMorph(fighter: any, side: 'a' | 'b', morphIndex: number) {
|
||
|
|
const savedScaleX = fighter.scale.x
|
||
|
|
const savedScaleY = fighter.scale.y
|
||
|
|
const isHumanFighter = side === 'a' ? isHumanA : isHumanB
|
||
|
|
const morphSpriteKey = side === 'a' ? 'botA_morph' : 'botB_morph'
|
||
|
|
const normalSpriteKey = side === 'a' ? 'botA' : 'botB'
|
||
|
|
|
||
|
|
// Flash + announcement
|
||
|
|
screenFlash('#ffffff', 0.15)
|
||
|
|
sfxZoomWhoosh()
|
||
|
|
sfxSpecial()
|
||
|
|
const name = side === 'a' ? botA.name : botB.name
|
||
|
|
announceHype(`${name} — ${morphNames[morphIndex]}!`)
|
||
|
|
|
||
|
|
// Scale up dramatically
|
||
|
|
const growFactor = 1.3
|
||
|
|
await k.tween(0, 1, 0.3, (t) => {
|
||
|
|
fighter.scale.x = savedScaleX * (1 + t * (growFactor - 1))
|
||
|
|
fighter.scale.y = savedScaleY * (1 + t * (growFactor - 1))
|
||
|
|
}, k.easings.easeOutBack)
|
||
|
|
|
||
|
|
// Human fighters morph into bots (opposite direction!)
|
||
|
|
if (isHumanFighter) {
|
||
|
|
fighter.use(k.sprite(morphSpriteKey, { anim: 'idle' }))
|
||
|
|
}
|
||
|
|
|
||
|
|
// Apply the morph visuals
|
||
|
|
morphAppliers[morphIndex](fighter, side)
|
||
|
|
|
||
|
|
// Shockwave + shake
|
||
|
|
spawnShockwave(fighter.pos.x, fighter.pos.y - 20, '#ffffff')
|
||
|
|
k.shake(8)
|
||
|
|
glitchRGB(0.2)
|
||
|
|
|
||
|
|
return {
|
||
|
|
revert: async () => {
|
||
|
|
destroyMorphOverlays()
|
||
|
|
screenFlash(theme.accent, 0.1)
|
||
|
|
fighter.color = safeColor('#ffffff')
|
||
|
|
fighter.opacity = 1
|
||
|
|
// Revert human fighters back to their human sprite
|
||
|
|
if (isHumanFighter) {
|
||
|
|
fighter.use(k.sprite(normalSpriteKey, { anim: 'idle' }))
|
||
|
|
}
|
||
|
|
await k.tween(0, 1, 0.2, (t) => {
|
||
|
|
fighter.scale.x = savedScaleX * growFactor + t * (savedScaleX - savedScaleX * growFactor)
|
||
|
|
fighter.scale.y = savedScaleY * growFactor + t * (savedScaleY - savedScaleY * growFactor)
|
||
|
|
}, k.easings.easeInQuad)
|
||
|
|
},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return { destroyMorphOverlays, playMorph, applyCreatorOmniMorph, getMorphOrder }
|
||
|
|
}
|