fix: quest available filtering + 6 side quests level 2-4
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 36s

Fix: getAvailable filtre maintenant les quêtes active/completed (pas juste
claimed). Plus de doublons dailies, plus d'internal server error.

6 quêtes secondaires pour combler le gap level 2-5:
  Chasseur de champignons (lv2, 150 XP), La menace rampante (lv3, 180 XP),
  Guerrier éprouvé (lv2, 250 XP), Collecteur de trophées (lv3, 500 XP),
  Exterminateur (lv4, 400 XP), Première forge (lv2, 120 XP).
This commit is contained in:
2026-03-24 18:08:49 +01:00
parent 60d10a5423
commit 95fcf325dc
2 changed files with 29 additions and 7 deletions

View File

@@ -86,6 +86,23 @@ export async function seedQuests(dataSource: DataSource) {
},
];
// Side quests — fill the level gaps
const SIDE_QUESTS = [
{ name: 'Chasseur de champignons', description: 'Les Champis Vénéneux infestent les sous-bois. Éliminez-en 3.', objectiveType: 'kill_monster', objectiveTargetId: monsterMap.get('Champi Vénéneux') ?? null as string | null, objectiveCount: 3, rewardXp: 150, rewardGold: 40, rewardTitle: null, arcId: null, arcOrder: 0, minLevel: 2, repeatable: false },
{ name: 'La menace rampante', description: 'Les Serpents des Marais sont de plus en plus agressifs. Tuez-en 3.', objectiveType: 'kill_monster', objectiveTargetId: monsterMap.get('Serpent des Marais') ?? null as string | null, objectiveCount: 3, rewardXp: 180, rewardGold: 50, rewardTitle: null, arcId: null, arcOrder: 0, minLevel: 3, repeatable: false },
{ name: 'Guerrier éprouvé', description: 'Prouvez votre valeur en remportant 20 combats.', objectiveType: 'kill_any', objectiveTargetId: null as string | null, objectiveCount: 20, rewardXp: 250, rewardGold: 80, rewardTitle: null, arcId: null, arcOrder: 0, minLevel: 2, repeatable: false },
{ name: 'Collecteur de trophées', description: 'Remportez 50 combats au total.', objectiveType: 'kill_any', objectiveTargetId: null as string | null, objectiveCount: 50, rewardXp: 500, rewardGold: 200, rewardTitle: 'Vétéran', arcId: null, arcOrder: 0, minLevel: 3, repeatable: false },
{ name: 'Exterminateur', description: 'Éliminez 10 monstres de chaque espèce des marais.', objectiveType: 'kill_any', objectiveTargetId: null as string | null, objectiveCount: 40, rewardXp: 400, rewardGold: 150, rewardTitle: null, arcId: null, arcOrder: 0, minLevel: 4, repeatable: false },
{ name: 'Première forge', description: 'Améliorez un item à la forge 3 fois.', objectiveType: 'forge_item', objectiveTargetId: null as string | null, objectiveCount: 3, rewardXp: 120, rewardGold: 40, rewardTitle: null, arcId: null, arcOrder: 0, minLevel: 2, repeatable: false },
];
for (const data of SIDE_QUESTS) {
const existing = await questRepo.findOne({ where: { name: data.name } });
if (!existing) {
await questRepo.save(questRepo.create(data));
}
}
// Standalone repeatable quests (daily feel)
const STANDALONE = [
{

View File

@@ -32,22 +32,27 @@ export class QuestService {
const character = await this.characterRepo.findOne({ where: { id: characterId } });
if (!character) throw new BadRequestException('Aucun personnage');
// All quests the player hasn't completed (or repeatable)
const completed = await this.playerQuestRepo.find({
where: { characterId, status: 'claimed' },
select: ['questId'],
// All player quests (any status)
const playerQuests = await this.playerQuestRepo.find({
where: { characterId },
select: ['questId', 'status'],
});
const completedIds = new Set(completed.map((pq) => pq.questId));
const questStatusMap = new Map(playerQuests.map((pq) => [pq.questId, pq.status]));
const quests = await this.questRepo.find({
where: { minLevel: undefined }, // load all, filter in code
relations: ['arc'],
order: { arcOrder: 'ASC' },
});
return quests.filter((q) => {
if (q.minLevel > character.level) return false;
if (completedIds.has(q.id) && !q.repeatable) return false;
const status = questStatusMap.get(q.id);
// Already active or completed (waiting claim) → not available
if (status === 'active' || status === 'completed') return false;
// Already claimed and not repeatable → not available
if (status === 'claimed' && !q.repeatable) return false;
return true;
});
}