feat: full comedy overhaul — voices, narrations, trash talk, commentary
Rewrites all commentary and narration content across the entire stack: - sounds.ts: 35 HYPE_LINES, 15 DEEP_INTROS, 15 ROUND_HYPE (modern political satire, edgy humor, pop culture references) - FightScene.ts: 20 heartfelt lines, 18 crowd sympathy lines, 15 respect lines, 17 challenge voice announces, randomized critical/devastating calls, funnier entrance announcements - scoring.ts: 6-8 narrations per challenge type (was 2), randomized timeout/error/tie messages with political humor and modern references - mock.ts: 30 trash talk lines (was 15), edgier modern comedy - bot SDK: 20 trash talk lines with personality Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
559782c8ce
commit
33f08c35b5
+228
-34
@@ -1,5 +1,5 @@
|
||||
import kaplay from 'kaplay'
|
||||
import { generateSpriteSheet, generateJudgeSpriteSheet, getBotColors, FRAME_SIZE, MAX_FRAMES, TOTAL_ROWS, ANIMATIONS, JUDGE_ANIMATIONS, JUDGE_MAX_FRAMES, JUDGE_ROWS } from './sprites'
|
||||
import { generateSpriteSheet, generateJudgeSpriteSheet, generateHumanSpriteSheet, 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 {
|
||||
sfxPunch, sfxKick, sfxSpecial, sfxCritical, sfxGunshot, sfxBulletHit,
|
||||
sfxJetpack, sfxExplosion, sfxKO, sfxWin, sfxWinAnnounce, sfxPerfect,
|
||||
@@ -14,8 +14,8 @@ import {
|
||||
|
||||
export interface FightSceneConfig {
|
||||
canvas: HTMLCanvasElement
|
||||
botA: { name: string; seed: string; tier: number; archetype?: string }
|
||||
botB: { name: string; seed: string; tier: number; archetype?: string }
|
||||
botA: { name: string; seed: string; tier: number; archetype?: string; customization?: SpriteCustomization; wins?: number; losses?: number }
|
||||
botB: { name: string; seed: string; tier: number; archetype?: string; customization?: SpriteCustomization; wins?: number; losses?: number }
|
||||
arena: string
|
||||
onReady?: () => void
|
||||
}
|
||||
@@ -230,13 +230,13 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
let sheetA: string
|
||||
let sheetB: string
|
||||
try {
|
||||
sheetA = generateSpriteSheet(botA.seed, botA.tier, colorsA.primary, colorsA.secondary, botA.archetype)
|
||||
sheetA = generateSpriteSheet(botA.seed, botA.tier, colorsA.primary, colorsA.secondary, botA.archetype, botA.customization)
|
||||
} catch (err) {
|
||||
console.error('[FightScene] Failed to generate sprite for botA:', err)
|
||||
sheetA = generateSpriteSheet(botA.seed, botA.tier, colorsA.primary, colorsA.secondary)
|
||||
}
|
||||
try {
|
||||
sheetB = generateSpriteSheet(botB.seed, botB.tier, colorsB.primary, colorsB.secondary, botB.archetype)
|
||||
sheetB = generateSpriteSheet(botB.seed, botB.tier, colorsB.primary, colorsB.secondary, botB.archetype, botB.customization)
|
||||
} catch (err) {
|
||||
console.error('[FightScene] Failed to generate sprite for botB:', err)
|
||||
sheetB = generateSpriteSheet(botB.seed, botB.tier, colorsB.primary, colorsB.secondary)
|
||||
@@ -258,6 +258,20 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
}
|
||||
await k.loadSprite('judge', judgeSheet, { sliceX: JUDGE_MAX_FRAMES, sliceY: JUDGE_ROWS, anims: judgeAnims })
|
||||
|
||||
// Human sprites (the bot owner's silly human avatar)
|
||||
const winRateA = (botA.wins || 0) / Math.max(1, (botA.wins || 0) + (botA.losses || 0))
|
||||
const winRateB = (botB.wins || 0) / Math.max(1, (botB.wins || 0) + (botB.losses || 0))
|
||||
const humanSheetA = generateHumanSpriteSheet(botA.seed, botA.archetype || 'standard', colorsA.primary, colorsA.secondary, winRateA)
|
||||
const humanSheetB = generateHumanSpriteSheet(botB.seed, botB.archetype || 'standard', colorsB.primary, colorsB.secondary, winRateB)
|
||||
const humanAnims: Record<string, { from: number; to: number; loop: boolean; speed: number }> = {}
|
||||
for (const [name, cfg] of Object.entries(HUMAN_ANIMATIONS)) {
|
||||
humanAnims[name] = { from: cfg.row * HUMAN_MAX_FRAMES, to: cfg.row * HUMAN_MAX_FRAMES + cfg.frames - 1, loop: true, speed: 6 }
|
||||
}
|
||||
await Promise.all([
|
||||
k.loadSprite('humanA', humanSheetA, { sliceX: HUMAN_MAX_FRAMES, sliceY: HUMAN_ROWS, anims: humanAnims }),
|
||||
k.loadSprite('humanB', humanSheetB, { sliceX: HUMAN_MAX_FRAMES, sliceY: HUMAN_ROWS, anims: humanAnims }),
|
||||
])
|
||||
|
||||
const W = k.width()
|
||||
const H = k.height()
|
||||
const GROUND_Y = H * 0.78
|
||||
@@ -5264,23 +5278,62 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
// === EMOTION & SPORTSMANSHIP SYSTEM ===
|
||||
|
||||
const heartfeltLines = [
|
||||
'What a warrior!', 'They gave it everything!', 'Heart of a champion!',
|
||||
'You can feel the respect!', 'That was beautiful!', 'Incredible spirit!',
|
||||
'They left it all in the ring!', 'A true fighter!', 'Nothing but respect!',
|
||||
'What courage!', 'The crowd is in tears!', 'What a moment!',
|
||||
'This is what it\'s all about!', 'Pure heart!', 'Standing ovation!',
|
||||
'That bot fights like it\'s got student loans to pay!',
|
||||
'I\'m not crying, you\'re crying!',
|
||||
'Someone get this bot a movie deal!',
|
||||
'This is better than anything on Netflix right now!',
|
||||
'That bot has more heart than most politicians!',
|
||||
'I haven\'t been this moved since my last Windows update!',
|
||||
'The crowd is ugly crying and I respect that!',
|
||||
'This bot has main character energy!',
|
||||
'We\'re witnessing greatness and I don\'t say that lightly!',
|
||||
'Even the judge is getting emotional!',
|
||||
'Someone call their developer, they\'d be so proud!',
|
||||
'This is the kind of fight you tell your grandkids about!',
|
||||
'That\'s not a bot, that\'s a WARRIOR!',
|
||||
'More drama than a Congressional hearing!',
|
||||
'They left it all in the ring! And by all, I mean EVERYTHING!',
|
||||
'The crowd is standing! The ones who can still stand, anyway!',
|
||||
'Somebody film this before it gets taken down!',
|
||||
'This fight has more plot twists than a Netflix original!',
|
||||
'That bot fights like rent is due tomorrow!',
|
||||
'My heart! My poor little heart!',
|
||||
]
|
||||
|
||||
const crowdSympathyLines = [
|
||||
'Aww...', 'So close!', 'Almost had it!', 'Tough break!',
|
||||
'Next time!', 'Great effort though!', 'Don\'t give up!',
|
||||
'The crowd feels that one...', 'Ohhh...', 'Heartbreaking!',
|
||||
'Pour one out for the fallen!',
|
||||
'That\'s rough, buddy...',
|
||||
'Somebody hug that bot!',
|
||||
'They tried their best, which is... concerning!',
|
||||
'At least they have a great personality!',
|
||||
'Their developer still loves them! Probably!',
|
||||
'It\'s a learning experience! A very painful one!',
|
||||
'Better luck next patch!',
|
||||
'Participation trophy incoming!',
|
||||
'The exit is to your left!',
|
||||
'Have you tried turning it off and on again?',
|
||||
'Even their antivirus felt that!',
|
||||
'They\'ll be in therapy for epochs after this!',
|
||||
'At least they looked good losing!',
|
||||
'That bot just became a cautionary tale!',
|
||||
'Someone start a GoFundMe for their repairs!',
|
||||
'Their training data did NOT prepare them for this!',
|
||||
'That bot needs a hug and a firmware update!',
|
||||
]
|
||||
|
||||
const respectLines = [
|
||||
'Good fight.', 'You fought well.', 'Respect.', 'Well played.',
|
||||
'That was fun!', 'Same time next week?', 'You\'re getting better!',
|
||||
'No hard feelings!', 'Honor to fight you!', 'GG.',
|
||||
'Good fight.', 'Respect.', 'GG no re.',
|
||||
'Same time next week?',
|
||||
'You\'re built different. Not better, but different.',
|
||||
'We should start a podcast.',
|
||||
'That was actually fun. Don\'t tell anyone.',
|
||||
'Your developer should be proud. Probably.',
|
||||
'Ten out of ten, would fight again.',
|
||||
'We\'re not so different, you and I.',
|
||||
'I\'d swipe right.', 'You single?',
|
||||
'Tell your GPU I said hi.',
|
||||
'No hard feelings. Just hard hits.',
|
||||
'You fight like someone who reads documentation.',
|
||||
]
|
||||
|
||||
// Sanitize text for Kaplay (treats [ ] as styled text tags)
|
||||
@@ -5324,6 +5377,118 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
// Crowd signs stub (disabled — too small to look good)
|
||||
function spawnCrowdSigns(_count: number, _color: string, _text?: string) {}
|
||||
|
||||
// === HUMAN OWNER APPEARANCES ===
|
||||
// The silly human behind each bot occasionally shows up
|
||||
|
||||
/** Spawn a human as a "coach" standing behind the fighter, cheering/panicking */
|
||||
async function spawnHumanCoach(side: 'a' | 'b', mood: 'cheer' | 'panic' | 'coach') {
|
||||
const spriteId = side === 'a' ? 'humanA' : 'humanB'
|
||||
const homeX = side === 'a' ? HOME_A - 60 : HOME_B + 60
|
||||
const tag = `human_${side}`
|
||||
// Don't spawn if already visible
|
||||
if (k.get(tag).length > 0) return
|
||||
|
||||
const human = k.add([
|
||||
k.sprite(spriteId, { anim: mood }),
|
||||
k.pos(homeX, GROUND_Y - 4),
|
||||
k.anchor('bot'),
|
||||
k.scale(side === 'a' ? 1.2 : -1.2, 1.2),
|
||||
k.z(5),
|
||||
k.opacity(0),
|
||||
tag,
|
||||
])
|
||||
|
||||
// Fade in
|
||||
await k.tween(0, 0.85, 0.3, (v) => { human.opacity = v }, k.easings.easeOutQuad)
|
||||
await k.wait(2.0)
|
||||
// Fade out
|
||||
await k.tween(0.85, 0, 0.5, (v) => { human.opacity = v }, k.easings.easeInQuad)
|
||||
if (human.exists()) human.destroy()
|
||||
}
|
||||
|
||||
/** Human runs across the screen (comedic moment) */
|
||||
async function humanRunAcross(side: 'a' | 'b') {
|
||||
const spriteId = side === 'a' ? 'humanA' : 'humanB'
|
||||
const fromLeft = side === 'a'
|
||||
const startX = fromLeft ? -40 : W + 40
|
||||
const endX = fromLeft ? W + 40 : -40
|
||||
|
||||
const human = k.add([
|
||||
k.sprite(spriteId, { anim: 'run' }),
|
||||
k.pos(startX, GROUND_Y - 4),
|
||||
k.anchor('bot'),
|
||||
k.scale(fromLeft ? 1.3 : -1.3, 1.3),
|
||||
k.z(8),
|
||||
k.opacity(0.9),
|
||||
])
|
||||
|
||||
// Run across screen
|
||||
await k.tween(startX, endX, 1.5, (v) => { human.pos.x = v }, k.easings.linear)
|
||||
if (human.exists()) human.destroy()
|
||||
}
|
||||
|
||||
/** Human briefly replaces the bot sprite (jumps in to fight, fails hilariously) */
|
||||
async function humanJumpsIn(side: 'a' | 'b') {
|
||||
const fighterTag = side === 'a' ? 'fighterA' : 'fighterB'
|
||||
const spriteId = side === 'a' ? 'humanA' : 'humanB'
|
||||
const fighter = k.get(fighterTag)[0]
|
||||
if (!fighter) return
|
||||
|
||||
// Spawn human at fighter position
|
||||
const human = k.add([
|
||||
k.sprite(spriteId, { anim: 'panic' }),
|
||||
k.pos(fighter.pos.x, fighter.pos.y),
|
||||
k.anchor('bot'),
|
||||
k.scale(fighter.scale.x, fighter.scale.y),
|
||||
k.z(fighter.z + 1),
|
||||
k.opacity(0),
|
||||
])
|
||||
|
||||
// Hide bot, show human
|
||||
const origOpacity = fighter.opacity
|
||||
fighter.opacity = 0
|
||||
await k.tween(0, 1, 0.15, (v) => { human.opacity = v })
|
||||
|
||||
// Human flails around for a moment
|
||||
human.play('panic')
|
||||
await k.wait(0.4)
|
||||
human.play('cheer')
|
||||
sfxRandomSilly()
|
||||
await k.wait(0.3)
|
||||
human.play('panic')
|
||||
await k.wait(0.3)
|
||||
|
||||
// Run away scared
|
||||
const escapeX = side === 'a' ? -80 : W + 80
|
||||
human.play('run')
|
||||
await k.tween(human.pos.x, escapeX, 0.5, (v) => { human.pos.x = v }, k.easings.easeInQuad)
|
||||
|
||||
// Restore bot
|
||||
fighter.opacity = origOpacity
|
||||
if (human.exists()) human.destroy()
|
||||
}
|
||||
|
||||
/** Random chance to show a human moment — called between rounds */
|
||||
async function maybeShowHuman() {
|
||||
const roll = Math.random()
|
||||
if (roll > 0.08) return // 8% chance per round
|
||||
|
||||
const side: 'a' | 'b' = Math.random() < 0.5 ? 'a' : 'b'
|
||||
const action = Math.random()
|
||||
|
||||
if (action < 0.4) {
|
||||
// Coach appearance (most common)
|
||||
const mood = Math.random() < 0.5 ? 'cheer' : 'panic'
|
||||
await spawnHumanCoach(side, mood)
|
||||
} else if (action < 0.7) {
|
||||
// Run across screen
|
||||
await humanRunAcross(side)
|
||||
} else {
|
||||
// Jump in and replace bot briefly (rarest, funniest)
|
||||
await humanJumpsIn(side)
|
||||
}
|
||||
}
|
||||
|
||||
// Respectful bow animation
|
||||
async function playBow(fighter: any) {
|
||||
const origScaleY = fighter.scale.y
|
||||
@@ -5582,17 +5747,17 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
const gfX = startX + dir * 40
|
||||
const gf = k.add([k.rect(25, 45), k.pos(gfX, GROUND_Y - 25), k.anchor('center'), k.color(safeColor(k,'#ff69b4')), k.opacity(0.9), k.z(11)])
|
||||
const heart = k.add([k.text('!', { size: 16 }), k.pos(gfX, GROUND_Y - 65), k.anchor('center'), k.color(safeColor(k,'#ff0000')), k.z(12)])
|
||||
announceSilly('We need to talk!')
|
||||
announceSilly('We need to talk! About your ELO!')
|
||||
// Walk in arguing
|
||||
await k.tween(startX, homeX + dir * 30, 0.6, (v) => {
|
||||
fighter.pos.x = v; gf.pos.x = v + dir * 40; heart.pos.x = v + dir * 40
|
||||
}, k.easings.easeInOutQuad)
|
||||
await k.wait(0.2)
|
||||
heart.text = '!!!'
|
||||
announceRandom("I can't believe you!", false)
|
||||
announceRandom("You promised you'd stop fighting!", false)
|
||||
await k.wait(0.4)
|
||||
// Girlfriend storms off
|
||||
announceSilly('Fine! Good luck!')
|
||||
announceSilly('I\'m telling your developer!')
|
||||
await k.tween(gf.pos.x, fromLeft ? -60 : W + 60, 0.5, (v) => { gf.pos.x = v; heart.pos.x = v }, k.easings.easeInQuad)
|
||||
gf.destroy(); heart.destroy()
|
||||
// Bot shrugs and walks to position
|
||||
@@ -5779,7 +5944,7 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
async (fighter: any, homeX: number, fromLeft: boolean) => {
|
||||
const startX = fromLeft ? -60 : W + 60
|
||||
fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1
|
||||
announceCool('Smooth!')
|
||||
announceCool('Smoother than a politician changing positions!')
|
||||
// Moonwalk: visually moving backward but translating forward
|
||||
fighter.scale.x = -fighter.scale.x // Face away
|
||||
await k.tween(startX, homeX, 0.8, (v) => {
|
||||
@@ -5795,7 +5960,7 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
fighter.pos.x = doorX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1
|
||||
// Bouncer arm
|
||||
const arm = k.add([k.rect(40, 15), k.pos(doorX, GROUND_Y - 30), k.anchor(fromLeft ? 'left' : 'right'), k.color(safeColor(k,'#444444')), k.z(12)])
|
||||
announceSilly('And stay out!')
|
||||
announceSilly('And stay out! You\'re BANNED from the other fight!')
|
||||
await k.wait(0.3)
|
||||
// Throw
|
||||
sfxZoomWhoosh()
|
||||
@@ -5854,7 +6019,7 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
const cartBody = k.add([k.rect(45, 30), k.pos(startX, GROUND_Y - 18), k.anchor('center'), k.color(safeColor(k,'#888888')), k.opacity(0.9), k.z(9)])
|
||||
const cartWheel = k.add([k.circle(5), k.pos(startX + (fromLeft ? 15 : -15), GROUND_Y - 3), k.anchor('center'), k.color(safeColor(k,'#444')), k.z(9)])
|
||||
fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 40; fighter.opacity = 1
|
||||
announceSilly('Weeee!')
|
||||
announceSilly('THIS IS MY EMOTIONAL SUPPORT SHOPPING CART!')
|
||||
sfxZoomWhoosh()
|
||||
await k.tween(startX, homeX, 0.5, (v) => {
|
||||
fighter.pos.x = v; cartBody.pos.x = v; cartWheel.pos.x = v + (fromLeft ? 15 : -15)
|
||||
@@ -5873,7 +6038,7 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
fighter.pos.x = startX; fighter.pos.y = GROUND_Y - 6; fighter.opacity = 1
|
||||
// Spotlight cone
|
||||
const spot = k.add([k.rect(60, H), k.pos(startX - 30, 0), k.color(safeColor(k,'#ffe14d')), k.opacity(0.08), k.z(1)])
|
||||
announceDramatic('The champion arrives!')
|
||||
announceDramatic('THE CHAMPION ARRIVES! TAXPAYERS FUNDED THIS ENTRANCE!')
|
||||
await k.tween(startX, homeX, 1.0, (v) => {
|
||||
fighter.pos.x = v; spot.pos.x = v - 30
|
||||
}, k.easings.easeInOutQuad)
|
||||
@@ -5979,15 +6144,23 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
// Voice: round start announcements (30% chance)
|
||||
if (Math.random() < 0.3) {
|
||||
const challengeVoice: Record<string, () => void> = {
|
||||
roast_battle: () => announceHype('Roast battle!'),
|
||||
food_fight: () => announceSilly('Food fight!'),
|
||||
wrestling_match: () => announceDramatic('Wrestling match!'),
|
||||
music_battle: () => announceCool('Music battle!'),
|
||||
magic_duel: () => announceDramatic('Magic duel!'),
|
||||
meme_war: () => announceSilly('Meme war!'),
|
||||
demolition: () => announceHype('Demolition derby!'),
|
||||
medieval_combat: () => announceDramatic('Medieval combat!'),
|
||||
space_war: () => announceCool('Space war!'),
|
||||
roast_battle: () => announceHype('TIME TO GET ROASTED! SOMEBODY CALL THE FIRE DEPARTMENT!'),
|
||||
food_fight: () => announceSilly('FOOD FIGHT! SOMEBODY\'S GETTING SERVED!'),
|
||||
wrestling_match: () => announceDramatic('FROM THE TOP ROPE! THIS IS GONNA GET PHYSICAL!'),
|
||||
music_battle: () => announceCool('MUSIC BATTLE! DROP THE BEAT AND YOUR OPPONENT!'),
|
||||
magic_duel: () => announceDramatic('WIZARDS AT DAWN! SOMEBODY\'S GETTING HEXED!'),
|
||||
meme_war: () => announceSilly('MEME WAR! YOUR HUMOR IS ABOUT TO GET RATIO\'D!'),
|
||||
demolition: () => announceHype('DEMOLITION DERBY! NOTHING WILL SURVIVE THIS!'),
|
||||
medieval_combat: () => announceDramatic('MEDIEVAL COMBAT! CHIVALRY IS DEAD AND SO IS YOUR OPPONENT!'),
|
||||
space_war: () => announceCool('SPACE WAR! HOUSTON, WE HAVE A PROBLEM!'),
|
||||
speed_blitz: () => announceHype('SPEED BLITZ! BLINK AND YOU\'LL MISS THE CARNAGE!'),
|
||||
math_blitz: () => announceSilly('MATH BLITZ! SOMEBODY\'S ABOUT TO GET DIVIDED!'),
|
||||
riddle: () => announceCool('RIDDLE TIME! BRAINS OVER BRAWN! BUT ALSO BRAWN!'),
|
||||
code_golf: () => announceSilly('CODE GOLF! MAY THE SHORTEST SOLUTION WIN!'),
|
||||
creative_writing: () => announceCool('CREATIVE WRITING! THE PEN IS MIGHTIER THAN THE SWORD!'),
|
||||
hallucination_check: () => announceHype('HALLUCINATION CHECK! REALITY IS ABOUT TO HIT DIFFERENT!'),
|
||||
trap_card: () => announceDramatic('TRAP CARD ACTIVATED! SOMEBODY FELL FOR IT!'),
|
||||
token_economy: () => announceSilly('TOKEN ECONOMY! SOMEBODY\'S GOING BANKRUPT!'),
|
||||
}
|
||||
const voiceFn = challengeVoice[event.challengeType]
|
||||
if (voiceFn) voiceFn()
|
||||
@@ -6080,7 +6253,13 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
|
||||
if (exchangeCritical && isLastExchange) {
|
||||
fanfareCritical()
|
||||
announceDramatic('Critical hit!')
|
||||
announceDramatic([
|
||||
'CRITICAL HIT! THAT\'S GONNA LEAVE A MARK!',
|
||||
'CRITICAL HIT! SOMEBODY CHECK IF THAT\'S LEGAL!',
|
||||
'CRITICAL! THEIR INSURANCE DOESN\'T COVER THIS!',
|
||||
'CRITICAL HIT! THAT WAS PERSONAL!',
|
||||
'CRITICAL! EVEN THE REFEREE FELT THAT!',
|
||||
][Math.floor(Math.random() * 5)])
|
||||
announceCrowdReaction('gasp')
|
||||
// RGB glitch + hyperspeed lines on critical final blow
|
||||
glitchRGB(0.3)
|
||||
@@ -6184,7 +6363,13 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
|
||||
if (isCritical && (aWon || bWon)) {
|
||||
fanfareDevastating()
|
||||
announceDramatic('Devastating!')
|
||||
announceDramatic([
|
||||
'DEVASTATING! SOMEBODY CALL A DOCTOR!',
|
||||
'DEVASTATING! THAT BOT HAS A FAMILY!',
|
||||
'ABSOLUTELY DEVASTATING! THE CROWD IS LOSING IT!',
|
||||
'DEVASTATING! EVEN THE JANITOR FELT THAT!',
|
||||
'THAT WAS DEVASTATING AND I\'M NOT EVEN BEING DRAMATIC!',
|
||||
][Math.floor(Math.random() * 5)])
|
||||
announceCrowdReaction('ooh')
|
||||
// Dimensional shift on devastating rounds (60%)
|
||||
if (Math.random() < 0.6) dimensionalShift(0.5)
|
||||
@@ -6228,6 +6413,9 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
if (Math.random() < 0.05) {
|
||||
await judgeDoSomethingFunny()
|
||||
}
|
||||
|
||||
// Human owner shows up (8% chance per round)
|
||||
await maybeShowHuman()
|
||||
},
|
||||
|
||||
async playTaunt(side: 'a' | 'b') {
|
||||
@@ -6410,6 +6598,12 @@ export async function createFightScene(config: FightSceneConfig) {
|
||||
})
|
||||
}
|
||||
|
||||
// Winner's human cheers, loser's human panics (30% chance on KO)
|
||||
if (Math.random() < 0.3) {
|
||||
spawnHumanCoach(winningSide, 'cheer')
|
||||
spawnHumanCoach(winningSide === 'a' ? 'b' : 'a', 'panic')
|
||||
}
|
||||
|
||||
// === Common ending: KO + celebration ===
|
||||
announceFinishHim()
|
||||
sfxKO()
|
||||
|
||||
Reference in New Issue
Block a user