feat: arc quests accept from arc panel + side quests only in available
All checks were successful
CI/CD — Build & Deploy / Build & Deploy (push) Successful in 35s

Arc panel: boutons Accepter/Réclamer directement sur chaque quête d'arc,
progress affiché (3/5), arcs lockés avec 🔒 et opacity réduite.
Quêtes disponibles: seulement les secondaires (pas les arcs).
Quêtes d'arc abandonnées: ré-acceptables depuis le panel arc.
Zone locking respecté dans getArcs (zoneUnlocked flag).
This commit is contained in:
2026-03-24 18:31:42 +01:00
parent 810ad5ee64
commit 9aadc326e1
2 changed files with 107 additions and 46 deletions

View File

@@ -50,9 +50,8 @@ export class QuestService {
return quests.filter((q) => {
if (q.minLevel > character.level) return false;
// Zone filter: if quest belongs to an arc with a zone, check zone is unlocked
if (q.arc?.zone && !unlockedZones.includes(q.arc.zone)) return false;
// Arc quests managed from arc panel — not in available
if (q.arcId) return false;
const status = questStatusMap.get(q.id);
if (status === 'active' || status === 'completed') return false;
@@ -287,6 +286,11 @@ export class QuestService {
// --- Arcs ---
async getArcs(characterId: string) {
const character = await this.characterRepo.findOne({ where: { id: characterId } });
const playerLevel = character?.level ?? 1;
const unlockedZones = await getUnlockedZones(characterId, this.arcRepo, this.playerArcRepo);
const arcs = await this.arcRepo.find({
relations: ['quests'],
order: { sortOrder: 'ASC' },
@@ -297,24 +301,35 @@ export class QuestService {
const playerQuests = await this.playerQuestRepo.find({
where: { characterId },
select: ['questId', 'status'],
select: ['questId', 'status', 'id', 'progress'],
});
const questStatusMap = new Map(playerQuests.map((pq) => [pq.questId, pq.status]));
const questDataMap = new Map(playerQuests.map((pq) => [pq.questId, pq]));
return arcs.map((arc) => ({
...arc,
completed: arcMap.get(arc.id)?.completed ?? false,
completedAt: arcMap.get(arc.id)?.completedAt ?? null,
quests: arc.quests
.sort((a, b) => a.arcOrder - b.arcOrder)
.map((q) => ({
...q,
playerStatus: questStatusMap.get(q.id) ?? 'available',
})),
progress: {
completed: arc.quests.filter((q) => questStatusMap.get(q.id) === 'claimed').length,
total: arc.quests.length,
},
}));
return arcs.map((arc) => {
const zoneUnlocked = !arc.zone || unlockedZones.includes(arc.zone);
return {
...arc,
zoneUnlocked,
completed: arcMap.get(arc.id)?.completed ?? false,
completedAt: arcMap.get(arc.id)?.completedAt ?? null,
quests: arc.quests
.sort((a, b) => a.arcOrder - b.arcOrder)
.map((q) => {
const pq = questDataMap.get(q.id);
return {
...q,
playerStatus: pq?.status ?? 'available',
playerQuestId: pq?.id ?? null,
progress: pq?.progress ?? 0,
canAccept: zoneUnlocked && !pq && q.minLevel <= playerLevel,
levelOk: q.minLevel <= playerLevel,
};
}),
progress: {
completed: arc.quests.filter((q) => questDataMap.get(q.id)?.status === 'claimed').length,
total: arc.quests.length,
},
};
});
}
}