// ---------- Éditeur de quêtes ----------
// 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. Un clic sur une ligne
// ouvre la modale de FICHE (titre/objectif/récompense/statut/résultat —
// "je crée"), qui ouvre à son tour, sur un bouton dédié, la modale
// PLEIN ÉCRAN d'arbre de dialogue ("j'ajoute les dialogues") — deux
// étapes séparées, jamais le même formulaire. Les deux modales sont
// aussi ouvertes 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éées dynamiquement (ensureQuest*Modal)
// plutôt que dupliquées 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 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 (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 : [];
// Noms de personnages nommés (voir "ℹ️ Informations", templates/
// scene_edit.html) — proposés comme "qui parle" pour une réplique, en
// plus de "Joueur" (toujours disponible, jamais un objet de scène).
var PERSONNAGE_NAMES = (typeof PERSONNAGE_NAMES_JSON !== 'undefined') ? PERSONNAGE_NAMES_JSON : [];
// Une réplique n'a que deux COULEURS possibles (voir .questBubble-joueur/
// .questBubble-pnj, static/style.css) même si le nom affiché est
// n'importe quel personnage nommé — "Joueur" à droite en vert, n'importe
// quel autre nom à gauche en bleu (voir demande utilisateur : "les bulles
// du pnj et du joueur sont d'une couleur différente").
function questBubbleKind(speaker) {
return speaker === 'Joueur' ? 'joueur' : 'pnj';
}
function questById(questId) {
return QUESTS.find(function (q) { return q.id === questId; });
}
function questListRowHtml(quest) {
var resultatHtml = quest.resultat ? '' + QUEST_RESULT_LABELS[quest.resultat] + '' : '—';
return (
'
'
);
}
function renderQuestListPage() {
var container = document.getElementById('questsTabPanel');
if (!container) return;
var wrap = document.getElementById('questsListWrap');
if (!QUESTS.length) {
wrap.innerHTML = '
Aucune quête pour l\'instant — crée-en une avec le bouton ci-dessus.
';
}
function createQuestFromListPage() {
fetch('/game/' + GAME_SLUG + '/quests/new', {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}),
})
.then(function (r) { return r.json(); })
.then(function (json) {
QUESTS.unshift(json.quest);
renderQuestListPage();
openQuestFieldsModal(json.quest.id);
});
}
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();
});
}
// ---------- Deux modales distinctes ----------
// 1. #questFieldsModal (taille normale) : "je crée" — titre/objectif/
// récompense/statut/résultat, + un bouton qui ouvre...
// 2. #questDialogueModal (PLEIN ÉCRAN) : "j'ajoute les dialogues" — les
// 3 colonnes de répliques, rien d'autre. Étapes séparées demandées
// explicitement par l'utilisateur (pas un seul gros formulaire).
// Créées une seule fois chacune (au premier appel), réutilisées ensuite —
// partagées avec l'assistant "+ Action" de l'éditeur de collision,
// jamais dupliquées dans les templates.
var _questModalOnUse = null;
var _questModalCurrentId = null;
function ensureQuestFieldsModal() {
var modal = document.getElementById('questFieldsModal');
if (modal) return modal;
modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'questFieldsModal';
modal.innerHTML =
'' +
'
' +
'' +
'
';
document.body.appendChild(modal);
return modal;
}
// `onUse(quest)` optionnel : posé par collisionWizardChooseAction('quete', ...)
// pour finaliser la règle de collision avec cette quête, affiche alors un
// bouton "✓ Utiliser cette quête" dans les deux modales.
function openQuestFieldsModal(questId, onUse) {
_questModalOnUse = onUse || null;
_questModalCurrentId = questId;
var modal = ensureQuestFieldsModal();
modal.classList.add('is-active');
renderQuestFieldsModalBody(questId);
}
// _questModalOnUse/_questModalCurrentId ne sont PAS remis à null ici :
// openQuestDialogueModal() ferme cette modale en s'ouvrant (voir plus
// bas) et a besoin de les garder pour son propre bouton "✓ Utiliser
// cette quête" — chaque point d'entrée (openQuestFieldsModal) les fixe
// de toute façon fraîchement à chaque ouverture.
function closeQuestFieldsModal() {
var modal = document.getElementById('questFieldsModal');
if (modal) modal.classList.remove('is-active');
}
function questUseThisQuest(questId) {
var quest = questById(questId);
if (_questModalOnUse && quest) _questModalOnUse(quest);
closeQuestDialogueModal();
closeQuestFieldsModal();
_questModalOnUse = null;
_questModalCurrentId = null;
}
function questFieldRowHtml(quest) {
return (
'
' +
'" +
'" +
'" +
'' +
'' +
'
'
);
}
function renderQuestFieldsModalBody(questId) {
var quest = questById(questId);
var body = document.getElementById('questFieldsModalBody');
if (!quest || !body) return;
var useBtn = _questModalOnUse
? ''
: '';
body.innerHTML =
'