Éditeur de quêtes : onglet à côté de Collision, pas une page à part
Build and deploy / test-python (push) Successful in 6m7s
Build and deploy / test-js (push) Successful in 59s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Corrige la mauvaise interprétation de la demande initiale : la page
dédiée "🗺️ Quêtes" (nav du jeu) est retirée au profit d'un nouvel
onglet "🗺️ Quêtes" DANS l'éditeur de scène 2D, juste à côté de
"🧩 Collision" — même liste tabulaire que l'onglet "📣 Événements"
(titre/objectif/récompense/statut/résultat par ligne, pas des cartes),
un clic sur une ligne ouvrant la modale d'arbre de dialogue déjà
construite. L'assistant "+ Action" de l'éditeur de collision ouvre
toujours cette même modale pour "Déclencher une quête".

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-02 20:46:27 +02:00
co-authored by Claude Sonnet 5
parent 51427b5297
commit 78c1aba8a0
10 changed files with 72 additions and 92 deletions
+44 -27
View File
@@ -1,56 +1,63 @@
// ---------- Éditeur de quêtes ----------
// Module partagé entre templates/quests.html (page dédiée : liste de
// cartes + "+ Nouvelle quête") et static/js/scenes/collision-rules-editor.js
// (l'assistant "+ Action" de l'éditeur de collision ouvre CETTE MÊME
// modale de dialogue quand l'auteur choisit "Déclencher une quête",
// plutôt qu'un simple champ texte pour un id). La modale (arbre de
// dialogue) est créée dynamiquement (ensureQuestDialogueModal) plutôt que
// dupliquée dans deux templates.
// Onglet "🗺️ Quêtes" à côté de "🧩 Collision" (templates/scene_edit.html,
// jamais une page à part) : une LISTE (mêmes tableau/lignes que l'onglet
// "📣 Événements") plutôt qu'une grille de cartes, chaque ligne portant
// toutes les infos de la quête + un clic dessus ouvrant la modale
// d'arbre de dialogue. Cette même modale est aussi ouverte depuis
// l'assistant "+ Action" de l'éditeur de collision (voir
// static/js/scenes/collision-rules-editor.js) quand l'auteur choisit
// "Déclencher une quête" — créée dynamiquement (ensureQuestDialogueModal)
// plutôt que dupliquée dans deux endroits.
//
// Une quête (voir db/quests/) : id, title, objectif, recompense_score,
// status (nouvelle/en_cours/terminee — statut COURANT, informatif),
// resultat (reussi/echoue), dialogues (une liste de répliques PAR
// STATUT — 3 colonnes dans l'éditeur : quel dialogue jouer selon l'état
// STATUT — 3 colonnes dans la modale : quel dialogue jouer selon l'état
// de la quête au moment où le joueur parle au PNJ).
var QUEST_STATUS_LABELS = { nouvelle: 'Nouvelle', en_cours: 'En cours', terminee: 'Terminée' };
var QUEST_RESULT_LABELS = { reussi: '✅ Réussi', echoue: '❌ Échoué' };
var QUEST_STATUS_ORDER = ['nouvelle', 'en_cours', 'terminee'];
// Quêtes connues côté client — préremplies par QUESTS_JSON (quests.html)
// si présent, sinon vide tant qu'on n'a pas encore fetch (contexte
// éditeur de collision, voir questPickerEnsureLoaded).
// Quêtes connues côté client — préremplies par QUESTS_JSON (voir
// templates/scene_edit.html), toujours présentes puisque l'onglet
// Quêtes vit dans le même éditeur que l'onglet Collision qui en a
// besoin (collisionRuleActionSummary).
var QUESTS = (typeof QUESTS_JSON !== 'undefined') ? QUESTS_JSON : [];
function questById(questId) {
return QUESTS.find(function (q) { return q.id === questId; });
}
function questCardHtml(quest, i) {
var resultatHtml = quest.resultat ? '<span class="questPill questPill-resultat">' + QUEST_RESULT_LABELS[quest.resultat] + '</span>' : '';
function questListRowHtml(quest) {
var resultatHtml = quest.resultat ? '<span class="questPill questPill-resultat">' + QUEST_RESULT_LABELS[quest.resultat] + '</span>' : '<span class="hint">—</span>';
return (
'<div class="questCard" style="animation-delay:' + (i * 50) + 'ms" onclick="openQuestDialogueModal(' + quest.id + ')">' +
'<div class="questCardHeader">' +
'<h3 class="questCardTitle">' + (quest.title || '').replace(/</g, '&lt;') + '</h3>' +
'<span class="questPill questPill-status questPill-status-' + quest.status + '">' + QUEST_STATUS_LABELS[quest.status] + '</span>' +
'</div>' +
'<p class="questCardObjectif">' + (quest.objectif ? quest.objectif.replace(/</g, '&lt;') : '<span class="hint">Aucun objectif défini.</span>') + '</p>' +
'<div class="questCardFooter">' +
'<span class="questPill questPill-score">🏆 +' + (quest.recompense_score || 0) + '</span>' +
resultatHtml +
'</div>' +
'</div>'
'<tr class="questListRow" onclick="openQuestDialogueModal(' + quest.id + ')">' +
'<td><strong>' + (quest.title || '').replace(/</g, '&lt;') + '</strong></td>' +
'<td>' + (quest.objectif ? quest.objectif.replace(/</g, '&lt;') : '<span class="hint">—</span>') + '</td>' +
'<td><span class="questPill questPill-score">🏆 +' + (quest.recompense_score || 0) + '</span></td>' +
'<td><span class="questPill questPill-status questPill-status-' + quest.status + '">' + QUEST_STATUS_LABELS[quest.status] + '</span></td>' +
'<td>' + resultatHtml + '</td>' +
'<td class="fieldsTableActions">' +
'<button type="button" class="button is-small is-danger" title="Supprimer" onclick="event.stopPropagation(); questDeleteFromList(' + quest.id + ')">🗑️</button>' +
'</td>' +
'</tr>'
);
}
function renderQuestListPage() {
var container = document.getElementById('questsGrid');
var container = document.getElementById('questsTabPanel');
if (!container) return;
var wrap = document.getElementById('questsListWrap');
if (!QUESTS.length) {
container.innerHTML = '<p class="hint">Aucune quête pour l\'instant — crée-en une avec le bouton ci-dessus.</p>';
wrap.innerHTML = '<p class="hint">Aucune quête pour l\'instant — crée-en une avec le bouton ci-dessus.</p>';
return;
}
container.innerHTML = QUESTS.map(questCardHtml).join('');
wrap.innerHTML =
'<div class="fieldsTableWrap"><table class="table is-fullwidth is-hoverable fieldsTable">' +
'<thead><tr><th>Titre</th><th>Objectif</th><th>Récompense</th><th>Statut</th><th>Résultat</th><th></th></tr></thead>' +
'<tbody>' + QUESTS.map(questListRowHtml).join('') + '</tbody>' +
'</table></div>';
}
function createQuestFromListPage() {
@@ -65,6 +72,16 @@ function createQuestFromListPage() {
});
}
function questDeleteFromList(questId) {
var quest = questById(questId);
if (!quest || !confirm('Supprimer la quête « ' + quest.title + ' » ? Toute règle de collision qui la référence perdra sa cible.')) return;
fetch('/game/' + GAME_SLUG + '/quests/' + questId + '/delete', { method: 'POST' })
.then(function () {
QUESTS = QUESTS.filter(function (q) { return q.id !== questId; });
renderQuestListPage();
});
}
// ---------- Modale "arbre de dialogue" ----------
// Créée une seule fois (au premier appel), réutilisée ensuite — partagée
// entre la page dédiée et l'assistant "+ Action" de l'éditeur de