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 type { Fighter, ChoreoContext } from '../types'
import { createFactories } from './factories' import { createFactories } from './factories'
import type { ChoreoFn } from './factories' import type { ChoreoFn } from './factories'
@@ -534,7 +535,7 @@ async function bulletTimeAttack(atk: Fighter, def: Fighter, dir: number, origAX:
atk.play('special') atk.play('special')
sfxGunshot() sfxGunshot()
const bulletCount = isCritical ? 5 : 3 const bulletCount = isCritical ? 5 : 3
const bullets: any[] = [] const bullets: GameObj[] = []
for (let i = 0; i < bulletCount; i++) { for (let i = 0; i < bulletCount; i++) {
const bx = atk.pos.x + dir * 25 const bx = atk.pos.x + dir * 25
const by = atk.pos.y - 30 + (i - 1) * 12 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.color(safeColor(Math.random() > 0.5 ? '#ff6600' : '#ffcc00')),
k.opacity(0.7), k.opacity(0.7),
k.z(11), 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) }, 40)
const push = dir * (isCritical ? 110 : 50) const push = dir * (isCritical ? 110 : 50)
k.tween(def.pos.x, origDX + push, 0.3, (v) => { def.pos.x = v }, k.easings.easeOutQuad) 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() sfxZap()
// Spawn 3-5 "ghost" clones that zip around // Spawn 3-5 "ghost" clones that zip around
const cloneCount = isCritical ? 5 : 3 const cloneCount = isCritical ? 5 : 3
const clones: any[] = [] const clones: GameObj[] = []
for (let i = 0; i < cloneCount; i++) { for (let i = 0; i < cloneCount; i++) {
const clone = k.add([ const clone = k.add([
k.rect(20, 35), k.rect(20, 35),
@@ -1,3 +1,4 @@
import type { GameObj } from 'kaplay'
import type { Fighter, ChoreoContext } from '../types' import type { Fighter, ChoreoContext } from '../types'
import { createFactories } from './factories' import { createFactories } from './factories'
import type { ChoreoFn } 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) atk.play('special'); sfxSpecial(); await k.wait(0.12)
const segments = isCritical ? 8 : 5 const segments = isCritical ? 8 : 5
let px = atk.pos.x + dir * 30, py = atk.pos.y - 40 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++) { for (let i = 0; i < segments; i++) {
const nx = px + dir * 30 + (Math.random() - 0.5) * 20 const nx = px + dir * 30 + (Math.random() - 0.5) * 20
const ny = py + (Math.random() - 0.5) * 30 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) { async function merkleRoot(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special'); sfxSpecial(); await k.wait(0.1) atk.play('special'); sfxSpecial(); await k.wait(0.1)
// Roots grow up from ground under defender // Roots grow up from ground under defender
const roots: any[] = [] const roots: GameObj[] = []
for (let i = 0; i < (isCritical ? 6 : 3); i++) { for (let i = 0; i < (isCritical ? 6 : 3); i++) {
const rx = def.pos.x + (i - (isCritical ? 2.5 : 1)) * 12 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)]) 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) { async function blockReward(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special'); sfxBoing() atk.play('special'); sfxBoing()
// Gold coins gather around attacker then blast at defender // Gold coins gather around attacker then blast at defender
const coins: any[] = [] const coins: GameObj[] = []
for (let i = 0; i < (isCritical ? 10 : 5); i++) { 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)]) 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) coins.push(c)
@@ -1,3 +1,4 @@
import type { GameObj } from 'kaplay'
import type { Fighter, ChoreoContext } from '../types' import type { Fighter, ChoreoContext } from '../types'
import { createFactories } from './factories' import { createFactories } from './factories'
import type { ChoreoFn } 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) await k.tween(atk.pos.x, contactX, 0.12, (v: number) => { atk.pos.x = v }, k.easings.easeOutQuad)
atk.play('attack') atk.play('attack')
// Chain of blocks swinging as weapon // Chain of blocks swinging as weapon
const blocks: any[] = [] const blocks: GameObj[] = []
const blockCount = isCritical ? 6 : 4 const blockCount = isCritical ? 6 : 4
for (let i = 0; i < blockCount; i++) { for (let i = 0; i < blockCount; i++) {
const bk = k.add([ const bk = k.add([
@@ -329,7 +330,7 @@ async function bitcoinSwarm(atk: Fighter, def: Fighter, dir: number, origAX: num
sfxSpecial() sfxSpecial()
await k.wait(0.15) await k.wait(0.15)
const total = isCritical ? 30 : 18 const total = isCritical ? 30 : 18
const swarmThings: any[] = [] const swarmThings: GameObj[] = []
for (let i = 0; i < total; i++) { for (let i = 0; i < total; i++) {
const sx = atk.pos.x + dir * 10 + (Math.random() - 0.5) * 30 const sx = atk.pos.x + dir * 10 + (Math.random() - 0.5) * 30
const sy = atk.pos.y - 50 + (Math.random() - 0.5) * 60 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() sfxSpecial()
sfxZoomWhoosh() sfxZoomWhoosh()
const total = isCritical ? 24 : 14 const total = isCritical ? 24 : 14
const vortex: any[] = [] const vortex: GameObj[] = []
for (let i = 0; i < total; i++) { for (let i = 0; i < total; i++) {
const sz = 7 + Math.random() * 7 const sz = 7 + Math.random() * 7
const colors = ['#ffd700', '#ffee88', '#ff8c00', '#ffffff'] 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) await k.wait(0.1)
const rows = isCritical ? 6 : 4 const rows = isCritical ? 6 : 4
const cols = isCritical ? 10 : 7 const cols = isCritical ? 10 : 7
const wave: any[] = [] const wave: GameObj[] = []
const startX = atk.pos.x + dir * 25 const startX = atk.pos.x + dir * 25
const screenTop = -20 const screenTop = -20
const screenBot = GROUND_Y + 10 const screenBot = GROUND_Y + 10
@@ -1,3 +1,4 @@
import type { GameObj } from 'kaplay'
import type { Fighter, ChoreoContext } from '../types' import type { Fighter, ChoreoContext } from '../types'
export type PropDef = { parts: { shape: 'rect' | 'circle'; x: number; y: number; w: number; h: number; color: string }[] } 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] const def = PROPS[propName]
if (!def) { if (!def) {
// Fallback: single colored circle // 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')])] 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) { for (const p of def.parts) {
const px = flipX ? x - p.x : x + p.x const px = flipX ? x - p.x : x + p.x
const obj = k.add([ const obj = k.add([
@@ -319,7 +320,7 @@ function spawnWeaponProp(propName: string, x: number, y: number, flipX: boolean
return objs 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++) { for (let i = 0; i < objs.length && i < def.parts.length; i++) {
const p = def.parts[i] const p = def.parts[i]
objs[i].pos.x = flipX ? x - p.x : x + p.x 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() }) 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) 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 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)]) 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)]) } 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() atk.play('attack'); sfxSpecial()
await k.tween(dir === 1 ? -40 : 40, dir === 1 ? 85 : -85, 0.13, (v) => { shaft.angle = v }, k.easings.easeInQuad) 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 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 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 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) { 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)])) 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) => { return async (atk, def, dir, origAX, origDX, isCritical) => {
atk.play('special'); sfxSpecial(); await k.wait(0.15) atk.play('special'); sfxSpecial(); await k.wait(0.15)
const total = count + (isCritical ? 3 : 0) const total = count + (isCritical ? 3 : 0)
const swarmThings: any[] = [] const swarmThings: GameObj[] = []
for (let i = 0; i < total; i++) { for (let i = 0; i < total; i++) {
const sx = atk.pos.x - dir * 20 + (Math.random() - 0.5) * 30 const sx = atk.pos.x - dir * 20 + (Math.random() - 0.5) * 30
const sy = atk.pos.y - 40 + (Math.random() - 0.5) * 50 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 type { Fighter, ChoreoContext } from '../types'
import { createFactories } from './factories' import { createFactories } from './factories'
import type { ChoreoFn } 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) { async function touchGrassSmack(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
atk.play('special'); sfxSpecial(); await k.wait(0.1) atk.play('special'); sfxSpecial(); await k.wait(0.1)
// Grass shoots up from ground // Grass shoots up from ground
const blades: any[] = [] const blades: GameObj[] = []
for (let i = 0; i < (isCritical ? 8 : 4); i++) { for (let i = 0; i < (isCritical ? 8 : 4); i++) {
const gx = def.pos.x + (i - (isCritical ? 3.5 : 1.5)) * 10 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)]) 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 type { Fighter, ChoreoContext } from '../types'
import { createFactories } from './factories' import { createFactories } from './factories'
import type { ChoreoFn } 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) { async function ultimateCloneSurround(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, _isCrit: boolean) {
// Spawn 4 clones around the defender, all attack simultaneously // Spawn 4 clones around the defender, all attack simultaneously
const clones: any[] = [] const clones: GameObj[] = []
const positions = [ const positions = [
{ x: def.pos.x - 60, y: GROUND_Y }, { x: def.pos.x + 60, y: GROUND_Y }, { 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 }, { 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) { 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 // Summon an army of 8 mini clones that charge across the screen
atk.play('special'); sfxSpecial() atk.play('special'); sfxSpecial()
const army: any[] = [] const army: GameObj[] = []
for (let i = 0; i < 8; i++) { for (let i = 0; i < 8; i++) {
const ax = atk.pos.x + (Math.random() - 0.5) * 30 const ax = atk.pos.x + (Math.random() - 0.5) * 30
const ay = GROUND_Y - Math.random() * 10 const ay = GROUND_Y - Math.random() * 10
@@ -443,7 +444,7 @@ async function ultimateMeteorCrash(atk: Fighter, def: Fighter, dir: number, orig
// Impact // Impact
atk.pos.x = def.pos.x; atk.pos.y = -80; atk.opacity = 1 atk.pos.x = def.pos.x; atk.pos.y = -80; atk.opacity = 1
// Fire trail // Fire trail
const fireTrail: any[] = [] const fireTrail: GameObj[] = []
await k.tween(-80, GROUND_Y, 0.15, (v) => { await k.tween(-80, GROUND_Y, 0.15, (v) => {
atk.pos.y = 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)]) 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() atk.play('special'); sfxCritical()
def.play('knockback'); k.shake(25) def.play('knockback'); k.shake(25)
// Create "shatter" fragments // Create "shatter" fragments
const frags: any[] = [] const frags: GameObj[] = []
for (let i = 0; i < 12; i++) { for (let i = 0; i < 12; i++) {
const fx = W * Math.random() const fx = W * Math.random()
const fy = H * Math.random() * 0.8 const fy = H * Math.random() * 0.8
@@ -1,3 +1,4 @@
import type { GameObj } from 'kaplay'
import type { Fighter, ChoreoContext } from '../types' import type { Fighter, ChoreoContext } from '../types'
import { createFactories } from './factories' import { createFactories } from './factories'
import type { ChoreoFn } 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') atk.play('special')
sfxSpecial() sfxSpecial()
// Draw sword arc (series of rects forming a crescent) // Draw sword arc (series of rects forming a crescent)
const arcParts: any[] = [] const arcParts: GameObj[] = []
for (let i = 0; i < 8; i++) { for (let i = 0; i < 8; i++) {
const angle = (i / 8) * Math.PI - Math.PI / 4 const angle = (i / 8) * Math.PI - Math.PI / 4
const ax = contactX + dir * 20 + Math.cos(angle) * 60 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() sfxSpecial()
await k.wait(0.1) await k.wait(0.1)
// Whip extends from attacker to defender (series of small segments) // Whip extends from attacker to defender (series of small segments)
const segments: any[] = [] const segments: GameObj[] = []
const fromX = atk.pos.x + dir * 20 const fromX = atk.pos.x + dir * 20
const fromY = atk.pos.y - 40 const fromY = atk.pos.y - 40
const toX = def.pos.x 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 type { Fighter, ChoreoContext } from './types'
import { sfxRandomComedy, sfxBoneCrack, sfxVineBoom, sfxSlideWhistleDown, sfxPowerUp, sfxDrumRoll, announceDramatic, announceDeepIntro, announceSilly, announceRandom, announceCool, announceCrowdReaction, announceCreatorEntrance } from '../audio' 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 = [ const entrances = [
// 0: Drive in with a car // 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 dir = fromLeft ? 1 : -1
const startX = fromLeft ? -120 : W + 120 const startX = fromLeft ? -120 : W + 120
const carY = GROUND_Y - 20 const carY = GROUND_Y - 20
@@ -55,13 +56,13 @@ async function playEntrance() {
}, k.easings.easeInQuad).then(() => { carBody.destroy(); wheel1.destroy(); wheel2.destroy() }) }, k.easings.easeInQuad).then(() => { carBody.destroy(); wheel1.destroy(); wheel2.destroy() })
}, },
// 1: Fall from space // 1: Fall from space
async (fighter: any, homeX: number, _fromLeft: boolean) => { async (fighter: Fighter, homeX: number, _fromLeft: boolean) => {
fighter.pos.x = homeX fighter.pos.x = homeX
fighter.pos.y = -200 fighter.pos.y = -200
fighter.opacity = 1 fighter.opacity = 1
sfxZoomWhoosh() sfxZoomWhoosh()
// Trail of fire // Trail of fire
const trail: any[] = [] const trail: GameObj[] = []
for (let i = 0; i < 6; i++) { for (let i = 0; i < 6; i++) {
trackedTimeout(() => { 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)]) 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() }) trail.forEach(t => { if (t.exists()) t.destroy() })
}, },
// 2: Robe entrance (boxing style) // 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 const startX = fromLeft ? -60 : W + 60
fighter.pos.x = startX fighter.pos.x = startX
fighter.pos.y = GROUND_Y - 6 fighter.pos.y = GROUND_Y - 6
@@ -98,7 +99,7 @@ async function playEntrance() {
spawnSparks(homeX, GROUND_Y - 40, 8, robeColor) spawnSparks(homeX, GROUND_Y - 40, 8, robeColor)
}, },
// 3: Girlfriend argument // 3: Girlfriend argument
async (fighter: any, homeX: number, fromLeft: boolean) => { async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const dir = fromLeft ? 1 : -1 const dir = fromLeft ? 1 : -1
const startX = fromLeft ? -60 : W + 60 const startX = fromLeft ? -60 : W + 60
fighter.pos.x = startX fighter.pos.x = startX
@@ -126,7 +127,7 @@ async function playEntrance() {
sfxBoing() sfxBoing()
}, },
// 4: Helicopter drop // 4: Helicopter drop
async (fighter: any, homeX: number, _fromLeft: boolean) => { async (fighter: Fighter, homeX: number, _fromLeft: boolean) => {
fighter.pos.x = homeX fighter.pos.x = homeX
fighter.pos.y = -100 fighter.pos.y = -100
fighter.opacity = 1 fighter.opacity = 1
@@ -148,7 +149,7 @@ async function playEntrance() {
.then(() => { heli.destroy(); blade.destroy() }) .then(() => { heli.destroy(); blade.destroy() })
}, },
// 5: Teleport glitch // 5: Teleport glitch
async (fighter: any, homeX: number, _fromLeft: boolean) => { async (fighter: Fighter, homeX: number, _fromLeft: boolean) => {
fighter.opacity = 0 fighter.opacity = 0
sfxZap() sfxZap()
// Glitch flickers at random positions // Glitch flickers at random positions
@@ -170,7 +171,7 @@ async function playEntrance() {
k.shake(5) k.shake(5)
}, },
// 6: Skateboard ride in // 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 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 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)]) 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) sfxBonk(); k.shake(3)
}, },
// 7: Rocket jetpack // 7: Rocket jetpack
async (fighter: any, homeX: number, fromLeft: boolean) => { async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const startX = fromLeft ? -80 : W + 80 const startX = fromLeft ? -80 : W + 80
fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 80; fighter.opacity = 1 fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 80; fighter.opacity = 1
sfxJetpack() sfxJetpack()
// Fly in with flame trail // Fly in with flame trail
const flames: any[] = [] const flames: GameObj[] = []
const interval = trackedInterval(() => { 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)]) 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) flames.push(f)
@@ -210,7 +211,7 @@ async function playEntrance() {
flames.forEach(f => { if (f.exists()) f.destroy() }) flames.forEach(f => { if (f.exists()) f.destroy() })
}, },
// 8: Emerge from portal // 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)] const portalColor = ['#b83dff', '#00f0ff', '#39ff14', '#ff2d7b'][Math.floor(Math.random() * 4)]
// Draw portal // 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)]) 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() portal.destroy(); portalRing.destroy()
}, },
// 9: Backflip entrance // 9: Backflip entrance
async (fighter: any, homeX: number, fromLeft: boolean) => { async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const startX = fromLeft ? -40 : W + 40 const startX = fromLeft ? -40 : W + 40
fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1 fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1
sfxZoomWhoosh() sfxZoomWhoosh()
@@ -250,7 +251,7 @@ async function playEntrance() {
spawnSparks(homeX, GROUND_Y - 10, 6, '#ffe14d') spawnSparks(homeX, GROUND_Y - 10, 6, '#ffe14d')
}, },
// 10: Rise from underground // 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 fighter.pos.x = homeX; fighter.pos.y = GROUND_Y + 60; fighter.opacity = 0.5
// Crack the ground // 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)]) 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() crack1.destroy(); crack2.destroy()
}, },
// 11: Slide in on ice // 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 const startX = fromLeft ? -80 : W + 80
fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1 fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1
sfxSlideDown() sfxSlideDown()
// Ice trail // Ice trail
const iceTrail: any[] = [] const iceTrail: GameObj[] = []
await k.tween(startX, homeX + (fromLeft ? 40 : -40), 0.4, (v) => { await k.tween(startX, homeX + (fromLeft ? 40 : -40), 0.4, (v) => {
fighter.pos.x = v fighter.pos.x = v
if (Math.random() < 0.3) { if (Math.random() < 0.3) {
@@ -283,7 +284,7 @@ async function playEntrance() {
trackedTimeout(() => { iceTrail.forEach(i => { if (i.exists()) i.destroy() }) }, 800) trackedTimeout(() => { iceTrail.forEach(i => { if (i.exists()) i.destroy() }) }, 800)
}, },
// 12: Parachute drop // 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 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 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)]) 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) sfxBonk(); k.shake(4)
}, },
// 13: Moonwalk in // 13: Moonwalk in
async (fighter: any, homeX: number, fromLeft: boolean) => { async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const startX = fromLeft ? -60 : W + 60 const startX = fromLeft ? -60 : W + 60
fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1 fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1
announceCool('Smoother than a politician changing positions!') announceCool('Smoother than a politician changing positions!')
@@ -316,7 +317,7 @@ async function playEntrance() {
sfxBoing(); k.shake(2) sfxBoing(); k.shake(2)
}, },
// 14: Thrown in by bouncer // 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 const doorX = fromLeft ? -30 : W + 30
fighter.pos.x = doorX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1 fighter.pos.x = doorX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1
// Bouncer arm // Bouncer arm
@@ -336,7 +337,7 @@ async function playEntrance() {
spawnSparks(homeX, GROUND_Y - 10, 6, '#ff6600') spawnSparks(homeX, GROUND_Y - 10, 6, '#ff6600')
}, },
// 15: Lightning strike entrance // 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 fighter.pos.x = homeX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 0
// Lightning bolt from sky // 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)]) 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') spawnSparks(homeX, GROUND_Y - 20, 10, '#ffff44')
}, },
// 16: Crowd surf in // 16: Crowd surf in
async (fighter: any, homeX: number, fromLeft: boolean) => { async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const startX = fromLeft ? -60 : W + 60 const startX = fromLeft ? -60 : W + 60
fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 50; fighter.opacity = 1 fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 50; fighter.opacity = 1
// Crowd hands // Crowd hands
const hands: any[] = [] const hands: GameObj[] = []
for (let i = 0; i < 8; i++) { for (let i = 0; i < 8; i++) {
const hx = startX + (fromLeft ? 1 : -1) * (Math.abs(homeX - startX) / 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)]) 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() }) hands.forEach(h => { if (h.exists()) h.destroy() })
}, },
// 17: Riding a shopping cart // 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 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 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)]) 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') spawnSparks(homeX, GROUND_Y - 10, 5, '#888888')
}, },
// 18: Dramatic slow walk with spotlight // 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 const startX = fromLeft ? -60 : W + 60
fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1 fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1
// Spotlight cone // Spotlight cone
@@ -409,7 +410,7 @@ async function playEntrance() {
spawnSparks(homeX, GROUND_Y - 30, 8, '#ffe14d') spawnSparks(homeX, GROUND_Y - 30, 8, '#ffe14d')
}, },
// 19: Cannon launch // 19: Cannon launch
async (fighter: any, homeX: number, fromLeft: boolean) => { async (fighter: Fighter, homeX: number, fromLeft: boolean) => {
const cannonX = fromLeft ? -40 : W + 40 const cannonX = fromLeft ? -40 : W + 40
// Draw cannon // 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)]) 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 // 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.x = homeX
fighter.pos.y = -100 fighter.pos.y = -100
fighter.opacity = 0 fighter.opacity = 0
// Golden code rain columns // Golden code rain columns
const codeChars = '₿01∞⚡⛏§∂∆≈≠'.split('') const codeChars = '₿01∞⚡⛏§∂∆≈≠'.split('')
const rainDrops: any[] = [] const rainDrops: GameObj[] = []
for (let col = 0; col < 12; col++) { for (let col = 0; col < 12; col++) {
const cx = homeX - 60 + col * 10 const cx = homeX - 60 + col * 10
for (let row = 0; row < 4; row++) { 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 type { Fighter, ChoreoContext } from './types'
import { import {
announceHype, announceDramatic, announceSilly, announceFast, announceRobot, announceHype, announceDramatic, announceSilly, announceFast, announceRobot,
@@ -19,13 +20,13 @@ interface FinisherDeps {
spawnEmoteText: (x: number, y: number, text: string, color: string, duration?: number) => void spawnEmoteText: (x: number, y: number, text: string, color: string, duration?: number) => void
spawnCrowdSigns: (count: number, color: string, text?: string) => void spawnCrowdSigns: (count: number, color: string, text?: string) => void
spawnHeart: (x: number, y: number) => void spawnHeart: (x: number, y: number) => void
spawnWeaponProp: (propName: string, x: number, y: number, flipX?: boolean, zIndex?: number) => any[] spawnWeaponProp: (propName: string, x: number, y: number, flipX?: boolean, zIndex?: number) => GameObj[]
destroyProp: (objs: any[]) => void 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> 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> playBow: (fighter: Fighter) => Promise<void>
playFistBump: (fighterA: any, fighterB: any) => Promise<void> playFistBump: (fighterA: Fighter, fighterB: Fighter) => Promise<void>
playHelpUp: (winner: any, loser: any, winningSide: 'a' | 'b') => Promise<void> playHelpUp: (winner: Fighter, loser: Fighter, winningSide: 'a' | 'b') => 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 hyperSpeedLines: (targetX: number, targetY: number, duration?: number) => void
showSpeechBubble: (side: 'a' | 'b', text: string, duration?: number) => void showSpeechBubble: (side: 'a' | 'b', text: string, duration?: number) => void
spawnHumanCoach: (side: 'a' | 'b', mood: 'cheer' | 'panic' | 'coach') => Promise<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 { generateSpriteSheet, MAX_FRAMES, TOTAL_ROWS, type SpriteCustomization } from '../sprites'
import { spriteAnims } from './constants' import { spriteAnims } from './constants'
import { announceHype } from '../audio' import { announceHype } from '../audio'
@@ -14,14 +15,14 @@ interface MorphDeps {
colorsB: { primary: string; secondary: string } colorsB: { primary: string; secondary: string }
isHumanA: boolean isHumanA: boolean
isHumanB: 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) { export function createMorphSystem(ctx: ChoreoContext, deps: MorphDeps) {
const { k, safeColor, screenFlash, spawnShockwave, glitchRGB, sfxZoomWhoosh, sfxSpecial, theme } = ctx const { k, safeColor, screenFlash, spawnShockwave, glitchRGB, sfxZoomWhoosh, sfxSpecial, theme } = ctx
const { botA, botB, colorsA, colorsB, isHumanA, isHumanB, loadSpriteWithTimeout } = deps const { botA, botB, colorsA, colorsB, isHumanA, isHumanB, loadSpriteWithTimeout } = deps
interface MorphOverlay { obj: any; type: string } interface MorphOverlay { obj: GameObj; type: string }
let activeMorphs: MorphOverlay[] = [] let activeMorphs: MorphOverlay[] = []
function destroyMorphOverlays() { function destroyMorphOverlays() {
@@ -30,7 +31,7 @@ function destroyMorphOverlays() {
} }
// Morph 0: ELEMENTAL — fire/ice/electric aura wraps around the fighter // 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 const seed = side === 'a' ? botA.seed : botB.seed
let h = 0 let h = 0
for (let i = 0; i < seed.length; i++) h = ((h << 5) - h + seed.charCodeAt(i)) | 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 // 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 const dir = fighter.scale.x > 0 ? 1 : -1
// Shoulder armor plates // Shoulder armor plates
@@ -152,7 +153,7 @@ function applyMechMorph(fighter: any, _side: 'a' | 'b') {
} }
// Morph 2: BEAST — grows horns/tail/claws, goes wild // 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 dir = fighter.scale.x > 0 ? 1 : -1
const seed = side === 'a' ? botA.seed : botB.seed const seed = side === 'a' ? botA.seed : botB.seed
let h = 0 let h = 0
@@ -255,7 +256,7 @@ const OMNI_MORPH_ARCHETYPES = [
let omniMorphCounter = 0 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)] const pick = OMNI_MORPH_ARCHETYPES[Math.floor(Math.random() * OMNI_MORPH_ARCHETYPES.length)]
// Generate and load a sprite sheet for the picked archetype // 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] 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 savedScaleX = fighter.scale.x
const savedScaleY = fighter.scale.y const savedScaleY = fighter.scale.y
const isHumanFighter = side === 'a' ? isHumanA : isHumanB 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 speed = (intense ? 150 : 100) + Math.random() * (intense ? 400 : 300)
const size = (intense ? 3 : 2) + Math.random() * (intense ? 6 : 4) const size = (intense ? 3 : 2) + Math.random() * (intense ? 6 : 4)
const rotSpeed = (Math.random() - 0.5) * 600 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([ const spark = k.add([
k.rect(size, size * (0.5 + Math.random() * 0.5)), k.rect(size, size * (0.5 + Math.random() * 0.5)),
k.pos(x, y), k.pos(x, y),
@@ -18,13 +21,12 @@ export function spawnSparks(ctx: FightContext, x: number, y: number, count: numb
k.z(20), k.z(20),
k.rotate(Math.random() * 360), k.rotate(Math.random() * 360),
k.anchor('center'), k.anchor('center'),
{ vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed - 100, rotV: rotSpeed },
]) ])
spark.onUpdate(() => { spark.onUpdate(() => {
spark.pos.x += (spark as any).vx * k.dt() spark.pos.x += vx * k.dt()
spark.pos.y += (spark as any).vy * k.dt() spark.pos.y += vy * k.dt()
;(spark as any).vy += 500 * k.dt() vy += 500 * k.dt()
spark.angle += (spark as any).rotV * k.dt() spark.angle += rotV * k.dt()
spark.opacity -= 1.5 * k.dt() spark.opacity -= 1.5 * k.dt()
if (spark.opacity <= 0) spark.destroy() 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 { Fighter, ChoreoContext, RoundEvent } from './types'
import type { ChoreoFn } from './choreography/factories' import type { ChoreoFn } from './choreography/factories'
import { pickChoreography } from '../FightScene' import { pickChoreography } from '../FightScene'
@@ -28,14 +29,14 @@ interface RoundDeps {
choreographyMap: Record<string, ChoreoFn> choreographyMap: Record<string, ChoreoFn>
playBlock: (side: 'a' | 'b') => Promise<void> playBlock: (side: 'a' | 'b') => Promise<void>
destroyMorphOverlays: () => 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[] 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 spawnEmoteText: (x: number, y: number, text: string, color: string, duration?: number) => void
spawnCrowdSigns: (count: number, color: string, text?: string) => void spawnCrowdSigns: (count: number, color: string, text?: string) => void
judgeDoSomethingFunny: () => Promise<void> judgeDoSomethingFunny: () => Promise<void>
maybeShowHuman: () => 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 hyperSpeedLines: (targetX: number, targetY: number, duration?: number) => void
schizoCut: () => Promise<void> schizoCut: () => Promise<void>
showSpeechBubble: (side: 'a' | 'b', text: string, duration?: number) => 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 ===
// === PHYSICAL CONTACT SYSTEMS === // === PHYSICAL CONTACT SYSTEMS ===
// Helper: spawn dizzy stars orbiting a fighter's head // Helper: spawn dizzy stars orbiting a fighter's head
function _spawnDizzyStars(fighter: any, duration: number) { function _spawnDizzyStars(fighter: Fighter, duration: number) {
const stars: any[] = [] const stars: GameObj[] = []
const starChars = ['*', '\u2605', '\u00d7', '!', '?'] const starChars = ['*', '\u2605', '\u00d7', '!', '?']
const starColors = ['#ffff00', '#ffffff', '#ff4444', '#44ff44', '#ff88ff'] const starColors = ['#ffff00', '#ffffff', '#ff4444', '#44ff44', '#ff88ff']
for (let i = 0; i < 4; i++) { for (let i = 0; i < 4; i++) {
@@ -767,7 +768,7 @@ async function playRound(event: RoundEvent) {
} }
// Super-speed: persistent speed lines during round // Super-speed: persistent speed lines during round
let speedLines: any[] = [] let speedLines: GameObj[] = []
if (superSpeed) { if (superSpeed) {
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const lineY = GROUND_Y - 5 - Math.random() * 130 const lineY = GROUND_Y - 5 - Math.random() * 130
@@ -782,7 +783,7 @@ async function playRound(event: RoundEvent) {
} }
// === RETRO MODE GAMEPAD OVERLAY === // === RETRO MODE GAMEPAD OVERLAY ===
const retroObjs: any[] = [] const retroObjs: GameObj[] = []
if (event.challengeType === 'retro_mode') { if (event.challengeType === 'retro_mode') {
const padW = 90 const padW = 90
const padH = 70 const padH = 70
+1 -1
View File
@@ -62,7 +62,7 @@ export interface ChoreoContext {
sfxBlock: () => void sfxBlock: () => void
// Other effects // Other effects
spawnBullet: (fromX: number, fromY: number, toX: number, toY: number) => Promise<void> 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 destroyGrotesqueDetails: () => void
} }