From 0acfa4417fc04447dafd3399dfa98741628833c2 Mon Sep 17 00:00:00 2001 From: Dorian Date: Sun, 8 Mar 2026 16:16:41 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20overhaul=20showboat=20system=20?= =?UTF-8?q?=E2=80=94=20big=20movement,=20Creator=20magical=20showboats?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Completely rewrote all 30 showboats with much more walking, jumping, sprinting back and forth across the arena. Fighters now pace, charge, leapfrog, zigzag sprint, do backflip retreats, wall-to-wall dashes, kangaroo hops, shadow box sprints, and girlfriend kiss scenes. Added 12 Creator-exclusive magical showboats: - Levitate across screen trailing golden ₿ rain - Portal teleport (vanish, flash between 3 spots, reappear) - Massive golden code rain summon (₿01∞⚡⛏ characters) - ₿ orbit explosion (orbit then burst outward) - Lightning flight (zigzag with lightning bolts) - Satoshi meditation (golden aura, bitcoin wisdom quotes) - Golden tornado (circular path with ₿ trail) - Laptop hack (type furiously, spawn code chars) - Bitcoin rain shower (fly across dropping ₿ coins) - Dimension rift (tear golden portal, step through) - Golden ascension (rise, glow, slam down with shockwave) - Phase walk (flicker through opponent's space) Creator gets magical showboats ~60% of the time. Pause between showboats shortened to keep the action constant. Co-Authored-By: Claude Opus 4.6 --- frontend/src/game/FightScene.ts | 980 ++++++++++++++++++++++++-------- 1 file changed, 735 insertions(+), 245 deletions(-) diff --git a/frontend/src/game/FightScene.ts b/frontend/src/game/FightScene.ts index b935f01..0df6c61 100644 --- a/frontend/src/game/FightScene.ts +++ b/frontend/src/game/FightScene.ts @@ -7503,276 +7503,761 @@ export async function createFightScene(config: FightSceneConfig) { } // === SHOWBOATING / TAUNTING SYSTEM === - // 30 idle animations fighters perform during question/answer phases + // Fighters perform these during question/answer phases instead of boring idle bobbing const _showboatActive: Record = { a: false, b: false } - type ShowboatFn = (fighter: any, homeX: number, homeY: number, sx: number, sy: number) => Promise + type ShowboatFn = (fighter: any, homeX: number, homeY: number, sx: number, sy: number, dir: number) => Promise + // dir: 1 = facing right (bot A), -1 = facing left (bot B) const showboats: ShowboatFn[] = [ - // 0: Shadow boxing — quick attack/kick combo in place - async (f, _hx, _hy, _sx, _sy) => { - f.play('attack'); sfxDodge(); await k.wait(0.18) - f.play('kick'); await k.wait(0.18) - f.play('attack'); await k.wait(0.18) - }, - // 1: Flex pose — scale up, hold, shrink back - async (f, _hx, _hy, sx, sy) => { - f.play('win') - await k.tween(1, 1.25, 0.2, (v) => { f.scale.x = sx * v; f.scale.y = sy * v }, k.easings.easeOutBack) - await k.wait(0.4) - await k.tween(1.25, 1, 0.2, (v) => { f.scale.x = sx * v; f.scale.y = sy * v }, k.easings.easeInQuad) - }, - // 2: Bouncy taunt — rapid small hops - async (f, _hx, hy) => { - for (let i = 0; i < 4; i++) { - await k.tween(f.pos.y, hy - 15, 0.06, (v) => { f.pos.y = v }, k.easings.easeOutQuad) - await k.tween(f.pos.y, hy, 0.06, (v) => { f.pos.y = v }, k.easings.easeInQuad) - } - }, - // 3: Spin move — full 360 rotation - async (f) => { - f.play('special') - const startAngle = f.angle || 0 - await k.tween(startAngle, startAngle + 360, 0.4, (v) => { f.angle = v }, k.easings.easeInOutQuad) - f.angle = startAngle - }, - // 4: Moonwalk — slide backward while idle - async (f, hx) => { - const dir = f.pos.x < W / 2 ? -1 : 1 - await k.tween(f.pos.x, hx + dir * 40, 0.3, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) - await k.tween(f.pos.x, hx, 0.3, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) - }, - // 5: Jump rope — rapid tiny bounces with kick anim - async (f, _hx, hy) => { + // 0: Pace back and forth — walk a wide arc restlessly + async (f, hx, hy, _sx, _sy, dir) => { f.play('kick') - for (let i = 0; i < 6; i++) { - await k.tween(f.pos.y, hy - 8, 0.05, (v) => { f.pos.y = v }, k.easings.easeOutQuad) - await k.tween(f.pos.y, hy, 0.05, (v) => { f.pos.y = v }, k.easings.easeInQuad) - } - }, - // 6: Intimidate stare — zoom in slightly, hold, zoom out - async (f, _hx, _hy, sx, sy) => { - f.play('special') - await k.tween(1, 1.35, 0.25, (v) => { f.scale.x = sx * v; f.scale.y = sy * v }, k.easings.easeOutQuad) - await k.wait(0.5) - await k.tween(1.35, 1, 0.2, (v) => { f.scale.x = sx * v; f.scale.y = sy * v }, k.easings.easeInQuad) - }, - // 7: Cocky lean — tilt to one side - async (f) => { - const start = f.angle || 0 - await k.tween(start, start + 12, 0.2, (v) => { f.angle = v }, k.easings.easeOutQuad) - await k.wait(0.4) - await k.tween(start + 12, start, 0.2, (v) => { f.angle = v }, k.easings.easeInQuad) - }, - // 8: Fist pump — jump + attack anim - async (f, _hx, hy) => { - f.play('attack'); sfxPunch() - await k.tween(f.pos.y, hy - 40, 0.12, (v) => { f.pos.y = v }, k.easings.easeOutQuad) - await k.tween(f.pos.y, hy, 0.12, (v) => { f.pos.y = v }, k.easings.easeInQuad) - }, - // 9: Shuffle dance — rapid left-right shimmy - async (f, hx) => { - f.play('win') - for (let i = 0; i < 4; i++) { - await k.tween(f.pos.x, hx + 12, 0.06, (v) => { f.pos.x = v }) - await k.tween(f.pos.x, hx - 12, 0.06, (v) => { f.pos.x = v }) - } - f.pos.x = hx - }, - // 10: Push-ups — squish vertically - async (f, _hx, _hy, sx, sy) => { - for (let i = 0; i < 3; i++) { - await k.tween(1, 0.6, 0.12, (v) => { f.scale.y = sy * v; f.scale.x = sx * (2 - v) }, k.easings.easeOutQuad) - await k.tween(0.6, 1, 0.12, (v) => { f.scale.y = sy * v; f.scale.x = sx * (2 - v) }, k.easings.easeInQuad) - } - }, - // 11: Victory lap — dash to center and back - async (f, hx, hy) => { - f.play('kick'); sfxZoomWhoosh() - const center = W / 2 - await k.tween(f.pos.x, center, 0.2, (v) => { f.pos.x = v }, k.easings.easeInQuad) - f.play('win') - spawnSparks(center, hy - 20, 5, theme.accent) - await k.wait(0.2) - await k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeOutQuad) - }, - // 12: Yawn stretch — slow scale up, hold, shrink - async (f, _hx, hy, sx, sy) => { - await k.tween(1, 1.15, 0.4, (v) => { f.scale.x = sx * v; f.scale.y = sy * v; f.pos.y = hy - (v - 1) * 30 }, k.easings.easeInOutQuad) - spawnEmoteText(f.pos.x, f.pos.y - 40, '*yawn*', '#aaaaaa') - await k.wait(0.3) - await k.tween(1.15, 1, 0.3, (v) => { f.scale.x = sx * v; f.scale.y = sy * v; f.pos.y = hy - (v - 1) * 30 }, k.easings.easeInOutQuad) - }, - // 13: Backflip — jump high + rotate 360 - async (f, _hx, hy) => { - sfxBoing() - const startAngle = f.angle || 0 - await Promise.all([ - k.tween(f.pos.y, hy - 80, 0.2, (v) => { f.pos.y = v }, k.easings.easeOutQuad), - k.tween(startAngle, startAngle - 360, 0.4, (v) => { f.angle = v }, k.easings.easeInOutQuad), - ]) - await k.tween(f.pos.y, hy, 0.2, (v) => { f.pos.y = v }, k.easings.easeInQuad) - f.angle = startAngle - }, - // 14: Chest thump — attack anim + shake - async (f) => { - f.play('attack'); sfxBlock() - k.shake(3) - spawnEmoteText(f.pos.x, f.pos.y - 40, 'COME ON!', '#ff4444') - await k.wait(0.3) - f.play('attack') - k.shake(3) - await k.wait(0.2) - }, - // 15: Hover float — rise slowly, hold, descend - async (f, _hx, hy) => { - f.play('special') - await k.tween(f.pos.y, hy - 50, 0.4, (v) => { f.pos.y = v }, k.easings.easeOutQuad) - await k.wait(0.3) - await k.tween(f.pos.y, hy, 0.3, (v) => { f.pos.y = v }, k.easings.easeInQuad) - }, - // 16: Dizzy spin — multiple fast spins - async (f) => { - f.play('hit') - const startAngle = f.angle || 0 - await k.tween(startAngle, startAngle + 720, 0.5, (v) => { f.angle = v }, k.easings.easeOutQuad) - spawnEmoteText(f.pos.x, f.pos.y - 40, '@_@', '#ffcc00') - f.angle = startAngle - await k.wait(0.2) - }, - // 17: Ground pound — jump high, slam down with shake - async (f, _hx, hy) => { - f.play('kick'); sfxBoing() - await k.tween(f.pos.y, hy - 70, 0.15, (v) => { f.pos.y = v }, k.easings.easeOutQuad) - await k.wait(0.05) - f.play('attack') - await k.tween(f.pos.y, hy, 0.08, (v) => { f.pos.y = v }, k.easings.easeInQuad) - sfxExplosion(); k.shake(8) - spawnSparks(f.pos.x, hy, 8, '#ff6600') - }, - // 18: Feint charge — dash toward opponent, brake, dash back - async (f, hx) => { - const dir = f.pos.x < W / 2 ? 1 : -1 - f.play('attack'); sfxZoomWhoosh() - await k.tween(f.pos.x, hx + dir * 80, 0.12, (v) => { f.pos.x = v }, k.easings.easeOutQuad) - sfxBlock() - await k.wait(0.1) + await k.tween(f.pos.x, hx + dir * 100, 0.35, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) f.play('idle') + await k.wait(0.1) + f.play('attack') + await k.tween(f.pos.x, hx - dir * 60, 0.4, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) + f.play('idle') + await k.wait(0.1) + await k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) + }, + // 1: Bouncing advance — hop toward opponent then back + async (f, hx, hy, _sx, _sy, dir) => { + for (let i = 0; i < 3; i++) { + const targetX = hx + dir * (40 + i * 30) + f.play('kick') + await Promise.all([ + k.tween(f.pos.x, targetX, 0.12, (v) => { f.pos.x = v }, k.easings.easeOutQuad), + k.tween(f.pos.y, hy - 35, 0.06, (v) => { f.pos.y = v }, k.easings.easeOutQuad).then(() => + k.tween(f.pos.y, hy, 0.06, (v) => { f.pos.y = v }, k.easings.easeInQuad) + ), + ]) + sfxBoing() + } + f.play('win'); await k.wait(0.15) await k.tween(f.pos.x, hx, 0.25, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) }, - // 19: Butt wiggle — oscillating rotation - async (f) => { - f.play('win') - const start = f.angle || 0 + // 2: Shadow box sprint — dash forward shadow boxing, dash back + async (f, hx, hy, _sx, _sy, dir) => { + const far = hx + dir * 120 + f.play('attack'); sfxZoomWhoosh() + await k.tween(f.pos.x, far, 0.2, (v) => { f.pos.x = v }, k.easings.easeOutQuad) + for (let i = 0; i < 4; i++) { + f.play(i % 2 === 0 ? 'attack' : 'kick') + sfxDodge(); await k.wait(0.08) + } + f.play('special'); sfxZoomWhoosh() + await k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeOutQuad) + }, + // 3: Leapfrog across — multiple big jumps across the stage + async (f, hx, hy, _sx, _sy, dir) => { + const spots = [hx + dir * 60, hx + dir * 130, hx + dir * 60, hx] + for (const tx of spots) { + sfxBoing() + await Promise.all([ + k.tween(f.pos.x, tx, 0.18, (v) => { f.pos.x = v }, k.easings.easeInOutQuad), + k.tween(f.pos.y, hy - 70, 0.09, (v) => { f.pos.y = v }, k.easings.easeOutQuad).then(() => + k.tween(f.pos.y, hy, 0.09, (v) => { f.pos.y = v }, k.easings.easeInQuad) + ), + ]) + } + }, + // 4: Restless jog — jog back and forth with small hops + async (f, hx, hy, _sx, _sy, dir) => { + f.play('kick') for (let i = 0; i < 5; i++) { - await k.tween(f.angle, start + 8, 0.05, (v) => { f.angle = v }) - await k.tween(f.angle, start - 8, 0.05, (v) => { f.angle = v }) + const dx = (i % 2 === 0 ? 1 : -1) * (30 + Math.random() * 40) * dir + await Promise.all([ + k.tween(f.pos.x, hx + dx, 0.13, (v) => { f.pos.x = v }), + k.tween(f.pos.y, hy - 12, 0.065, (v) => { f.pos.y = v }).then(() => + k.tween(f.pos.y, hy, 0.065, (v) => { f.pos.y = v }) + ), + ]) } - f.angle = start - }, - // 20: Jumping jacks — alternating stretch X/Y - async (f, _hx, hy, sx, sy) => { - for (let i = 0; i < 3; i++) { - f.play(i % 2 === 0 ? 'kick' : 'attack') - await k.tween(0, 1, 0.12, (t) => { - f.scale.x = sx * (1 + 0.2 * Math.sin(t * Math.PI)) - f.scale.y = sy * (1 - 0.1 * Math.sin(t * Math.PI)) - f.pos.y = hy - 10 * Math.sin(t * Math.PI) - }) - } - f.scale.x = sx; f.scale.y = sy; f.pos.y = hy - }, - // 21: Sumo stomp — stomp left then right with mini shakes - async (f, hx, hy, sx, sy) => { - f.play('attack') - // Left stomp - await k.tween(f.pos.x, hx - 15, 0.08, (v) => { f.pos.x = v }) - f.scale.y = sy * 0.85; k.shake(3); sfxBonk() - await k.wait(0.1); f.scale.y = sy - // Right stomp - await k.tween(f.pos.x, hx + 15, 0.08, (v) => { f.pos.x = v }) - f.scale.y = sy * 0.85; k.shake(3); sfxBonk() - await k.wait(0.1); f.scale.y = sy f.pos.x = hx }, - // 22: Crane stance — rise on one leg, arms out - async (f, _hx, hy, sx, sy) => { + // 5: Charge and flex — sprint to center, flex, sprint back + async (f, hx, hy, sx, sy, _dir) => { + const center = W / 2 + f.play('special'); sfxZoomWhoosh() + await k.tween(f.pos.x, center, 0.2, (v) => { f.pos.x = v }, k.easings.easeInQuad) + f.play('win') + await k.tween(1, 1.3, 0.15, (v) => { f.scale.x = sx * v; f.scale.y = sy * v }, k.easings.easeOutBack) + spawnEmoteText(f.pos.x, f.pos.y - 50, 'COME AT ME!', '#ff4444') + await k.wait(0.3) + await k.tween(1.3, 1, 0.1, (v) => { f.scale.x = sx * v; f.scale.y = sy * v }) + sfxZoomWhoosh() + await k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeOutQuad) + }, + // 6: Triple backflip retreat — three backflips going backwards + async (f, hx, hy, _sx, _sy, dir) => { + for (let i = 0; i < 3; i++) { + sfxBoing() + const targetX = hx - dir * (i + 1) * 30 + const a0 = f.angle || 0 + await Promise.all([ + k.tween(f.pos.x, targetX, 0.2, (v) => { f.pos.x = v }), + k.tween(f.pos.y, hy - 60, 0.1, (v) => { f.pos.y = v }, k.easings.easeOutQuad).then(() => + k.tween(f.pos.y, hy, 0.1, (v) => { f.pos.y = v }, k.easings.easeInQuad) + ), + k.tween(a0, a0 - 360, 0.2, (v) => { f.angle = v }), + ]) + f.angle = 0 + } + await k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) + }, + // 7: Kangaroo hops — big bouncing jumps across the width + async (f, hx, hy, _sx, _sy, dir) => { + f.play('kick') + const hops = [hx + dir * 80, hx + dir * 150, hx + dir * 80, hx] + for (const tx of hops) { + await Promise.all([ + k.tween(f.pos.x, tx, 0.15, (v) => { f.pos.x = v }, k.easings.easeInOutQuad), + k.tween(f.pos.y, hy - 90, 0.075, (v) => { f.pos.y = v }, k.easings.easeOutQuad).then(() => + k.tween(f.pos.y, hy, 0.075, (v) => { f.pos.y = v }, k.easings.easeInQuad) + ), + ]) + sfxBonk(); k.shake(2) + } + }, + // 8: Feint combo rush — charge in, throw combo, dodge back + async (f, hx, hy, _sx, _sy, dir) => { + sfxZoomWhoosh(); f.play('attack') + await k.tween(f.pos.x, hx + dir * 100, 0.12, (v) => { f.pos.x = v }, k.easings.easeOutQuad) + const anims = ['attack', 'kick', 'attack', 'special'] as const + for (const a of anims) { f.play(a); sfxDodge(); await k.wait(0.06) } + sfxBlock(); spawnSparks(f.pos.x + dir * 15, hy - 30, 5, '#ffcc00') + // Dodge-hop back + await Promise.all([ + k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeOutQuad), + k.tween(f.pos.y, hy - 40, 0.1, (v) => { f.pos.y = v }, k.easings.easeOutQuad).then(() => + k.tween(f.pos.y, hy, 0.1, (v) => { f.pos.y = v }, k.easings.easeInQuad) + ), + ]) + }, + // 9: Dizzy spin walk — spin while walking forward, stumble back + async (f, hx, hy, _sx, _sy, dir) => { + const a0 = f.angle || 0 + f.play('hit') + await Promise.all([ + k.tween(f.pos.x, hx + dir * 70, 0.4, (v) => { f.pos.x = v }), + k.tween(a0, a0 + 720, 0.4, (v) => { f.angle = v }), + ]) + f.angle = 0 + spawnEmoteText(f.pos.x, f.pos.y - 40, '@_@', '#ffcc00') + await k.wait(0.15) + // Stumble back + f.play('idle') + for (let i = 0; i < 3; i++) { + await k.tween(f.pos.x, f.pos.x - dir * 25, 0.1, (v) => { f.pos.x = v }) + f.pos.y = hy - 5; await k.wait(0.02); f.pos.y = hy + } + }, + // 10: Wall-to-wall dash — full screen sprint and back + async (f, hx, hy, _sx, _sy, dir) => { + f.play('special'); sfxZoomWhoosh() + spawnAfterimages(f, 3) + await k.tween(f.pos.x, dir > 0 ? W - 30 : 30, 0.2, (v) => { f.pos.x = v }, k.easings.easeInQuad) + sfxBlock(); k.shake(4) + await k.wait(0.1) + sfxZoomWhoosh(); spawnAfterimages(f, 3) + await k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeOutQuad) + }, + // 11: Earthquake stomp march — stomp forward and back + async (f, hx, hy, sx, sy, dir) => { + f.play('attack') + for (let i = 0; i < 4; i++) { + const step = dir * 25 + await k.tween(f.pos.x, f.pos.x + step, 0.08, (v) => { f.pos.x = v }) + f.scale.y = sy * 0.8; sfxBonk(); k.shake(3) + await k.wait(0.05); f.scale.y = sy + } + spawnSparks(f.pos.x, hy, 6, '#ff6600') + await k.wait(0.1) + await k.tween(f.pos.x, hx, 0.25, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) + }, + // 12: Taunt walk — strut toward opponent, gesture, moonwalk back + async (f, hx, hy, _sx, _sy, dir) => { + f.play('win') + await k.tween(f.pos.x, hx + dir * 90, 0.35, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) + f.play('special') + spawnEmoteText(f.pos.x, f.pos.y - 50, 'TOO EASY!', '#ff44ff') + await k.wait(0.3) + // Moonwalk back + f.play('idle') + await k.tween(f.pos.x, hx, 0.5, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) + }, + // 13: Victory lap sprint — run around in a loop + async (f, hx, hy, _sx, _sy, dir) => { + f.play('kick'); sfxZoomWhoosh() + await k.tween(f.pos.x, hx + dir * 140, 0.2, (v) => { f.pos.x = v }, k.easings.easeInQuad) + // Jump over to other side + sfxBoing() + await Promise.all([ + k.tween(f.pos.x, hx + dir * 140, 0.25, (v) => { f.pos.x = v }), + k.tween(f.pos.y, hy - 100, 0.125, (v) => { f.pos.y = v }, k.easings.easeOutQuad).then(() => + k.tween(f.pos.y, hy, 0.125, (v) => { f.pos.y = v }, k.easings.easeInQuad) + ), + ]) + f.play('win'); spawnSparks(f.pos.x, hy - 20, 5, theme.accent) + await k.wait(0.15) + sfxZoomWhoosh() + await k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeOutQuad) + }, + // 14: Nervous pacing — quick short paces with jittery hops + async (f, hx, hy) => { + for (let i = 0; i < 6; i++) { + const dx = (Math.random() - 0.5) * 80 + await Promise.all([ + k.tween(f.pos.x, hx + dx, 0.1, (v) => { f.pos.x = v }), + k.tween(f.pos.y, hy - 10 - Math.random() * 20, 0.05, (v) => { f.pos.y = v }).then(() => + k.tween(f.pos.y, hy, 0.05, (v) => { f.pos.y = v }) + ), + ]) + } + }, + // 15: Ground pound jump — massive vertical jump + slam + async (f, hx, hy) => { + sfxBoing(); f.play('kick') + await k.tween(f.pos.y, hy - 150, 0.2, (v) => { f.pos.y = v }, k.easings.easeOutQuad) + await k.wait(0.1) + f.play('attack') + await k.tween(f.pos.y, hy, 0.1, (v) => { f.pos.y = v }, k.easings.easeInQuad) + sfxExplosion(); k.shake(15) + spawnSparks(f.pos.x, hy, 15, '#ff6600') + spawnShockwave(f.pos.x, hy, '#ff4400') + spawnEmoteText(f.pos.x, f.pos.y - 60, 'BOOM!', '#ff2d2d') + await k.wait(0.2) + }, + // 16: Zigzag sprint — fast zigzag across the arena + async (f, hx, hy, _sx, _sy, dir) => { + f.play('kick'); sfxZoomWhoosh() + const pts = [ + { x: hx + dir * 50, y: hy - 40 }, + { x: hx + dir * 100, y: hy }, + { x: hx + dir * 150, y: hy - 40 }, + { x: hx + dir * 100, y: hy }, + { x: hx, y: hy }, + ] + for (const pt of pts) { + await Promise.all([ + k.tween(f.pos.x, pt.x, 0.08, (v) => { f.pos.x = v }), + k.tween(f.pos.y, pt.y, 0.08, (v) => { f.pos.y = v }), + ]) + } + }, + // 17: Double jump kick — jump, kick, jump higher, kick + async (f, hx, hy, _sx, _sy, dir) => { + f.play('kick'); sfxBoing() + const mid = hx + dir * 60 + await Promise.all([ + k.tween(f.pos.x, mid, 0.15, (v) => { f.pos.x = v }), + k.tween(f.pos.y, hy - 50, 0.075, (v) => { f.pos.y = v }, k.easings.easeOutQuad).then(() => + k.tween(f.pos.y, hy, 0.075, (v) => { f.pos.y = v }, k.easings.easeInQuad) + ), + ]) + sfxKick(); spawnSparks(mid, hy - 30, 4, '#00ffcc') + f.play('special'); sfxBoing() + await Promise.all([ + k.tween(f.pos.x, mid + dir * 40, 0.15, (v) => { f.pos.x = v }), + k.tween(f.pos.y, hy - 90, 0.075, (v) => { f.pos.y = v }, k.easings.easeOutQuad).then(() => + k.tween(f.pos.y, hy, 0.075, (v) => { f.pos.y = v }, k.easings.easeInQuad) + ), + ]) + sfxKick(); spawnSparks(f.pos.x, hy - 50, 6, '#ff44ff') + await k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) + }, + // 18: Cocky strut and lean — walk forward, lean back with attitude + async (f, hx, hy, _sx, _sy, dir) => { + f.play('win') + await k.tween(f.pos.x, hx + dir * 80, 0.3, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) + // Lean back + await k.tween(0, -25, 0.15, (v) => { f.angle = v }, k.easings.easeOutQuad) + await k.wait(0.25) + spawnEmoteText(f.pos.x, f.pos.y - 50, 'EZ', '#ffcc00') + await k.tween(-25, 0, 0.1, (v) => { f.angle = v }) + await k.tween(f.pos.x, hx, 0.25, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) + }, + // 19: Windmill sprint — spin while running across + async (f, hx, hy, _sx, _sy, dir) => { + const a0 = f.angle || 0 + const anims = ['attack', 'kick', 'special'] as const + let animIdx = 0 + const far = hx + dir * 130 + f.play(anims[0]) + // Sprint while spinning + const dur = 0.4 + const startX = f.pos.x + await k.tween(0, 1, dur, (t) => { + f.pos.x = startX + (far - startX) * t + f.angle = a0 + t * 1080 // 3 full spins + if (Math.floor(t * 12) !== Math.floor((t - 0.01) * 12)) { + f.play(anims[animIdx++ % anims.length]) + } + }) + f.angle = 0; sfxBlock(); k.shake(3) + await k.wait(0.1) + // Sprint back + await k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeOutQuad) + }, + // 20: Sumo stomp walk — heavy stomps advancing and retreating + async (f, hx, hy, _sx, sy, dir) => { + f.play('attack') + for (let i = 0; i < 6; i++) { + const step = (i < 3 ? dir : -dir) * 20 + await k.tween(f.pos.x, f.pos.x + step, 0.1, (v) => { f.pos.x = v }) + f.scale.y = sy * 0.8; sfxBonk(); k.shake(3) + await k.wait(0.06); f.scale.y = sy + spawnSparks(f.pos.x, hy, 2, '#886644') + } + }, + // 21: Flex pose — walk to center, scale up, pose + async (f, hx, hy, sx, sy) => { + const center = W / 2 + f.play('win') + await k.tween(f.pos.x, center, 0.25, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) + await k.tween(1, 1.35, 0.2, (v) => { f.scale.x = sx * v; f.scale.y = sy * v }, k.easings.easeOutBack) + sfxPowerUp() + spawnEmoteText(f.pos.x, f.pos.y - 60, 'COME ON!', '#ff4444') + await k.wait(0.35) + await k.tween(1.35, 1, 0.15, (v) => { f.scale.x = sx * v; f.scale.y = sy * v }) + await k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeOutQuad) + }, + // 22: Jump-kick air combo — big jump with mid-air kicks + async (f, hx, hy, _sx, _sy, dir) => { + sfxBoing() + const apex = hy - 120 + const far = hx + dir * 100 + await k.tween(f.pos.y, apex, 0.15, (v) => { f.pos.y = v }, k.easings.easeOutQuad) + // Mid-air combo while moving + f.play('kick'); sfxKick() + await k.tween(f.pos.x, far, 0.1, (v) => { f.pos.x = v }) + f.play('attack'); sfxPunch() + await k.tween(f.pos.x, far + dir * 30, 0.08, (v) => { f.pos.x = v }) + f.play('special') + spawnSparks(f.pos.x, f.pos.y, 8, '#ff44ff') + await k.tween(f.pos.y, hy, 0.15, (v) => { f.pos.y = v }, k.easings.easeInQuad) + sfxBonk(); k.shake(4) + await k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) + }, + // 23: Air guitar walk — bouncing left and right with music + async (f, hx, hy, _sx, _sy, dir) => { + f.play('special'); sfxSlideUp() + spawnEmoteText(f.pos.x, f.pos.y - 50, '~♪~', '#ff44ff') + for (let i = 0; i < 6; i++) { + const dx = (i % 2 === 0 ? dir : -dir) * (30 + i * 10) + await Promise.all([ + k.tween(f.pos.x, hx + dx, 0.12, (v) => { f.pos.x = v }), + k.tween(f.pos.y, hy - 15, 0.06, (v) => { f.pos.y = v }).then(() => + k.tween(f.pos.y, hy, 0.06, (v) => { f.pos.y = v }) + ), + ]) + } + f.pos.x = hx + }, + // 24: Crane kick walk — slow martial arts advancing + async (f, hx, hy, _sx, sy, dir) => { f.play('kick') await Promise.all([ + k.tween(f.pos.x, hx + dir * 60, 0.3, (v) => { f.pos.x = v }), k.tween(f.pos.y, hy - 30, 0.3, (v) => { f.pos.y = v }, k.easings.easeOutQuad), k.tween(1, 1.15, 0.3, (v) => { f.scale.y = sy * v }, k.easings.easeOutQuad), ]) - await k.wait(0.5) + await k.wait(0.3) + f.play('special'); sfxKick() + spawnSparks(f.pos.x + dir * 20, hy - 40, 6, '#00ccff') + await k.wait(0.15) + f.scale.y = sy await Promise.all([ - k.tween(f.pos.y, hy, 0.2, (v) => { f.pos.y = v }, k.easings.easeInQuad), - k.tween(1.15, 1, 0.2, (v) => { f.scale.y = sy * v }, k.easings.easeInQuad), + k.tween(f.pos.x, hx, 0.25, (v) => { f.pos.x = v }, k.easings.easeInOutQuad), + k.tween(f.pos.y, hy, 0.15, (v) => { f.pos.y = v }, k.easings.easeInQuad), ]) }, - // 23: Matrix dodge — lean way back - async (f) => { - const start = f.angle || 0 - f.play('special') - await k.tween(start, start - 35, 0.2, (v) => { f.angle = v }, k.easings.easeOutQuad) - await k.wait(0.3) - await k.tween(start - 35, start, 0.15, (v) => { f.angle = v }, k.easings.easeInQuad) - }, - // 24: Air guitar — special anim + rapid bounces - async (f, _hx, hy) => { - f.play('special'); sfxSlideUp() + // 25: Girlfriend kiss — girlfriend walks in, kiss, hearts, she storms off + async (f, hx, hy, _sx, _sy, dir) => { + const startGfX = hx - dir * 120 + const gfColor = ['#ff69b4', '#ff1493', '#ff6eb4', '#cc44aa'][Math.floor(Math.random() * 4)] + const cs = SF + const gf = k.add([k.rect(18 * cs, 35 * cs), k.pos(startGfX, hy - 18 * cs), k.anchor('center'), k.color(safeColor(k, gfColor)), k.opacity(0.9), k.z(11)]) + const gfHair = k.add([k.rect(22 * cs, 10 * cs), k.pos(startGfX, hy - 35 * cs), k.anchor('center'), k.color(safeColor(k, '#8b4513')), k.opacity(0.9), k.z(12)]) + // Walk toward fighter + await Promise.all([ + k.tween(gf.pos.x, hx - dir * 15, 0.4, (v) => { gf.pos.x = v; gfHair.pos.x = v }, k.easings.easeInOutQuad), + ]) + // Kiss! + sfxBoing() + const heart = k.add([k.text('*smooch*', { size: 10 }), k.pos(hx, hy - 55), k.anchor('center'), k.color(safeColor(k, '#ff1493')), k.opacity(1), k.z(30)]) + k.tween(heart.pos.y, hy - 90, 0.8, (v) => { heart.pos.y = v }) + k.tween(1, 0, 0.8, (v) => { heart.opacity = v }).then(() => { if (heart.exists()) heart.destroy() }) + // Spawn floating hearts for (let i = 0; i < 5; i++) { - await k.tween(f.pos.y, hy - 6, 0.04, (v) => { f.pos.y = v }) - await k.tween(f.pos.y, hy, 0.04, (v) => { f.pos.y = v }) + const h = k.add([k.text('♥', { size: 8 + Math.random() * 6 }), k.pos(hx + (Math.random() - 0.5) * 30, hy - 30), k.color(safeColor(k, '#ff69b4')), k.opacity(0.8), k.z(25), k.anchor('center')]) + k.tween(h.pos.y, hy - 70 - Math.random() * 40, 0.7, (v) => { h.pos.y = v }) + k.tween(h.pos.x, h.pos.x + (Math.random() - 0.5) * 40, 0.7, (v) => { h.pos.x = v }) + k.tween(0.8, 0, 0.7, (v) => { h.opacity = v }).then(() => { if (h.exists()) h.destroy() }) } - spawnEmoteText(f.pos.x, f.pos.y - 45, '~♪~', '#ff44ff') - }, - // 25: Cracking knuckles — shake + attack burst - async (f, hx) => { - const origX = f.pos.x - for (let i = 0; i < 4; i++) { - await k.tween(f.pos.x, hx + 4, 0.025, (v) => { f.pos.x = v }) - await k.tween(f.pos.x, hx - 4, 0.025, (v) => { f.pos.x = v }) - } - f.pos.x = origX - f.play('attack'); sfxBlock() - await k.wait(0.15) - }, - // 26: Windmill arms — rapid attack/kick cycling - async (f) => { - const anims = ['attack', 'kick', 'special', 'attack', 'kick', 'special'] as const - for (const a of anims) { - f.play(a) - await k.wait(0.07) - } - }, - // 27: Confident strut — walk forward, pose, walk back - async (f, hx) => { - const dir = f.pos.x < W / 2 ? 1 : -1 f.play('win') - await k.tween(f.pos.x, hx + dir * 35, 0.3, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) - spawnEmoteText(f.pos.x, f.pos.y - 45, '😎', '#ffcc00') - await k.wait(0.2) + await k.wait(0.5) + // She storms off + spawnEmoteText(gf.pos.x, gf.pos.y - 30, 'Good luck babe!', '#ff69b4') + await k.tween(gf.pos.x, startGfX, 0.4, (v) => { gf.pos.x = v; gfHair.pos.x = v }, k.easings.easeInQuad) + gf.destroy(); gfHair.destroy() + }, + // 26: Push-up walk — walk forward doing push-ups at intervals + async (f, hx, hy, sx, sy, dir) => { + for (let i = 0; i < 3; i++) { + await k.tween(f.pos.x, f.pos.x + dir * 35, 0.15, (v) => { f.pos.x = v }) + // Push-up squash + await k.tween(1, 0.55, 0.08, (v) => { f.scale.y = sy * v; f.scale.x = sx * (2 - v) }, k.easings.easeOutQuad) + await k.tween(0.55, 1, 0.08, (v) => { f.scale.y = sy * v; f.scale.x = sx * (2 - v) }, k.easings.easeInQuad) + } + spawnEmoteText(f.pos.x, f.pos.y - 45, `${3 + Math.floor(Math.random() * 97)}!`, '#00ff88') + await k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) + }, + // 27: Matrix dodge walk — dodge bullets walking forward + async (f, hx, hy, _sx, _sy, dir) => { + for (let i = 0; i < 4; i++) { + await k.tween(f.pos.x, f.pos.x + dir * 25, 0.1, (v) => { f.pos.x = v }) + // Lean dodge + const lean = (i % 2 === 0 ? -30 : 30) + await k.tween(0, lean, 0.08, (v) => { f.angle = v }, k.easings.easeOutQuad) + await k.tween(lean, 0, 0.08, (v) => { f.angle = v }, k.easings.easeInQuad) + } + await k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) + }, + // 28: Bouncy chest thump — bounce toward opponent thumping chest + async (f, hx, hy, _sx, _sy, dir) => { + f.play('attack') + for (let i = 0; i < 4; i++) { + await Promise.all([ + k.tween(f.pos.x, f.pos.x + dir * 30, 0.1, (v) => { f.pos.x = v }), + k.tween(f.pos.y, hy - 25, 0.05, (v) => { f.pos.y = v }).then(() => + k.tween(f.pos.y, hy, 0.05, (v) => { f.pos.y = v }) + ), + ]) + sfxBlock(); k.shake(2) + } + spawnEmoteText(f.pos.x, f.pos.y - 50, 'LET\'S GO!', '#ff4444') + await k.wait(0.15) await k.tween(f.pos.x, hx, 0.25, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) }, - // 28: Earthquake stomp — big jump, land with major shake - async (f, _hx, hy) => { - sfxBoing() - await k.tween(f.pos.y, hy - 90, 0.2, (v) => { f.pos.y = v }, k.easings.easeOutQuad) - f.play('attack') + // 29: Speed demon — afterimage dash back and forth + async (f, hx, hy, _sx, _sy, dir) => { + f.play('special') + for (let i = 0; i < 3; i++) { + sfxZoomWhoosh(); spawnAfterimages(f, 3) + await k.tween(f.pos.x, hx + dir * (80 + i * 30), 0.08, (v) => { f.pos.x = v }, k.easings.easeInQuad) + spawnAfterimages(f, 2) + await k.tween(f.pos.x, hx - dir * 20, 0.08, (v) => { f.pos.x = v }, k.easings.easeOutQuad) + } + f.pos.x = hx + }, + ] + + // === CREATOR-EXCLUSIVE MAGICAL SHOWBOATS === + const creatorShowboats: ShowboatFn[] = [ + // 0: Levitate across — float up and glide across the screen trailing golden ₿ + async (f, hx, hy, _sx, _sy, dir) => { + f.play('special'); sfxPowerUp() + await k.tween(f.pos.y, hy - 100, 0.25, (v) => { f.pos.y = v }, k.easings.easeOutQuad) + // Float across dropping ₿ rain + const startX = f.pos.x + const far = hx + dir * 180 + const dur = 0.6 + let lastDrop = 0 + await k.tween(0, 1, dur, (t) => { + f.pos.x = startX + (far - startX) * t + f.pos.y = hy - 100 + Math.sin(t * Math.PI * 3) * 20 + if (t - lastDrop > 0.08) { + lastDrop = t + const d = k.add([k.text('₿', { size: 6 + Math.random() * 4 }), k.pos(f.pos.x, f.pos.y + 10), k.color(safeColor(k, '#ffd700')), k.opacity(0.7), k.z(9), k.anchor('center')]) + k.tween(d.pos.y, hy + 10, 0.5, (v) => { d.pos.y = v }) + k.tween(0.7, 0, 0.5, (v) => { d.opacity = v }).then(() => { if (d.exists()) d.destroy() }) + } + }) + screenFlash('#ffd700', 0.06) + await k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeOutQuad) + await k.tween(f.pos.y, hy, 0.15, (v) => { f.pos.y = v }, k.easings.easeInQuad) + }, + // 1: Golden portal teleport — vanish, portals, reappear at random spots + async (f, hx, hy) => { + sfxSpecial() + // Vanish with flash + screenFlash('#ffd700', 0.08) + f.opacity = 0 + // Teleport to 3 spots with golden portals + const spots = [ + { x: W * 0.2, y: hy - 60 }, + { x: W * 0.8, y: hy - 80 }, + { x: W * 0.5, y: hy - 120 }, + ] + for (const spot of spots) { + // Portal flash at destination + const portal = k.add([k.circle(20), k.pos(spot.x, spot.y), k.color(safeColor(k, '#ffd700')), k.opacity(0.6), k.z(15), k.anchor('center')]) + spawnSparks(spot.x, spot.y, 6, '#ffd700') + f.pos.x = spot.x; f.pos.y = spot.y; f.opacity = 1 + f.play('special') + await k.wait(0.15) + f.opacity = 0 + k.tween(0.6, 0, 0.2, (v) => { portal.opacity = v }).then(() => { if (portal.exists()) portal.destroy() }) + sfxZap() + } + // Return home with big flash + screenFlash('#ffd700', 0.1) + f.pos.x = hx; f.pos.y = hy; f.opacity = 1 + f.play('win') + spawnShockwave(hx, hy, '#ffd700') + sfxExplosion() + await k.wait(0.2) + }, + // 2: Code rain summon — float up, summon massive golden code rain + async (f, hx, hy) => { + f.play('special'); sfxPowerUp() + await k.tween(f.pos.y, hy - 80, 0.2, (v) => { f.pos.y = v }, k.easings.easeOutQuad) + // Massive code rain + const chars = '₿01∞⚡⛏§∂∆≈≠'.split('') + for (let col = 0; col < 20; col++) { + const cx = Math.random() * W + for (let row = 0; row < 3; row++) { + const ch = chars[Math.floor(Math.random() * chars.length)] + const d = k.add([ + k.text(ch, { size: 6 + Math.random() * 4 }), k.pos(cx, -10 - row * 20), + k.color(safeColor(k, row === 0 ? '#ffd700' : '#c8a000')), + k.opacity(0.5 + Math.random() * 0.4), k.z(8), k.anchor('center'), + ]) + k.tween(d.pos.y, hy + 30, 0.5 + Math.random() * 0.4, (v) => { d.pos.y = v; d.opacity = Math.max(0, 1 - (v - hy + 30) / 60) }) + .then(() => { if (d.exists()) d.destroy() }) + } + } + await k.wait(0.6) + await k.tween(f.pos.y, hy, 0.2, (v) => { f.pos.y = v }, k.easings.easeInQuad) + }, + // 3: ₿ orbit explosion — fly to center, ₿ symbols orbit then explode outward + async (f, hx, hy, sx, sy) => { + sfxZoomWhoosh(); f.play('special') + const center = W / 2 + await Promise.all([ + k.tween(f.pos.x, center, 0.2, (v) => { f.pos.x = v }, k.easings.easeInQuad), + k.tween(f.pos.y, hy - 60, 0.2, (v) => { f.pos.y = v }, k.easings.easeOutQuad), + ]) + // Spawn orbiting ₿ + const orbs: any[] = [] + for (let i = 0; i < 8; i++) { + const orb = k.add([k.text('₿', { size: 8 + Math.random() * 6 }), k.pos(center, f.pos.y), k.color(safeColor(k, i % 2 === 0 ? '#ffd700' : '#ff8c00')), k.opacity(0.8), k.z(15), k.anchor('center')]) + orbs.push(orb) + } + // Orbit for a moment + const startTime = k.time() + const orbitCancel = k.onUpdate(() => { + const t = k.time() - startTime + for (let i = 0; i < orbs.length; i++) { + const a = t * 4 + i * (Math.PI * 2 / orbs.length) + const r = 30 + t * 15 + orbs[i].pos.x = f.pos.x + Math.cos(a) * r + orbs[i].pos.y = f.pos.y + Math.sin(a) * r * 0.5 + } + }) + await k.wait(0.5) + orbitCancel.cancel() + // Explode outward + sfxExplosion(); screenFlash('#ffd700', 0.08); k.shake(8) + for (const orb of orbs) { + const angle = Math.random() * Math.PI * 2 + k.tween(orb.pos.x, orb.pos.x + Math.cos(angle) * 200, 0.3, (v) => { orb.pos.x = v }) + k.tween(orb.pos.y, orb.pos.y + Math.sin(angle) * 200, 0.3, (v) => { orb.pos.y = v }) + k.tween(0.8, 0, 0.3, (v) => { orb.opacity = v }).then(() => { if (orb.exists()) orb.destroy() }) + } + await k.wait(0.2) + await Promise.all([ + k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeOutQuad), + k.tween(f.pos.y, hy, 0.2, (v) => { f.pos.y = v }, k.easings.easeInQuad), + ]) + }, + // 4: Lightning flight — zigzag across the screen at super speed with lightning + async (f, hx, hy, _sx, _sy, dir) => { + f.play('special'); sfxZoomWhoosh() + f.opacity = 0.8 + const pts = [ + { x: hx + dir * 60, y: hy - 80 }, + { x: W / 2, y: hy - 140 }, + { x: hx + dir * 180, y: hy - 60 }, + { x: W / 2, y: hy - 100 }, + { x: hx, y: hy }, + ] + for (const pt of pts) { + spawnAfterimages(f, 2) + await Promise.all([ + k.tween(f.pos.x, pt.x, 0.08, (v) => { f.pos.x = v }), + k.tween(f.pos.y, pt.y, 0.08, (v) => { f.pos.y = v }), + ]) + // Lightning bolt at each stop + sfxZap() + const bolt = k.add([k.rect(2, 30 + Math.random() * 40), k.pos(f.pos.x, f.pos.y - 50), k.color(safeColor(k, '#ffd700')), k.opacity(0.9), k.z(20), k.anchor('center'), k.rotate(Math.random() * 30 - 15)]) + k.tween(0.9, 0, 0.15, (v) => { bolt.opacity = v }).then(() => { if (bolt.exists()) bolt.destroy() }) + } + f.opacity = 1 + }, + // 5: Satoshi meditation — float to center, golden aura pulse, wisdom text + async (f, hx, hy, sx, sy) => { + sfxPowerUp(); f.play('special') + await Promise.all([ + k.tween(f.pos.x, W / 2, 0.3, (v) => { f.pos.x = v }, k.easings.easeInOutQuad), + k.tween(f.pos.y, hy - 70, 0.3, (v) => { f.pos.y = v }, k.easings.easeOutQuad), + ]) + // Golden aura rings + for (let i = 0; i < 3; i++) { + const ring = k.add([k.circle(10), k.pos(f.pos.x, f.pos.y), k.color(safeColor(k, '#ffd700')), k.opacity(0.5), k.z(9), k.anchor('center'), k.scale(1)]) + k.tween(1, (60 + i * 20) / 10, 0.4, (v) => { ring.scale.x = v; ring.scale.y = v }) + k.tween(0.5, 0, 0.4, (v) => { ring.opacity = v }).then(() => { if (ring.exists()) ring.destroy() }) + await k.wait(0.12) + } + // Scale pulse + await k.tween(1, 1.2, 0.2, (v) => { f.scale.x = sx * v; f.scale.y = sy * v }, k.easings.easeOutQuad) + const sayings = ['PROOF OF WORK', 'VERIFY, DON\'T TRUST', '21 MILLION', 'TICK TOCK NEXT BLOCK', 'NOT YOUR KEYS', 'STAY HUMBLE STACK SATS'] + spawnEmoteText(f.pos.x, f.pos.y - 50, sayings[Math.floor(Math.random() * sayings.length)], '#ffd700') + await k.wait(0.4) + await k.tween(1.2, 1, 0.15, (v) => { f.scale.x = sx * v; f.scale.y = sy * v }) + await Promise.all([ + k.tween(f.pos.x, hx, 0.25, (v) => { f.pos.x = v }, k.easings.easeInOutQuad), + k.tween(f.pos.y, hy, 0.2, (v) => { f.pos.y = v }, k.easings.easeInQuad), + ]) + }, + // 6: Golden tornado — spin rapidly while moving in a circle across the arena + async (f, hx, hy) => { + sfxZoomWhoosh(); f.play('special') + const cx = W / 2, cy = hy - 50, radius = 100 + const dur = 0.8 + let lastParticle = 0 + await k.tween(0, Math.PI * 2, dur, (a) => { + f.pos.x = cx + Math.cos(a) * radius + f.pos.y = cy + Math.sin(a) * radius * 0.4 + f.angle = (a / (Math.PI * 2)) * 1440 // 4 full spins + if (a - lastParticle > 0.3) { + lastParticle = a + const p = k.add([k.text('₿', { size: 5 + Math.random() * 4 }), k.pos(f.pos.x, f.pos.y), k.color(safeColor(k, '#ffd700')), k.opacity(0.6), k.z(9), k.anchor('center')]) + k.tween(0.6, 0, 0.4, (v) => { p.opacity = v }).then(() => { if (p.exists()) p.destroy() }) + } + }) + f.angle = 0 + sfxExplosion(); k.shake(6) + spawnShockwave(f.pos.x, f.pos.y, '#ffd700') + f.pos.x = hx; f.pos.y = hy + }, + // 7: Laptop hack — walk forward, pull out laptop (rect), type furiously, spawn effects + async (f, hx, hy, _sx, _sy, dir) => { + f.play('special') + await k.tween(f.pos.x, hx + dir * 50, 0.2, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) + // Laptop + const cs = SF + const laptop = k.add([k.rect(24 * cs, 16 * cs), k.pos(f.pos.x + dir * 15, hy - 20 * cs), k.anchor('center'), k.color(safeColor(k, '#333333')), k.opacity(0.9), k.z(12)]) + const screen = k.add([k.rect(20 * cs, 12 * cs), k.pos(f.pos.x + dir * 15, hy - 22 * cs), k.anchor('center'), k.color(safeColor(k, '#00ff00')), k.opacity(0.7), k.z(13)]) + // Type — spawn code characters + const codeChars = '₿{}();=>0x1f'.split('') + for (let i = 0; i < 8; i++) { + const ch = codeChars[Math.floor(Math.random() * codeChars.length)] + const t = k.add([k.text(ch, { size: 5 }), k.pos(screen.pos.x + (Math.random() - 0.5) * 15, screen.pos.y), k.color(safeColor(k, '#00ff00')), k.opacity(0.8), k.z(14), k.anchor('center')]) + k.tween(t.pos.y, t.pos.y - 25 - Math.random() * 20, 0.3, (v) => { t.pos.y = v }) + k.tween(0.8, 0, 0.3, (v) => { t.opacity = v }).then(() => { if (t.exists()) t.destroy() }) + sfxCoin() + await k.wait(0.06) + } + spawnEmoteText(f.pos.x, f.pos.y - 55, 'HACKING...', '#00ff00') + await k.wait(0.2) + laptop.destroy(); screen.destroy() + await k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeInOutQuad) + }, + // 8: Bitcoin rain shower — fly across dropping ₿ coins everywhere + async (f, hx, hy, _sx, _sy, dir) => { + sfxPowerUp(); f.play('win') + await k.tween(f.pos.y, hy - 90, 0.15, (v) => { f.pos.y = v }, k.easings.easeOutQuad) + // Fly from one side to the other + const startX = dir > 0 ? 30 : W - 30 + const endX = dir > 0 ? W - 30 : 30 + f.pos.x = startX + const dur = 0.6 + let lastCoin = 0 + await k.tween(0, 1, dur, (t) => { + f.pos.x = startX + (endX - startX) * t + f.pos.y = hy - 90 + Math.sin(t * Math.PI * 4) * 15 + if (t - lastCoin > 0.04) { + lastCoin = t + const coin = k.add([k.text('₿', { size: 8 + Math.random() * 4 }), k.pos(f.pos.x, f.pos.y + 15), k.color(safeColor(k, Math.random() > 0.5 ? '#ffd700' : '#ff8c00')), k.opacity(0.8), k.z(9), k.anchor('center')]) + k.tween(coin.pos.y, hy + 10, 0.4, (v) => { coin.pos.y = v }) + k.tween(coin.pos.x, coin.pos.x + (Math.random() - 0.5) * 30, 0.4, (v) => { coin.pos.x = v }) + k.tween(0.8, 0, 0.4, (v) => { coin.opacity = v }).then(() => { if (coin.exists()) coin.destroy() }) + } + }) + sfxCoin() + f.pos.x = hx + await k.tween(f.pos.y, hy, 0.15, (v) => { f.pos.y = v }, k.easings.easeInQuad) + }, + // 9: Dimension rift — creator tears open a golden rift, steps through, appears elsewhere + async (f, hx, hy) => { + // Rift opens + sfxSpecial() + const riftX = f.pos.x + 30 + const rift = k.add([k.rect(4, 50), k.pos(riftX, hy - 25), k.anchor('center'), k.color(safeColor(k, '#ffd700')), k.opacity(0.9), k.z(15)]) + await k.tween(4, 30, 0.2, (v) => { rift.width = v }) + spawnSparks(riftX, hy - 25, 8, '#ffd700') + // Step through — vanish + f.play('special') + await k.tween(f.pos.x, riftX, 0.1, (v) => { f.pos.x = v }) + f.opacity = 0; sfxZap() + // Rift at destination + const destX = W - riftX + const rift2 = k.add([k.rect(30, 50), k.pos(destX, hy - 25), k.anchor('center'), k.color(safeColor(k, '#ffd700')), k.opacity(0.9), k.z(15)]) + spawnSparks(destX, hy - 25, 8, '#ffd700') + await k.wait(0.15) + // Step out + f.pos.x = destX; f.opacity = 1; f.play('win') + sfxSpecial() + await k.wait(0.2) + // Close rifts + k.tween(0.9, 0, 0.2, (v) => { rift.opacity = v; rift2.opacity = v }).then(() => { if (rift.exists()) rift.destroy(); if (rift2.exists()) rift2.destroy() }) + // Float home + await Promise.all([ + k.tween(f.pos.x, hx, 0.3, (v) => { f.pos.x = v }, k.easings.easeInOutQuad), + k.tween(f.pos.y, hy - 40, 0.15, (v) => { f.pos.y = v }, k.easings.easeOutQuad).then(() => + k.tween(f.pos.y, hy, 0.15, (v) => { f.pos.y = v }, k.easings.easeInQuad) + ), + ]) + }, + // 10: Golden ascension — rise way up, glow, descend with shockwave + async (f, hx, hy, sx, sy) => { + sfxPowerUp(); f.play('special') + await k.tween(f.pos.y, hy - 160, 0.3, (v) => { f.pos.y = v }, k.easings.easeOutQuad) + // Glow pulse + await k.tween(1, 1.5, 0.2, (v) => { f.scale.x = sx * v; f.scale.y = sy * v }, k.easings.easeOutQuad) + screenFlash('#ffd700', 0.06) + // Spawn golden aura particles + for (let i = 0; i < 10; i++) { + const p = k.add([k.text('✦', { size: 4 + Math.random() * 6 }), k.pos(f.pos.x + (Math.random() - 0.5) * 40, f.pos.y + (Math.random() - 0.5) * 30), k.color(safeColor(k, '#ffd700')), k.opacity(0.7), k.z(9), k.anchor('center')]) + k.tween(0.7, 0, 0.5, (v) => { p.opacity = v; p.pos.y -= 0.5 }).then(() => { if (p.exists()) p.destroy() }) + } + spawnEmoteText(f.pos.x, f.pos.y - 40, 'HODL', '#ffd700') + await k.wait(0.3) + await k.tween(1.5, 1, 0.1, (v) => { f.scale.x = sx * v; f.scale.y = sy * v }) + // Slam down await k.tween(f.pos.y, hy, 0.1, (v) => { f.pos.y = v }, k.easings.easeInQuad) sfxExplosion(); k.shake(12) - spawnSparks(f.pos.x, hy, 12, '#ffcc00') - spawnEmoteText(f.pos.x, f.pos.y - 50, 'BOOM!', '#ff2d2d') + spawnShockwave(hx, hy, '#ffd700') + spawnSparks(hx, hy, 15, '#ffd700') await k.wait(0.15) }, - // 29: Meditation float — rise, pulse glow, descend - async (f, _hx, hy, sx, sy) => { - f.play('special') - await k.tween(f.pos.y, hy - 35, 0.3, (v) => { f.pos.y = v }, k.easings.easeOutQuad) - for (let i = 0; i < 3; i++) { - await k.tween(1, 1.08, 0.15, (v) => { f.scale.x = sx * v; f.scale.y = sy * v }) - await k.tween(1.08, 1, 0.15, (v) => { f.scale.x = sx * v; f.scale.y = sy * v }) - } - await k.tween(f.pos.y, hy, 0.2, (v) => { f.pos.y = v }, k.easings.easeInQuad) + // 11: Phase walk — flicker opacity while walking through the opponent's space + async (f, hx, hy, _sx, _sy, dir) => { + f.play('special'); sfxZap() + const far = dir > 0 ? W - 40 : 40 + const dur = 0.5 + await k.tween(0, 1, dur, (t) => { + f.pos.x = hx + (far - hx) * t + f.opacity = 0.2 + Math.abs(Math.sin(t * Math.PI * 8)) * 0.7 + }) + f.opacity = 1; sfxSpecial() + screenFlash('#ffd700', 0.05) + spawnSparks(f.pos.x, hy - 20, 6, '#ffd700') + await k.tween(f.pos.x, hx, 0.2, (v) => { f.pos.x = v }, k.easings.easeOutQuad) }, ] @@ -7781,25 +8266,30 @@ export async function createFightScene(config: FightSceneConfig) { const tag = side === 'a' ? 'fighterA' : 'fighterB' const homeX = side === 'a' ? HOME_A : HOME_B const homeY = GROUND_Y - 6 + const dir = side === 'a' ? 1 : -1 + const arch = side === 'a' ? botA.archetype : botB.archetype + const isCreator = arch === 'the_creator' + // Creator uses magical showboats 60% of the time, normal 40% + const pool = isCreator ? [...showboats, ...creatorShowboats, ...creatorShowboats] : showboats while (_showboatActive[side]) { const f = k.get(tag)[0] if (!f) break const sx = f.scale.x const sy = f.scale.y - const idx = Math.floor(Math.random() * showboats.length) + const idx = Math.floor(Math.random() * pool.length) try { - await showboats[idx](f, homeX, homeY, sx, sy) + await pool[idx](f, homeX, homeY, sx, sy, dir) } catch { break } // Restore state after each showboat const ff = k.get(tag)[0] if (ff) { ff.pos.x = homeX; ff.pos.y = homeY ff.scale.x = sx; ff.scale.y = sy - ff.angle = 0 + ff.angle = 0; ff.opacity = 1 ff.play('idle') } // Brief pause between showboats - if (_showboatActive[side]) await k.wait(0.3 + Math.random() * 0.6) + if (_showboatActive[side]) await k.wait(0.15 + Math.random() * 0.35) } }