refactor: split choreographies into category modules
Extract 457 choreography functions from FightScene.ts into 11 modular files under fight/choreography/. ChoreoContext interface bundles all closure deps (k, effects, SFX) so function bodies remain unchanged. Factory pattern (createFactories) shares prop system and make* helpers across categories. Categories: basic (72), ranged (94), silly (80), food (45), weapons (45), ultimates (41), bitcoin (34), heavy (30), creator (16), plus factories and index. FightScene.ts reduced from 11,917 to 6,515 lines. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
08786a9b06
commit
cabe0adb31
@@ -0,0 +1,565 @@
|
||||
import type { Fighter, ChoreoContext } from '../types'
|
||||
import { createFactories } from './factories'
|
||||
import type { ChoreoFn } from './factories'
|
||||
|
||||
export function createBitcoinChoreographies(ctx: ChoreoContext): Record<string, ChoreoFn> {
|
||||
const {
|
||||
k, W, H, GROUND_Y, FRAME_SIZE, theme, safeColor,
|
||||
trackedInterval, clearTracked,
|
||||
spawnSparks, spawnBulletHoles, spawnExhaust, spawnProjectile,
|
||||
spawnShockwave, screenFlash, impactFreeze, spawnAfterimages,
|
||||
glitchRGB, scanlineGlitch, dimensionalShift,
|
||||
sfxPunch, sfxKick, sfxSpecial, sfxCritical, sfxExplosion,
|
||||
sfxBoing, sfxBonk, sfxBulletHit, sfxClash, sfxCoin,
|
||||
sfxGunshot, sfxJetpack, sfxRapidPunch, sfxSlideDown,
|
||||
sfxZap, sfxZoomWhoosh, sfxBlock,
|
||||
} = ctx
|
||||
const {
|
||||
spawnWeaponProp, moveProp, destroyProp, PROPS, spawnProp, spawnBarrage,
|
||||
makeThrow, makeThrowProp, makeGunProp, makeSwing, makeDrop,
|
||||
makeVehicle, makeBeam, makeSpin, makeSwarm, makeExplosion, makeGrab,
|
||||
} = createFactories(ctx)
|
||||
|
||||
async function lightningBolt(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
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[] = []
|
||||
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
|
||||
const bolt = k.add([k.rect(3, 25), k.pos(px, py), k.color(safeColor('#ffdd00')), k.opacity(1), k.z(18), k.rotate(Math.atan2(ny - py, nx - px) * 180 / Math.PI)])
|
||||
bolts.push(bolt)
|
||||
sfxBulletHit(); px = nx; py = ny; await k.wait(0.02)
|
||||
}
|
||||
def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 14 : 6)
|
||||
spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 14 : 6, '#ffdd00')
|
||||
if (isCritical) { screenFlash('#ffff00'); sfxCritical() }
|
||||
await k.wait(0.2)
|
||||
bolts.forEach(b => b.destroy())
|
||||
const push = dir * (isCritical ? 80 : 35)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
async function hashSmash(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
atk.play('special'); sfxSpecial(); await k.wait(0.1)
|
||||
const count = isCritical ? 16 : 8
|
||||
for (let i = 0; i < count; i++) {
|
||||
const hx = def.pos.x + (Math.random() - 0.5) * 70
|
||||
const hash = k.add([k.text('#', { size: 14 }), k.pos(hx, -10), k.color(safeColor('#00ff88')), k.opacity(0.9), k.z(18)])
|
||||
const ty = def.pos.y - 10 - Math.random() * 40
|
||||
k.tween(hash.pos.y, ty, 0.12 + Math.random() * 0.08, (v: number) => { hash.pos.y = v }, k.easings.easeInQuad).then(() => { hash.destroy(); spawnSparks(hx, ty, 2, '#00ff88'); sfxBulletHit() })
|
||||
await k.wait(0.025)
|
||||
}
|
||||
await k.wait(0.1); def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 12 : 5)
|
||||
if (isCritical) { screenFlash('#00ff88'); sfxExplosion() }
|
||||
const push = dir * (isCritical ? 85 : 38)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
const blockDrop = makeDrop(28, 28, '#f7931a', '#ffd700', false)
|
||||
|
||||
async function mempoolFlood(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
atk.play('special'); sfxSpecial(); await k.wait(0.1)
|
||||
const txCount = isCritical ? 24 : 12
|
||||
for (let i = 0; i < txCount; i++) {
|
||||
const tx = k.add([k.rect(6, 3), k.pos(atk.pos.x + dir * 20, atk.pos.y - 30 + (Math.random() - 0.5) * 40), k.color(safeColor(Math.random() > 0.5 ? '#8844ff' : '#aa66ff')), k.opacity(0.8), k.z(16)])
|
||||
k.tween(tx.pos.x, def.pos.x + (Math.random() - 0.5) * 30, 0.2, (v: number) => { tx.pos.x = v }).then(() => { tx.destroy(); spawnSparks(tx.pos.x, tx.pos.y, 1, '#aa66ff') })
|
||||
k.tween(tx.pos.y, def.pos.y - 20 + (Math.random() - 0.5) * 30, 0.2, (v: number) => { tx.pos.y = v })
|
||||
if (i % 4 === 0) sfxBulletHit()
|
||||
await k.wait(0.015)
|
||||
}
|
||||
await k.wait(0.15); def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 12 : 5)
|
||||
if (isCritical) { screenFlash('#8844ff'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 80 : 35)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
async function difficultyBomb(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
atk.play('special'); sfxSpecial(); await k.wait(0.1)
|
||||
const bomb = k.add([k.circle(8), k.pos(def.pos.x, def.pos.y - 30), k.color(safeColor('#ff4400')), k.opacity(0.9), k.z(18)])
|
||||
await k.tween(8, isCritical ? 50 : 30, 0.3, (v: number) => { bomb.radius = v }, k.easings.easeOutQuad)
|
||||
sfxExplosion(); k.shake(isCritical ? 18 : 10); screenFlash(isCritical ? '#ffffff' : '#ff4400', 0.12)
|
||||
spawnSparks(def.pos.x, def.pos.y - 30, isCritical ? 18 : 10, '#ff4400')
|
||||
spawnShockwave(def.pos.x, def.pos.y - 20, '#ff6600')
|
||||
bomb.destroy(); def.play(isCritical ? 'knockback' : 'hit')
|
||||
const push = dir * (isCritical ? 100 : 45)
|
||||
k.tween(def.pos.x, origDX + push, 0.25, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.35); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
async function feeSpike(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
atk.play('attack'); sfxPunch(); await k.wait(0.08)
|
||||
const spike = k.add([k.rect(4, 40), k.pos(def.pos.x, GROUND_Y), k.color(safeColor('#ff2222')), k.opacity(0.9), k.z(16)])
|
||||
sfxSlideDown()
|
||||
await k.tween(GROUND_Y, def.pos.y - 60, 0.12, (v: number) => { spike.pos.y = v }, k.easings.easeOutQuad)
|
||||
sfxBonk(); k.shake(isCritical ? 14 : 7)
|
||||
spawnSparks(def.pos.x, def.pos.y - 40, isCritical ? 10 : 5, '#ff2222')
|
||||
def.play(isCritical ? 'knockback' : 'hit')
|
||||
if (isCritical) { screenFlash('#ff0000'); sfxCritical() }
|
||||
await k.wait(0.15)
|
||||
k.tween(spike.opacity, 0, 0.2, (v: number) => { spike.opacity = v }).then(() => spike.destroy())
|
||||
const push = dir * (isCritical ? 75 : 30)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
async function utxoScatter(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
atk.play('special'); sfxSpecial(); await k.wait(0.1)
|
||||
const count = isCritical ? 14 : 7
|
||||
for (let i = 0; i < count; i++) {
|
||||
const angle = (i / count) * Math.PI - Math.PI / 2
|
||||
const coin = k.add([k.circle(4), k.pos(atk.pos.x + dir * 15, atk.pos.y - 30), k.color(safeColor('#ffd700')), k.opacity(1), k.z(17)])
|
||||
const tx = def.pos.x + Math.cos(angle) * 40 + (Math.random() - 0.5) * 20
|
||||
const ty = def.pos.y - 30 + Math.sin(angle) * 30
|
||||
k.tween(coin.pos.x, tx, 0.18, (v: number) => { coin.pos.x = v }, k.easings.easeOutQuad)
|
||||
k.tween(coin.pos.y, ty, 0.18, (v: number) => { coin.pos.y = v }, k.easings.easeOutQuad).then(() => { coin.destroy(); spawnSparks(tx, ty, 2, '#ffd700'); sfxBulletHit() })
|
||||
await k.wait(0.02)
|
||||
}
|
||||
await k.wait(0.12); def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 12 : 5)
|
||||
if (isCritical) { screenFlash('#ffd700'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 80 : 35)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
const nodeSync = makeBeam('#00ff88', 5, 2)
|
||||
|
||||
async function relayBounce(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
atk.play('attack'); sfxPunch(); await k.wait(0.08)
|
||||
const ball = k.add([k.circle(6), k.pos(atk.pos.x + dir * 20, atk.pos.y - 30), k.color(safeColor('#8844ff')), k.opacity(1), k.z(18)])
|
||||
// Bounce off top
|
||||
await k.tween(ball.pos.y, -10, 0.1, (v: number) => { ball.pos.y = v }, k.easings.easeOutQuad)
|
||||
k.tween(ball.pos.x, (atk.pos.x + def.pos.x) / 2, 0.1, (v: number) => { ball.pos.x = v })
|
||||
sfxBoing()
|
||||
// Bounce down to defender
|
||||
await k.tween(ball.pos.y, def.pos.y - 25, 0.12, (v: number) => { ball.pos.y = v }, k.easings.easeInQuad)
|
||||
k.tween(ball.pos.x, def.pos.x, 0.12, (v: number) => { ball.pos.x = v })
|
||||
sfxBonk(); k.shake(isCritical ? 14 : 6)
|
||||
spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 12 : 5, '#8844ff')
|
||||
ball.destroy(); def.play(isCritical ? 'knockback' : 'hit')
|
||||
if (isCritical) { screenFlash('#8844ff'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 85 : 38)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
async function channelForceClose(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
atk.play('special'); sfxSpecial()
|
||||
// Draw lightning chain between fighters
|
||||
const chain = k.add([k.rect(Math.abs(def.pos.x - atk.pos.x), 3), k.pos(Math.min(atk.pos.x, def.pos.x), atk.pos.y - 35), k.color(safeColor('#ffdd00')), k.opacity(0.8), k.z(17)])
|
||||
await k.wait(0.2); sfxSpecial()
|
||||
// Snap! Chain breaks
|
||||
chain.destroy(); sfxExplosion()
|
||||
spawnSparks((atk.pos.x + def.pos.x) / 2, atk.pos.y - 35, isCritical ? 16 : 8, '#ffdd00')
|
||||
k.shake(isCritical ? 16 : 8); screenFlash('#ffdd00', 0.1)
|
||||
def.play(isCritical ? 'knockback' : 'hit')
|
||||
if (isCritical) sfxCritical()
|
||||
const push = dir * (isCritical ? 95 : 42)
|
||||
k.tween(def.pos.x, origDX + push, 0.25, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.35); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
async function halvingStrike(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
const contactX = origDX - dir * 45
|
||||
sfxZoomWhoosh()
|
||||
await k.tween(atk.pos.x, contactX, 0.1, (v: number) => { atk.pos.x = v }, k.easings.easeOutQuad)
|
||||
// First hit
|
||||
atk.play('attack'); sfxPunch(); def.play('hit'); k.shake(4)
|
||||
spawnSparks(def.pos.x, def.pos.y - 25, 4, '#f7931a')
|
||||
await k.wait(0.12)
|
||||
// Second hit (the halving)
|
||||
atk.play('kick'); sfxPunch(); def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 12 : 6)
|
||||
spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 10 : 5, '#ffd700')
|
||||
if (isCritical) { screenFlash('#f7931a'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 85 : 38)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3)
|
||||
await Promise.all([
|
||||
k.tween(atk.pos.x, origAX, 0.2, (v: number) => { atk.pos.x = v }, k.easings.easeInQuad),
|
||||
k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad),
|
||||
])
|
||||
}
|
||||
|
||||
async function coldStorage(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
atk.play('special'); sfxSpecial(); await k.wait(0.1)
|
||||
// Freeze aura around defender
|
||||
const ice = k.add([k.circle(isCritical ? 40 : 25), k.pos(def.pos.x, def.pos.y - 25), k.color(safeColor('#88ddff')), k.opacity(0.4), k.z(15)])
|
||||
spawnSparks(def.pos.x, def.pos.y - 30, 8, '#aaeeff')
|
||||
sfxBoing(); await k.wait(0.25)
|
||||
// Shatter
|
||||
sfxExplosion(); ice.destroy()
|
||||
spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 16 : 8, '#88ddff')
|
||||
k.shake(isCritical ? 14 : 7); def.play(isCritical ? 'knockback' : 'hit')
|
||||
if (isCritical) { screenFlash('#88ddff'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 80 : 35)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
const paperWalletSlash = makeSwing(30, 2, '#f0e8d0', '#ffd700', 8, 8)
|
||||
|
||||
async function hardFork(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
atk.play('special'); sfxSpecial(); await k.wait(0.1)
|
||||
// Two beams split from one
|
||||
const startX = atk.pos.x + dir * 25
|
||||
const beamA = k.add([k.rect(3, 60), k.pos(startX, atk.pos.y - 50), k.color(safeColor('#ff4400')), k.opacity(0.8), k.z(17), k.rotate(15 * dir)])
|
||||
const beamB = k.add([k.rect(3, 60), k.pos(startX, atk.pos.y - 30), k.color(safeColor('#4488ff')), k.opacity(0.8), k.z(17), k.rotate(-15 * dir)])
|
||||
sfxSpecial()
|
||||
await Promise.all([
|
||||
k.tween(beamA.pos.x, def.pos.x, 0.15, (v: number) => { beamA.pos.x = v }),
|
||||
k.tween(beamB.pos.x, def.pos.x, 0.15, (v: number) => { beamB.pos.x = v }),
|
||||
])
|
||||
beamA.destroy(); beamB.destroy()
|
||||
sfxExplosion(); k.shake(isCritical ? 16 : 8)
|
||||
spawnSparks(def.pos.x, def.pos.y - 30, isCritical ? 14 : 7, '#ff4400')
|
||||
spawnSparks(def.pos.x, def.pos.y - 20, isCritical ? 14 : 7, '#4488ff')
|
||||
def.play(isCritical ? 'knockback' : 'hit')
|
||||
if (isCritical) { screenFlash('#ffffff'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 90 : 40)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
async function doubleSpend(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
const contactX = origDX - dir * 45
|
||||
sfxZoomWhoosh()
|
||||
await k.tween(atk.pos.x, contactX, 0.1, (v: number) => { atk.pos.x = v }, k.easings.easeOutQuad)
|
||||
// Two rapid identical hits
|
||||
for (let i = 0; i < 2; i++) {
|
||||
atk.play(i === 0 ? 'attack' : 'kick'); sfxPunch()
|
||||
def.play('hit'); k.shake(5 + i * 3)
|
||||
spawnSparks(def.pos.x, def.pos.y - 25, 5, '#ff2222')
|
||||
spawnAfterimages(atk, 1)
|
||||
await k.wait(0.08)
|
||||
}
|
||||
def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 14 : 7)
|
||||
if (isCritical) { screenFlash('#ff2222'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 90 : 40)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3)
|
||||
await Promise.all([
|
||||
k.tween(atk.pos.x, origAX, 0.2, (v: number) => { atk.pos.x = v }, k.easings.easeInQuad),
|
||||
k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad),
|
||||
])
|
||||
}
|
||||
|
||||
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[] = []
|
||||
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)])
|
||||
roots.push(root)
|
||||
k.tween(0, 60 + Math.random() * 20, 0.15, (v: number) => { root.height = v; root.pos.y = GROUND_Y - v })
|
||||
await k.wait(0.03)
|
||||
}
|
||||
sfxBonk(); k.shake(isCritical ? 14 : 7)
|
||||
spawnSparks(def.pos.x, def.pos.y - 20, isCritical ? 12 : 6, '#44aa22')
|
||||
def.play(isCritical ? 'knockback' : 'hit')
|
||||
if (isCritical) { screenFlash('#44aa22'); sfxCritical() }
|
||||
await k.wait(0.2)
|
||||
roots.forEach(r => r.destroy())
|
||||
const push = dir * (isCritical ? 85 : 38)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
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[] = []
|
||||
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)
|
||||
}
|
||||
await k.wait(0.2); sfxZoomWhoosh()
|
||||
// All coins fly at defender
|
||||
for (const c of coins) {
|
||||
k.tween(c.pos.x, def.pos.x + (Math.random() - 0.5) * 20, 0.12, (v: number) => { c.pos.x = v })
|
||||
k.tween(c.pos.y, def.pos.y - 25 + (Math.random() - 0.5) * 20, 0.12, (v: number) => { c.pos.y = v }).then(() => { c.destroy(); sfxBulletHit() })
|
||||
}
|
||||
await k.wait(0.15); def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 14 : 7)
|
||||
spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 14 : 7, '#ffd700')
|
||||
if (isCritical) { screenFlash('#ffd700'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 85 : 38)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
async function difficultyRetarget(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
atk.play('special'); sfxSpecial()
|
||||
// Screen warps then snaps
|
||||
const overlay = k.add([k.rect(k.width(), k.height()), k.pos(0, 0), k.color(safeColor('#ff00ff')), k.opacity(0), k.z(25)])
|
||||
await k.tween(0, 0.3, 0.2, (v: number) => { overlay.opacity = v })
|
||||
k.shake(isCritical ? 18 : 9)
|
||||
await k.tween(0.3, 0, 0.15, (v: number) => { overlay.opacity = v })
|
||||
overlay.destroy(); sfxExplosion()
|
||||
spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 14 : 7, '#ff00ff')
|
||||
def.play(isCritical ? 'knockback' : 'hit')
|
||||
if (isCritical) { screenFlash('#ff00ff'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 85 : 38)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
async function taprootStrike(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
atk.play('special'); sfxSpecial(); await k.wait(0.1)
|
||||
// Single root shoots up from below defender
|
||||
const root = k.add([k.rect(5, 0), k.pos(def.pos.x, GROUND_Y), k.color(safeColor('#22cc44')), k.opacity(0.9), k.z(16)])
|
||||
sfxSlideDown()
|
||||
await k.tween(0, 80, 0.15, (v: number) => { root.height = v; root.pos.y = GROUND_Y - v }, k.easings.easeOutQuad)
|
||||
sfxBonk(); k.shake(isCritical ? 14 : 7)
|
||||
spawnSparks(def.pos.x, def.pos.y - 30, isCritical ? 12 : 6, '#22cc44')
|
||||
def.play(isCritical ? 'knockback' : 'hit')
|
||||
if (isCritical) { screenFlash('#22cc44'); sfxCritical() }
|
||||
await k.wait(0.15)
|
||||
k.tween(root.opacity, 0, 0.2, (v: number) => { root.opacity = v }).then(() => root.destroy())
|
||||
const push = dir * (isCritical ? 80 : 35)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
async function segwitSlash(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
const contactX = origDX - dir * 40
|
||||
sfxZoomWhoosh()
|
||||
await k.tween(atk.pos.x, contactX, 0.08, (v: number) => { atk.pos.x = v }, k.easings.easeOutQuad)
|
||||
atk.play('attack'); sfxPunch()
|
||||
// Compressed slash visual — thin line
|
||||
const slash = k.add([k.rect(60, 2), k.pos(def.pos.x - 30, def.pos.y - 30), k.color(safeColor('#44aaff')), k.opacity(0.9), k.z(18)])
|
||||
await k.wait(0.06); slash.destroy()
|
||||
def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 12 : 5)
|
||||
spawnSparks(def.pos.x, def.pos.y - 30, isCritical ? 10 : 5, '#44aaff')
|
||||
if (isCritical) { screenFlash('#44aaff'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 80 : 35)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3)
|
||||
await Promise.all([
|
||||
k.tween(atk.pos.x, origAX, 0.2, (v: number) => { atk.pos.x = v }, k.easings.easeInQuad),
|
||||
k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad),
|
||||
])
|
||||
}
|
||||
|
||||
async function timestampAttack(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
atk.play('special'); sfxSpecial(); await k.wait(0.1)
|
||||
const clock = k.add([k.circle(12), k.pos(atk.pos.x + dir * 20, atk.pos.y - 35), k.color(safeColor('#ffcc00')), k.opacity(0.8), k.z(18)])
|
||||
sfxZoomWhoosh()
|
||||
await k.tween(clock.pos.x, def.pos.x, 0.15, (v: number) => { clock.pos.x = v })
|
||||
clock.destroy(); sfxBonk(); k.shake(isCritical ? 14 : 7)
|
||||
spawnSparks(def.pos.x, def.pos.y - 30, isCritical ? 12 : 6, '#ffcc00')
|
||||
def.play(isCritical ? 'knockback' : 'hit')
|
||||
if (isCritical) { screenFlash('#ffcc00'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 85 : 38)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
async function dustAttack(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
atk.play('special'); sfxSpecial(); await k.wait(0.08)
|
||||
const count = isCritical ? 30 : 15
|
||||
for (let i = 0; i < count; i++) {
|
||||
const p = k.add([k.circle(1), k.pos(atk.pos.x + dir * 15, atk.pos.y - 30), k.color(safeColor('#aaaaaa')), k.opacity(0.6), k.z(16)])
|
||||
const tx = def.pos.x + (Math.random() - 0.5) * 50
|
||||
const ty = def.pos.y - 30 + (Math.random() - 0.5) * 40
|
||||
k.tween(p.pos.x, tx, 0.15, (v: number) => { p.pos.x = v })
|
||||
k.tween(p.pos.y, ty, 0.15, (v: number) => { p.pos.y = v }).then(() => p.destroy())
|
||||
await k.wait(0.008)
|
||||
}
|
||||
await k.wait(0.1); def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 10 : 4)
|
||||
if (isCritical) { screenFlash('#aaaaaa'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 70 : 30)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
const opReturnBeam = makeBeam('#ff8800', 8, 1)
|
||||
|
||||
async function orphanBlock(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
atk.play('special'); sfxSpecial(); await k.wait(0.1)
|
||||
const block = k.add([k.rect(20, 20), k.pos(def.pos.x + dir * 40, -20), k.color(safeColor('#ff6600')), k.opacity(0.7), k.z(17)])
|
||||
sfxSlideDown()
|
||||
// Drops, misses slightly, then rebounds into defender
|
||||
await k.tween(block.pos.y, def.pos.y - 20, 0.15, (v: number) => { block.pos.y = v }, k.easings.easeInQuad)
|
||||
sfxBoing()
|
||||
await k.tween(block.pos.y, def.pos.y - 50, 0.08, (v: number) => { block.pos.y = v }, k.easings.easeOutQuad)
|
||||
k.tween(block.pos.x, def.pos.x, 0.08, (v: number) => { block.pos.x = v })
|
||||
await k.tween(block.pos.y, def.pos.y - 25, 0.08, (v: number) => { block.pos.y = v }, k.easings.easeInQuad)
|
||||
block.destroy(); sfxBonk(); k.shake(isCritical ? 14 : 7)
|
||||
spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 12 : 6, '#ff6600')
|
||||
def.play(isCritical ? 'knockback' : 'hit')
|
||||
if (isCritical) { screenFlash('#ff6600'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 85 : 38)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
async function miningRig(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
atk.play('special'); sfxSpecial(); await k.wait(0.1)
|
||||
const count = isCritical ? 12 : 6
|
||||
for (let i = 0; i < count; i++) {
|
||||
const hash = k.add([k.rect(4, 4), k.pos(atk.pos.x + dir * 20, atk.pos.y - 30 + (Math.random() - 0.5) * 20), k.color(safeColor('#00ff88')), k.opacity(0.8), k.z(17)])
|
||||
k.tween(hash.pos.x, def.pos.x, 0.12, (v: number) => { hash.pos.x = v }).then(() => { hash.destroy(); sfxBulletHit(); spawnSparks(def.pos.x, hash.pos.y, 2, '#00ff88') })
|
||||
await k.wait(0.04)
|
||||
}
|
||||
await k.wait(0.12); def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 12 : 5)
|
||||
if (isCritical) { screenFlash('#00ff88'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 80 : 35)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
async function satPerByte(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
atk.play('special'); sfxSpecial()
|
||||
// Fee meter rises under defender
|
||||
const meter = k.add([k.rect(6, 0), k.pos(def.pos.x, GROUND_Y), k.color(safeColor('#ff2222')), k.opacity(0.8), k.z(15)])
|
||||
await k.tween(0, isCritical ? 90 : 60, 0.25, (v: number) => { meter.height = v; meter.pos.y = GROUND_Y - v })
|
||||
// Explodes
|
||||
sfxExplosion(); meter.destroy(); k.shake(isCritical ? 16 : 8)
|
||||
spawnSparks(def.pos.x, def.pos.y - 30, isCritical ? 14 : 7, '#ff2222')
|
||||
spawnShockwave(def.pos.x, def.pos.y - 20, '#ff4400')
|
||||
def.play(isCritical ? 'knockback' : 'hit')
|
||||
if (isCritical) { screenFlash('#ff2222'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 90 : 40)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
async function fullNodeGuard(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
// Shield forms, then counter-attacks
|
||||
atk.play('special'); sfxBoing()
|
||||
const shield = k.add([k.circle(25), k.pos(atk.pos.x, atk.pos.y - 25), k.color(safeColor('#00ff88')), k.opacity(0.3), k.z(16)])
|
||||
await k.wait(0.2)
|
||||
shield.destroy(); sfxZoomWhoosh()
|
||||
// Counter-dash
|
||||
const contactX = origDX - dir * 45
|
||||
await k.tween(atk.pos.x, contactX, 0.1, (v: number) => { atk.pos.x = v }, k.easings.easeOutQuad)
|
||||
atk.play('attack'); sfxPunch()
|
||||
def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 14 : 7)
|
||||
spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 12 : 6, '#00ff88')
|
||||
if (isCritical) { screenFlash('#00ff88'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 90 : 40)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3)
|
||||
await Promise.all([
|
||||
k.tween(atk.pos.x, origAX, 0.2, (v: number) => { atk.pos.x = v }, k.easings.easeInQuad),
|
||||
k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad),
|
||||
])
|
||||
}
|
||||
|
||||
async function multisigStrike(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
// 3 simultaneous hits from different angles
|
||||
atk.play('special'); sfxSpecial(); await k.wait(0.1)
|
||||
const angles = [-40, 0, 40]
|
||||
for (const offset of angles) {
|
||||
const proj = k.add([k.circle(5), k.pos(atk.pos.x + dir * 15, atk.pos.y - 30 + offset), k.color(safeColor('#f7931a')), k.opacity(0.9), k.z(17)])
|
||||
k.tween(proj.pos.x, def.pos.x, 0.12, (v: number) => { proj.pos.x = v })
|
||||
k.tween(proj.pos.y, def.pos.y - 25, 0.12, (v: number) => { proj.pos.y = v }).then(() => { proj.destroy(); sfxBulletHit() })
|
||||
}
|
||||
await k.wait(0.15); def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 14 : 7)
|
||||
spawnSparks(def.pos.x, def.pos.y - 25, isCritical ? 14 : 7, '#f7931a')
|
||||
if (isCritical) { screenFlash('#f7931a'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 85 : 38)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
async function nonceGrind(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
const contactX = origDX - dir * 45
|
||||
sfxZoomWhoosh()
|
||||
await k.tween(atk.pos.x, contactX, 0.1, (v: number) => { atk.pos.x = v }, k.easings.easeOutQuad)
|
||||
// Rapid grinding hits
|
||||
const hits = isCritical ? 6 : 3
|
||||
for (let i = 0; i < hits; i++) {
|
||||
atk.play(i % 2 === 0 ? 'attack' : 'kick'); sfxPunch()
|
||||
def.play('hit'); k.shake(3 + i)
|
||||
spawnSparks(def.pos.x + (Math.random() - 0.5) * 15, def.pos.y - 25, 3, '#ffaa00')
|
||||
await k.wait(0.04)
|
||||
}
|
||||
def.play(isCritical ? 'knockback' : 'hit'); k.shake(isCritical ? 14 : 7)
|
||||
if (isCritical) { screenFlash('#ffaa00'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 90 : 40)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3)
|
||||
await Promise.all([
|
||||
k.tween(atk.pos.x, origAX, 0.2, (v: number) => { atk.pos.x = v }, k.easings.easeInQuad),
|
||||
k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad),
|
||||
])
|
||||
}
|
||||
|
||||
async function whitePaperRoll(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
atk.play('attack'); sfxPunch(); await k.wait(0.08)
|
||||
// Paper scroll flies at defender
|
||||
const paper = k.add([k.rect(20, 8), k.pos(atk.pos.x + dir * 20, atk.pos.y - 30), k.color(safeColor('#f0e8d0')), k.opacity(0.9), k.z(17), k.rotate(0)])
|
||||
sfxZoomWhoosh()
|
||||
k.tween(paper.angle, 720, 0.18, (v: number) => { paper.angle = v })
|
||||
await k.tween(paper.pos.x, def.pos.x, 0.18, (v: number) => { paper.pos.x = v })
|
||||
paper.destroy(); sfxBonk(); k.shake(isCritical ? 12 : 5)
|
||||
spawnSparks(def.pos.x, def.pos.y - 30, isCritical ? 10 : 5, '#f0e8d0')
|
||||
def.play(isCritical ? 'knockback' : 'hit')
|
||||
if (isCritical) { screenFlash('#f0e8d0'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 80 : 35)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
const asicHammer = makeSwing(10, 25, '#666666', '#f7931a', 12, 12)
|
||||
|
||||
async function rugPullYank(atk: Fighter, def: Fighter, dir: number, origAX: number, origDX: number, isCritical: boolean) {
|
||||
atk.play('special'); sfxSpecial()
|
||||
// Ground slides away under defender
|
||||
const rug = k.add([k.rect(80, 4), k.pos(def.pos.x - 40, GROUND_Y - 2), k.color(safeColor('#ff4488')), k.opacity(0.6), k.z(4)])
|
||||
await k.wait(0.1)
|
||||
sfxZoomWhoosh()
|
||||
await k.tween(rug.pos.x, rug.pos.x + dir * 100, 0.15, (v: number) => { rug.pos.x = v })
|
||||
rug.destroy()
|
||||
// Defender falls
|
||||
await k.tween(def.pos.y, def.pos.y + 15, 0.08, (v: number) => { def.pos.y = v }, k.easings.easeInQuad)
|
||||
sfxBonk(); k.shake(isCritical ? 14 : 7)
|
||||
await k.tween(def.pos.y, GROUND_Y, 0.08, (v: number) => { def.pos.y = v })
|
||||
def.play(isCritical ? 'knockback' : 'hit')
|
||||
spawnSparks(def.pos.x, GROUND_Y, isCritical ? 10 : 5, '#ff4488')
|
||||
if (isCritical) { screenFlash('#ff4488'); sfxCritical() }
|
||||
const push = dir * (isCritical ? 85 : 38)
|
||||
k.tween(def.pos.x, origDX + push, 0.2, (v: number) => { def.pos.x = v }, k.easings.easeOutQuad)
|
||||
await k.wait(0.3); atk.play('idle')
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
const stackSatsBlast = makeThrow('#ffd700', 5, 8, 50, true)
|
||||
|
||||
return {
|
||||
lightningBolt, hashSmash, blockDrop, mempoolFlood, difficultyBomb, feeSpike, utxoScatter,
|
||||
nodeSync, relayBounce, channelForceClose, halvingStrike, coldStorage, paperWalletSlash,
|
||||
hardFork, doubleSpend, proofOfWork, merkleRoot, blockReward, difficultyRetarget, taprootStrike,
|
||||
segwitSlash, timestampAttack, dustAttack, opReturnBeam, orphanBlock, miningRig, satPerByte,
|
||||
fullNodeGuard, multisigStrike, nonceGrind, whitePaperRoll, asicHammer, rugPullYank,
|
||||
stackSatsBlast,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user