// ---------- 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 : " / 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 = ''; } // 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()"). 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 : ", 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 = '' + ''; } 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, }; }