import { useTranslation } from 'react-i18next'
import type { CombatState, CombatLogEntry, Profile, Equipment, PlayerState } from './types'
import { getTranslatedText } from '../../utils/i18nUtils'
interface CombatViewProps {
combatState: CombatState
combatLog: CombatLogEntry[]
profile: Profile | null
playerState: PlayerState | null
equipment: Equipment
enemyName: string
enemyImage: string
enemyTurnMessage: string
pvpTimeRemaining: number | null
turnTimeRemaining: number | null
onCombatAction: (action: string) => void
onFlee: () => void
onPvPAction: (action: string) => void
onExitCombat: () => void
onExitPvPCombat: () => void
flashEnemy?: boolean
buttonsDisabled?: boolean
floatingTexts?: { id: number, text: string, x: number, y: number, type: 'damage-player' | 'damage-enemy' | 'damage-player-dealt' | 'heal' }[]
}
function CombatView({
combatState,
combatLog,
profile: _profile,
playerState,
enemyName,
enemyImage,
enemyTurnMessage,
pvpTimeRemaining,
turnTimeRemaining,
onCombatAction,
onPvPAction,
onExitCombat,
onExitPvPCombat,
flashEnemy,
buttonsDisabled,
floatingTexts = []
}: CombatViewProps) {
const { t } = useTranslation()
const displayEnemyName = typeof enemyName === 'object' ? getTranslatedText(enemyName) : (enemyName || 'Enemy')
// Render structured combat messages
const renderCombatMessage = (msg: any) => {
// Support both old string format and new structured format
if (typeof msg === 'string') {
return msg // Legacy format
}
if (!msg || !msg.type) {
return String(msg)
}
const { type, data } = msg
switch (type) {
case 'combat_start':
return t('combat.messages.combat_start', { enemy: getTranslatedText(data.npc_name) })
case 'player_attack':
return t('combat.messages.player_attack', { damage: data.damage })
case 'enemy_attack':
return t('combat.messages.enemy_attack', {
enemy: getTranslatedText(data.npc_name),
damage: data.damage
})
case 'victory':
return t('combat.messages.victory', { enemy: getTranslatedText(data.npc_name) })
case 'flee_fail':
return t('combat.messages.flee_fail', {
enemy: getTranslatedText(data.npc_name),
damage: data.damage
})
default:
return JSON.stringify(msg)
}
}
return (
{combatState.is_pvp ? `⚔️ ${t('combat.title')} - PvP` : `⚔️ ${t('combat.title')} - ${displayEnemyName}`}
{combatState.is_pvp ? (
/* PvP Combat UI - Unified Layout */
{/* Opponent Display (using same structure as PvE Enemy) */}
{floatingTexts.map(ft => (
{ft.text}
))}
{(() => {
if (!combatState.pvp_combat) return null
const opponent = combatState.pvp_combat.is_attacker ?
combatState.pvp_combat.defender :
combatState.pvp_combat.attacker
if (!opponent) return
❓
// Use a default avatar if no image, or maybe the class image if available?
// For now, let's use a placeholder or try to get it from profile if passed?
// The opponent object has: username, level, hp, max_hp.
// It might not have an image url.
return (
👤
{opponent.username} (Lv. {opponent.level})
)
})()}
{/* Opponent HP Bar */}
{(() => {
if (!combatState.pvp_combat) return null
const opponent = combatState.pvp_combat.is_attacker ?
combatState.pvp_combat.defender :
combatState.pvp_combat.attacker
if (!opponent) return null
return (
{opponent.username}: {opponent.hp} / {opponent.max_hp}
)
})()}
{/* Player HP Bar */}
{(() => {
if (!combatState.pvp_combat) return null
const you = combatState.pvp_combat.is_attacker ?
combatState.pvp_combat.attacker :
combatState.pvp_combat.defender
if (!you) return null
return (
You: {you.hp} / {you.max_hp}
)
})()}
{combatState.pvp_combat.combat_over ? (
{combatState.pvp_combat.attacker_fled || combatState.pvp_combat.defender_fled ? "🏃 Combat Ended" : "💀 Combat Over"}
) : combatState.pvp_combat.your_turn ? (
✅ Your Turn ({pvpTimeRemaining ?? combatState.pvp_combat.time_remaining}s)
) : (
⏳ Opponent's Turn ({pvpTimeRemaining ?? combatState.pvp_combat.time_remaining}s)
)}
{!combatState.pvp_combat.combat_over ? (
<>
onPvPAction('attack')}
disabled={!combatState.pvp_combat.your_turn || buttonsDisabled}
>
{t('game.attack')}
onPvPAction('flee')}
disabled={!combatState.pvp_combat.your_turn || buttonsDisabled}
>
{t('game.flee')}
>
) : (
{combatState.pvp_combat.attacker_fled || combatState.pvp_combat.defender_fled ? '✅ Continue' : '💀 Return'}
)}
{/* Combat Log */}
{t('combat.combatLog')}
{combatLog.length > 0 ? (
combatLog.map((entry: any) => (
[{entry.time}]
{renderCombatMessage(entry.message)}
))
) : (
PvP Combat started...
)}
) : (
/* PvE Combat UI */
<>
{/* Intent Bubble - Moved here to avoid overflow:hidden clipping */}
{combatState.combat?.npc_intent && !combatState.combat_over && (
{combatState.combat.npc_intent === 'attack' ? '⚔️' :
combatState.combat.npc_intent === 'defend' ? '🛡️' :
combatState.combat.npc_intent === 'special' ? '🔥' : '❓'}
{combatState.combat.npc_intent}
)}
{floatingTexts.map(ft => (
{ft.text}
))}
{t('combat.enemyHp')}: {combatState.combat?.npc_hp || 0} / {combatState.combat?.npc_max_hp || 100}
{playerState && (
{t('combat.playerHp')}: {playerState.health} / {playerState.max_health}
)}
{!combatState.combat_over ? (
enemyTurnMessage ? (
🗡️ Enemy's turn...
) : combatState.combat?.turn === 'player' ? (
<>
✅ {t('combat.yourTurn')}
{turnTimeRemaining !== null && (
⏱️ {Math.floor(turnTimeRemaining / 60)}:{String(Math.floor(turnTimeRemaining % 60)).padStart(2, '0')}
)}
>
) : (
⚠️ {t('combat.enemyTurn')}
)
) : (
{combatState.player_won ? `✅ ${t('combat.victory')}` : combatState.player_fled ? `🏃 ${t('combat.fleeSuccess')}` : `💀 ${t('combat.defeat')}`}
)}
{/* PvE Combat Actions */}
{!combatState.combat_over ? (
<>
onCombatAction('attack')}
disabled={combatState.combat?.turn !== 'player' || !!enemyTurnMessage || buttonsDisabled}
>
{t('game.attack')}
onCombatAction('flee')}
disabled={combatState.combat?.turn !== 'player' || !!enemyTurnMessage || buttonsDisabled}
>
{t('game.flee')}
>
) : (
{combatState.player_won ? '✅ Continue' : combatState.player_fled ? '✅ Continue' : '💀 Return'}
)}
{/* Combat Log */}
{t('combat.combatLog')}
{combatLog.length > 0 ? (
combatLog.map((entry: any) => (
[{entry.time}]
{renderCombatMessage(entry.message)}
))
) : (
Combat started...
)}
>
)}
)
}
export default CombatView