fix: endurance regen 6min→3min dans combat/forge/craft + potions d'énergie
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 32s
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 32s
Bug: combat/forge/craft calculaient la regen à 1pt/6min (ancien) alors que character.service utilisait 1pt/3min (nouveau). Le joueur voyait 8 endurance dans le HUD mais le backend refusait le combat avec 4. Potions d'énergie: Potion (30 endurance, 20 or) + Grande (60 endurance, 45 or). Consommable instantané via la boutique — le joueur peut acheter du temps de jeu.
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { characterApi } from '../api/endpoints';
|
import { characterApi } from '../api/endpoints';
|
||||||
import { api } from '../api/client';
|
import { api } from '../api/client';
|
||||||
import { Coins, ShoppingBag, Sword, Shield, Heart } from 'lucide-react';
|
import { Coins, ShoppingBag, Sword, Shield, Heart, Zap } from 'lucide-react';
|
||||||
|
|
||||||
const RARITY_COLORS: Record<string, string> = {
|
const RARITY_COLORS: Record<string, string> = {
|
||||||
common: '#9ca3af',
|
common: '#9ca3af',
|
||||||
@@ -58,7 +58,10 @@ function ShopItemCard({ item, onBuy, buying }: { item: ShopItem; onBuy: () => vo
|
|||||||
<div style={{ display: 'flex', gap: 12, fontSize: 11, color: '#6b7a99' }}>
|
<div style={{ display: 'flex', gap: 12, fontSize: 11, color: '#6b7a99' }}>
|
||||||
{item.attackBonus > 0 && <span style={{ display: 'flex', alignItems: 'center', gap: 3 }}><Sword size={10} color="#f4c94e" /> +{item.attackBonus} ATK</span>}
|
{item.attackBonus > 0 && <span style={{ display: 'flex', alignItems: 'center', gap: 3 }}><Sword size={10} color="#f4c94e" /> +{item.attackBonus} ATK</span>}
|
||||||
{item.defenseBonus > 0 && <span style={{ display: 'flex', alignItems: 'center', gap: 3 }}><Shield size={10} color="#5ba4f5" /> +{item.defenseBonus} DEF</span>}
|
{item.defenseBonus > 0 && <span style={{ display: 'flex', alignItems: 'center', gap: 3 }}><Shield size={10} color="#5ba4f5" /> +{item.defenseBonus} DEF</span>}
|
||||||
{item.type === 'consumable' && <span style={{ display: 'flex', alignItems: 'center', gap: 3 }}><Heart size={10} color="#e84040" /> +50% PV</span>}
|
{item.type === 'consumable' && (item as any).forceBonus > 0
|
||||||
|
? <span style={{ display: 'flex', alignItems: 'center', gap: 3 }}><Zap size={10} color="#5ba4f5" /> +{(item as any).forceBonus} endurance</span>
|
||||||
|
: item.type === 'consumable' && <span style={{ display: 'flex', alignItems: 'center', gap: 3 }}><Heart size={10} color="#e84040" /> +50% PV</span>
|
||||||
|
}
|
||||||
{item.minLevel > 1 && <span>Niv. {item.minLevel}+</span>}
|
{item.minLevel > 1 && <span>Niv. {item.minLevel}+</span>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -131,8 +134,10 @@ export function ShopPage() {
|
|||||||
|
|
||||||
{buyMut.isSuccess && (
|
{buyMut.isSuccess && (
|
||||||
<div className="card card-gold" style={{ marginBottom: '1rem', padding: '0.5rem 1rem', fontSize: 13, textAlign: 'center' }}>
|
<div className="card card-gold" style={{ marginBottom: '1rem', padding: '0.5rem 1rem', fontSize: 13, textAlign: 'center' }}>
|
||||||
{(buyMut.data as any)?.type === 'consumable'
|
{(buyMut.data as any)?.effectType === 'endurance'
|
||||||
? `🧪 ${(buyMut.data as any)?.item} utilisé ! +${(buyMut.data as any)?.effect?.healed} PV`
|
? `⚡ ${(buyMut.data as any)?.item} — +${(buyMut.data as any)?.effect?.restored} endurance`
|
||||||
|
: (buyMut.data as any)?.effectType === 'hp'
|
||||||
|
? `🧪 ${(buyMut.data as any)?.item} — +${(buyMut.data as any)?.effect?.healed} PV`
|
||||||
: `✅ ${(buyMut.data as any)?.item} acheté ! (-${(buyMut.data as any)?.goldSpent} or)`
|
: `✅ ${(buyMut.data as any)?.item} acheté ! (-${(buyMut.data as any)?.goldSpent} or)`
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export class CombatService {
|
|||||||
|
|
||||||
// Calculer l'endurance actuelle (lazy pattern)
|
// Calculer l'endurance actuelle (lazy pattern)
|
||||||
const elapsedMinutes = (Date.now() - character.lastEnduranceTs.getTime()) / 60_000;
|
const elapsedMinutes = (Date.now() - character.lastEnduranceTs.getTime()) / 60_000;
|
||||||
const recharge = Math.floor(elapsedMinutes / 6);
|
const recharge = Math.floor(elapsedMinutes / 3);
|
||||||
const enduranceCurrent = Math.min(character.enduranceSaved + recharge, character.enduranceMax);
|
const enduranceCurrent = Math.min(character.enduranceSaved + recharge, character.enduranceMax);
|
||||||
|
|
||||||
if (enduranceCurrent < COMBAT_ENDURANCE_COST) {
|
if (enduranceCurrent < COMBAT_ENDURANCE_COST) {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export class CraftService {
|
|||||||
|
|
||||||
// Calculer endurance actuelle (lazy pattern)
|
// Calculer endurance actuelle (lazy pattern)
|
||||||
const elapsedMinutes = (Date.now() - char.lastEnduranceTs.getTime()) / 60_000;
|
const elapsedMinutes = (Date.now() - char.lastEnduranceTs.getTime()) / 60_000;
|
||||||
const recharge = Math.floor(elapsedMinutes / 6);
|
const recharge = Math.floor(elapsedMinutes / 3);
|
||||||
const enduranceCurrent = Math.min(char.enduranceSaved + recharge, char.enduranceMax);
|
const enduranceCurrent = Math.min(char.enduranceSaved + recharge, char.enduranceMax);
|
||||||
|
|
||||||
if (enduranceCurrent < recipe.enduranceCost) {
|
if (enduranceCurrent < recipe.enduranceCost) {
|
||||||
|
|||||||
@@ -47,8 +47,11 @@ const ITEMS = [
|
|||||||
{ name: 'Armure du Pharaon', type: 'armor', rarity: 'epic', attackBonus: 0, defenseBonus: 18, buyPrice: 1300, minLevel: 13, zone: 'desert', description: 'Dorée et ancienne, elle irradie de puissance.' },
|
{ name: 'Armure du Pharaon', type: 'armor', rarity: 'epic', attackBonus: 0, defenseBonus: 18, buyPrice: 1300, minLevel: 13, zone: 'desert', description: 'Dorée et ancienne, elle irradie de puissance.' },
|
||||||
|
|
||||||
// Potions — consommables
|
// Potions — consommables
|
||||||
{ name: 'Potion de soin', type: 'consumable', rarity: 'common', attackBonus: 0, defenseBonus: 0, buyPrice: 15, minLevel: 1, zone: null, description: 'Restaure 50% des PV.' },
|
// HP potions: forceBonus = 0 → heal 50% HP
|
||||||
{ name: 'Grande potion de soin', type: 'consumable', rarity: 'rare', attackBonus: 0, defenseBonus: 0, buyPrice: 40, minLevel: 5, zone: null, description: 'Restaure 50% des PV. (même effet, plus cher — placeholder pour futur)' },
|
{ name: 'Potion de soin', type: 'consumable', rarity: 'common', attackBonus: 0, defenseBonus: 0, forceBonus: 0, buyPrice: 15, minLevel: 1, zone: null, description: 'Restaure 50% des PV.' },
|
||||||
|
// Endurance potions: forceBonus > 0 → restore N endurance
|
||||||
|
{ name: 'Potion d\'énergie', type: 'consumable', rarity: 'common', attackBonus: 0, defenseBonus: 0, forceBonus: 30, buyPrice: 20, minLevel: 1, zone: null, description: 'Restaure 30 points d\'endurance.' },
|
||||||
|
{ name: 'Grande potion d\'énergie', type: 'consumable', rarity: 'rare', attackBonus: 0, defenseBonus: 0, forceBonus: 60, buyPrice: 45, minLevel: 3, zone: null, description: 'Restaure 60 points d\'endurance.' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function seedZones(dataSource: DataSource) {
|
export async function seedZones(dataSource: DataSource) {
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export class ForgeService {
|
|||||||
|
|
||||||
// Vérifier endurance
|
// Vérifier endurance
|
||||||
const elapsedMinutes = (Date.now() - char.lastEnduranceTs.getTime()) / 60_000;
|
const elapsedMinutes = (Date.now() - char.lastEnduranceTs.getTime()) / 60_000;
|
||||||
const recharge = Math.floor(elapsedMinutes / 6);
|
const recharge = Math.floor(elapsedMinutes / 3);
|
||||||
const enduranceCurrent = Math.min(char.enduranceSaved + recharge, char.enduranceMax);
|
const enduranceCurrent = Math.min(char.enduranceSaved + recharge, char.enduranceMax);
|
||||||
|
|
||||||
if (enduranceCurrent < FORGE_ENDURANCE_COST) {
|
if (enduranceCurrent < FORGE_ENDURANCE_COST) {
|
||||||
|
|||||||
@@ -62,12 +62,42 @@ export class ShopService {
|
|||||||
|
|
||||||
// Consumable = effet immédiat, pas d'inventaire
|
// Consumable = effet immédiat, pas d'inventaire
|
||||||
if (item.type === 'consumable') {
|
if (item.type === 'consumable') {
|
||||||
char.gold -= item.buyPrice;
|
// Endurance potion: forceBonus > 0 = endurance restore amount
|
||||||
|
const isEndurancePotion = item.forceBonus > 0;
|
||||||
|
|
||||||
|
if (isEndurancePotion) {
|
||||||
|
// Calc current endurance (lazy)
|
||||||
|
const elapsed = (Date.now() - char.lastEnduranceTs.getTime()) / 60_000;
|
||||||
|
const recharge = Math.floor(elapsed / 3);
|
||||||
|
const currentEndurance = Math.min(char.enduranceSaved + recharge, char.enduranceMax);
|
||||||
|
|
||||||
|
if (currentEndurance >= char.enduranceMax) {
|
||||||
|
throw new BadRequestException('Endurance déjà au maximum');
|
||||||
|
}
|
||||||
|
|
||||||
|
char.gold -= item.buyPrice;
|
||||||
|
const endBefore = currentEndurance;
|
||||||
|
const restored = Math.min(item.forceBonus, char.enduranceMax - currentEndurance);
|
||||||
|
char.enduranceSaved = currentEndurance + restored;
|
||||||
|
char.lastEnduranceTs = new Date();
|
||||||
|
await manager.save(char);
|
||||||
|
|
||||||
|
return {
|
||||||
|
bought: true,
|
||||||
|
item: item.name,
|
||||||
|
type: 'consumable',
|
||||||
|
effectType: 'endurance',
|
||||||
|
goldSpent: item.buyPrice,
|
||||||
|
effect: { enduranceBefore: endBefore, enduranceAfter: char.enduranceSaved, restored },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// HP potion
|
||||||
if (char.hpCurrent >= char.hpMax) {
|
if (char.hpCurrent >= char.hpMax) {
|
||||||
throw new BadRequestException('PV déjà au maximum');
|
throw new BadRequestException('PV déjà au maximum');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char.gold -= item.buyPrice;
|
||||||
const hpBefore = char.hpCurrent;
|
const hpBefore = char.hpCurrent;
|
||||||
char.hpCurrent = Math.min(char.hpMax, char.hpCurrent + Math.floor(char.hpMax * POTION_HEAL_RATIO));
|
char.hpCurrent = Math.min(char.hpMax, char.hpCurrent + Math.floor(char.hpMax * POTION_HEAL_RATIO));
|
||||||
await manager.save(char);
|
await manager.save(char);
|
||||||
@@ -76,6 +106,7 @@ export class ShopService {
|
|||||||
bought: true,
|
bought: true,
|
||||||
item: item.name,
|
item: item.name,
|
||||||
type: 'consumable',
|
type: 'consumable',
|
||||||
|
effectType: 'hp',
|
||||||
goldSpent: item.buyPrice,
|
goldSpent: item.buyPrice,
|
||||||
effect: { hpBefore, hpAfter: char.hpCurrent, healed: char.hpCurrent - hpBefore },
|
effect: { hpBefore, hpAfter: char.hpCurrent, healed: char.hpCurrent - hpBefore },
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user