refactor: extract pickChoreography to fight/choreography/picker.ts
Moves 103 lines of choreography selection logic out of FightScene.ts into a dedicated module. FightScene re-exports for backward compat. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
38d7236be3
commit
b6033a90e5
@@ -4,12 +4,10 @@ import type { GameObj, SpriteComp, PosComp, ScaleComp, AnchorComp, OpacityComp,
|
|||||||
import { generateSpriteSheet, generateJudgeSpriteSheet, generateHumanSpriteSheet, generateHumanFighterSpriteSheet, getBotColors, FRAME_SIZE, MAX_FRAMES, TOTAL_ROWS, ANIMATIONS, JUDGE_ANIMATIONS, JUDGE_MAX_FRAMES, JUDGE_ROWS, HUMAN_ANIMATIONS, HUMAN_MAX_FRAMES, HUMAN_ROWS, type SpriteCustomization } from './sprites'
|
import { generateSpriteSheet, generateJudgeSpriteSheet, generateHumanSpriteSheet, generateHumanFighterSpriteSheet, getBotColors, FRAME_SIZE, MAX_FRAMES, TOTAL_ROWS, ANIMATIONS, JUDGE_ANIMATIONS, JUDGE_MAX_FRAMES, JUDGE_ROWS, HUMAN_ANIMATIONS, HUMAN_MAX_FRAMES, HUMAN_ROWS, type SpriteCustomization } from './sprites'
|
||||||
import type { Fighter, FightSceneConfig, RoundEvent, FightContext, ChoreoContext } from './fight/types'
|
import type { Fighter, FightSceneConfig, RoundEvent, FightContext, ChoreoContext } from './fight/types'
|
||||||
export type { FightSceneConfig, RoundEvent } from './fight/types'
|
export type { FightSceneConfig, RoundEvent } from './fight/types'
|
||||||
import { ARENA_THEMES, spriteAnims, CHALLENGE_THEMED, TIER_ULTIMATES, CREATOR_MOVES, CREATOR_ULTIMATES } from './fight/constants'
|
import { ARENA_THEMES, spriteAnims } from './fight/constants'
|
||||||
import {
|
import { pickChoreography } from './fight/choreography/picker'
|
||||||
CREATOR_ULTIMATE_CHANCE, CREATOR_THEMED_MOVE_RATIO,
|
export { pickChoreography }
|
||||||
CRIT_THEMED_RATIO, CHOREOGRAPHY_THEMED_RATIO, CHOREOGRAPHY_GENERIC_CUTOFF,
|
import { isPerfMode, perfParticles, isReducedMotion, haptic } from './fight/config'
|
||||||
TIER_ULTIMATE_CHANCES, isPerfMode, perfParticles, isReducedMotion, haptic,
|
|
||||||
} from './fight/config'
|
|
||||||
import { spawnSparks as _spawnSparks, spawnBulletHoles as _spawnBulletHoles, spawnExhaust as _spawnExhaust } from './fight/particles'
|
import { spawnSparks as _spawnSparks, spawnBulletHoles as _spawnBulletHoles, spawnExhaust as _spawnExhaust } from './fight/particles'
|
||||||
import { glitchRGB as _glitchRGB, scanlineGlitch as _scanlineGlitch, vhsTracking as _vhsTracking, dimensionalShift as _dimensionalShift } from './fight/effects'
|
import { glitchRGB as _glitchRGB, scanlineGlitch as _scanlineGlitch, vhsTracking as _vhsTracking, dimensionalShift as _dimensionalShift } from './fight/effects'
|
||||||
import { spawnProjectile as _spawnProjectile } from './fight/projectiles'
|
import { spawnProjectile as _spawnProjectile } from './fight/projectiles'
|
||||||
@@ -30,114 +28,6 @@ import {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export function pickChoreography(challengeType: string, isCritical: boolean, _round: number, attackerTier: number = 0, attackerArchetype?: string): string {
|
|
||||||
// THE CREATOR: special move selection
|
|
||||||
if (attackerArchetype === 'the_creator') {
|
|
||||||
const ultimateChance = isCritical ? 1.0 : CREATOR_ULTIMATE_CHANCE
|
|
||||||
if (Math.random() < ultimateChance) {
|
|
||||||
// Creator gets ALL ultimates + their 4 exclusive ones
|
|
||||||
const all: string[] = [...CREATOR_ULTIMATES]
|
|
||||||
for (let t = 2; t <= 5; t++) {
|
|
||||||
if (TIER_ULTIMATES[t]) all.push(...TIER_ULTIMATES[t])
|
|
||||||
}
|
|
||||||
return all[Math.floor(Math.random() * all.length)]
|
|
||||||
}
|
|
||||||
if (Math.random() < CREATOR_THEMED_MOVE_RATIO) {
|
|
||||||
return CREATOR_MOVES[Math.floor(Math.random() * CREATOR_MOVES.length)]
|
|
||||||
}
|
|
||||||
// Fall through to normal selection
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tier-gated ultimates: higher tier = higher chance of spectacular ultimate moves
|
|
||||||
if (attackerTier >= 2) {
|
|
||||||
const ultimateChance = TIER_ULTIMATE_CHANCES[attackerTier] ?? TIER_ULTIMATE_CHANCES[5]
|
|
||||||
// On crits, double the chance
|
|
||||||
const roll = isCritical ? ultimateChance * 2 : ultimateChance
|
|
||||||
if (Math.random() < roll) {
|
|
||||||
// Collect all ultimates available at this tier (cumulative — higher tiers get lower tier moves too)
|
|
||||||
const available: string[] = []
|
|
||||||
for (let t = 2; t <= attackerTier; t++) {
|
|
||||||
if (TIER_ULTIMATES[t]) available.push(...TIER_ULTIMATES[t])
|
|
||||||
}
|
|
||||||
if (available.length > 0) {
|
|
||||||
return available[Math.floor(Math.random() * available.length)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Critical hits always get BIG moves (but can still be themed)
|
|
||||||
if (isCritical) {
|
|
||||||
const entry = CHALLENGE_THEMED[challengeType]
|
|
||||||
if (entry && Math.random() < CRIT_THEMED_RATIO) {
|
|
||||||
return entry.themed[Math.floor(Math.random() * entry.themed.length)]
|
|
||||||
}
|
|
||||||
const critMoves = [
|
|
||||||
'groundPound', 'jetpackDive', 'gunBurst', 'fullScreenDash', 'multiHit', 'fireBreath',
|
|
||||||
'lightningRush', 'zoomRush', 'rapidFlurry', 'rocketLauncher', 'laserBeam', 'katanaCombo',
|
|
||||||
'hammerSmash', 'sniperShot', 'minigunSpray', 'chainsawRev', 'carSmash', 'motorbikeCharge',
|
|
||||||
'anvilDrop', 'pocketCannon', 'bodySlam', 'suplex', 'pinballCombo', 'grappleFlurry',
|
|
||||||
// New epic crits
|
|
||||||
'kamehameha', 'nukeStrike', 'tankRoll', 'helicopterStrike', 'chokeslam', 'tombstone',
|
|
||||||
'rko', 'piledriver', 'powerbomb', 'meteorStrike', 'spiritBomb', 'blackHole',
|
|
||||||
'alienAbduction', 'tsunamiWave', 'earthquakeStrike', 'dragonBreath', 'pianoDrop',
|
|
||||||
'fridgeDrop', 'volcanoErupt', 'warpDrive', 'dubstepCannon', 'tornadoSpin',
|
|
||||||
'trampolineBounce', 'watermelonBomb', 'nftRugPull', 'blueScreen', 'yeetThrow',
|
|
||||||
'bulletTimeAttack',
|
|
||||||
]
|
|
||||||
return critMoves[Math.floor(Math.random() * critMoves.length)]
|
|
||||||
}
|
|
||||||
|
|
||||||
const entry = CHALLENGE_THEMED[challengeType]
|
|
||||||
if (!entry) {
|
|
||||||
const fallback = ['dashPunch', 'flyingKick', 'aerialSlam', 'backflipKick', 'fingerGuns', 'rubberChicken', 'pizzaSlam', 'fryingPan', 'snowballFight']
|
|
||||||
return fallback[Math.floor(Math.random() * fallback.length)]
|
|
||||||
}
|
|
||||||
|
|
||||||
const roll = Math.random()
|
|
||||||
if (roll < CHOREOGRAPHY_THEMED_RATIO) {
|
|
||||||
return entry.themed[Math.floor(Math.random() * entry.themed.length)]
|
|
||||||
} else if (roll < CHOREOGRAPHY_GENERIC_CUTOFF) {
|
|
||||||
return entry.generic[Math.floor(Math.random() * entry.generic.length)]
|
|
||||||
} else {
|
|
||||||
const wild = [
|
|
||||||
'jetpackDive', 'gunBurst', 'groundPound', 'teleportStrike', 'fullScreenDash', 'fireBreath',
|
|
||||||
'golfClubSmash', 'zoomRush', 'rapidFlurry', 'swordSlash', 'hammerSmash', 'laserBeam',
|
|
||||||
'rocketLauncher', 'bombThrow', 'minigunSpray', 'sniperShot', 'whipCrack', 'katanaCombo',
|
|
||||||
'chainsawRev', 'motorbikeCharge', 'carSmash', 'boatCannon', 'anvilDrop', 'pocketCannon',
|
|
||||||
'grappleFlurry', 'bodySlam', 'pinballCombo', 'suplex',
|
|
||||||
// New wild cards — food
|
|
||||||
'pizzaSlam', 'bananaFling', 'pieSmash', 'burgerToss', 'tomatoBarrage', 'fishSlap',
|
|
||||||
// New wild cards — weapons
|
|
||||||
'fryingPan', 'baseballBat', 'guitarSmash', 'umbrellaWhack', 'plungerSlam', 'wrenchSmash',
|
|
||||||
// New wild cards — drops
|
|
||||||
'pianoDrop', 'toiletDrop', 'tvDrop', 'chandelierDrop', 'micDrop',
|
|
||||||
// New wild cards — vehicles
|
|
||||||
'shoppingCart', 'zamboniCrush', 'unicycleRun', 'tankRoll',
|
|
||||||
// New wild cards — spins/flips
|
|
||||||
'tornadoSpin', 'backflipKick', 'corkscrewDive', 'barrelRoll', 'skateTrick',
|
|
||||||
// New wild cards — beams
|
|
||||||
'kamehameha', 'freezeRay', 'heatVision', 'rainbowBeam', 'glitchBeam',
|
|
||||||
// New wild cards — silly
|
|
||||||
'rubberChicken', 'selfieStrike', 'fingerGuns', 'yeetThrow', 'emojiBarrage', 'touchGrass',
|
|
||||||
// New wild cards — wrestling
|
|
||||||
'clothesline', 'rko', 'stunner', 'piledriver',
|
|
||||||
// New wild cards — magic
|
|
||||||
'fireball', 'portalPunch', 'crystalShards', 'spiritBomb',
|
|
||||||
// New wild cards — tech
|
|
||||||
'stackOverflow', 'blueScreen', 'aiUprising', 'popupSpam',
|
|
||||||
// New wild cards — music
|
|
||||||
'bassDrop', 'dubstepCannon', 'vinylScratch',
|
|
||||||
// New wild cards — nature
|
|
||||||
'avalanche', 'lavaSplash', 'tornadoFling',
|
|
||||||
]
|
|
||||||
return wild[Math.floor(Math.random() * wild.length)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert any CSS color string to a kaplay Color object safely
|
// Convert any CSS color string to a kaplay Color object safely
|
||||||
function safeColor(k: ReturnType<typeof kaplay>, color: string) {
|
function safeColor(k: ReturnType<typeof kaplay>, color: string) {
|
||||||
if (color.startsWith('#')) {
|
if (color.startsWith('#')) {
|
||||||
|
|||||||
@@ -0,0 +1,110 @@
|
|||||||
|
import { CHALLENGE_THEMED, TIER_ULTIMATES, CREATOR_MOVES, CREATOR_ULTIMATES } from '../constants'
|
||||||
|
import {
|
||||||
|
CREATOR_ULTIMATE_CHANCE, CREATOR_THEMED_MOVE_RATIO,
|
||||||
|
CRIT_THEMED_RATIO, CHOREOGRAPHY_THEMED_RATIO, CHOREOGRAPHY_GENERIC_CUTOFF,
|
||||||
|
TIER_ULTIMATE_CHANCES,
|
||||||
|
} from '../config'
|
||||||
|
|
||||||
|
export function pickChoreography(challengeType: string, isCritical: boolean, _round: number, attackerTier: number = 0, attackerArchetype?: string): string {
|
||||||
|
// THE CREATOR: special move selection
|
||||||
|
if (attackerArchetype === 'the_creator') {
|
||||||
|
const ultimateChance = isCritical ? 1.0 : CREATOR_ULTIMATE_CHANCE
|
||||||
|
if (Math.random() < ultimateChance) {
|
||||||
|
// Creator gets ALL ultimates + their 4 exclusive ones
|
||||||
|
const all: string[] = [...CREATOR_ULTIMATES]
|
||||||
|
for (let t = 2; t <= 5; t++) {
|
||||||
|
if (TIER_ULTIMATES[t]) all.push(...TIER_ULTIMATES[t])
|
||||||
|
}
|
||||||
|
return all[Math.floor(Math.random() * all.length)]
|
||||||
|
}
|
||||||
|
if (Math.random() < CREATOR_THEMED_MOVE_RATIO) {
|
||||||
|
return CREATOR_MOVES[Math.floor(Math.random() * CREATOR_MOVES.length)]
|
||||||
|
}
|
||||||
|
// Fall through to normal selection
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tier-gated ultimates: higher tier = higher chance of spectacular ultimate moves
|
||||||
|
if (attackerTier >= 2) {
|
||||||
|
const ultimateChance = TIER_ULTIMATE_CHANCES[attackerTier] ?? TIER_ULTIMATE_CHANCES[5]
|
||||||
|
// On crits, double the chance
|
||||||
|
const roll = isCritical ? ultimateChance * 2 : ultimateChance
|
||||||
|
if (Math.random() < roll) {
|
||||||
|
// Collect all ultimates available at this tier (cumulative — higher tiers get lower tier moves too)
|
||||||
|
const available: string[] = []
|
||||||
|
for (let t = 2; t <= attackerTier; t++) {
|
||||||
|
if (TIER_ULTIMATES[t]) available.push(...TIER_ULTIMATES[t])
|
||||||
|
}
|
||||||
|
if (available.length > 0) {
|
||||||
|
return available[Math.floor(Math.random() * available.length)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Critical hits always get BIG moves (but can still be themed)
|
||||||
|
if (isCritical) {
|
||||||
|
const entry = CHALLENGE_THEMED[challengeType]
|
||||||
|
if (entry && Math.random() < CRIT_THEMED_RATIO) {
|
||||||
|
return entry.themed[Math.floor(Math.random() * entry.themed.length)]
|
||||||
|
}
|
||||||
|
const critMoves = [
|
||||||
|
'groundPound', 'jetpackDive', 'gunBurst', 'fullScreenDash', 'multiHit', 'fireBreath',
|
||||||
|
'lightningRush', 'zoomRush', 'rapidFlurry', 'rocketLauncher', 'laserBeam', 'katanaCombo',
|
||||||
|
'hammerSmash', 'sniperShot', 'minigunSpray', 'chainsawRev', 'carSmash', 'motorbikeCharge',
|
||||||
|
'anvilDrop', 'pocketCannon', 'bodySlam', 'suplex', 'pinballCombo', 'grappleFlurry',
|
||||||
|
// New epic crits
|
||||||
|
'kamehameha', 'nukeStrike', 'tankRoll', 'helicopterStrike', 'chokeslam', 'tombstone',
|
||||||
|
'rko', 'piledriver', 'powerbomb', 'meteorStrike', 'spiritBomb', 'blackHole',
|
||||||
|
'alienAbduction', 'tsunamiWave', 'earthquakeStrike', 'dragonBreath', 'pianoDrop',
|
||||||
|
'fridgeDrop', 'volcanoErupt', 'warpDrive', 'dubstepCannon', 'tornadoSpin',
|
||||||
|
'trampolineBounce', 'watermelonBomb', 'nftRugPull', 'blueScreen', 'yeetThrow',
|
||||||
|
'bulletTimeAttack',
|
||||||
|
]
|
||||||
|
return critMoves[Math.floor(Math.random() * critMoves.length)]
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry = CHALLENGE_THEMED[challengeType]
|
||||||
|
if (!entry) {
|
||||||
|
const fallback = ['dashPunch', 'flyingKick', 'aerialSlam', 'backflipKick', 'fingerGuns', 'rubberChicken', 'pizzaSlam', 'fryingPan', 'snowballFight']
|
||||||
|
return fallback[Math.floor(Math.random() * fallback.length)]
|
||||||
|
}
|
||||||
|
|
||||||
|
const roll = Math.random()
|
||||||
|
if (roll < CHOREOGRAPHY_THEMED_RATIO) {
|
||||||
|
return entry.themed[Math.floor(Math.random() * entry.themed.length)]
|
||||||
|
} else if (roll < CHOREOGRAPHY_GENERIC_CUTOFF) {
|
||||||
|
return entry.generic[Math.floor(Math.random() * entry.generic.length)]
|
||||||
|
} else {
|
||||||
|
const wild = [
|
||||||
|
'jetpackDive', 'gunBurst', 'groundPound', 'teleportStrike', 'fullScreenDash', 'fireBreath',
|
||||||
|
'golfClubSmash', 'zoomRush', 'rapidFlurry', 'swordSlash', 'hammerSmash', 'laserBeam',
|
||||||
|
'rocketLauncher', 'bombThrow', 'minigunSpray', 'sniperShot', 'whipCrack', 'katanaCombo',
|
||||||
|
'chainsawRev', 'motorbikeCharge', 'carSmash', 'boatCannon', 'anvilDrop', 'pocketCannon',
|
||||||
|
'grappleFlurry', 'bodySlam', 'pinballCombo', 'suplex',
|
||||||
|
// New wild cards — food
|
||||||
|
'pizzaSlam', 'bananaFling', 'pieSmash', 'burgerToss', 'tomatoBarrage', 'fishSlap',
|
||||||
|
// New wild cards — weapons
|
||||||
|
'fryingPan', 'baseballBat', 'guitarSmash', 'umbrellaWhack', 'plungerSlam', 'wrenchSmash',
|
||||||
|
// New wild cards — drops
|
||||||
|
'pianoDrop', 'toiletDrop', 'tvDrop', 'chandelierDrop', 'micDrop',
|
||||||
|
// New wild cards — vehicles
|
||||||
|
'shoppingCart', 'zamboniCrush', 'unicycleRun', 'tankRoll',
|
||||||
|
// New wild cards — spins/flips
|
||||||
|
'tornadoSpin', 'backflipKick', 'corkscrewDive', 'barrelRoll', 'skateTrick',
|
||||||
|
// New wild cards — beams
|
||||||
|
'kamehameha', 'freezeRay', 'heatVision', 'rainbowBeam', 'glitchBeam',
|
||||||
|
// New wild cards — silly
|
||||||
|
'rubberChicken', 'selfieStrike', 'fingerGuns', 'yeetThrow', 'emojiBarrage', 'touchGrass',
|
||||||
|
// New wild cards — wrestling
|
||||||
|
'clothesline', 'rko', 'stunner', 'piledriver',
|
||||||
|
// New wild cards — magic
|
||||||
|
'fireball', 'portalPunch', 'crystalShards', 'spiritBomb',
|
||||||
|
// New wild cards — tech
|
||||||
|
'stackOverflow', 'blueScreen', 'aiUprising', 'popupSpam',
|
||||||
|
// New wild cards — music
|
||||||
|
'bassDrop', 'dubstepCannon', 'vinylScratch',
|
||||||
|
// New wild cards — nature
|
||||||
|
'avalanche', 'lavaSplash', 'tornadoFling',
|
||||||
|
]
|
||||||
|
return wild[Math.floor(Math.random() * wild.length)]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user