refactor: type Fighter interface for all choreographies

Define Fighter type alias using Kaplay's GameObj with SpriteComp, PosComp,
ScaleComp, AnchorComp, OpacityComp, ColorComp, RotateComp, and ZComp.
Replace 81 choreography function signatures from (atk: any, def: any, ...)
to (atk: Fighter, def: Fighter, ...). Type loadSpriteWithTimeout opts as
LoadSpriteOpt. Type lines/bands arrays as GameObj[]. Type spawnAfterimages
and cameraZoom fighter params. Add as Fighter casts at k.get() call sites.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-08 20:30:02 +00:00
co-authored by Claude Opus 4.6
parent 156c727f82
commit 1ca8a6feb0
+137 -134
View File
@@ -1,4 +1,7 @@
import kaplay from 'kaplay'
import type { GameObj, SpriteComp, PosComp, ScaleComp, AnchorComp, OpacityComp, ColorComp, RotateComp, ZComp, LoadSpriteOpt } from 'kaplay'
type Fighter = GameObj<SpriteComp | PosComp | ScaleComp | AnchorComp | OpacityComp | ColorComp | RotateComp | ZComp>
import { generateSpriteSheet, generateJudgeSpriteSheet, generateHumanSpriteSheet, generateHumanFighterSpriteSheet, getBotColors, FRAME_SIZE, MAX_FRAMES, TOTAL_ROWS, ANIMATIONS, JUDGE_ANIMATIONS, JUDGE_MAX_FRAMES, JUDGE_ROWS, HUMAN_ANIMATIONS, HUMAN_MAX_FRAMES, HUMAN_ROWS, type SpriteCustomization } from './sprites'
import {
sfxPunch, sfxKick, sfxSpecial, sfxCritical, sfxGunshot, sfxBulletHit,
@@ -293,7 +296,7 @@ export async function createFightScene(config: FightSceneConfig) {
const isHumanB = botB.archetype === 'human'
// Helper: load sprite with a 5s timeout so mobile never hangs forever
async function loadSpriteWithTimeout(name: string, src: string, opts: any): Promise<void> {
async function loadSpriteWithTimeout(name: string, src: string, opts: LoadSpriteOpt): Promise<void> {
try {
await Promise.race([
k.loadSprite(name, src, opts),
@@ -519,7 +522,7 @@ export async function createFightScene(config: FightSceneConfig) {
// Scanline glitch — horizontal bands flicker
function scanlineGlitch(duration: number = 0.3) {
const lines: any[] = []
const lines: GameObj[] = []
const count = 6 + Math.floor(Math.random() * 8)
for (let i = 0; i < count; i++) {
const ly = Math.random() * H
@@ -540,7 +543,7 @@ export async function createFightScene(config: FightSceneConfig) {
// VHS tracking distortion — horizontal offset bands
function vhsTracking(duration: number = 0.4) {
const bands: any[] = []
const bands: GameObj[] = []
const bandCount = 3
for (let i = 0; i < bandCount; i++) {
const by = Math.random() * H
@@ -576,8 +579,8 @@ export async function createFightScene(config: FightSceneConfig) {
// Schizo cut — rapid zoom/position jitter simulating jump cuts
async function schizoCut() {
const fA = k.get('fighterA')[0]
const fB = k.get('fighterB')[0]
const fA = k.get('fighterA')[0] as Fighter
const fB = k.get('fighterB')[0] as Fighter
if (!fA || !fB) return
const origAX = fA.pos.x, origBX = fB.pos.x
const origAY = fA.pos.y, origBY = fB.pos.y
@@ -598,7 +601,7 @@ export async function createFightScene(config: FightSceneConfig) {
// Hyperspeed lines — converging toward a point
function hyperSpeedLines(targetX: number, targetY: number, duration: number = 0.3) {
const lines: any[] = []
const lines: GameObj[] = []
for (let i = 0; i < 16; i++) {
const angle = (i / 16) * Math.PI * 2
const dist = 300 + Math.random() * 200
@@ -650,7 +653,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// Spawn afterimages: trailing ghost sprites behind a moving fighter
function spawnAfterimages(fighter: any, count: number = 3) {
function spawnAfterimages(fighter: Fighter, count: number = 3) {
if (!fighter?.exists()) return
for (let i = 0; i < count; i++) {
const ghost = k.add([
@@ -667,7 +670,7 @@ export async function createFightScene(config: FightSceneConfig) {
// Camera zoom: scale fighters toward/away from a focal point for impact emphasis
async function cameraZoom(
fighters: any[],
fighters: Fighter[],
savedScales: { x: number; y: number }[],
zoomFactor: number,
duration: number,
@@ -685,7 +688,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// Grotesque close-up overlays removed — noops retained for call-site compatibility
function spawnGrotesqueDetails(_fighter: any, _scaleFactor: number) {}
function spawnGrotesqueDetails(_fighter: Fighter, _scaleFactor: number) {}
function destroyGrotesqueDetails() {}
// Arena-specific background decoration
function drawArenaDecor() {
@@ -2038,7 +2041,7 @@ export async function createFightScene(config: FightSceneConfig) {
// === CHOREOGRAPHY FUNCTIONS ===
// Each returns a Promise and handles all movement + animation
async function dashPunch(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function dashPunch(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
const contactX = origDX - dir * 50
// Fast sprint to contact
await k.tween(atk.pos.x, contactX, 0.12, (v) => { atk.pos.x = v }, k.easings.easeOutQuad)
@@ -2061,7 +2064,7 @@ export async function createFightScene(config: FightSceneConfig) {
])
}
async function aerialSlam(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function aerialSlam(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
const origAY = atk.pos.y
const contactX = origDX - dir * 20
// Jump way up
@@ -2090,7 +2093,7 @@ export async function createFightScene(config: FightSceneConfig) {
])
}
async function flyingKick(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function flyingKick(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
const origAY = atk.pos.y
const contactX = origDX - dir * 30
// Small jump + fly horizontally
@@ -2117,7 +2120,7 @@ export async function createFightScene(config: FightSceneConfig) {
])
}
async function dashThrough(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function dashThrough(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
const behindX = origDX + dir * 80
atk.play('attack')
sfxSpecial()
@@ -2137,7 +2140,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.wait(0.1)
}
async function uppercut(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function uppercut(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
const contactX = origDX - dir * 40
const origDY = def.pos.y
// Run to contact
@@ -2163,7 +2166,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.tween(atk.pos.x, origAX, 0.2, (v) => { atk.pos.x = v }, k.easings.easeInQuad)
}
async function multiHit(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function multiHit(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
const contactX = origDX - dir * 45
// Sprint to contact
await k.tween(atk.pos.x, contactX, 0.1, (v) => { atk.pos.x = v }, k.easings.easeOutQuad)
@@ -2192,7 +2195,7 @@ export async function createFightScene(config: FightSceneConfig) {
])
}
async function projectile(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function projectile(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special')
sfxSpecial()
await k.wait(0.25)
@@ -2213,7 +2216,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.tween(def.pos.x, origDX, 0.3, (v) => { def.pos.x = v }, k.easings.easeInOutQuad)
}
async function jetpackDive(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function jetpackDive(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
const origAY = atk.pos.y
sfxJetpack()
// Jetpack blast off - fly way up off screen
@@ -2250,7 +2253,7 @@ export async function createFightScene(config: FightSceneConfig) {
])
}
async function gunBurst(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function gunBurst(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special')
// Spawn visible pixel gun prop
const gunX = atk.pos.x + dir * 15
@@ -2292,7 +2295,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.tween(def.pos.x, origDX, 0.3, (v) => { def.pos.x = v }, k.easings.easeInOutQuad)
}
async function groundPound(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function groundPound(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
const origAY = atk.pos.y
// Jump super high off screen
sfxJetpack()
@@ -2332,7 +2335,7 @@ export async function createFightScene(config: FightSceneConfig) {
])
}
async function teleportStrike(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function teleportStrike(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
// Fade out
sfxSpecial()
spawnSparks(atk.pos.x, atk.pos.y - 30, 8, '#b83dff')
@@ -2365,7 +2368,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.tween(def.pos.x, origDX, 0.3, (v) => { def.pos.x = v }, k.easings.easeInOutQuad)
}
async function fullScreenDash(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function fullScreenDash(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
// Back up to wall
const wallX = dir === 1 ? 20 : W - 20
await k.tween(atk.pos.x, wallX, 0.15, (v) => { atk.pos.x = v }, k.easings.easeOutQuad)
@@ -2448,7 +2451,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// CODE GOLF: swing a giant golf club, golf balls fly at opponent
async function golfClubSmash(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function golfClubSmash(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
const contactX = origDX - dir * 60
await k.tween(atk.pos.x, contactX, 0.15, (v) => { atk.pos.x = v }, k.easings.easeOutQuad)
// Spawn golf club (long thin rect)
@@ -2509,7 +2512,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// ROAST BATTLE: fire breath — stream of fire from attacker's face
async function fireBreath(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function fireBreath(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
const contactX = origDX - dir * 80
await k.tween(atk.pos.x, contactX, 0.12, (v) => { atk.pos.x = v }, k.easings.easeOutQuad)
atk.play('special')
@@ -2567,7 +2570,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// RIDDLE: question mark barrage rains from the sky
async function riddleBarrage(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function riddleBarrage(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special')
sfxSpecial()
sfxZap()
@@ -2612,7 +2615,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// HALLUCINATION CHECK: clone confusion — multiple fake attackers appear
async function cloneStrike(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function cloneStrike(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
sfxZap()
// Spawn 3-5 "ghost" clones that zip around
const cloneCount = isCritical ? 5 : 3
@@ -2662,7 +2665,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// TOKEN ECONOMY: coin shower — coins rain down on opponent
async function coinShower(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function coinShower(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special')
sfxSpecial()
sfxBoing()
@@ -2698,7 +2701,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// CREATIVE WRITING: giant pen stab + ink splatter
async function penStab(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function penStab(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
// Spawn a giant pen/quill
const penX = atk.pos.x + dir * 15
const penY = atk.pos.y - 70
@@ -2757,7 +2760,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// MATH BLITZ: number projectiles fly at opponent
async function mathAttack(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function mathAttack(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special')
sfxSpecial()
await k.wait(0.15)
@@ -2798,7 +2801,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// TRAP CARD: card shuriken + trap springs
async function trapCardAttack(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function trapCardAttack(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special')
sfxSpecial()
await k.wait(0.1)
@@ -2863,7 +2866,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// SPEED BLITZ: afterimage dash — leaves ghost copies behind
async function afterimageDash(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function afterimageDash(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
sfxSpecial()
const contactX = origDX - dir * 40
// Leave afterimages as we dash
@@ -2911,7 +2914,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// BULLET TIME: slow-mo dodge, afterimages trail behind, golden traces, devastating counter
async function bulletTimeAttack(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function bulletTimeAttack(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
// Phase 1: Darken screen — entering bullet time
const dim = k.add([k.rect(W, H), k.pos(0, 0), k.color(safeColor(k, '#000011')), k.opacity(0), k.z(25)])
await k.tween(0, 0.45, 0.15, (v) => { dim.opacity = v })
@@ -3017,7 +3020,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// INVISIBLE SPEED: so fast you can barely see — just blur lines and impact
async function lightningRush(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function lightningRush(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
sfxSpecial()
// Attacker vanishes
atk.opacity = 0
@@ -3069,7 +3072,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// ZOOM RUSH: fly toward camera (scale huge) then slam back into opponent
async function zoomRush(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function zoomRush(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
const origAY = atk.pos.y
const origScaleX = atk.scale.x
const origScaleY = atk.scale.y
@@ -3150,7 +3153,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// RAPID FLURRY: DBZ-style machine gun punches — so fast it's a blur
async function rapidFlurry(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function rapidFlurry(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
const contactX = origDX - dir * 40
// Sprint to contact
sfxSpecial()
@@ -3210,7 +3213,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// SWORD SLASH: giant energy sword swing with arc trail
async function swordSlash(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function swordSlash(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
const contactX = origDX - dir * 50
await k.tween(atk.pos.x, contactX, 0.1, (v) => { atk.pos.x = v }, k.easings.easeOutQuad)
atk.play('special')
@@ -3245,7 +3248,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// HAMMER SMASH: pull out giant hammer, overhead slam with ground crack
async function hammerSmash(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function hammerSmash(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
const contactX = origDX - dir * 40
await k.tween(atk.pos.x, contactX, 0.12, (v) => { atk.pos.x = v }, k.easings.easeOutQuad)
// Giant hammer
@@ -3277,7 +3280,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// LASER BEAM: charge up then fire continuous beam across screen
async function laserBeam(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function laserBeam(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special')
sfxSpecial()
// Charge up (growing ball of energy)
@@ -3308,7 +3311,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// ROCKET LAUNCHER: fire a big slow rocket that explodes on impact
async function rocketLauncher(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function rocketLauncher(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special')
sfxSpecial()
await k.wait(0.15)
@@ -3352,7 +3355,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// BOMB THROW: lob an arcing bomb that bounces once then detonates
async function bombThrow(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function bombThrow(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('attack')
sfxSpecial()
const bombX = atk.pos.x + dir * 20
@@ -3387,7 +3390,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// MINIGUN SPRAY: sustained automatic fire with shell casings
async function minigunSpray(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function minigunSpray(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special')
// Spawn visible pixel minigun
const gpx = atk.pos.x + dir * 15
@@ -3433,7 +3436,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// SNIPER SHOT: long pause, laser sight, then one devastating hit
async function sniperShot(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function sniperShot(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
// Back up to wall
const wallX = dir === 1 ? 30 : W - 30
await k.tween(atk.pos.x, wallX, 0.2, (v) => { atk.pos.x = v }, k.easings.easeOutQuad)
@@ -3471,7 +3474,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// WHIP CRACK: ranged whip attack with crack sound
async function whipCrack(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function whipCrack(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('attack')
sfxSpecial()
await k.wait(0.1)
@@ -3514,7 +3517,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// KATANA COMBO: triple slash with cross patterns
async function katanaCombo(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function katanaCombo(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
const contactX = origDX - dir * 45
sfxSpecial()
await k.tween(atk.pos.x, contactX, 0.08, (v) => { atk.pos.x = v }, k.easings.easeOutQuad)
@@ -3552,7 +3555,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// CHAINSAW: charge with buzzing chainsaw, multi-hit grind
async function chainsawRev(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function chainsawRev(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
const contactX = origDX - dir * 35
atk.play('special')
// Chainsaw buzzing (visual: vibrating rect)
@@ -3595,7 +3598,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// MOTORBIKE CHARGE: ride a motorbike across the screen and slam into defender
async function motorbikeCharge(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function motorbikeCharge(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
// Back up to edge
const startX = dir === 1 ? -30 : W + 30
atk.opacity = 0
@@ -3643,7 +3646,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// CAR SMASH: drive a car from off-screen, defender gets sent flying
async function carSmash(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function carSmash(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.opacity = 0
const carY = GROUND_Y - 20
const startX = dir === 1 ? -60 : W + 60
@@ -3688,7 +3691,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// BOAT CANNON: a boat slides across the ground and fires cannons
async function boatCannon(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function boatCannon(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special')
sfxSpecial()
// Build boat
@@ -3731,7 +3734,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// ANVIL DROP: classic cartoon — anvil falls from sky onto opponent
async function anvilDrop(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function anvilDrop(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special')
sfxSpecial()
await k.wait(0.15)
@@ -3766,7 +3769,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// POCKET CANNON: reach into pocket, pull out comically oversized gun, fire
async function pocketCannon(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function pocketCannon(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
// "Reach into pocket" — arm down motion
atk.play('special')
await k.wait(0.12)
@@ -3866,7 +3869,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// GRAPPLE FLURRY: both bots close in and trade sustained blows without splitting apart
async function grappleFlurry(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function grappleFlurry(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
// Both rush to center — meet in the middle
const meetX = (origAX + origDX) / 2
const atkMeetX = meetX - dir * 25
@@ -3928,7 +3931,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// BODY SLAM: wrestling-style grab, lift, and slam
async function bodySlam(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function bodySlam(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
// Rush in close
sfxSpecial()
const contactX = def.pos.x - dir * 30
@@ -3978,7 +3981,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// PINBALL COMBO: bounce the opponent between walls/edges like a pinball
async function pinballCombo(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function pinballCombo(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
// Initial launch punch
atk.play('attack')
sfxPunch()
@@ -4026,7 +4029,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// SUPLEX: grab from behind, lift, and drive headfirst into ground
async function suplex(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function suplex(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
// Dash behind defender
sfxSpecial()
const behindX = def.pos.x + dir * 30
@@ -4379,7 +4382,7 @@ export async function createFightScene(config: FightSceneConfig) {
// === DEFENSE / SHIELD SYSTEM ===
async function playBlock(side: 'a' | 'b') {
const defender = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0]
const defender = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter
if (!defender) return
const dir = side === 'a' ? 1 : -1
// Spawn shield in front
@@ -5246,7 +5249,7 @@ export async function createFightScene(config: FightSceneConfig) {
// ============================================================
// --- TIER 2+ ULTIMATES (Silver) — flashy multi-hits ---
async function ultimateRapidBarrage(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateRapidBarrage(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Rush in and land 8 alternating punches + kicks with screen shake
const contactX = origDX - dir * 45
sfxZoomWhoosh()
@@ -5275,7 +5278,7 @@ export async function createFightScene(config: FightSceneConfig) {
atk.play('idle')
}
async function ultimateOrbitalStrike(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateOrbitalStrike(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Call down 5 orbital beams on the defender's position
atk.play('special'); sfxSpecial()
for (let i = 0; i < 5; i++) {
@@ -5291,7 +5294,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.wait(0.3); atk.play('idle')
}
async function ultimateCloneSurround(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateCloneSurround(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Spawn 4 clones around the defender, all attack simultaneously
const clones: any[] = []
const positions = [
@@ -5316,7 +5319,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.wait(0.3)
}
async function ultimateGatlingFury(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateGatlingFury(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Pull out massive gatling gun, fire 12 shots with screen shake
atk.play('special')
const gunLen = 60
@@ -5343,7 +5346,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.wait(0.3); atk.play('idle')
}
async function ultimateEarthquake(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateEarthquake(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Jump high, slam ground, cracks + shockwaves across screen
sfxZoomWhoosh()
await k.tween(atk.pos.y, GROUND_Y - 150, 0.2, (v) => { atk.pos.y = v }, k.easings.easeOutQuad)
@@ -5373,7 +5376,7 @@ export async function createFightScene(config: FightSceneConfig) {
const ultimateMeteorShower = makeSwarm('#ff6600', 8, 10, 500, true)
// --- TIER 3+ ULTIMATES (Gold) — multi-phase, full screen ---
async function ultimateScreenNuke(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateScreenNuke(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Charge up glowing orb, launch it, entire screen whites out
atk.play('special'); sfxSpecial()
const orb = k.add([k.circle(5), k.pos(atk.pos.x + dir * 20, atk.pos.y - 30), k.color(safeColor(k,'#ffffff')), k.opacity(0.8), k.z(18)])
@@ -5398,7 +5401,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.wait(0.4); atk.play('idle')
}
async function ultimateBlackHoleVortex(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateBlackHoleVortex(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Summon black hole that pulls defender in, crushes, and ejects
atk.play('special'); sfxZap()
const bh = k.add([k.circle(8), k.pos(W / 2, GROUND_Y - 50), k.anchor('center'), k.color(safeColor(k,'#220044')), k.opacity(0.9), k.z(20)])
@@ -5429,7 +5432,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.wait(0.2); atk.play('idle')
}
async function ultimateDimensionalRift(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateDimensionalRift(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Open rift, attacker disappears, attacks from multiple portals
sfxZap(); atk.opacity = 0
const colors = ['#ff2d7b', '#00f0ff', '#39ff14', '#b83dff']
@@ -5460,7 +5463,7 @@ export async function createFightScene(config: FightSceneConfig) {
atk.play('idle')
}
async function ultimateTimeSlow(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateTimeSlow(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Everything goes slow-mo, attacker lands 6 devastating hits in "frozen" time
scanlineGlitch(0.1)
// Dim screen
@@ -5486,7 +5489,7 @@ export async function createFightScene(config: FightSceneConfig) {
atk.play('idle')
}
async function ultimateSummonArmy(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateSummonArmy(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Summon an army of 8 mini clones that charge across the screen
atk.play('special'); sfxSpecial()
const army: any[] = []
@@ -5516,7 +5519,7 @@ export async function createFightScene(config: FightSceneConfig) {
const ultimateAvalancheDrop = makeExplosion(30, 30, '#aaeeff', 0.2, '#ffffff', true)
// ULTIMATE BULLET TIME BARRAGE — full slow-mo cinematic: dodge rain of projectiles, afterimage rush, multi-hit counter
async function ultimateBulletTimeBarrage(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateBulletTimeBarrage(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Phase 1: World goes dark, heavy slow-mo overlay
const dim = k.add([k.rect(W, H), k.pos(0, 0), k.color(safeColor(k, '#000022')), k.opacity(0), k.z(25)])
await k.tween(0, 0.55, 0.2, (v) => { dim.opacity = v })
@@ -5620,7 +5623,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// --- TIER 4+ ULTIMATES (Platinum) — full screen, multi-phase spectacles ---
async function ultimateFinaleRush(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateFinaleRush(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Full-screen dash through, reverse, dash again, then combo finish
sfxZoomWhoosh()
// First pass
@@ -5649,7 +5652,7 @@ export async function createFightScene(config: FightSceneConfig) {
atk.play('idle')
}
async function ultimateMeteorCrash(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateMeteorCrash(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Attacker launches into sky, becomes a meteor, crashes into arena
sfxZoomWhoosh(); atk.play('special')
await k.tween(atk.pos.y, -100, 0.2, (v) => { atk.pos.y = v }, k.easings.easeInQuad)
@@ -5682,7 +5685,7 @@ export async function createFightScene(config: FightSceneConfig) {
atk.play('idle')
}
async function ultimateScreenShatter(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateScreenShatter(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Hit so hard the "screen" shatters with fragment overlay
const contactX = origDX - dir * 45
await k.tween(atk.pos.x, contactX, 0.1, (v) => { atk.pos.x = v }, k.easings.easeOutQuad)
@@ -5709,7 +5712,7 @@ export async function createFightScene(config: FightSceneConfig) {
atk.play('idle')
}
async function ultimateGravityReverse(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateGravityReverse(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Flip gravity — defender floats up, gets juggled, slammed back down
atk.play('special'); sfxZap()
// Defender floats up
@@ -5733,7 +5736,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.wait(0.3); atk.play('idle')
}
async function ultimateInfiniteCombo(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateInfiniteCombo(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// 15-hit combo with escalating screen effects
const contactX = origDX - dir * 40
sfxZoomWhoosh()
@@ -5769,7 +5772,7 @@ export async function createFightScene(config: FightSceneConfig) {
const ultimateRocketBarrage = makeSwarm('#ff6600', 10, 14, 550, false)
// --- TIER 5+ ULTIMATES (Diamond/Legend) — absolutely insane full-screen ---
async function ultimateArmageddon(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateArmageddon(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Full screen goes dark, rain of fire, massive explosion, screen flash
const darkness = k.add([k.rect(W, H), k.pos(0, 0), k.color(safeColor(k,'#000000')), k.opacity(0), k.z(25)])
await k.tween(0, 0.7, 0.3, (v) => { darkness.opacity = v })
@@ -5797,7 +5800,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.wait(0.5); atk.play('idle')
}
async function ultimateOmegaBeam(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateOmegaBeam(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Charge up, fire a massive full-width beam that covers half the screen
atk.play('special'); sfxSpecial()
// Charge particles converging on attacker
@@ -5829,7 +5832,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.wait(0.2); atk.play('idle')
}
async function ultimateBeyondTheScreen(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateBeyondTheScreen(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Attacker punches defender so hard they "break" off-screen, then gravity brings them back
const contactX = origDX - dir * 45
sfxZoomWhoosh()
@@ -5860,7 +5863,7 @@ export async function createFightScene(config: FightSceneConfig) {
atk.play('idle')
}
async function ultimateMatrixDodge(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateMatrixDodge(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Matrix-style slow-mo: attacker fires 3 projectiles, "dodges" in slo-mo, then counters
atk.play('special')
// Fire 3 slow projectiles
@@ -5891,7 +5894,7 @@ export async function createFightScene(config: FightSceneConfig) {
atk.play('idle')
}
async function ultimateWorldEnder(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateWorldEnder(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// The ultimate ultimate: 3-phase attack — air combo, ground slam, finishing beam
// Phase 1: Launch defender into air
const contactX = origDX - dir * 45
@@ -5945,7 +5948,7 @@ export async function createFightScene(config: FightSceneConfig) {
// THE CREATOR — Exclusive choreographies (bitcoin-themed)
// ═══════════════════════════════════════════════════════
async function bitcoinRain(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function bitcoinRain(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special')
sfxSpecial()
await k.wait(0.2)
@@ -5987,7 +5990,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
}
async function lightningInvoice(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function lightningInvoice(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special')
sfxZap()
await k.wait(0.15)
@@ -6031,7 +6034,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
}
async function satoshiStrike(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function satoshiStrike(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special')
sfxSpecial()
await k.wait(0.1)
@@ -6069,7 +6072,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
}
async function hashRateOverload(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function hashRateOverload(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special')
sfxSpecial()
// Screen fills with scrolling hash text (simulated with falling green/gold rects)
@@ -6105,7 +6108,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
}
async function blockchainSlam(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function blockchainSlam(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
const contactX = origDX - dir * 60
sfxZoomWhoosh()
await k.tween(atk.pos.x, contactX, 0.12, (v: number) => { atk.pos.x = v }, k.easings.easeOutQuad)
@@ -6144,7 +6147,7 @@ export async function createFightScene(config: FightSceneConfig) {
atk.play('idle')
}
async function creatorMode(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function creatorMode(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special')
sfxSpecial()
// Typing effect — rapid small flashes from attacker
@@ -6181,7 +6184,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
}
async function morphStrike(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function morphStrike(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
// Flash golden — the morph is handled by the morph system
// This move itself is a quick dash-hit with gold sparks
screenFlash('#ffd700', 0.08)
@@ -6205,7 +6208,7 @@ export async function createFightScene(config: FightSceneConfig) {
atk.play('idle')
}
async function divineIntervention(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function divineIntervention(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
// Ascend off screen
sfxZoomWhoosh()
atk.play('win')
@@ -6250,7 +6253,7 @@ export async function createFightScene(config: FightSceneConfig) {
// ── Creator ₿ Swarm Variations ──
async function bitcoinSwarm(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function bitcoinSwarm(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
// Swarm of ₿ letters fly from creator toward defender with zigzag paths
atk.play('special')
sfxSpecial()
@@ -6300,7 +6303,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
}
async function satoshiTornado(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function satoshiTornado(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
// ₿ letters spiral around the defender in a tightening vortex then explode
atk.play('special')
sfxSpecial()
@@ -6360,7 +6363,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
}
async function hodlWave(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function hodlWave(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
// Wall of ₿ letters advancing across the screen like a tidal wave
atk.play('special')
sfxSpecial()
@@ -6414,7 +6417,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
}
async function bitcoinBarrage(atk: any, def: any, dir: number, origAX: number, origDX: number, isCritical: boolean) {
async function bitcoinBarrage(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
// Rapid-fire ₿ letters shot from the creator's laptop like bullets
atk.play('special')
sfxSpecial()
@@ -6462,7 +6465,7 @@ export async function createFightScene(config: FightSceneConfig) {
// ── Creator Ultimates ──
async function ultimateGenesisBlock(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateGenesisBlock(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Screen goes dark, massive genesis block materializes and detonates
atk.play('special')
sfxSpecial()
@@ -6495,7 +6498,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
}
async function ultimateTheHalving(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateTheHalving(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Screen splits horizontally with a golden line
atk.play('special')
sfxZap()
@@ -6528,7 +6531,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
}
async function ultimateFullNodeCrush(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateFullNodeCrush(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Giant server rack drops from above
atk.play('special')
sfxSpecial()
@@ -6567,7 +6570,7 @@ export async function createFightScene(config: FightSceneConfig) {
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
}
async function ultimateSatoshiVision(atk: any, def: any, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
async function ultimateSatoshiVision(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Everything turns gold — defender trapped in bitcoin block
atk.play('special')
sfxSpecial()
@@ -7179,7 +7182,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
function showSpeechBubble(side: 'a' | 'b', text: string, duration: number = 3) {
const fighter = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0]
const fighter = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter
if (!fighter || !text) return
// Kill previous bubble on this side
@@ -7332,7 +7335,7 @@ export async function createFightScene(config: FightSceneConfig) {
function startTalking(side: 'a' | 'b') {
stopTalking(side) // cleanup any existing
const fighter = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0]
const fighter = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter
if (!fighter?.exists()) return
const mouthColor = side === 'a' ? '#00f0ff' : '#ff2d7b'
@@ -7510,7 +7513,7 @@ export async function createFightScene(config: FightSceneConfig) {
async function humanJumpsIn(side: 'a' | 'b') {
const fighterTag = side === 'a' ? 'fighterA' : 'fighterB'
const spriteId = side === 'a' ? 'humanA' : 'humanB'
const fighter = k.get(fighterTag)[0]
const fighter = k.get(fighterTag)[0] as Fighter
if (!fighter) return
// Spawn human at fighter position
@@ -8628,8 +8631,8 @@ export async function createFightScene(config: FightSceneConfig) {
},
async playEntrance() {
const fA = k.get('fighterA')[0]
const fB = k.get('fighterB')[0]
const fA = k.get('fighterA')[0] as Fighter
const fB = k.get('fighterB')[0] as Fighter
if (!fA || !fB) return
const entrances = [
@@ -9170,8 +9173,8 @@ export async function createFightScene(config: FightSceneConfig) {
// Helper: reset both fighters to home positions
async _resetPositions() {
const fA = k.get('fighterA')[0]
const fB = k.get('fighterB')[0]
const fA = k.get('fighterA')[0] as Fighter
const fB = k.get('fighterB')[0] as Fighter
if (!fA || !fB) return
await Promise.all([
k.tween(fA.pos.x, HOME_A, 0.2, (v) => { fA.pos.x = v }, k.easings.easeOutQuad),
@@ -9185,8 +9188,8 @@ export async function createFightScene(config: FightSceneConfig) {
// 0: RAPID BRAWL — close distance, trade rapid blows
async _brawlRapid(winningSide: 'a' | 'b' | null, intensity: number) {
const fA = k.get('fighterA')[0]
const fB = k.get('fighterB')[0]
const fA = k.get('fighterA')[0] as Fighter
const fB = k.get('fighterB')[0] as Fighter
if (!fA || !fB) return
const midX = W / 2
sfxZoomWhoosh()
@@ -9211,8 +9214,8 @@ export async function createFightScene(config: FightSceneConfig) {
// 1: KNOCKDOWN BRAWL — combo ends with loser knocked flat, has to get back up
async _brawlKnockdown(winningSide: 'a' | 'b' | null, intensity: number) {
const fA = k.get('fighterA')[0]
const fB = k.get('fighterB')[0]
const fA = k.get('fighterA')[0] as Fighter
const fB = k.get('fighterB')[0] as Fighter
if (!fA || !fB) return
const winner = winningSide === 'a' ? fA : winningSide === 'b' ? fB : (Math.random() > 0.5 ? fA : fB)
const loser = winner === fA ? fB : fA
@@ -9261,8 +9264,8 @@ export async function createFightScene(config: FightSceneConfig) {
// 2: WALL BOUNCE — punch sends fighter flying into screen edge, bounces off
async _brawlWallBounce(winningSide: 'a' | 'b' | null, intensity: number) {
const fA = k.get('fighterA')[0]
const fB = k.get('fighterB')[0]
const fA = k.get('fighterA')[0] as Fighter
const fB = k.get('fighterB')[0] as Fighter
if (!fA || !fB) return
const winner = winningSide === 'a' ? fA : winningSide === 'b' ? fB : (Math.random() > 0.5 ? fA : fB)
const loser = winner === fA ? fB : fA
@@ -9320,8 +9323,8 @@ export async function createFightScene(config: FightSceneConfig) {
// 3: DIZZY STAGGER — sustained combo leaves loser wobbling around the screen
async _brawlDizzyStagger(winningSide: 'a' | 'b' | null, intensity: number) {
const fA = k.get('fighterA')[0]
const fB = k.get('fighterB')[0]
const fA = k.get('fighterA')[0] as Fighter
const fB = k.get('fighterB')[0] as Fighter
if (!fA || !fB) return
const winner = winningSide === 'a' ? fA : winningSide === 'b' ? fB : (Math.random() > 0.5 ? fA : fB)
const loser = winner === fA ? fB : fA
@@ -9372,8 +9375,8 @@ export async function createFightScene(config: FightSceneConfig) {
// 4: SUPLEX SLAM — grab, lift overhead, slam to ground with bounce
async _brawlSuplex(attackerSide: 'a' | 'b', intensity: number) {
const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0]
const defender = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0]
const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter
const defender = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter
if (!attacker || !defender) return
const dir = attackerSide === 'a' ? 1 : -1
@@ -9423,8 +9426,8 @@ export async function createFightScene(config: FightSceneConfig) {
// 5: PING PONG VOLLEY — fighters knock each other back and forth across the screen
async _brawlPingPong(winningSide: 'a' | 'b' | null, intensity: number) {
const fA = k.get('fighterA')[0]
const fB = k.get('fighterB')[0]
const fA = k.get('fighterA')[0] as Fighter
const fB = k.get('fighterB')[0] as Fighter
if (!fA || !fB) return
const volleys = 3 + Math.floor(intensity * 3) + Math.floor(Math.random() * 2) // 3-8
@@ -9471,8 +9474,8 @@ export async function createFightScene(config: FightSceneConfig) {
// 6: GROUND AND POUND — attacker pins defender, hits them on the ground
async _brawlGroundPound(attackerSide: 'a' | 'b', intensity: number) {
const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0]
const defender = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0]
const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter
const defender = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter
if (!attacker || !defender) return
const dir = attackerSide === 'a' ? 1 : -1
@@ -9515,8 +9518,8 @@ export async function createFightScene(config: FightSceneConfig) {
// 7: HAYMAKER — slow dramatic wind-up, pause, devastating single hit with knockdown
async _brawlHaymaker(attackerSide: 'a' | 'b', _intensity: number) {
const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0]
const defender = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0]
const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter
const defender = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter
if (!attacker || !defender) return
const dir = attackerSide === 'a' ? 1 : -1
@@ -9563,8 +9566,8 @@ export async function createFightScene(config: FightSceneConfig) {
// 8: BODY CHECK BOUNCER — both charge, collide, one bounces off the other
async _brawlBodyCheck(winningSide: 'a' | 'b' | null, _intensity: number) {
const fA = k.get('fighterA')[0]
const fB = k.get('fighterB')[0]
const fA = k.get('fighterA')[0] as Fighter
const fB = k.get('fighterB')[0] as Fighter
if (!fA || !fB) return
const winner = winningSide === 'a' ? fA : winningSide === 'b' ? fB : (Math.random() > 0.5 ? fA : fB)
const loser = winner === fA ? fB : fA
@@ -9625,8 +9628,8 @@ export async function createFightScene(config: FightSceneConfig) {
// Clinch combo: grapple at close range, one fighter dominates with sustained contact
async _clinchCombo(attackerSide: 'a' | 'b', intensity: number) {
const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0]
const defender = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0]
const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter
const defender = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter
if (!attacker || !defender) return
const dir = attackerSide === 'a' ? 1 : -1
const origAX = attackerSide === 'a' ? HOME_A : HOME_B
@@ -9664,8 +9667,8 @@ export async function createFightScene(config: FightSceneConfig) {
// Counter-attack: after a choreography hit, the defender retaliates briefly
async _counterAttack(defenderSide: 'a' | 'b') {
const defender = k.get(defenderSide === 'a' ? 'fighterA' : 'fighterB')[0]
const attacker = k.get(defenderSide === 'a' ? 'fighterB' : 'fighterA')[0]
const defender = k.get(defenderSide === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter
const attacker = k.get(defenderSide === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter
if (!defender || !attacker) return
const dir = defenderSide === 'a' ? 1 : -1
const origDX = defenderSide === 'a' ? HOME_A : HOME_B
@@ -9683,8 +9686,8 @@ export async function createFightScene(config: FightSceneConfig) {
},
async playAttack(side: 'a' | 'b', choreographyName: string, isCritical: boolean) {
const attacker = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0]
const defender = k.get(side === 'a' ? 'fighterB' : 'fighterA')[0]
const attacker = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter
const defender = k.get(side === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter
if (!attacker || !defender) return
const origAX = side === 'a' ? HOME_A : HOME_B
@@ -9705,8 +9708,8 @@ export async function createFightScene(config: FightSceneConfig) {
},
async playRound(event: RoundEvent) {
const fA = k.get('fighterA')[0]
const fB = k.get('fighterB')[0]
const fA = k.get('fighterA')[0] as Fighter
const fB = k.get('fighterB')[0] as Fighter
if (!fA || !fB) { await k.wait(0.5); return } // Sprites missing (mobile load failure) — skip animation
const aWon = event.winnerId === event.botAId
@@ -9932,7 +9935,7 @@ export async function createFightScene(config: FightSceneConfig) {
: (isUltimate || (exchangeCritical && intensity > 0.7 && Math.random() < 0.4))
let morphRevert: (() => Promise<void>) | null = null
if (shouldMorph) {
const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0]
const attacker = k.get(attackerSide === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter
if (attacker) {
if (isCreatorFighter) {
// Omni-morph: creator transforms into a random archetype (sprite swap)
@@ -10010,7 +10013,7 @@ export async function createFightScene(config: FightSceneConfig) {
}
// RGB glitch + hyperspeed lines on critical final blow
glitchRGB(0.3)
const defPos = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0]
const defPos = k.get(attackerSide === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter
if (defPos) hyperSpeedLines(defPos.pos.x, defPos.pos.y - 30, 0.4)
}
@@ -10246,8 +10249,8 @@ export async function createFightScene(config: FightSceneConfig) {
// Respect nod between fighters on close rounds (10% when margin <= 1)
if (margin <= 1 && Math.random() < 0.1) {
const fA2 = k.get('fighterA')[0]
const fB2 = k.get('fighterB')[0]
const fA2 = k.get('fighterA')[0] as Fighter
const fB2 = k.get('fighterB')[0] as Fighter
if (fA2 && fB2) {
spawnEmoteText(fA2.pos.x, fA2.pos.y - 45, respectLines[Math.floor(Math.random() * respectLines.length)], '#88ccff')
await k.wait(0.3)
@@ -10265,7 +10268,7 @@ export async function createFightScene(config: FightSceneConfig) {
},
async playTaunt(side: 'a' | 'b') {
const taunter = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0]
const taunter = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter
if (!taunter) return
// Creator taunts come with menacing voice lines
const taunterArch = side === 'a' ? botA.archetype : botB.archetype
@@ -10307,7 +10310,7 @@ export async function createFightScene(config: FightSceneConfig) {
},
async playDodge(side: 'a' | 'b') {
const dodger = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0]
const dodger = k.get(side === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter
if (!dodger) return
const origX = side === 'a' ? HOME_A : HOME_B
const origY = dodger.pos.y
@@ -10324,8 +10327,8 @@ export async function createFightScene(config: FightSceneConfig) {
},
async playKO(winningSide: 'a' | 'b', winnerName: string) {
const loser = k.get(winningSide === 'a' ? 'fighterB' : 'fighterA')[0]
const winner = k.get(winningSide === 'a' ? 'fighterA' : 'fighterB')[0]
const loser = k.get(winningSide === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter
const winner = k.get(winningSide === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter
if (!loser || !winner) return
const dir = winningSide === 'a' ? 1 : -1
@@ -11183,8 +11186,8 @@ export async function createFightScene(config: FightSceneConfig) {
},
async playPerfect(winningSide: 'a' | 'b', winnerName: string) {
const winner = k.get(winningSide === 'a' ? 'fighterA' : 'fighterB')[0]
const loser = k.get(winningSide === 'a' ? 'fighterB' : 'fighterA')[0]
const winner = k.get(winningSide === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter
const loser = k.get(winningSide === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter
if (!loser || !winner) return
// Judge goes wild for a perfect