diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts
index a41d5fa..024ac69 100644
--- a/frontend/src/api/types.ts
+++ b/frontend/src/api/types.ts
@@ -7,6 +7,7 @@ export interface User {
export interface Character {
id: string;
+ userId: string;
name: string;
level: number;
xp: number;
@@ -18,21 +19,23 @@ export interface Character {
vitalite: number;
hpCurrent: number;
hpMax: number;
- endurance: number;
- enduranceCurrent: number; // calculé à la lecture (backend field)
+ enduranceSaved: number;
+ lastEnduranceTs: string;
enduranceMax: number;
+ enduranceCurrent: number;
statPoints: number;
- xpToNextLevel: number;
activeTitle: string | null;
totalGoldEarned: number;
+ xpToNextLevel: number;
createdAt: string;
+ updatedAt: string;
}
export interface Monster {
id: string;
name: string;
- levelMin: number;
- levelMax: number;
+ minLevel: number;
+ maxLevel: number;
hp: number;
attack: number;
defense: number;
@@ -40,37 +43,57 @@ export interface Monster {
xpReward: number;
goldMin: number;
goldMax: number;
+ dropMaterialId: string | null;
}
export interface CombatRound {
round: number;
- playerDamage: number;
- playerCrit: boolean;
- monsterDodged: boolean;
- monsterDamage: number;
- playerDodged: boolean;
+ playerAttack: { damage: number; isCrit: boolean; isDodged: boolean; log: string };
+ monsterAttack: { damage: number; isCrit: boolean; isDodged: boolean; log: string };
playerHp: number;
monsterHp: number;
log: string[];
}
+export interface CombatRewards {
+ xp: number;
+ gold: number;
+ goldLost: number;
+ levelUp: boolean;
+ newLevel: number;
+ statPointsGained: number;
+ loot: { name: string; quantity: number } | null;
+}
+
+export interface CombatCharacterState {
+ level: number;
+ xp: number;
+ xpToNextLevel: number;
+ gold: number;
+ hpCurrent: number;
+ hpMax: number;
+ enduranceCurrent: number;
+ enduranceMax: number;
+ statPoints: number;
+}
+
export interface CombatResult {
winner: 'player' | 'monster';
rounds: CombatRound[];
- xpGained?: number;
- goldGained?: number;
- enduranceCost: number;
- loot?: { material: Material; quantity: number } | null;
+ summary: string;
+ rewards: CombatRewards;
+ character: CombatCharacterState;
}
export interface CombatLog {
id: string;
- monsterId: string;
- monsterName?: string;
winner: 'player' | 'monster';
- xpGained: number;
- goldGained: number;
+ totalRounds: number;
+ xpEarned: number;
+ goldEarned: number;
+ levelUp: boolean;
createdAt: string;
+ monster: { id: string; name: string; minLevel: number; maxLevel: number };
}
export type Rarity = 'common' | 'rare' | 'epic' | 'legendary';
diff --git a/frontend/src/components/HudBar.tsx b/frontend/src/components/HudBar.tsx
index 93011ce..ab4f9ff 100644
--- a/frontend/src/components/HudBar.tsx
+++ b/frontend/src/components/HudBar.tsx
@@ -46,8 +46,8 @@ export function HudBar() {
if (!char) return null;
- const endurance = (char as any).enduranceCurrent ?? (char as any).endurance ?? 0;
- const xpNext = (char as any).xpToNextLevel ?? Math.round(100 * Math.pow(char.level, 1.5));
+ const endurance = char.enduranceCurrent;
+ const xpNext = char.xpToNextLevel;
const questCount = activeQuests?.filter((pq: any) => pq.status === 'active').length ?? 0;
const questReady = activeQuests?.filter((pq: any) => pq.status === 'completed').length ?? 0;
@@ -88,11 +88,11 @@ export function HudBar() {
{endurance}/{char.enduranceMax}
- {(char as any).lastEnduranceTs && (
+ {char.lastEnduranceTs && (
)}
diff --git a/frontend/src/pages/CombatPage.tsx b/frontend/src/pages/CombatPage.tsx
index 8f149d9..478909b 100644
--- a/frontend/src/pages/CombatPage.tsx
+++ b/frontend/src/pages/CombatPage.tsx
@@ -1,7 +1,7 @@
import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { combatApi, characterApi } from '../api/endpoints';
-import type { Monster, CombatResult } from '../api/types';
+import type { Monster, CombatResult, CombatLog } from '../api/types';
import { Swords, Trophy, Skull, Clock, Zap } from 'lucide-react';
const COMBAT_COST = 5;
@@ -12,16 +12,21 @@ const ATTACK_TYPES = [
{ id: 'magic', label: 'Magie', emoji: '✨', stat: 'Intelligence × 1.5' },
];
-function MonsterCard({ m, selected, onSelect }: { m: Monster; selected: boolean; onSelect: () => void }) {
+function MonsterCard({ m, selected, onSelect, playerLevel }: { m: Monster; selected: boolean; onSelect: () => void; playerLevel: number }) {
+ const tooHard = m.minLevel > playerLevel + 2;
+ const tooEasy = m.maxLevel < playerLevel - 3;
+
return (
{m.name}
- Niv. {(m as any).minLevel ?? m.levelMin}–{(m as any).maxLevel ?? m.levelMax}
+
+ Niv. {m.minLevel}–{m.maxLevel}
+
❤️ {m.hp}
@@ -30,49 +35,49 @@ function MonsterCard({ m, selected, onSelect }: { m: Monster; selected: boolean;
⭐ {m.xpReward} XP
💰 {m.goldMin}–{m.goldMax}
+ {tooHard &&
Niveau trop élevé
}
);
}
-function CombatLog({ result }: { result: CombatResult }) {
+function CombatLogView({ result }: { result: CombatResult }) {
const won = result.winner === 'player';
return (
- {/* Résultat */}
{won
?
- Victoire ! +{(result as any).rewards?.xp ?? result.xpGained} XP +{(result as any).rewards?.gold ?? result.goldGained} or
+ Victoire ! +{result.rewards.xp} XP +{result.rewards.gold} or
:
- Défaite… −50 endurance
+ Défaite… Retour à l'auberge
}
- {((result as any).rewards?.loot ?? result.loot) && (
+ {result.rewards.loot && (
- 🎁 Loot obtenu !
+ 🎁 Loot : {result.rewards.loot.name} ×{result.rewards.loot.quantity}
)}
- {(result as any).rewards?.levelUp && (
+ {result.rewards.levelUp && (
- 🎉 LEVEL UP ! Niveau {(result as any).rewards.newLevel} — +{(result as any).rewards.statPointsGained} points de stats
+ 🎉 LEVEL UP ! Niveau {result.rewards.newLevel} — +{result.rewards.statPointsGained} points de stats
)}
- {/* Log de combat */}
Log — {result.rounds.length} tour{result.rounds.length > 1 ? 's' : ''}
{result.rounds.flatMap(r =>
r.log.map((line, i) => {
- const cls = line.includes('frappe') && !line.includes('Monstre') ? 'log-player'
- : line.includes('Monstre') || line.includes('frappe') ? 'log-monster'
- : line.includes('CRITIQUE') ? 'log-crit'
- : 'log-system';
+ const cls = line.includes('CRITIQUE') ? 'log-crit'
+ : line.includes('esquive') ? 'log-crit'
+ : line.includes('HP') ? 'log-system'
+ : i === 0 ? 'log-player'
+ : 'log-monster';
return
[T{r.round}] {line}
;
})
)}
@@ -85,6 +90,19 @@ function CombatLog({ result }: { result: CombatResult }) {
);
}
+function HistoryEntry({ h }: { h: CombatLog }) {
+ return (
+
+
+ {h.winner === 'player' ? '✓' : '✗'} {h.monster.name}
+
+
+ {h.winner === 'player' ? `+${h.xpEarned}xp +${h.goldEarned}or` : `${h.totalRounds} tours`}
+
+
+ );
+}
+
export function CombatPage() {
const qc = useQueryClient();
const [selectedMonster, setSelectedMonster] = useState
(null);
@@ -92,7 +110,8 @@ export function CombatPage() {
const [lastResult, setLastResult] = useState(null);
const { data: char } = useQuery({ queryKey: ['character'], queryFn: characterApi.me });
- const endurance = (char as any)?.enduranceCurrent ?? (char as any)?.endurance ?? 0;
+ const endurance = char?.enduranceCurrent ?? 0;
+ const playerLevel = char?.level ?? 1;
const canFight = endurance >= COMBAT_COST;
const { data: monsters, isLoading } = useQuery({
@@ -111,11 +130,20 @@ export function CombatPage() {
setLastResult(result);
qc.invalidateQueries({ queryKey: ['character'] });
qc.invalidateQueries({ queryKey: ['combatHistory'] });
+ qc.invalidateQueries({ queryKey: ['questsActive'] });
},
});
if (isLoading) return Chargement des monstres…
;
+ // Trier : monstres appropriés en haut, trop forts en bas
+ const sorted = [...(monsters ?? [])].sort((a, b) => {
+ const aOk = a.minLevel <= playerLevel + 2 ? 0 : 1;
+ const bOk = b.minLevel <= playerLevel + 2 ? 0 : 1;
+ if (aOk !== bOk) return aOk - bOk;
+ return a.minLevel - b.minLevel;
+ });
+
return (
⚔️ Combat
@@ -127,12 +155,13 @@ export function CombatPage() {
Adversaire
- {monsters?.map(m => (
+ {sorted.map(m => (
setSelectedMonster(m)}
+ playerLevel={playerLevel}
/>
))}
@@ -150,7 +179,7 @@ export function CombatPage() {
key={a.id}
className={`card card-hover ${attackType === a.id ? 'card-gold' : ''}`}
onClick={() => setAttackType(a.id)}
- style={{ display: 'flex', alignItems: 'center', gap: 10 }}
+ style={{ display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer' }}
>
{a.emoji}
@@ -164,7 +193,7 @@ export function CombatPage() {
{/* Coût endurance */}
Coût : {COMBAT_COST} endurance — Disponible : {endurance}
- {canFight && ({Math.floor(endurance / COMBAT_COST)} combats possibles)}
+ {canFight && ({Math.floor(endurance / COMBAT_COST)} combats)}
{/* Bouton combattre */}
@@ -192,14 +221,7 @@ export function CombatPage() {
Historique récent
- {history.slice(0, 5).map(h => (
-
-
- {h.winner === 'player' ? '✓' : '✗'} {(h as any).monster?.name ?? h.monsterName ?? 'Monstre'}
-
- +{(h as any).xpEarned ?? h.xpGained}xp +{(h as any).goldEarned ?? h.goldGained}or
-
- ))}
+ {history.slice(0, 5).map(h =>
)}
)}
@@ -207,7 +229,7 @@ export function CombatPage() {
{/* Résultat du dernier combat */}
- {lastResult && }
+ {lastResult && }
);
}
diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx
index 3c25195..a44a51a 100644
--- a/frontend/src/pages/DashboardPage.tsx
+++ b/frontend/src/pages/DashboardPage.tsx
@@ -150,10 +150,10 @@ export function DashboardPage() {
if (isLoading) return
Chargement…
;
if (isError || !char) return
;
- const xpNext = (char as any).xpToNextLevel ?? Math.round(100 * Math.pow(char.level, 1.5));
- const statPoints = (char as any).statPoints ?? 0;
+ const xpNext = char.xpToNextLevel;
+ const statPoints = char.statPoints ?? 0;
const needsHeal = char.hpCurrent < char.hpMax;
- const endurance = (char as any).enduranceCurrent ?? (char as any).endurance ?? 0;
+ const endurance = char.enduranceCurrent;
const REST_COST = 10;
const COMBAT_COST = 5;
const FORGE_COST = 10;
@@ -202,9 +202,9 @@ export function DashboardPage() {
Endurance
-
{char.enduranceCurrent ?? char.endurance} / {char.enduranceMax}
+
{endurance} / {char.enduranceMax}
-
+
diff --git a/frontend/src/pages/ForgePage.tsx b/frontend/src/pages/ForgePage.tsx
index c8655a6..afa06c5 100644
--- a/frontend/src/pages/ForgePage.tsx
+++ b/frontend/src/pages/ForgePage.tsx
@@ -63,7 +63,7 @@ export function ForgePage() {
const [lastResult, setLastResult] = useState<{ success: boolean; newLevel: number } | null>(null);
const { data: char } = useQuery({ queryKey: ['character'], queryFn: characterApi.me });
- const endurance = (char as any)?.enduranceCurrent ?? (char as any)?.endurance ?? 0;
+ const endurance = char?.enduranceCurrent ?? 0;
const gold = char?.gold ?? 0;
const { data: inventory, isLoading } = useQuery({