refactor: replace 97 any types in fight system with Fighter/GameObj

- fighter: any → Fighter in entrances, morphs, finishers, rounds, types
- any[] → GameObj[] for spawned game objects across all choreography files
- as any cast in particles.ts replaced with closure variables
- Record<string, unknown> for morph sprite opts
- this: any → this: GameObj in basic choreography callback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-09 06:26:33 +00:00
co-authored by Claude Opus 4.6
parent 81048ff22f
commit a86d39b982
13 changed files with 90 additions and 77 deletions
@@ -1,3 +1,4 @@
import type { GameObj } from 'kaplay'
import type { Fighter, ChoreoContext } from '../types'
import { createFactories } from './factories'
import type { ChoreoFn } from './factories'
@@ -534,7 +535,7 @@ async function bulletTimeAttack(atk: Fighter, def: Fighter, dir: number, origAX:
atk.play('special')
sfxGunshot()
const bulletCount = isCritical ? 5 : 3
const bullets: any[] = []
const bullets: GameObj[] = []
for (let i = 0; i < bulletCount; i++) {
const bx = atk.pos.x + dir * 25
const by = atk.pos.y - 30 + (i - 1) * 12
@@ -734,7 +735,7 @@ async function fireBreath(atk: Fighter, def: Fighter, dir: number, origAX: numbe
k.color(safeColor(Math.random() > 0.5 ? '#ff6600' : '#ffcc00')),
k.opacity(0.7),
k.z(11),
]).onUpdate(function(this: any) { this.pos.y -= 80 * k.dt(); this.opacity -= 2 * k.dt(); if (this.opacity <= 0) this.destroy() })
]).onUpdate(function(this: GameObj) { this.pos.y -= 80 * k.dt(); this.opacity -= 2 * k.dt(); if (this.opacity <= 0) this.destroy() })
}, 40)
const push = dir * (isCritical ? 110 : 50)
k.tween(def.pos.x, origDX + push, 0.3, (v) => { def.pos.x = v }, k.easings.easeOutQuad)
@@ -794,7 +795,7 @@ async function cloneStrike(atk: Fighter, def: Fighter, dir: number, origAX: numb
sfxZap()
// Spawn 3-5 "ghost" clones that zip around
const cloneCount = isCritical ? 5 : 3
const clones: any[] = []
const clones: GameObj[] = []
for (let i = 0; i < cloneCount; i++) {
const clone = k.add([
k.rect(20, 35),
@@ -1,3 +1,4 @@
import type { GameObj } from 'kaplay'
import type { Fighter, ChoreoContext } from '../types'
import { createFactories } from './factories'
import type { ChoreoFn } from './factories'
@@ -24,7 +25,7 @@ async function lightningBolt(atk: Fighter, def: Fighter, dir: number, origAX: nu
atk.play('special'); sfxSpecial(); await k.wait(0.12)
const segments = isCritical ? 8 : 5
let px = atk.pos.x + dir * 30, py = atk.pos.y - 40
const bolts: any[] = []
const bolts: GameObj[] = []
for (let i = 0; i < segments; i++) {
const nx = px + dir * 30 + (Math.random() - 0.5) * 20
const ny = py + (Math.random() - 0.5) * 30
@@ -262,7 +263,7 @@ const proofOfWork = makeSwing(12, 30, '#888888', '#f7931a', 14, 14)
async function merkleRoot(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special'); sfxSpecial(); await k.wait(0.1)
// Roots grow up from ground under defender
const roots: any[] = []
const roots: GameObj[] = []
for (let i = 0; i < (isCritical ? 6 : 3); i++) {
const rx = def.pos.x + (i - (isCritical ? 2.5 : 1)) * 12
const root = k.add([k.rect(3, 0), k.pos(rx, GROUND_Y), k.color(safeColor('#44aa22')), k.opacity(0.8), k.z(15)])
@@ -285,7 +286,7 @@ async function merkleRoot(atk: Fighter, def: Fighter, dir: number, origAX: numbe
async function blockReward(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special'); sfxBoing()
// Gold coins gather around attacker then blast at defender
const coins: any[] = []
const coins: GameObj[] = []
for (let i = 0; i < (isCritical ? 10 : 5); i++) {
const c = k.add([k.circle(4), k.pos(atk.pos.x + (Math.random() - 0.5) * 40, atk.pos.y - 50 + (Math.random() - 0.5) * 30), k.color(safeColor('#ffd700')), k.opacity(1), k.z(17)])
coins.push(c)
@@ -1,3 +1,4 @@
import type { GameObj } from 'kaplay'
import type { Fighter, ChoreoContext } from '../types'
import { createFactories } from './factories'
import type { ChoreoFn } from './factories'
@@ -186,7 +187,7 @@ async function blockchainSlam(atk: Fighter, def: Fighter, dir: number, origAX: n
await k.tween(atk.pos.x, contactX, 0.12, (v: number) => { atk.pos.x = v }, k.easings.easeOutQuad)
atk.play('attack')
// Chain of blocks swinging as weapon
const blocks: any[] = []
const blocks: GameObj[] = []
const blockCount = isCritical ? 6 : 4
for (let i = 0; i < blockCount; i++) {
const bk = k.add([
@@ -329,7 +330,7 @@ async function bitcoinSwarm(atk: Fighter, def: Fighter, dir: number, origAX: num
sfxSpecial()
await k.wait(0.15)
const total = isCritical ? 30 : 18
const swarmThings: any[] = []
const swarmThings: GameObj[] = []
for (let i = 0; i < total; i++) {
const sx = atk.pos.x + dir * 10 + (Math.random() - 0.5) * 30
const sy = atk.pos.y - 50 + (Math.random() - 0.5) * 60
@@ -379,7 +380,7 @@ async function satoshiTornado(atk: Fighter, def: Fighter, dir: number, origAX: n
sfxSpecial()
sfxZoomWhoosh()
const total = isCritical ? 24 : 14
const vortex: any[] = []
const vortex: GameObj[] = []
for (let i = 0; i < total; i++) {
const sz = 7 + Math.random() * 7
const colors = ['#ffd700', '#ffee88', '#ff8c00', '#ffffff']
@@ -440,7 +441,7 @@ async function hodlWave(atk: Fighter, def: Fighter, dir: number, origAX: number,
await k.wait(0.1)
const rows = isCritical ? 6 : 4
const cols = isCritical ? 10 : 7
const wave: any[] = []
const wave: GameObj[] = []
const startX = atk.pos.x + dir * 25
const screenTop = -20
const screenBot = GROUND_Y + 10
@@ -1,3 +1,4 @@
import type { GameObj } from 'kaplay'
import type { Fighter, ChoreoContext } from '../types'
export type PropDef = { parts: { shape: 'rect' | 'circle'; x: number; y: number; w: number; h: number; color: string }[] }
@@ -297,13 +298,13 @@ async function spawnBarrage(
}
}
function spawnWeaponProp(propName: string, x: number, y: number, flipX: boolean = false, zIndex: number = 16): any[] {
function spawnWeaponProp(propName: string, x: number, y: number, flipX: boolean = false, zIndex: number = 16): GameObj[] {
const def = PROPS[propName]
if (!def) {
// Fallback: single colored circle
return [k.add([k.circle(8), k.pos(x, y), k.color(k.Color.fromHex('#ff8800')), k.opacity(1), k.z(zIndex), k.anchor('center')])]
}
const objs: any[] = []
const objs: GameObj[] = []
for (const p of def.parts) {
const px = flipX ? x - p.x : x + p.x
const obj = k.add([
@@ -319,7 +320,7 @@ function spawnWeaponProp(propName: string, x: number, y: number, flipX: boolean
return objs
}
function moveProp(objs: any[], def: PropDef, x: number, y: number, flipX: boolean = false) {
function moveProp(objs: GameObj[], def: PropDef, x: number, y: number, flipX: boolean = false) {
for (let i = 0; i < objs.length && i < def.parts.length; i++) {
const p = def.parts[i]
objs[i].pos.x = flipX ? x - p.x : x + p.x
@@ -327,7 +328,7 @@ function moveProp(objs: any[], def: PropDef, x: number, y: number, flipX: boolea
}
}
function destroyProp(objs: any[]) {
function destroyProp(objs: GameObj[]) {
objs.forEach(o => { if (o.exists()) o.destroy() })
}
@@ -431,7 +432,7 @@ function makeSwing(weaponW: number, weaponH: number, weaponColor: string, headCo
await k.tween(atk.pos.x, cx, 0.14, (v) => { atk.pos.x = v }, k.easings.easeOutQuad)
const wx = atk.pos.x + dir * 18, wy = atk.pos.y - 55
const shaft = k.add([k.rect(weaponW, weaponH), k.pos(wx, wy), k.color(safeColor(weaponColor)), k.anchor('bot'), k.z(18), k.rotate(dir === 1 ? -40 : 40)])
let head: any = null
let head: GameObj | null = null
if (headColor) { head = k.add([k.rect(headW || 18, headH || 14), k.pos(wx + dir * 10, wy - weaponH + 5), k.color(safeColor(headColor)), k.z(18)]) }
atk.play('attack'); sfxSpecial()
await k.tween(dir === 1 ? -40 : 40, dir === 1 ? 85 : -85, 0.13, (v) => { shaft.angle = v }, k.easings.easeInQuad)
@@ -481,7 +482,7 @@ function makeVehicle(bodyW: number, bodyH: number, bodyColor: string, wheelR: nu
const body = k.add([k.rect(bodyW, bodyH), k.pos(startX, vY), k.color(safeColor(isCritical ? '#ff2d2d' : bodyColor)), k.z(16)])
const w1 = k.add([k.circle(wheelR), k.pos(startX + dir * (bodyW / 3), vY + bodyH / 2 + wheelR - 2), k.color(safeColor('#222222')), k.z(16)])
const w2 = k.add([k.circle(wheelR), k.pos(startX - dir * (bodyW / 3), vY + bodyH / 2 + wheelR - 2), k.color(safeColor('#222222')), k.z(16)])
const extras: any[] = []
const extras: GameObj[] = []
if (parts) for (const p of parts) {
extras.push(k.add([k.rect(p.w, p.h), k.pos(startX + p.ox * dir, vY + p.oy), k.color(safeColor(p.color)), k.z(17)]))
}
@@ -598,7 +599,7 @@ function makeSwarm(color: string, count: number, size: number, speed: number = 4
return async (atk, def, dir, origAX, origDX, isCritical) => {
atk.play('special'); sfxSpecial(); await k.wait(0.15)
const total = count + (isCritical ? 3 : 0)
const swarmThings: any[] = []
const swarmThings: GameObj[] = []
for (let i = 0; i < total; i++) {
const sx = atk.pos.x - dir * 20 + (Math.random() - 0.5) * 30
const sy = atk.pos.y - 40 + (Math.random() - 0.5) * 50
@@ -1,3 +1,4 @@
import type { GameObj } from 'kaplay'
import type { Fighter, ChoreoContext } from '../types'
import { createFactories } from './factories'
import type { ChoreoFn } from './factories'
@@ -266,7 +267,7 @@ const airHornStun = makeBeam('#ff4444', 8, 1)
async function touchGrassSmack(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special'); sfxSpecial(); await k.wait(0.1)
// Grass shoots up from ground
const blades: any[] = []
const blades: GameObj[] = []
for (let i = 0; i < (isCritical ? 8 : 4); i++) {
const gx = def.pos.x + (i - (isCritical ? 3.5 : 1.5)) * 10
const blade = k.add([k.rect(2, 0), k.pos(gx, GROUND_Y), k.color(safeColor('#44cc22')), k.opacity(0.8), k.z(15)])
@@ -1,3 +1,4 @@
import type { GameObj } from 'kaplay'
import type { Fighter, ChoreoContext } from '../types'
import { createFactories } from './factories'
import type { ChoreoFn } from './factories'
@@ -67,7 +68,7 @@ async function ultimateOrbitalStrike(atk: Fighter, def: Fighter, dir: number, or
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 clones: GameObj[] = []
const positions = [
{ x: def.pos.x - 60, y: GROUND_Y }, { x: def.pos.x + 60, y: GROUND_Y },
{ x: def.pos.x - 30, y: GROUND_Y - 50 }, { x: def.pos.x + 30, y: GROUND_Y - 50 },
@@ -266,7 +267,7 @@ async function ultimateTimeSlow(atk: Fighter, def: Fighter, dir: number, origAX:
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[] = []
const army: GameObj[] = []
for (let i = 0; i < 8; i++) {
const ax = atk.pos.x + (Math.random() - 0.5) * 30
const ay = GROUND_Y - Math.random() * 10
@@ -443,7 +444,7 @@ async function ultimateMeteorCrash(atk: Fighter, def: Fighter, dir: number, orig
// Impact
atk.pos.x = def.pos.x; atk.pos.y = -80; atk.opacity = 1
// Fire trail
const fireTrail: any[] = []
const fireTrail: GameObj[] = []
await k.tween(-80, GROUND_Y, 0.15, (v) => {
atk.pos.y = v
const f = k.add([k.circle(6), k.pos(atk.pos.x + (Math.random() - 0.5) * 15, v - 15), k.color(safeColor('#ff6600')), k.opacity(0.7), k.z(9)])
@@ -468,7 +469,7 @@ async function ultimateScreenShatter(atk: Fighter, def: Fighter, dir: number, or
atk.play('special'); sfxCritical()
def.play('knockback'); k.shake(25)
// Create "shatter" fragments
const frags: any[] = []
const frags: GameObj[] = []
for (let i = 0; i < 12; i++) {
const fx = W * Math.random()
const fy = H * Math.random() * 0.8
@@ -1,3 +1,4 @@
import type { GameObj } from 'kaplay'
import type { Fighter, ChoreoContext } from '../types'
import { createFactories } from './factories'
import type { ChoreoFn } from './factories'
@@ -26,7 +27,7 @@ async function swordSlash(atk: Fighter, def: Fighter, dir: number, origAX: numbe
atk.play('special')
sfxSpecial()
// Draw sword arc (series of rects forming a crescent)
const arcParts: any[] = []
const arcParts: GameObj[] = []
for (let i = 0; i < 8; i++) {
const angle = (i / 8) * Math.PI - Math.PI / 4
const ax = contactX + dir * 20 + Math.cos(angle) * 60
@@ -90,7 +91,7 @@ async function whipCrack(atk: Fighter, def: Fighter, dir: number, origAX: number
sfxSpecial()
await k.wait(0.1)
// Whip extends from attacker to defender (series of small segments)
const segments: any[] = []
const segments: GameObj[] = []
const fromX = atk.pos.x + dir * 20
const fromY = atk.pos.y - 40
const toX = def.pos.x
+27 -26
View File
@@ -1,3 +1,4 @@
import type { GameObj } from 'kaplay'
import type { Fighter, ChoreoContext } from './types'
import { sfxRandomComedy, sfxBoneCrack, sfxVineBoom, sfxSlideWhistleDown, sfxPowerUp, sfxDrumRoll, announceDramatic, announceDeepIntro, announceSilly, announceRandom, announceCool, announceCrowdReaction, announceCreatorEntrance } from '../audio'
@@ -29,7 +30,7 @@ async function playEntrance() {
const entrances = [
// 0: Drive in with a car
async (fighter: any, homeX: number, fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const dir = fromLeft ? 1 : -1
const startX = fromLeft ? -120 : W + 120
const carY = GROUND_Y - 20
@@ -55,13 +56,13 @@ async function playEntrance() {
}, k.easings.easeInQuad).then(() => { carBody.destroy(); wheel1.destroy(); wheel2.destroy() })
},
// 1: Fall from space
async (fighter: any, homeX: number, _fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, _fromLeft: boolean) => {
fighter.pos.x = homeX
fighter.pos.y = -200
fighter.opacity = 1
sfxZoomWhoosh()
// Trail of fire
const trail: any[] = []
const trail: GameObj[] = []
for (let i = 0; i < 6; i++) {
trackedTimeout(() => {
const t = k.add([k.circle(4 + Math.random() * 6), k.pos(homeX + (Math.random() - 0.5) * 20, fighter.pos.y + 20), k.color(safeColor(i < 3 ? '#ff6600' : '#ffcc00')), k.opacity(0.7), k.z(9)])
@@ -78,7 +79,7 @@ async function playEntrance() {
trail.forEach(t => { if (t.exists()) t.destroy() })
},
// 2: Robe entrance (boxing style)
async (fighter: any, homeX: number, fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const startX = fromLeft ? -60 : W + 60
fighter.pos.x = startX
fighter.pos.y = GROUND_Y - 6
@@ -98,7 +99,7 @@ async function playEntrance() {
spawnSparks(homeX, GROUND_Y - 40, 8, robeColor)
},
// 3: Girlfriend argument
async (fighter: any, homeX: number, fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const dir = fromLeft ? 1 : -1
const startX = fromLeft ? -60 : W + 60
fighter.pos.x = startX
@@ -126,7 +127,7 @@ async function playEntrance() {
sfxBoing()
},
// 4: Helicopter drop
async (fighter: any, homeX: number, _fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, _fromLeft: boolean) => {
fighter.pos.x = homeX
fighter.pos.y = -100
fighter.opacity = 1
@@ -148,7 +149,7 @@ async function playEntrance() {
.then(() => { heli.destroy(); blade.destroy() })
},
// 5: Teleport glitch
async (fighter: any, homeX: number, _fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, _fromLeft: boolean) => {
fighter.opacity = 0
sfxZap()
// Glitch flickers at random positions
@@ -170,7 +171,7 @@ async function playEntrance() {
k.shake(5)
},
// 6: Skateboard ride in
async (fighter: any, homeX: number, fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const startX = fromLeft ? -80 : W + 80
const board = k.add([k.rect(40, 6), k.pos(startX, GROUND_Y - 3), k.anchor('center'), k.color(safeColor('#884422')), k.z(9)])
const wheelL = k.add([k.circle(4), k.pos(startX - 14, GROUND_Y + 1), k.anchor('center'), k.color(safeColor('#333')), k.z(9)])
@@ -189,12 +190,12 @@ async function playEntrance() {
sfxBonk(); k.shake(3)
},
// 7: Rocket jetpack
async (fighter: any, homeX: number, fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const startX = fromLeft ? -80 : W + 80
fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 80; fighter.opacity = 1
sfxJetpack()
// Fly in with flame trail
const flames: any[] = []
const flames: GameObj[] = []
const interval = trackedInterval(() => {
const f = k.add([k.circle(5 + Math.random() * 5), k.pos(fighter.pos.x, fighter.pos.y + 25), k.color(safeColor(Math.random() > 0.5 ? '#ff6600' : '#ffcc00')), k.opacity(0.8), k.z(9)])
flames.push(f)
@@ -210,7 +211,7 @@ async function playEntrance() {
flames.forEach(f => { if (f.exists()) f.destroy() })
},
// 8: Emerge from portal
async (fighter: any, homeX: number, _fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, _fromLeft: boolean) => {
const portalColor = ['#b83dff', '#00f0ff', '#39ff14', '#ff2d7b'][Math.floor(Math.random() * 4)]
// Draw portal
const portal = k.add([k.circle(35), k.pos(homeX, GROUND_Y - 30), k.anchor('center'), k.color(safeColor(portalColor)), k.opacity(0), k.z(9)])
@@ -228,7 +229,7 @@ async function playEntrance() {
portal.destroy(); portalRing.destroy()
},
// 9: Backflip entrance
async (fighter: any, homeX: number, fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const startX = fromLeft ? -40 : W + 40
fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1
sfxZoomWhoosh()
@@ -250,7 +251,7 @@ async function playEntrance() {
spawnSparks(homeX, GROUND_Y - 10, 6, '#ffe14d')
},
// 10: Rise from underground
async (fighter: any, homeX: number, _fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, _fromLeft: boolean) => {
fighter.pos.x = homeX; fighter.pos.y = GROUND_Y + 60; fighter.opacity = 0.5
// Crack the ground
const crack1 = k.add([k.rect(3, 15), k.pos(homeX - 10, GROUND_Y - 5), k.color(safeColor('#ffcc00')), k.opacity(0.7), k.z(9), k.rotate(15)])
@@ -264,12 +265,12 @@ async function playEntrance() {
crack1.destroy(); crack2.destroy()
},
// 11: Slide in on ice
async (fighter: any, homeX: number, fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const startX = fromLeft ? -80 : W + 80
fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1
sfxSlideDown()
// Ice trail
const iceTrail: any[] = []
const iceTrail: GameObj[] = []
await k.tween(startX, homeX + (fromLeft ? 40 : -40), 0.4, (v) => {
fighter.pos.x = v
if (Math.random() < 0.3) {
@@ -283,7 +284,7 @@ async function playEntrance() {
trackedTimeout(() => { iceTrail.forEach(i => { if (i.exists()) i.destroy() }) }, 800)
},
// 12: Parachute drop
async (fighter: any, homeX: number, _fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, _fromLeft: boolean) => {
fighter.pos.x = homeX + (Math.random() - 0.5) * 60; fighter.pos.y = -120; fighter.opacity = 1
const chute = k.add([k.circle(30), k.pos(fighter.pos.x, fighter.pos.y - 35), k.anchor('center'), k.color(safeColor(['#ff2d2d', '#2d7bff', '#39ff14', '#ffcc00'][Math.floor(Math.random() * 4)])), k.opacity(0.8), k.z(12)])
const line1 = k.add([k.rect(1, 30), k.pos(fighter.pos.x - 10, fighter.pos.y - 20), k.color(safeColor('#888')), k.z(11)])
@@ -302,7 +303,7 @@ async function playEntrance() {
sfxBonk(); k.shake(4)
},
// 13: Moonwalk in
async (fighter: any, homeX: number, fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const startX = fromLeft ? -60 : W + 60
fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1
announceCool('Smoother than a politician changing positions!')
@@ -316,7 +317,7 @@ async function playEntrance() {
sfxBoing(); k.shake(2)
},
// 14: Thrown in by bouncer
async (fighter: any, homeX: number, fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const doorX = fromLeft ? -30 : W + 30
fighter.pos.x = doorX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1
// Bouncer arm
@@ -336,7 +337,7 @@ async function playEntrance() {
spawnSparks(homeX, GROUND_Y - 10, 6, '#ff6600')
},
// 15: Lightning strike entrance
async (fighter: any, homeX: number, _fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, _fromLeft: boolean) => {
fighter.pos.x = homeX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 0
// Lightning bolt from sky
const bolt = k.add([k.rect(4, H), k.pos(homeX, 0), k.color(safeColor('#ffff44')), k.opacity(0.9), k.z(20)])
@@ -354,11 +355,11 @@ async function playEntrance() {
spawnSparks(homeX, GROUND_Y - 20, 10, '#ffff44')
},
// 16: Crowd surf in
async (fighter: any, homeX: number, fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const startX = fromLeft ? -60 : W + 60
fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 50; fighter.opacity = 1
// Crowd hands
const hands: any[] = []
const hands: GameObj[] = []
for (let i = 0; i < 8; i++) {
const hx = startX + (fromLeft ? 1 : -1) * (Math.abs(homeX - startX) / 8) * i
const h = k.add([k.rect(6, 20), k.pos(hx, GROUND_Y - 10), k.anchor('bot'), k.color(safeColor('#cc9966')), k.z(8)])
@@ -375,7 +376,7 @@ async function playEntrance() {
hands.forEach(h => { if (h.exists()) h.destroy() })
},
// 17: Riding a shopping cart
async (fighter: any, homeX: number, fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const startX = fromLeft ? -100 : W + 100
const cartBody = k.add([k.rect(45, 30), k.pos(startX, GROUND_Y - 18), k.anchor('center'), k.color(safeColor('#888888')), k.opacity(0.9), k.z(9)])
const cartWheel = k.add([k.circle(5), k.pos(startX + (fromLeft ? 15 : -15), GROUND_Y - 3), k.anchor('center'), k.color(safeColor('#444')), k.z(9)])
@@ -394,7 +395,7 @@ async function playEntrance() {
spawnSparks(homeX, GROUND_Y - 10, 5, '#888888')
},
// 18: Dramatic slow walk with spotlight
async (fighter: any, homeX: number, fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const startX = fromLeft ? -60 : W + 60
fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1
// Spotlight cone
@@ -409,7 +410,7 @@ async function playEntrance() {
spawnSparks(homeX, GROUND_Y - 30, 8, '#ffe14d')
},
// 19: Cannon launch
async (fighter: any, homeX: number, fromLeft: boolean) => {
async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const cannonX = fromLeft ? -40 : W + 40
// Draw cannon
const cannon = k.add([k.rect(50, 25), k.pos(cannonX, GROUND_Y - 20), k.anchor('center'), k.color(safeColor('#333333')), k.z(9), k.rotate(fromLeft ? -30 : 210)])
@@ -436,13 +437,13 @@ async function playEntrance() {
]
// THE CREATOR: Golden code rain portal entrance
const creatorEntrance = async (fighter: any, homeX: number, _fromLeft: boolean) => {
const creatorEntrance = async (fighter: Fighter, homeX: number, _fromLeft: boolean) => {
fighter.pos.x = homeX
fighter.pos.y = -100
fighter.opacity = 0
// Golden code rain columns
const codeChars = '₿01∞⚡⛏§∂∆≈≠'.split('')
const rainDrops: any[] = []
const rainDrops: GameObj[] = []
for (let col = 0; col < 12; col++) {
const cx = homeX - 60 + col * 10
for (let row = 0; row < 4; row++) {
+7 -6
View File
@@ -1,3 +1,4 @@
import type { GameObj } from 'kaplay'
import type { Fighter, ChoreoContext } from './types'
import {
announceHype, announceDramatic, announceSilly, announceFast, announceRobot,
@@ -19,13 +20,13 @@ interface FinisherDeps {
spawnEmoteText: (x: number, y: number, text: string, color: string, duration?: number) => void
spawnCrowdSigns: (count: number, color: string, text?: string) => void
spawnHeart: (x: number, y: number) => void
spawnWeaponProp: (propName: string, x: number, y: number, flipX?: boolean, zIndex?: number) => any[]
destroyProp: (objs: any[]) => void
spawnWeaponProp: (propName: string, x: number, y: number, flipX?: boolean, zIndex?: number) => GameObj[]
destroyProp: (objs: GameObj[]) => void
spawnProp: (fromX: number, fromY: number, toX: number, toY: number, w: number, h: number, color: string, speed?: number, isCircle?: boolean) => Promise<void>
playBow: (fighter: any) => Promise<void>
playFistBump: (fighterA: any, fighterB: any) => Promise<void>
playHelpUp: (winner: any, loser: any, winningSide: 'a' | 'b') => Promise<void>
cameraZoom: (fighters: any[], savedScales: { x: number; y: number }[], zoomFactor: number, duration: number, easing?: (t: number) => number) => Promise<void>
playBow: (fighter: Fighter) => Promise<void>
playFistBump: (fighterA: Fighter, fighterB: Fighter) => Promise<void>
playHelpUp: (winner: Fighter, loser: Fighter, winningSide: 'a' | 'b') => Promise<void>
cameraZoom: (fighters: Fighter[], savedScales: { x: number; y: number }[], zoomFactor: number, duration: number, easing?: (t: number) => number) => Promise<void>
hyperSpeedLines: (targetX: number, targetY: number, duration?: number) => void
showSpeechBubble: (side: 'a' | 'b', text: string, duration?: number) => void
spawnHumanCoach: (side: 'a' | 'b', mood: 'cheer' | 'panic' | 'coach') => Promise<void>
+9 -8
View File
@@ -1,4 +1,5 @@
import type { ChoreoContext } from './types'
import type { GameObj } from 'kaplay'
import type { Fighter, ChoreoContext } from './types'
import { generateSpriteSheet, MAX_FRAMES, TOTAL_ROWS, type SpriteCustomization } from '../sprites'
import { spriteAnims } from './constants'
import { announceHype } from '../audio'
@@ -14,14 +15,14 @@ interface MorphDeps {
colorsB: { primary: string; secondary: string }
isHumanA: boolean
isHumanB: boolean
loadSpriteWithTimeout: (name: string, src: string, opts: any) => Promise<void>
loadSpriteWithTimeout: (name: string, src: string, opts: Record<string, unknown>) => Promise<void>
}
export function createMorphSystem(ctx: ChoreoContext, deps: MorphDeps) {
const { k, safeColor, screenFlash, spawnShockwave, glitchRGB, sfxZoomWhoosh, sfxSpecial, theme } = ctx
const { botA, botB, colorsA, colorsB, isHumanA, isHumanB, loadSpriteWithTimeout } = deps
interface MorphOverlay { obj: any; type: string }
interface MorphOverlay { obj: GameObj; type: string }
let activeMorphs: MorphOverlay[] = []
function destroyMorphOverlays() {
@@ -30,7 +31,7 @@ function destroyMorphOverlays() {
}
// Morph 0: ELEMENTAL — fire/ice/electric aura wraps around the fighter
function applyElementalMorph(fighter: any, side: 'a' | 'b') {
function applyElementalMorph(fighter: Fighter, side: 'a' | 'b') {
const seed = side === 'a' ? botA.seed : botB.seed
let h = 0
for (let i = 0; i < seed.length; i++) h = ((h << 5) - h + seed.charCodeAt(i)) | 0
@@ -88,7 +89,7 @@ function applyElementalMorph(fighter: any, side: 'a' | 'b') {
}
// Morph 1: MECH — armor plates, wings/jets, metallic overlay
function applyMechMorph(fighter: any, _side: 'a' | 'b') {
function applyMechMorph(fighter: Fighter, _side: 'a' | 'b') {
const dir = fighter.scale.x > 0 ? 1 : -1
// Shoulder armor plates
@@ -152,7 +153,7 @@ function applyMechMorph(fighter: any, _side: 'a' | 'b') {
}
// Morph 2: BEAST — grows horns/tail/claws, goes wild
function applyBeastMorph(fighter: any, side: 'a' | 'b') {
function applyBeastMorph(fighter: Fighter, side: 'a' | 'b') {
const dir = fighter.scale.x > 0 ? 1 : -1
const seed = side === 'a' ? botA.seed : botB.seed
let h = 0
@@ -255,7 +256,7 @@ const OMNI_MORPH_ARCHETYPES = [
let omniMorphCounter = 0
async function applyCreatorOmniMorph(fighter: any, side: 'a' | 'b'): Promise<string> {
async function applyCreatorOmniMorph(fighter: Fighter, side: 'a' | 'b'): Promise<string> {
const pick = OMNI_MORPH_ARCHETYPES[Math.floor(Math.random() * OMNI_MORPH_ARCHETYPES.length)]
// Generate and load a sprite sheet for the picked archetype
@@ -346,7 +347,7 @@ function getMorphOrder(seed: string): number[] {
return orders[Math.abs(h) % 6]
}
async function playMorph(fighter: any, side: 'a' | 'b', morphIndex: number) {
async function playMorph(fighter: Fighter, side: 'a' | 'b', morphIndex: number) {
const savedScaleX = fighter.scale.x
const savedScaleY = fighter.scale.y
const isHumanFighter = side === 'a' ? isHumanA : isHumanB
+7 -5
View File
@@ -10,6 +10,9 @@ export function spawnSparks(ctx: FightContext, x: number, y: number, count: numb
const speed = (intense ? 150 : 100) + Math.random() * (intense ? 400 : 300)
const size = (intense ? 3 : 2) + Math.random() * (intense ? 6 : 4)
const rotSpeed = (Math.random() - 0.5) * 600
const vx = Math.cos(angle) * speed
let vy = Math.sin(angle) * speed - 100
const rotV = rotSpeed
const spark = k.add([
k.rect(size, size * (0.5 + Math.random() * 0.5)),
k.pos(x, y),
@@ -18,13 +21,12 @@ export function spawnSparks(ctx: FightContext, x: number, y: number, count: numb
k.z(20),
k.rotate(Math.random() * 360),
k.anchor('center'),
{ vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed - 100, rotV: rotSpeed },
])
spark.onUpdate(() => {
spark.pos.x += (spark as any).vx * k.dt()
spark.pos.y += (spark as any).vy * k.dt()
;(spark as any).vy += 500 * k.dt()
spark.angle += (spark as any).rotV * k.dt()
spark.pos.x += vx * k.dt()
spark.pos.y += vy * k.dt()
vy += 500 * k.dt()
spark.angle += rotV * k.dt()
spark.opacity -= 1.5 * k.dt()
if (spark.opacity <= 0) spark.destroy()
})
+8 -7
View File
@@ -1,3 +1,4 @@
import type { GameObj } from 'kaplay'
import type { Fighter, ChoreoContext, RoundEvent } from './types'
import type { ChoreoFn } from './choreography/factories'
import { pickChoreography } from '../FightScene'
@@ -28,14 +29,14 @@ interface RoundDeps {
choreographyMap: Record<string, ChoreoFn>
playBlock: (side: 'a' | 'b') => Promise<void>
destroyMorphOverlays: () => void
playMorph: (fighter: any, side: 'a' | 'b', morphIndex: number) => Promise<{ revert: () => Promise<void> }>
playMorph: (fighter: Fighter, side: 'a' | 'b', morphIndex: number) => Promise<{ revert: () => Promise<void> }>
getMorphOrder: (seed: string) => number[]
applyCreatorOmniMorph: (fighter: any, side: 'a' | 'b') => Promise<string>
applyCreatorOmniMorph: (fighter: Fighter, side: 'a' | 'b') => Promise<string>
spawnEmoteText: (x: number, y: number, text: string, color: string, duration?: number) => void
spawnCrowdSigns: (count: number, color: string, text?: string) => void
judgeDoSomethingFunny: () => Promise<void>
maybeShowHuman: () => Promise<void>
cameraZoom: (fighters: any[], savedScales: { x: number; y: number }[], zoomFactor: number, duration: number, easing?: (t: number) => number) => Promise<void>
cameraZoom: (fighters: Fighter[], savedScales: { x: number; y: number }[], zoomFactor: number, duration: number, easing?: (t: number) => number) => Promise<void>
hyperSpeedLines: (targetX: number, targetY: number, duration?: number) => void
schizoCut: () => Promise<void>
showSpeechBubble: (side: 'a' | 'b', text: string, duration?: number) => void
@@ -112,8 +113,8 @@ export function createRoundSystem(ctx: ChoreoContext, deps: RoundDeps) {
// === PHYSICAL CONTACT SYSTEMS ===
// === PHYSICAL CONTACT SYSTEMS ===
// Helper: spawn dizzy stars orbiting a fighter's head
function _spawnDizzyStars(fighter: any, duration: number) {
const stars: any[] = []
function _spawnDizzyStars(fighter: Fighter, duration: number) {
const stars: GameObj[] = []
const starChars = ['*', '\u2605', '\u00d7', '!', '?']
const starColors = ['#ffff00', '#ffffff', '#ff4444', '#44ff44', '#ff88ff']
for (let i = 0; i < 4; i++) {
@@ -767,7 +768,7 @@ async function playRound(event: RoundEvent) {
}
// Super-speed: persistent speed lines during round
let speedLines: any[] = []
let speedLines: GameObj[] = []
if (superSpeed) {
for (let i = 0; i < 10; i++) {
const lineY = GROUND_Y - 5 - Math.random() * 130
@@ -782,7 +783,7 @@ async function playRound(event: RoundEvent) {
}
// === RETRO MODE GAMEPAD OVERLAY ===
const retroObjs: any[] = []
const retroObjs: GameObj[] = []
if (event.challengeType === 'retro_mode') {
const padW = 90
const padH = 70
+1 -1
View File
@@ -62,7 +62,7 @@ export interface ChoreoContext {
sfxBlock: () => void
// Other effects
spawnBullet: (fromX: number, fromY: number, toX: number, toY: number) => Promise<void>
spawnGrotesqueDetails: (fighter: any, scaleFactor: number) => void
spawnGrotesqueDetails: (fighter: Fighter, scaleFactor: number) => void
destroyGrotesqueDetails: () => void
}