feat: 30 Bitcoin-themed choreographies
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. Added to token_economy, hack_battle, math_blitz, speed_blitz themed pools. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
9de36a9349
commit
561343164f
@@ -105,14 +105,14 @@ const spriteAnims = {
|
||||
|
||||
// Challenge type -> themed choreography (shown ~60% of the time) + generic fallbacks
|
||||
const CHALLENGE_THEMED: Record<string, { themed: string[]; generic: string[] }> = {
|
||||
speed_blitz: { themed: ['afterimageDash', 'lightningRush', 'rapidFlurry', 'tornadoSpin', 'corkscrewDive', 'warpDrive', 'backflipKick', 'bulletTimeAttack'], generic: ['dashPunch', 'multiHit', 'fullScreenDash', 'dashThrough', 'zoomRush', 'katanaCombo', 'cycloneKick', 'helicopterArms', 'breakdanceSweep'] },
|
||||
speed_blitz: { themed: ['afterimageDash', 'lightningRush', 'rapidFlurry', 'tornadoSpin', 'corkscrewDive', 'warpDrive', 'backflipKick', 'bulletTimeAttack', 'lightningBolt', 'channelForceClose', 'segwitSlash', 'doubleSpend'], generic: ['dashPunch', 'multiHit', 'fullScreenDash', 'dashThrough', 'zoomRush', 'katanaCombo', 'cycloneKick', 'helicopterArms', 'breakdanceSweep'] },
|
||||
riddle: { themed: ['riddleBarrage', 'portalPunch', 'cloneStrike', 'mindBlast', 'hexCurse'], generic: ['teleportStrike', 'projectile', 'zoomRush', 'whipCrack', 'bombThrow', 'captchaTrap', 'glitchBeam'] },
|
||||
code_golf: { themed: ['golfClubSmash', 'golfCartDrive', 'stackOverflow', 'segfault', 'blueScreen'], generic: ['projectile', 'flyingKick', 'dashPunch', 'rapidFlurry', 'hammerSmash', 'bodySlam', 'homeRunSwing'] },
|
||||
roast_battle: { themed: ['fireBreath', 'dragonBreath', 'fireball', 'lavaSplash', 'heatVision', 'volcanoErupt'], generic: ['gunBurst', 'jetpackDive', 'multiHit', 'zoomRush', 'laserBeam', 'chainsawRev', 'rocketLauncher', 'dynamiteBlast'] },
|
||||
hallucination_check: { themed: ['cloneStrike', 'portalPunch', 'shadowClone', 'glitchBeam', 'voidRift'], generic: ['teleportStrike', 'dashThrough', 'riddleBarrage', 'rapidFlurry', 'sniperShot', 'darkVoid', 'memeBeam'] },
|
||||
token_economy: { themed: ['coinShower', 'bitcoinCrash', 'nftRugPull', 'vendingDrop', 'popupSpam'], generic: ['dashPunch', 'flyingKick', 'uppercut', 'zoomRush', 'bombThrow', 'minigunSpray', 'pocketCannon'] },
|
||||
token_economy: { themed: ['coinShower', 'bitcoinCrash', 'nftRugPull', 'vendingDrop', 'popupSpam', 'blockReward', 'utxoScatter', 'feeSpike', 'satPerByte', 'stackSatsBlast', 'rugPullYank', 'dustAttack', 'multisigStrike'], generic: ['dashPunch', 'flyingKick', 'uppercut', 'zoomRush', 'bombThrow', 'minigunSpray', 'pocketCannon'] },
|
||||
creative_writing: { themed: ['penStab', 'scrollBlast', 'bookStorm', 'enchantedArrow', 'newspaperRoll'], generic: ['jetpackDive', 'aerialSlam', 'projectile', 'rapidFlurry', 'katanaCombo', 'arcaneBarrage', 'memeBeam'] },
|
||||
math_blitz: { themed: ['mathAttack', 'dataStream', 'pixelBeam', 'hackerAttack', 'chainLightning'], generic: ['multiHit', 'dashPunch', 'fullScreenDash', 'rapidFlurry', 'laserBeam', 'hammerSmash', 'pinballCombo'] },
|
||||
math_blitz: { themed: ['mathAttack', 'dataStream', 'pixelBeam', 'hackerAttack', 'chainLightning', 'merkleRoot', 'difficultyRetarget', 'halvingStrike'], generic: ['multiHit', 'dashPunch', 'fullScreenDash', 'rapidFlurry', 'laserBeam', 'hammerSmash', 'pinballCombo'] },
|
||||
trap_card: { themed: ['trapCardAttack', 'captchaTrap', 'spiderWeb', 'flashBang', 'smokeBomb'], generic: ['gunBurst', 'teleportStrike', 'groundPound', 'zoomRush', 'sniperShot', 'rocketLauncher', 'minigunSpray'] },
|
||||
food_fight: { themed: ['pizzaSlam', 'bananaFling', 'pieSmash', 'hotdogWhip', 'watermelonBomb', 'sushiBarrage', 'burgerToss', 'tacoStorm', 'eggBombard', 'tomatoBarrage'], generic: ['popcornBlast', 'iceCreamFling', 'donutBarrage', 'cookieFling', 'nachoVolley', 'meatballStorm', 'candyBarrage'] },
|
||||
wrestling_match: { themed: ['piledriver', 'powerbomb', 'chokeslam', 'tombstone', 'rko', 'clothesline', 'suplex', 'germanSuplex'], generic: ['bodySlam', 'grappleFlurry', 'hurricanrana', 'moonsault', 'elbowDrop', 'spear', 'stunner'] },
|
||||
@@ -121,7 +121,7 @@ const CHALLENGE_THEMED: Record<string, { themed: string[]; generic: string[] }>
|
||||
sports_showdown: { themed: ['homeRunSwing', 'slapShot', 'servingAce', 'fieldGoalKick', 'bodyCheck', 'soccerKick', 'basketballDunk'], generic: ['baseballBat', 'tennisRacket', 'hockeyStick', 'bowlingBallRoll', 'tennisBallVolley', 'dropKick'] },
|
||||
nature_clash: { themed: ['treeSmash', 'icebergDrop', 'lavaSplash', 'tornadoFling', 'tsunamiWave', 'avalanche', 'earthquakeStrike'], generic: ['vineWhip', 'sandstorm', 'thornBarrage', 'pollenCloud', 'windSlash', 'meteorStrike'] },
|
||||
space_war: { themed: ['blackHole', 'photonTorpedo', 'tractor_beam', 'warpDrive', 'alienAbduction', 'ionCannon', 'cosmicRay', 'bulletTimeAttack'], generic: ['laserSword', 'plasmaSword', 'asteroidBelt', 'satelliteLaser', 'plasmaBeam', 'solarFlare'] },
|
||||
hack_battle: { themed: ['hackerAttack', 'bugSwarm', 'stackOverflow', 'segfault', 'blueScreen', 'malwareInject', 'bulletTimeAttack'], generic: ['fourOhFour', 'ctrlAltDelete', 'aiUprising', 'captchaTrap', 'popupSpam', 'dataStream', 'glitchBeam'] },
|
||||
hack_battle: { themed: ['hackerAttack', 'bugSwarm', 'stackOverflow', 'segfault', 'blueScreen', 'malwareInject', 'bulletTimeAttack', 'hashSmash', 'miningRig', 'nodeSync', 'nonceGrind', 'proofOfWork'], generic: ['fourOhFour', 'ctrlAltDelete', 'aiUprising', 'captchaTrap', 'popupSpam', 'dataStream', 'glitchBeam'] },
|
||||
meme_war: { themed: ['selfieStrike', 'dabAttack', 'flossAttack', 'yeetThrow', 'tPoseAssert', 'emojiBarrage', 'memeBeam'], generic: ['rubberChicken', 'fingerGuns', 'micDrop', 'ratioAttack', 'capThrow', 'touchGrass', 'noScope'] },
|
||||
animal_kingdom: { themed: ['sharkBite', 'bearSwipe', 'eagleDive', 'bullCharge', 'gorillaSlam', 'wolfPack', 'batSwarm'], generic: ['snakeLunge', 'scorpionSting', 'crabPinch', 'spiderWeb', 'beeSwarm', 'catScratch', 'dogPile', 'dolphinFlip'] },
|
||||
demolition: { themed: ['dynamiteBlast', 'c4Detonation', 'nukeStrike', 'grenadeBlast', 'rocketLauncher', 'volcanoErupt'], generic: ['fireworksBurst', 'partyPopper', 'pinataSmash', 'cherryBomb', 'confettiCannon', 'anvilDrop', 'pianoDrop'] },
|
||||
@@ -6640,6 +6640,542 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
await k.tween(def.pos.x, origDX, 0.3, (v: number) => { def.pos.x = v }, k.easings.easeInOutQuad)
|
||||
}
|
||||
|
||||
// --- BITCOIN-THEMED CHOREOGRAPHIES (30) ---
|
||||
|
||||
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(k, '#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(k, '#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(k, 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(k, '#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(k, '#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(k, '#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(k, '#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(k, '#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(k, '#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(k, '#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(k, '#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(k, '#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(k, '#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(k, '#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(k, '#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(k, '#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(k, '#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(k, '#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(k, '#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(k, '#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(k, '#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(k, '#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(k, '#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(k, '#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(k, '#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)
|
||||
|
||||
const choreographyMap: Record<string, typeof dashPunch> = {
|
||||
dashPunch, aerialSlam, flyingKick, dashThrough, uppercut,
|
||||
multiHit, projectile, jetpackDive, gunBurst, groundPound,
|
||||
@@ -6743,6 +7279,14 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
// Tier 5+
|
||||
ultimateArmageddon, ultimateOmegaBeam, ultimateBeyondTheScreen, ultimateMatrixDodge, ultimateWorldEnder,
|
||||
ultimateAbsoluteZero, ultimateSolarCannon, ultimatePhantomStrike, ultimateGodPunch, ultimateVoidCollapse,
|
||||
// --- BITCOIN-THEMED (30 new) ---
|
||||
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,
|
||||
// --- THE CREATOR: Bitcoin-themed moves ---
|
||||
bitcoinRain, lightningInvoice, satoshiStrike, hashRateOverload, blockchainSlam, creatorMode,
|
||||
morphStrike, divineIntervention,
|
||||
|
||||
Reference in New Issue
Block a user