566 lines
32 KiB
TypeScript
566 lines
32 KiB
TypeScript
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,
|
||
|
|
}
|
||
|
|
}
|