feat: zone locking — progression par arcs narratifs + arcs Égouts/Désert
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 33s
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 33s
Zones verrouillées: marais toujours ouvert, égouts après arc Marais, désert après arc Égouts. Filtrage backend sur monstres ET boutique. Arc "Les Égouts de la Cité" (4 quêtes, lv4-7, boss Roi des Rats) Arc "Les Sables Brûlants" (3 quêtes, lv8-12, boss Sphinx) GET /api/monsters/zones — retourne les zones avec statut unlocked. Combat page: monstres groupés par zone, zones lockées avec icône cadenas. Boutique: items filtrés par zones débloquées (potions toujours visibles).
This commit is contained in:
@@ -24,6 +24,7 @@ export const characterApi = {
|
|||||||
|
|
||||||
// Combat
|
// Combat
|
||||||
export const combatApi = {
|
export const combatApi = {
|
||||||
|
zones: () => api.get<any[]>('/monsters/zones'),
|
||||||
monsters: () => api.get<Monster[]>('/monsters'),
|
monsters: () => api.get<Monster[]>('/monsters'),
|
||||||
start: (monsterId: string, attackType: string) => api.post<CombatResult>('/combat/start', { monsterId, attackType }),
|
start: (monsterId: string, attackType: string) => api.post<CombatResult>('/combat/start', { monsterId, attackType }),
|
||||||
history: () => api.get<CombatLog[]>('/combat/history'),
|
history: () => api.get<CombatLog[]>('/combat/history'),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useState } from 'react';
|
|||||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
import { combatApi, characterApi } from '../api/endpoints';
|
import { combatApi, characterApi } from '../api/endpoints';
|
||||||
import type { Monster, CombatResult, CombatLog } from '../api/types';
|
import type { Monster, CombatResult, CombatLog } from '../api/types';
|
||||||
import { Swords, Trophy, Skull, Clock, Zap, Heart } from 'lucide-react';
|
import { Swords, Trophy, Skull, Clock, Zap, Heart, Lock } from 'lucide-react';
|
||||||
|
|
||||||
const COMBAT_COST = 5;
|
const COMBAT_COST = 5;
|
||||||
const REST_COST = 10;
|
const REST_COST = 10;
|
||||||
@@ -127,6 +127,11 @@ export function CombatPage() {
|
|||||||
queryFn: combatApi.monsters,
|
queryFn: combatApi.monsters,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { data: zones } = useQuery({
|
||||||
|
queryKey: ['zones'],
|
||||||
|
queryFn: combatApi.zones,
|
||||||
|
});
|
||||||
|
|
||||||
const { data: history } = useQuery({
|
const { data: history } = useQuery({
|
||||||
queryKey: ['combatHistory'],
|
queryKey: ['combatHistory'],
|
||||||
queryFn: combatApi.history,
|
queryFn: combatApi.history,
|
||||||
@@ -144,26 +149,42 @@ export function CombatPage() {
|
|||||||
|
|
||||||
if (isLoading) return <div style={{ padding: '2rem', color: '#6b7a99' }}>Chargement des monstres…</div>;
|
if (isLoading) return <div style={{ padding: '2rem', color: '#6b7a99' }}>Chargement des monstres…</div>;
|
||||||
|
|
||||||
// Trier : monstres appropriés en haut, trop forts en bas
|
// Group monsters by zone
|
||||||
const sorted = [...(monsters ?? [])].sort((a, b) => {
|
const monstersByZone = new Map<string, Monster[]>();
|
||||||
const aOk = a.minLevel <= playerLevel + 2 ? 0 : 1;
|
for (const m of (monsters ?? [])) {
|
||||||
const bOk = b.minLevel <= playerLevel + 2 ? 0 : 1;
|
const zone = (m as any).zone ?? 'marais';
|
||||||
if (aOk !== bOk) return aOk - bOk;
|
const list = monstersByZone.get(zone) ?? [];
|
||||||
return a.minLevel - b.minLevel;
|
list.push(m);
|
||||||
});
|
monstersByZone.set(zone, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ZONE_LABELS: Record<string, { name: string; emoji: string }> = {
|
||||||
|
marais: { name: 'Les Marais', emoji: '🌿' },
|
||||||
|
egouts: { name: 'Les Égouts', emoji: '🕳️' },
|
||||||
|
desert: { name: 'Le Désert', emoji: '🏜️' },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Locked zones (zones not in monsters response = locked)
|
||||||
|
const lockedZones = (zones ?? []).filter((z: any) => !z.unlocked);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2 style={{ margin: '0 0 1rem', color: '#f4c94e', fontSize: 20 }}>⚔️ Combat</h2>
|
<h2 style={{ margin: '0 0 1rem', color: '#f4c94e', fontSize: 20 }}>⚔️ Combat</h2>
|
||||||
|
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem', marginBottom: '1rem' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem', marginBottom: '1rem' }}>
|
||||||
{/* Choix monstre */}
|
{/* Choix monstre par zone */}
|
||||||
<div>
|
<div>
|
||||||
<p style={{ margin: '0 0 0.5rem', fontSize: 12, fontWeight: 700, color: '#6b7a99', textTransform: 'uppercase' }}>
|
{Array.from(monstersByZone.entries()).map(([zone, zoneMonsters]) => {
|
||||||
Adversaire
|
const info = ZONE_LABELS[zone] ?? { name: zone, emoji: '📍' };
|
||||||
|
return (
|
||||||
|
<div key={zone} style={{ marginBottom: '1rem' }}>
|
||||||
|
<p style={{ margin: '0 0 0.5rem', fontSize: 12, fontWeight: 700, color: '#9ca3af' }}>
|
||||||
|
{info.emoji} {info.name}
|
||||||
</p>
|
</p>
|
||||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||||
{sorted.map(m => (
|
{zoneMonsters
|
||||||
|
.sort((a, b) => a.minLevel - b.minLevel)
|
||||||
|
.map(m => (
|
||||||
<MonsterCard
|
<MonsterCard
|
||||||
key={m.id}
|
key={m.id}
|
||||||
m={m}
|
m={m}
|
||||||
@@ -174,6 +195,19 @@ export function CombatPage() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
{/* Zones verrouillées */}
|
||||||
|
{lockedZones.map((z: any) => (
|
||||||
|
<div key={z.id} className="card" style={{ marginBottom: '0.5rem', opacity: 0.4, textAlign: 'center', padding: '1rem' }}>
|
||||||
|
<Lock size={16} color="#6b7a99" style={{ display: 'inline', marginRight: 6 }} />
|
||||||
|
<span style={{ fontSize: 13, color: '#6b7a99' }}>
|
||||||
|
{z.emoji} {z.name} — Complétez l'arc précédent pour débloquer
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Panneau droite */}
|
{/* Panneau droite */}
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
44
src/common/zone-access.ts
Normal file
44
src/common/zone-access.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { PlayerQuestArc } from '../quest/player-quest-arc.entity';
|
||||||
|
import { QuestArc } from '../quest/quest-arc.entity';
|
||||||
|
|
||||||
|
// Zone unlock chain: each zone requires completing the previous zone's arc
|
||||||
|
// marais → always open
|
||||||
|
// egouts → requires "Les Marais du Têtard" arc completed
|
||||||
|
// desert → requires the egouts arc completed
|
||||||
|
const ZONE_ORDER = ['marais', 'egouts', 'desert'];
|
||||||
|
|
||||||
|
export async function getUnlockedZones(
|
||||||
|
characterId: string,
|
||||||
|
arcRepo: Repository<QuestArc>,
|
||||||
|
playerArcRepo: Repository<PlayerQuestArc>,
|
||||||
|
): Promise<string[]> {
|
||||||
|
const unlocked: string[] = ['marais']; // always accessible
|
||||||
|
|
||||||
|
// Get all completed arcs for this character
|
||||||
|
const completedArcs = await playerArcRepo.find({
|
||||||
|
where: { characterId, completed: true },
|
||||||
|
relations: ['questArc'],
|
||||||
|
});
|
||||||
|
const completedArcZones = new Set(completedArcs.map(pa => pa.questArc?.zone).filter(Boolean));
|
||||||
|
|
||||||
|
// Check zone chain: each zone unlocks the next
|
||||||
|
for (let i = 0; i < ZONE_ORDER.length - 1; i++) {
|
||||||
|
const currentZone = ZONE_ORDER[i];
|
||||||
|
const nextZone = ZONE_ORDER[i + 1];
|
||||||
|
|
||||||
|
// Find arc for current zone
|
||||||
|
const arc = await arcRepo.findOne({ where: { zone: currentZone } });
|
||||||
|
if (!arc) continue;
|
||||||
|
|
||||||
|
// If this zone's arc is completed, unlock the next zone
|
||||||
|
const isCompleted = completedArcs.some(pa => pa.questArcId === arc.id);
|
||||||
|
if (isCompleted) {
|
||||||
|
unlocked.push(nextZone);
|
||||||
|
} else {
|
||||||
|
break; // Can't skip zones
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return unlocked;
|
||||||
|
}
|
||||||
@@ -156,6 +156,60 @@ export async function seedQuests(dataSource: DataSource) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log(`✅ 1 arc + ${QUESTS.length + STANDALONE.length} quêtes + ${QUEST_ACHIEVEMENTS.length} achievements seedés`);
|
console.log(`✅ 1 arc + ${QUESTS.length + STANDALONE.length} quêtes + ${QUEST_ACHIEVEMENTS.length} achievements seedés`);
|
||||||
|
|
||||||
|
// --- Arc 2: Les Égouts ---
|
||||||
|
let arcEgouts = await arcRepo.findOne({ where: { name: 'Les Égouts de la Cité' } });
|
||||||
|
if (!arcEgouts) {
|
||||||
|
arcEgouts = await arcRepo.save(arcRepo.create({
|
||||||
|
name: 'Les Égouts de la Cité',
|
||||||
|
description: 'Les égouts grouillent de créatures répugnantes. Nettoyez ces tunnels oubliés.',
|
||||||
|
zone: 'egouts',
|
||||||
|
sortOrder: 2,
|
||||||
|
minLevel: 4,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const ratId = monsterMap.get("Rat d'Égout") ?? null as string | null;
|
||||||
|
const crocoId = monsterMap.get('Crocodile') ?? null as string | null;
|
||||||
|
const roiId = monsterMap.get('Roi des Rats') ?? null as string | null;
|
||||||
|
|
||||||
|
const EGOUTS_QUESTS = [
|
||||||
|
{ name: 'Dératisation', description: 'Les rats pullulent. Éliminez-en 5.', objectiveType: 'kill_monster', objectiveTargetId: ratId, objectiveCount: 5, rewardXp: 200, rewardGold: 80, rewardTitle: null, arcId: arcEgouts.id, arcOrder: 1, minLevel: 4, repeatable: false },
|
||||||
|
{ name: 'Chasseur des profondeurs', description: 'Remportez 15 combats dans les égouts.', objectiveType: 'kill_any', objectiveTargetId: null as string | null, objectiveCount: 15, rewardXp: 300, rewardGold: 120, rewardTitle: null, arcId: arcEgouts.id, arcOrder: 2, minLevel: 5, repeatable: false },
|
||||||
|
{ name: 'Le reptile', description: 'Terrassez 3 Crocodiles qui rôdent dans les canaux.', objectiveType: 'kill_monster', objectiveTargetId: crocoId, objectiveCount: 3, rewardXp: 400, rewardGold: 150, rewardTitle: null, arcId: arcEgouts.id, arcOrder: 3, minLevel: 6, repeatable: false },
|
||||||
|
{ name: 'Le Roi des Rats', description: 'Mettez fin au règne du Roi des Rats.', objectiveType: 'kill_monster', objectiveTargetId: roiId, objectiveCount: 1, rewardXp: 800, rewardGold: 400, rewardTitle: 'Nettoyeur des Égouts', arcId: arcEgouts.id, arcOrder: 4, minLevel: 7, repeatable: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const q of EGOUTS_QUESTS) {
|
||||||
|
await questRepo.save(questRepo.create(q));
|
||||||
|
}
|
||||||
|
console.log(`✅ Arc Égouts + ${EGOUTS_QUESTS.length} quêtes seedés`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Arc 3: Le Désert ---
|
||||||
|
let arcDesert = await arcRepo.findOne({ where: { name: 'Les Sables Brûlants' } });
|
||||||
|
if (!arcDesert) {
|
||||||
|
arcDesert = await arcRepo.save(arcRepo.create({
|
||||||
|
name: 'Les Sables Brûlants',
|
||||||
|
description: 'Le désert cache des trésors anciens et des créatures redoutables.',
|
||||||
|
zone: 'desert',
|
||||||
|
sortOrder: 3,
|
||||||
|
minLevel: 8,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const scorpId = monsterMap.get('Scorpion') ?? null as string | null;
|
||||||
|
const sphinxId = monsterMap.get('Sphinx') ?? null as string | null;
|
||||||
|
|
||||||
|
const DESERT_QUESTS = [
|
||||||
|
{ name: 'Piqûres mortelles', description: 'Éliminez 5 Scorpions.', objectiveType: 'kill_monster', objectiveTargetId: scorpId, objectiveCount: 5, rewardXp: 400, rewardGold: 150, rewardTitle: null, arcId: arcDesert.id, arcOrder: 1, minLevel: 8, repeatable: false },
|
||||||
|
{ name: 'Survivant du désert', description: 'Remportez 20 combats dans le désert.', objectiveType: 'kill_any', objectiveTargetId: null as string | null, objectiveCount: 20, rewardXp: 600, rewardGold: 250, rewardTitle: null, arcId: arcDesert.id, arcOrder: 2, minLevel: 9, repeatable: false },
|
||||||
|
{ name: 'L\'énigme du Sphinx', description: 'Terrassez le Sphinx — gardien des sables.', objectiveType: 'kill_monster', objectiveTargetId: sphinxId, objectiveCount: 1, rewardXp: 1500, rewardGold: 800, rewardTitle: 'Conquérant du Désert', arcId: arcDesert.id, arcOrder: 3, minLevel: 12, repeatable: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const q of DESERT_QUESTS) {
|
||||||
|
await questRepo.save(questRepo.create(q));
|
||||||
|
}
|
||||||
|
console.log(`✅ Arc Désert + ${DESERT_QUESTS.length} quêtes seedés`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update monster XP rewards (nerf for quest-driven progression)
|
// Update monster XP rewards (nerf for quest-driven progression)
|
||||||
|
|||||||
@@ -1,14 +1,56 @@
|
|||||||
import { Controller, Get, UseGuards } from '@nestjs/common';
|
import { Controller, Get, UseGuards, Req, BadRequestException } from '@nestjs/common';
|
||||||
import { MonsterService } from './monster.service';
|
import { MonsterService } from './monster.service';
|
||||||
import { AuthGuard } from '../auth/guards/auth.guard';
|
import { AuthGuard } from '../auth/guards/auth.guard';
|
||||||
|
import { Request } from 'express';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { Character } from '../character/entities/character.entity';
|
||||||
|
import { QuestArc } from '../quest/quest-arc.entity';
|
||||||
|
import { PlayerQuestArc } from '../quest/player-quest-arc.entity';
|
||||||
|
import { getUnlockedZones } from '../common/zone-access';
|
||||||
|
|
||||||
@Controller('monsters')
|
@Controller('monsters')
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
export class MonsterController {
|
export class MonsterController {
|
||||||
constructor(private readonly monsterService: MonsterService) {}
|
constructor(
|
||||||
|
private readonly monsterService: MonsterService,
|
||||||
|
@InjectRepository(Character)
|
||||||
|
private readonly characterRepo: Repository<Character>,
|
||||||
|
@InjectRepository(QuestArc)
|
||||||
|
private readonly arcRepo: Repository<QuestArc>,
|
||||||
|
@InjectRepository(PlayerQuestArc)
|
||||||
|
private readonly playerArcRepo: Repository<PlayerQuestArc>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get('zones')
|
||||||
|
async getZones(@Req() req: Request) {
|
||||||
|
const user = (req as any).user;
|
||||||
|
const char = await this.characterRepo.findOne({ where: { userId: user.id } });
|
||||||
|
if (!char) throw new BadRequestException('Aucun personnage');
|
||||||
|
|
||||||
|
const unlockedZones = await getUnlockedZones(char.id, this.arcRepo, this.playerArcRepo);
|
||||||
|
|
||||||
|
const ALL_ZONES = [
|
||||||
|
{ id: 'marais', name: 'Les Marais', emoji: '🌿', minLevel: 1 },
|
||||||
|
{ id: 'egouts', name: 'Les Égouts', emoji: '🕳️', minLevel: 4 },
|
||||||
|
{ id: 'desert', name: 'Le Désert', emoji: '🏜️', minLevel: 8 },
|
||||||
|
];
|
||||||
|
|
||||||
|
return ALL_ZONES.map(z => ({
|
||||||
|
...z,
|
||||||
|
unlocked: unlockedZones.includes(z.id),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
findAll() {
|
async findAll(@Req() req: Request) {
|
||||||
return this.monsterService.findAll();
|
const user = (req as any).user;
|
||||||
|
const char = await this.characterRepo.findOne({ where: { userId: user.id } });
|
||||||
|
if (!char) throw new BadRequestException('Aucun personnage');
|
||||||
|
|
||||||
|
const unlockedZones = await getUnlockedZones(char.id, this.arcRepo, this.playerArcRepo);
|
||||||
|
const allMonsters = await this.monsterService.findAll();
|
||||||
|
|
||||||
|
return allMonsters.filter(m => unlockedZones.includes(m.zone));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,15 @@ import { Monster } from './monster.entity';
|
|||||||
import { MonsterService } from './monster.service';
|
import { MonsterService } from './monster.service';
|
||||||
import { MonsterController } from './monster.controller';
|
import { MonsterController } from './monster.controller';
|
||||||
import { AuthModule } from '../auth/auth.module';
|
import { AuthModule } from '../auth/auth.module';
|
||||||
|
import { Character } from '../character/entities/character.entity';
|
||||||
|
import { QuestArc } from '../quest/quest-arc.entity';
|
||||||
|
import { PlayerQuestArc } from '../quest/player-quest-arc.entity';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([Monster]), AuthModule],
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([Monster, Character, QuestArc, PlayerQuestArc]),
|
||||||
|
AuthModule,
|
||||||
|
],
|
||||||
controllers: [MonsterController],
|
controllers: [MonsterController],
|
||||||
providers: [MonsterService],
|
providers: [MonsterService],
|
||||||
exports: [MonsterService, TypeOrmModule],
|
exports: [MonsterService, TypeOrmModule],
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import { ShopController } from './shop.controller';
|
|||||||
import { Item } from '../item/item.entity';
|
import { Item } from '../item/item.entity';
|
||||||
import { CharacterItem } from '../item/character-item.entity';
|
import { CharacterItem } from '../item/character-item.entity';
|
||||||
import { Character } from '../character/entities/character.entity';
|
import { Character } from '../character/entities/character.entity';
|
||||||
|
import { QuestArc } from '../quest/quest-arc.entity';
|
||||||
|
import { PlayerQuestArc } from '../quest/player-quest-arc.entity';
|
||||||
import { AuthModule } from '../auth/auth.module';
|
import { AuthModule } from '../auth/auth.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([Item, CharacterItem, Character]),
|
TypeOrmModule.forFeature([Item, CharacterItem, Character, QuestArc, PlayerQuestArc]),
|
||||||
AuthModule,
|
AuthModule,
|
||||||
],
|
],
|
||||||
controllers: [ShopController],
|
controllers: [ShopController],
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import { DataSource, Repository, LessThanOrEqual } from 'typeorm';
|
|||||||
import { Item } from '../item/item.entity';
|
import { Item } from '../item/item.entity';
|
||||||
import { CharacterItem } from '../item/character-item.entity';
|
import { CharacterItem } from '../item/character-item.entity';
|
||||||
import { Character } from '../character/entities/character.entity';
|
import { Character } from '../character/entities/character.entity';
|
||||||
|
import { QuestArc } from '../quest/quest-arc.entity';
|
||||||
|
import { PlayerQuestArc } from '../quest/player-quest-arc.entity';
|
||||||
|
import { getUnlockedZones } from '../common/zone-access';
|
||||||
|
|
||||||
const SELL_RATIO = 0.4; // 40% du prix d'achat
|
const SELL_RATIO = 0.4; // 40% du prix d'achat
|
||||||
const POTION_HEAL_RATIO = 0.5; // 50% HP max
|
const POTION_HEAL_RATIO = 0.5; // 50% HP max
|
||||||
@@ -17,6 +20,10 @@ export class ShopService {
|
|||||||
private readonly charItemRepo: Repository<CharacterItem>,
|
private readonly charItemRepo: Repository<CharacterItem>,
|
||||||
@InjectRepository(Character)
|
@InjectRepository(Character)
|
||||||
private readonly characterRepo: Repository<Character>,
|
private readonly characterRepo: Repository<Character>,
|
||||||
|
@InjectRepository(QuestArc)
|
||||||
|
private readonly arcRepo: Repository<QuestArc>,
|
||||||
|
@InjectRepository(PlayerQuestArc)
|
||||||
|
private readonly playerArcRepo: Repository<PlayerQuestArc>,
|
||||||
private readonly dataSource: DataSource,
|
private readonly dataSource: DataSource,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -24,6 +31,8 @@ export class ShopService {
|
|||||||
const char = await this.characterRepo.findOne({ where: { id: characterId } });
|
const char = await this.characterRepo.findOne({ where: { id: characterId } });
|
||||||
if (!char) throw new BadRequestException('Aucun personnage');
|
if (!char) throw new BadRequestException('Aucun personnage');
|
||||||
|
|
||||||
|
const unlockedZones = await getUnlockedZones(char.id, this.arcRepo, this.playerArcRepo);
|
||||||
|
|
||||||
const items = await this.itemRepo.find({
|
const items = await this.itemRepo.find({
|
||||||
where: { buyPrice: LessThanOrEqual(999999) },
|
where: { buyPrice: LessThanOrEqual(999999) },
|
||||||
order: { type: 'ASC', minLevel: 'ASC', buyPrice: 'ASC' },
|
order: { type: 'ASC', minLevel: 'ASC', buyPrice: 'ASC' },
|
||||||
@@ -31,6 +40,7 @@ export class ShopService {
|
|||||||
|
|
||||||
return items
|
return items
|
||||||
.filter(i => i.buyPrice > 0)
|
.filter(i => i.buyPrice > 0)
|
||||||
|
.filter(i => !i.zone || unlockedZones.includes(i.zone)) // zone null = toujours visible (potions)
|
||||||
.map(i => ({
|
.map(i => ({
|
||||||
...i,
|
...i,
|
||||||
affordable: char.gold >= i.buyPrice,
|
affordable: char.gold >= i.buyPrice,
|
||||||
|
|||||||
Reference in New Issue
Block a user