feat: victory celebration animations
Normal wins: winner flex pose with camera flash bursts + confetti rain. Upset wins (100+ Elo underdog): dramatic zoom on winner with golden flash. Upset tag shown on win overlay. Wired into FightViewer after KO/Perfect sequence. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
cc0fb4553a
commit
88ceba43b0
@@ -587,12 +587,19 @@ async function _doReplay() {
|
||||
await sleep(300) // let fatality voice finish
|
||||
}
|
||||
|
||||
// Victory celebration — detect upsets (winner had lower Elo)
|
||||
const winnerElo = winningSide === 'a' ? (props.fight.botA?.eloRating ?? 1200) : (props.fight.botB?.eloRating ?? 1200)
|
||||
const loserElo = winningSide === 'a' ? (props.fight.botB?.eloRating ?? 1200) : (props.fight.botA?.eloRating ?? 1200)
|
||||
const isUpset = winnerElo < loserElo - 100
|
||||
await scene.playVictoryCelebration(winningSide, isUpset)
|
||||
|
||||
glitching.value = true
|
||||
sfxApplause()
|
||||
sfxCrowdCheer()
|
||||
await sleep(200)
|
||||
glitching.value = false
|
||||
await showOverlay(`${winnerName} WINS!`, '#00f0ff', 1800)
|
||||
const upsetTag = isUpset ? ' UPSET!' : ''
|
||||
await showOverlay(`${winnerName} WINS!${upsetTag}`, isUpset ? '#ff6600' : '#00f0ff', 1800)
|
||||
|
||||
logItems.value.push(
|
||||
{ type: 'divider', round: 99, text: '', color: '' },
|
||||
|
||||
@@ -12118,6 +12118,48 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
await k.wait(0.7)
|
||||
},
|
||||
|
||||
async playVictoryCelebration(winningSide: 'a' | 'b', isUpset: boolean) {
|
||||
const winner = k.get(winningSide === 'a' ? 'fighterA' : 'fighterB')[0] as Fighter
|
||||
const loser = k.get(winningSide === 'a' ? 'fighterB' : 'fighterA')[0] as Fighter
|
||||
if (!winner) return
|
||||
|
||||
if (isUpset) {
|
||||
// UPSET: Dramatic zoom on winner + shocked gasp
|
||||
const origSX = winner.scale.x
|
||||
const origSY = winner.scale.y
|
||||
sfxZoomWhoosh()
|
||||
await Promise.all([
|
||||
k.tween(Math.abs(origSX), 4, 0.25, (v: number) => { winner.scale.x = origSX > 0 ? v : -v; winner.scale.y = v }, k.easings.easeOutQuad),
|
||||
k.tween(winner.pos.x, W / 2, 0.25, (v: number) => { winner.pos.x = v }, k.easings.easeOutQuad),
|
||||
])
|
||||
screenFlash('#ffe14d', 0.15)
|
||||
k.shake(8)
|
||||
await k.wait(0.6)
|
||||
// Zoom back
|
||||
await Promise.all([
|
||||
k.tween(winner.scale.y, Math.abs(origSY), 0.2, (v: number) => { winner.scale.x = origSX > 0 ? v : -v; winner.scale.y = v }, k.easings.easeInQuad),
|
||||
k.tween(winner.pos.x, winningSide === 'a' ? HOME_A : HOME_B, 0.2, (v: number) => { winner.pos.x = v }, k.easings.easeInQuad),
|
||||
])
|
||||
} else {
|
||||
// NORMAL: Winner flex pose with camera flash
|
||||
winner.play('win')
|
||||
await k.wait(0.3)
|
||||
screenFlash('#ffffff', 0.08)
|
||||
sfxSpecial()
|
||||
await k.wait(0.15)
|
||||
screenFlash('#ffffff', 0.06)
|
||||
await k.wait(0.15)
|
||||
screenFlash('#ffffff', 0.04)
|
||||
// Confetti shower
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const cx = Math.random() * W
|
||||
const confetti = k.add([k.rect(4, 4), k.pos(cx, -5), k.color(safeColor(k, ['#ff2d7b', '#00f0ff', '#ffe14d', '#39ff14', '#b83dff', '#ff6600'][i % 6])), k.opacity(0.9), k.z(20), k.rotate(Math.random() * 360)])
|
||||
k.tween(confetti.pos.y, GROUND_Y + 10, 0.8 + Math.random() * 0.5, (v: number) => { confetti.pos.y = v; confetti.angle += 180 * k.dt() }).then(() => confetti.destroy())
|
||||
}
|
||||
}
|
||||
await k.wait(0.4)
|
||||
},
|
||||
|
||||
startMusic() { startMusic() },
|
||||
stopMusic() { stopMusic() },
|
||||
|
||||
|
||||
Reference in New Issue
Block a user