Une fois le dialogue épuisé, si la quête est encore "nouvelle", la boîte de dialogue bascule sur un écran d'offre au lieu de se masquer : header "Quête : <titre>", body l'objectif, pied deux boutons. - Accepter -> la quête passe "en_cours" (en MÉMOIRE seulement, gameData.quests — jamais persisté en base : le statut en base est celui de DÉPART pour toute nouvelle partie, pas un état de partie en cours, voir PLAYER_SHARED/full_game_payload.py). - Refuser -> la boîte se referme SANS toucher au statut, qui reste "nouvelle" : la prochaine interaction rejoue exactement le même dialogue depuis le début — impossible d'avancer la quête sans l'accepter un jour. Pour tout autre statut (déjà "en_cours"/"terminee"), la boîte se masque normalement à la fin du dialogue, comme avant. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
148 lines
7.0 KiB
JavaScript
148 lines
7.0 KiB
JavaScript
// ---------- Widget "💬 Boîte de dialogue" — moteur d'exécution ----------
|
|
// Fait le lien entre l'éditeur de collision (action "quete", voir
|
|
// static/js/play/collision-rules-controller.js) et le widget d'interface
|
|
// posé par l'auteur (voir "🖥️ Interface", templates/scene_edit.html,
|
|
// screens/rendering/dialogue_box_style.py) : à l'exécution d'une action
|
|
// "quete", affiche la réplique en cours du dialogue correspondant au
|
|
// STATUT ACTUEL de la quête (gameData.quests, voir full_game_payload.py),
|
|
// avance d'une réplique à chaque clic sur "Suivant".
|
|
//
|
|
// Une fois le dialogue épuisé : si la quête est encore "nouvelle", la
|
|
// boîte bascule en écran d'OFFRE ("Quête : <titre>" / objectif / boutons
|
|
// Accepter-Refuser) au lieu de se masquer — accepter fait passer la
|
|
// quête "en_cours" (LOCAL à cette partie, jamais persisté en base :
|
|
// gameData.quests n'est qu'un instantané par joueur, voir
|
|
// full_game_payload.py ; muter la ligne en base changerait le statut de
|
|
// DÉPART de la quête pour toutes les parties futures) ; refuser ferme
|
|
// simplement la boîte SANS changer le statut — la quête reste "nouvelle",
|
|
// donc la prochaine interaction rejoue EXACTEMENT le même dialogue depuis
|
|
// le début (impossible d'avancer sans accepter). Pour tout autre statut
|
|
// (déjà "en_cours"/"terminee"), la boîte se masque normalement à la fin.
|
|
// Silencieux (aucun effet) si la quête est introuvable ou si l'auteur n'a
|
|
// posé aucun widget sur l'écran — même esprit que le reste du moteur.
|
|
|
|
var forgeDialogueBoxState = {};
|
|
|
|
function forgeQuestById(questId) {
|
|
return (gameData.quests || []).find(function (q) { return q.id === questId; });
|
|
}
|
|
|
|
function forgeDialogueBoxWidgetOnCurrentScreen() {
|
|
return document.querySelector('.playScreen[data-screen-id="' + window.currentScreenId + '"] .dialogueBoxWidget');
|
|
}
|
|
|
|
function forgeDialogueBoxWidgetById(objectId) {
|
|
return document.querySelector('.dialogueBoxWidget[data-object-id="' + objectId + '"]');
|
|
}
|
|
|
|
function forgeDialogueBoxFooterEl(widget) {
|
|
return widget.querySelector('[data-dialogue-role="footer"]');
|
|
}
|
|
|
|
function forgeRenderDialogueBoxLine(widget, line) {
|
|
var header = widget.querySelector('[data-dialogue-role="header"]');
|
|
var body = widget.querySelector('[data-dialogue-role="body"]');
|
|
if (header) header.textContent = line.speaker;
|
|
if (body) body.textContent = line.text;
|
|
}
|
|
|
|
// Remet le pied de la boîte dans son état "dialogue" (un seul bouton
|
|
// "Suivant →", voir render_scene_object.py) — nécessaire pour effacer
|
|
// les boutons Accepter/Refuser d'une offre PRÉCÉDENTE avant de rejouer
|
|
// un nouveau dialogue dans la même boîte.
|
|
function forgeResetDialogueBoxFooter(widget, objectId) {
|
|
var footer = forgeDialogueBoxFooterEl(widget);
|
|
if (footer) footer.innerHTML = '<button type="button" class="dialogueBoxNextBtn" data-dialogue-role="next-btn" onclick="forgeDialogueBoxAdvance(' + objectId + ')">Suivant →</button>';
|
|
}
|
|
|
|
// Appelée par l'action "quete" (collision-rules-controller.js) — trouve
|
|
// le widget posé sur l'écran ACTUELLEMENT affiché (un seul à la fois,
|
|
// comme le reste du moteur 2D).
|
|
function forgeShowDialogueBox(questId) {
|
|
var quest = forgeQuestById(questId);
|
|
if (!quest) return;
|
|
var lines = (quest.dialogues && quest.dialogues[quest.status]) || [];
|
|
var widget = forgeDialogueBoxWidgetOnCurrentScreen();
|
|
if (!widget) return;
|
|
var objectId = widget.getAttribute('data-object-id');
|
|
// Aucune réplique posée pour ce statut : une quête "nouvelle" propose
|
|
// quand même directement l'offre (accepter/refuser), les autres
|
|
// statuts restent silencieux (rien à montrer).
|
|
if (!lines.length) {
|
|
if (quest.status === 'nouvelle') forgeShowQuestOffer(widget, objectId, quest);
|
|
return;
|
|
}
|
|
forgeResetDialogueBoxFooter(widget, objectId);
|
|
forgeDialogueBoxState[objectId] = { questId: questId, lines: lines, index: 0 };
|
|
forgeRenderDialogueBoxLine(widget, lines[0]);
|
|
widget.classList.add('is-active');
|
|
}
|
|
|
|
// Appelée par le bouton "Suivant →" rendu dans le widget lui-même (voir
|
|
// render_scene_object.py, onclick="forgeDialogueBoxAdvance(<id>)").
|
|
function forgeDialogueBoxAdvance(objectId) {
|
|
var state = forgeDialogueBoxState[objectId];
|
|
var widget = forgeDialogueBoxWidgetById(objectId);
|
|
if (!state || !widget) return;
|
|
state.index += 1;
|
|
if (state.index >= state.lines.length) {
|
|
var quest = forgeQuestById(state.questId);
|
|
if (quest && quest.status === 'nouvelle') {
|
|
forgeShowQuestOffer(widget, objectId, quest);
|
|
return;
|
|
}
|
|
widget.classList.remove('is-active');
|
|
delete forgeDialogueBoxState[objectId];
|
|
return;
|
|
}
|
|
forgeRenderDialogueBoxLine(widget, state.lines[state.index]);
|
|
}
|
|
|
|
// Écran d'offre : header = "Quête : <titre>", body = l'objectif, pied =
|
|
// Accepter/Refuser au lieu du "Suivant" habituel.
|
|
function forgeShowQuestOffer(widget, objectId, quest) {
|
|
var header = widget.querySelector('[data-dialogue-role="header"]');
|
|
var body = widget.querySelector('[data-dialogue-role="body"]');
|
|
var footer = forgeDialogueBoxFooterEl(widget);
|
|
if (header) header.textContent = 'Quête : ' + quest.title;
|
|
if (body) body.textContent = quest.objectif || '';
|
|
if (footer) {
|
|
footer.innerHTML =
|
|
'<button type="button" class="dialogueBoxNextBtn dialogueBoxAcceptBtn" onclick="forgeDialogueBoxAcceptQuest(' + quest.id + ', ' + objectId + ')">Accepter</button>' +
|
|
'<button type="button" class="dialogueBoxNextBtn dialogueBoxDeclineBtn" onclick="forgeDialogueBoxDeclineQuest(' + objectId + ')">Refuser</button>';
|
|
}
|
|
forgeDialogueBoxState[objectId] = { questId: quest.id, lines: [], index: 0 };
|
|
widget.classList.add('is-active');
|
|
}
|
|
|
|
// Fait passer la quête "en_cours" — UNIQUEMENT dans l'instantané en
|
|
// mémoire (gameData.quests), jamais persisté en base : voir la note en
|
|
// tête de fichier (le statut en base est celui de DÉPART pour toute
|
|
// nouvelle partie, pas un état de partie en cours).
|
|
function forgeDialogueBoxAcceptQuest(questId, objectId) {
|
|
var quest = forgeQuestById(questId);
|
|
if (quest) quest.status = 'en_cours';
|
|
var widget = forgeDialogueBoxWidgetById(objectId);
|
|
if (widget) widget.classList.remove('is-active');
|
|
delete forgeDialogueBoxState[objectId];
|
|
}
|
|
|
|
// Referme la boîte SANS toucher au statut de la quête (reste "nouvelle")
|
|
// — la prochaine interaction rejoue donc le même dialogue depuis le
|
|
// début : impossible d'avancer la quête sans un jour l'accepter.
|
|
function forgeDialogueBoxDeclineQuest(objectId) {
|
|
var widget = forgeDialogueBoxWidgetById(objectId);
|
|
if (widget) widget.classList.remove('is-active');
|
|
delete forgeDialogueBoxState[objectId];
|
|
}
|
|
|
|
// static/js/play/__tests__/ (node:test) — même garde que les autres
|
|
// contrôleurs de ce dossier.
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = {
|
|
forgeQuestById, forgeShowDialogueBox, forgeDialogueBoxAdvance,
|
|
forgeDialogueBoxWidgetOnCurrentScreen, forgeDialogueBoxWidgetById, forgeRenderDialogueBoxLine,
|
|
forgeShowQuestOffer, forgeDialogueBoxAcceptQuest, forgeDialogueBoxDeclineQuest, forgeResetDialogueBoxFooter,
|
|
};
|
|
}
|