Remplace le système de quêtes par des déclencheurs, ajoute l'action variable et le chaînage
Build and deploy / test-python (push) Successful in 8m45s
Build and deploy / test-js (push) Successful in 49s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Supprime le concept de "quête" au profit d'un onglet unique "Déclencheurs"
portant toute la logique (dialogue, condition, marquage terminé) directement
sur l'objet de scène. Ajoute une nouvelle action "Modifier une variable"
(réutilisant le vocabulaire du graphe de flow) utilisable après une
collision, une interaction ou une branche de condition, ainsi qu'un
chaînage d'actions ("then") permettant d'enchaîner plusieurs actions à la
suite et d'étendre un déclencheur déjà posé sans le recréer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-07 22:05:06 +02:00
co-authored by Claude Sonnet 5
parent c504ace167
commit d664ed5637
62 changed files with 3129 additions and 2266 deletions
+165 -32
View File
@@ -725,7 +725,7 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d
static/js/play/dialogue-box-controller.js) : header/body/footer,
toujours VISIBLE dans l'éditeur (texte d'exemple, pour le positionner/
styliser) mais masqué par défaut EN JEU tant qu'aucune règle de
collision "quete" n'a de réplique à afficher. */
collision "dialogue" n'a de réplique à afficher. */
.dialogueBoxWidget{
display:flex; flex-direction:column; border-radius:10px; overflow:hidden;
box-shadow:0 6px 18px rgba(0,0,0,.35); pointer-events:auto;
@@ -740,14 +740,6 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d
border-radius:6px; padding:5px 12px; font:inherit; font-size:12px; cursor:pointer;
}
.dialogueBoxNextBtn:hover{ background:rgba(255,255,255,.28); }
/* Écran d'offre de quête (voir forgeShowQuestOffer, dialogue-box-controller.js) :
Accepter/Refuser remplacent le "Suivant" habituel dans le pied de la
boîte — verts/rouges pour rester lisibles sur n'importe quelle couleur
de pied choisie par l'auteur (voir "🎨 Style"). */
.dialogueBoxAcceptBtn{ background:#2ecc71; border-color:#2ecc71; color:#0b1a10; font-weight:700; }
.dialogueBoxAcceptBtn:hover{ background:#3ddb80; }
.dialogueBoxDeclineBtn{ background:rgba(224,82,77,.25); border-color:#e0524d; }
.dialogueBoxDeclineBtn:hover{ background:rgba(224,82,77,.4); }
/* Widget "❓ Boîte à quiz" (voir screens/rendering/dialogue_box_style.py,
static/js/play/dialogue-box-controller.js) : même squelette que
@@ -942,6 +934,14 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d
.collisionWizardChoice:hover{ transform:translateY(-3px) scale(1.02); border-color:var(--accent); box-shadow:0 8px 18px rgba(0,0,0,.25); }
.collisionWizardChoice .emoji{ font-size:22px; }
/* Étape "condition" de l'assistant (voir renderCollisionWizardConditionStep,
collision-rules-editor.js) : variable / opérateur / valeur sur une
ligne — les <select>/<input> reprennent déjà le style générique
(voir plus haut, input[type=text]/select). */
.collisionWizardConditionForm{ display:flex; gap:8px; flex-wrap:wrap; }
.collisionWizardConditionForm select{ flex:1.3; min-width:120px; }
.collisionWizardConditionForm input{ flex:0.7; min-width:80px; }
/* Bulle "Appuie sur X" (voir forgeShowCollisionInteractBubble,
collision-rules-controller.js) : SŒUR de l'objet dans son parent
(.sceneWorld/.sceneUI, déjà position:relative) plutôt qu'ENFANT
@@ -956,10 +956,9 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d
white-space:nowrap; pointer-events:none; z-index:20;
}
/* ---- Éditeur de quêtes (onglet "🗺️ Quêtes" à côté de "🧩 Collision",
templates/scene_edit.html + modale de dialogue partagée avec
l'assistant "+ Action" de l'éditeur de collision, voir
static/js/quests/quest-editor.js) ---- */
/* ---- Reliquat de l'ancien onglet "🗺️ Quêtes" (retiré, voir onglet
"Déclencheurs" et static/js/triggers/trigger-editor.js) — règles
orphelines gardées telles quelles, aucune ne s'applique plus. ---- */
.questListRow{ cursor:pointer; }
.questPill{ display:inline-flex; align-items:center; border-radius:999px; padding:3px 10px; font-size:11.5px; font-weight:700; }
.questPill-score{ background:rgba(242,153,74,.18); color:#f2994a; }
@@ -984,33 +983,166 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d
.questDialogueModal{ z-index:61; }
.questDialogueModal .modal-content{ width:100vw; height:100vh; max-width:100vw; max-height:100vh; margin:0; }
/* La modale entière est une colonne flex (jamais un scroll global, voir
overflow:hidden ici) : le header/la fiche restent fixes en haut, les 3
colonnes de répliques se partagent TOUT le reste de la hauteur
(demandé explicitement) et défilent CHACUNE indépendamment (voir
.questDialogueColumnBubbles plus bas), jamais toute la page. */
overflow:hidden ici) : le header reste fixe en haut, le panneau de
déclencheurs prend TOUT le reste de la hauteur. */
.questDialogueModal .box{ width:100%; height:100%; border-radius:0; overflow:hidden; display:flex; flex-direction:column; }
.questDialogueModalContent{ width:100%; height:100%; max-width:100%; }
#questDialogueModalBody{ display:flex; flex-direction:column; flex:1; min-height:0; }
/* Onglet "Déclencheurs" (templates/scene_edit.html, #triggersTabPanel) —
MÊME comportement que #questDialogueModalBody ci-dessus (juste déplacé
hors de la modale plein écran dans un onglet, demande explicite :
"sans rien toucher au style en place") : colonne flex qui prend tout
l'espace du panneau d'onglet actif (voir .builderTabPanel.active). */
#triggersTabPanelBody{ display:flex; flex-direction:column; flex:1; min-height:0; }
.questDialogueModalHeader{ flex:0 0 auto; margin-bottom:10px; }
.questFieldsGrid{ display:grid; grid-template-columns:repeat(auto-fill, minmax(160px, 1fr)); gap:10px; margin-bottom:14px; }
.questField{ display:flex; flex-direction:column; gap:4px; font-size:12px; font-weight:600; }
.questField-wide{ grid-column:span 2; }
.questDialogueColumns{ flex:1; min-height:0; display:grid; grid-template-columns:repeat(3, minmax(220px, 1fr)); gap:14px; }
/* La modale de dialogue d'une quête est un GRAPHE DE LOGIQUE lu par
déclencheur (demande explicite : "un RH doit le comprendre, un visuel
global") : UN SEUL panneau (.questDialogueColumn, 90% de largeur,
centré, TOUTE LA HAUTEUR disponible) — les déclencheurs de la quête
côte à côte (voir .questTriggersSlot plus bas), chacun avec SON PROPRE
dialogue édité EN LIGNE (voir .dialogueBox plus bas), PUIS les 3
boutons Réplique/Question/Action TOUJOURS tout en bas — ils ajoutent
dans le dialogue du déclencheur ACTIF (voir .questTriggerCol.active),
jamais dans une liste séparée. */
.questDialogueColumns{ flex:1; min-height:0; display:flex; }
.questDialogueColumn{
background:rgba(0,0,0,.15); border:1px solid var(--border); border-radius:12px; padding:10px;
display:flex; flex-direction:column; gap:8px; height:100%; min-height:0;
width:90%; margin:0 auto; height:100%; min-height:0; display:flex; flex-direction:column; gap:8px;
background:var(--panel2); border:1px solid var(--border); border-radius:12px; padding:12px;
}
.questDialogueColumnTitle{ flex:0 0 auto; margin:0 0 2px; font-size:13px; text-align:center; }
/* Bulles alignées VERTICALEMENT, centrées dans la colonne, reliées par un
trait entre chacune (voir ::before sur .questBubbleRow ci-dessous) —
demandé explicitement plutôt qu'un alignement gauche/droite. Seule
CETTE zone défile (flex:1 + overflow-y:auto) — le titre de colonne et
le bouton "+ Réplique" restent fixes, en haut/en bas. */
.questDialogueColumnBubbles{ display:flex; flex-direction:column; align-items:center; flex:1; min-height:0; overflow-y:auto; padding-top:4px; }
.questDialogueColumnButtons{ flex:0 0 auto; display:flex; gap:8px; justify-content:center; margin-top:4px; }
.questDialogueColumnBody{ display:flex; flex-direction:column; align-items:center; flex:1; min-height:0; overflow-y:auto; gap:14px; padding-top:4px; }
.questDialogueColumnButtons{ flex:0 0 auto; display:flex; gap:8px; justify-content:center; flex-wrap:wrap; }
.questDialogueAddBtn{ align-self:center; padding:6px 14px; font-size:12px; }
.questQuestionAddBtn{ background:linear-gradient(135deg, #e0b93d, #b8890f); }
/* TOUS les déclencheurs du jeu (voir screens/rendering/
trigger_graph.py::list_all_triggers) : réutilise TELLES QUELLES
.collisionRuleChain/.collisionRuleLink (collision-rules-editor.js) pour
la chaîne de pastilles, juste une petite fiche objet au-dessus. Vit dans le MÊME
flux que les répliques (.questDialogueColumnBody ci-dessus), aucun
scroll ni cadre séparé ici — plusieurs chaînes possibles côte à côte
(flex-wrap). */
.questTriggersSlot{ width:100%; display:flex; flex-wrap:wrap; gap:20px; justify-content:center; }
.questTriggersSlot:empty::before{ content:"Aucun déclencheur pour l'instant — ajoutes-en un avec le bouton ci-dessous."; color:var(--muted); font-size:13px; }
/* Carte objet + pastilles empilées VERTICALEMENT, reliées par des flèches
(maquette validée avec l'utilisateur) — PAS la chaîne horizontale en
chevrons de l'éditeur de collision : deux présentations volontairement
différentes pour le même modèle de données sous-jacent. */
.questTriggerCol{ position:relative; display:flex; flex-direction:column; align-items:center; width:70%; }
/* Modale d'ajout de déclencheur (voir static/js/triggers/trigger-editor.js::
openAddTriggerModal) — au-dessus de l'onglet "Déclencheurs", jamais
recouverte. */
.questTriggerModal{ z-index:62; }
.questTriggerDelete{
position:absolute; top:-8px; right:2px; width:20px; height:20px; border-radius:50%;
background:var(--panel2); border:1px solid var(--border); color:var(--muted, #8b95a7);
font-size:10px; display:flex; align-items:center; justify-content:center; cursor:pointer; z-index:2;
}
.questTriggerDelete:hover{ background:#e0524d; color:#fff; border-color:#e0524d; }
.questTriggerObjectCard{
width:100%; background:var(--panel2); border:1px solid var(--border); border-radius:12px;
padding:10px; display:flex; flex-direction:column; align-items:center; gap:6px; cursor:pointer;
}
.questTriggerObjectCard img{ width:36px; height:36px; border-radius:8px; object-fit:cover; }
.questTriggerObjectCard .questTriggerObjectName{ font-weight:700; font-size:12.5px; text-align:center; }
/* Déclencheur ACTIF (voir questClickTriggerObjectCard) : c'est LUI qui
reçoit les prochaines répliques/questions ajoutées par les boutons du
bas de la colonne — surligné pour que ce soit visible sans avoir à
deviner. */
.questTriggerCol.active .questTriggerObjectCard{ border-color:#3bb3c9; box-shadow:0 0 0 2px rgba(59,179,201,.35); }
/* Accordéon (demande explicite : "ça retracte toute la logique associée") :
replié = seule la carte objet reste visible, chevron tourné. */
.questTriggerCol.collapsed{ width:auto; min-width:140px; }
.questTriggerObjectCard .questTriggerCollapseChevron{ font-size:10px; color:var(--muted); transition:transform .15s ease; }
.questTriggerCol.collapsed .questTriggerCollapseChevron{ transform:rotate(-90deg); }
.questTriggerConnector{ width:2px; height:16px; background:var(--border); position:relative; flex-shrink:0; }
.questTriggerConnector::after{
content:""; position:absolute; bottom:0; left:50%; transform:translate(-50%,45%);
border:5px solid transparent; border-top-color:var(--border);
}
/* Flèche menant à une condition — plus grande que les connecteurs
ordinaires (demande explicite : "la flèche qui part de interagir vers
le losange doit être plus grande"). */
.questTriggerConnector.tall{ height:32px; width:3px; }
.questTriggerConnector.tall::after{ border-width:7px; }
.questTriggerPill{ width:100%; border-radius:100px; padding:8px 14px; font-size:12px; font-weight:700; text-align:center; }
.questTriggerPill.trigger{ background:#f2994a; color:#1a1002; }
.questTriggerPill.interagir{ background:#d4a72c; color:#1a1002; }
.questTriggerPill.quete{ background:#9b59b6; color:#fff; }
.questTriggerPill.none{ background:transparent; border:1.5px dashed var(--border); color:var(--muted, #8b95a7); }
/* Rectangle "dialogue" — même largeur que le reste de la chaîne (100% de
son conteneur, comme les pastilles, maquette validée : "le rectangle
doit avoir la même width que les autres éléments"). Un bouton "+" en
haut à droite ajoute une réplique DIRECTEMENT dedans (voir
static/js/triggers/trigger-editor.js::triggerInlineAddBubble) — chaque
occurrence de "Déclencher ce dialogue" porte le sien, édité ici même,
à l'endroit où la flèche pointe (plus de colonne séparée). */
.dialogueBox{
width:100%; background:rgba(0,0,0,.18); border:1.5px solid #3bb3c9; border-radius:14px;
padding:10px; display:flex; flex-direction:column; gap:8px;
}
.dialogueBoxHeader{ display:flex; align-items:center; justify-content:space-between; gap:8px; }
.dialogueBoxTitle{ font-size:11px; font-weight:800; color:#3bb3c9; text-transform:uppercase; letter-spacing:.04em; }
.dialogueBoxActions{ display:flex; gap:4px; }
.addReplyBtn{
background:#3bb3c9; color:#04222a; border:none; border-radius:999px; padding:5px 14px;
font-size:12px; font-weight:800; line-height:1.4; cursor:pointer; flex-shrink:0;
}
.addReplyBtn:hover{ filter:brightness(1.1); }
.addQuestionBtn{ background:linear-gradient(135deg, #e0b93d, #b8890f); color:#1a1002; }
/* Feuille "Modifier une variable" (voir screens/rendering/collision_rules.py
::"variable") — même famille visuelle que .dialogueBox (rectangle
pleine largeur), bordure jaune pour la distinguer d'un dialogue au
premier coup d'œil, pas de bulles dedans. */
.questVariableAction{ border-color:#e0b93d; }
.questVariableActionLabel{ margin:0; font-size:13px; color:var(--text); }
/* "+ Ajouter une action" en bout de chaîne (demande explicite : "pouvoir
modifier un déclencheur pour lui ajouter des actions") — même largeur
que le reste de la chaîne, discret (pointillés) tant qu'on ne survole
pas. */
.questTriggerAddChainedActionBtn{
width:100%; background:transparent; border:1.5px dashed var(--border); color:var(--muted, #8b95a7);
}
.questTriggerAddChainedActionBtn:hover{ border-color:var(--accent); color:var(--accent); }
/* Losange "condition" — lecture ENTIÈREMENT VERTICALE (demande explicite :
"il faut tout mettre verticalement", le losange lui-même ne change
pas) : le losange reste petit et centré, la branche qui MÈNE à cette
quête descend tout droit en premier (flèche verte ou rouge selon
Vrai/Faux) vers "Déclencher ce dialogue" (pleine largeur, comme un
déclencheur sans condition) puis son rectangle de dialogue, PUIS
l'autre branche descend à son tour vers "Ne rien faire" — jamais deux
branches côte à côte. */
.questCondColumn{ width:100%; display:flex; flex-direction:column; align-items:center; }
/* Un VRAI losange (carré tourné à 45°, pas un rectangle aplati) — voir
demande explicite "je veut un vrai losange comme avant". */
.questCondDiamond{
width:150px; height:150px; margin:16px 0; transform:rotate(45deg);
background:#3bb3c9; color:#04222a; display:flex; align-items:center; justify-content:center; border-radius:16%;
box-shadow:0 4px 14px rgba(59,179,201,.35);
}
.questCondDiamond span{ transform:rotate(-45deg); font-size:11.5px; font-weight:800; text-align:center; line-height:1.3; padding:0 12px; }
/* Flèches Vrai/Faux plus grandes — demande explicite "c'est trop petit
trop tassé". */
.questCondArrowWrap{ position:relative; display:flex; flex-direction:column; align-items:center; width:100%; height:68px; }
.questCondArrowWrap svg{ width:34px; height:100%; overflow:visible; }
.questCondArrowLabel{
position:absolute; left:calc(50% + 26px); top:14px; z-index:2; border-radius:999px; padding:6px 16px;
font-size:12.5px; font-weight:800; white-space:nowrap;
}
.questCondArrowLabel.yes{ background:rgba(46,204,113,.18); color:#2ecc71; border:1px solid rgba(46,204,113,.4); }
.questCondArrowLabel.no{ background:rgba(224,82,77,.18); color:#e0524d; border:1px solid rgba(224,82,77,.4); }
.questTriggerObjectThumb{ width:40px; height:40px; border-radius:8px; overflow:hidden; background:var(--panel2); display:flex; align-items:center; justify-content:center; }
.questTriggerObjectThumb img{ width:100%; height:100%; object-fit:cover; }
.questTriggerObjectChoice{ align-items:center; }
.questTriggerObjectChoice.is-selected{ border-color:var(--accent); box-shadow:0 0 0 2px var(--accent); }
.questBubbleRow{ display:flex; justify-content:center; width:100%; position:relative; }
.questBubbleRow:not(:first-child)::before{
content:''; position:absolute; top:-14px; left:50%; transform:translateX(-50%);
@@ -1029,14 +1161,15 @@ body.builderBody > main.content{ flex:1 1 auto; min-height:0; overflow:hidden; d
.questBubble-pnj .questBubbleBody{ background:rgba(47,111,237,.16); }
.questBubble-joueur .questBubbleHeader{ background:#1e9e57; }
.questBubble-joueur .questBubbleBody{ background:rgba(46,204,113,.16); }
/* "❓ Question" (voir questQuestionBubbleHtml, static/js/quests/
quest-editor.js) — bulle JAUNE distincte des répliques de dialogue,
/* "❓ Question" (voir triggerQuestionBubbleHtml, static/js/triggers/
trigger-editor.js) — bulle JAUNE distincte des répliques de dialogue,
même chaîne/alignement vertical (.questBubbleRow). */
.questBubble-question .questBubbleHeader{ background:#b8890f; flex-wrap:wrap; row-gap:4px; }
.questBubble-question .questBubbleBody{ background:rgba(224,185,61,.14); }
.questQuestionHeaderField{
display:inline-flex; align-items:center; gap:4px; font-size:11px; font-weight:700; color:#fff; white-space:nowrap;
display:inline-flex; align-items:center; gap:6px; font-size:11px; font-weight:700; color:#fff; white-space:nowrap;
}
.questQuestionHeaderFieldLabel{ font-weight:700; text-transform:uppercase; font-size:10px; letter-spacing:.02em; opacity:.85; }
.questQuestionHeaderField input{
width:44px; background:rgba(0,0,0,.25); border:1px solid rgba(255,255,255,.3); border-radius:4px;
color:#fff; font:inherit; font-size:11px; padding:2px 4px;
@@ -1196,7 +1329,7 @@ code{ background:var(--panel2); border:1px solid var(--border); border-radius:4p
.builderTabBtn.active{ color:var(--accent); border-bottom-color:var(--accent); font-weight:600; }
/* overflow-y:auto : contrairement à "screen" (voir .canvasFrame plus haut,
qui défile LUI-MÊME), "collision-rules"/"quests"/"events" n'ont pas de
qui défile LUI-MÊME), "triggers"/"events" n'ont pas de
sous-conteneur dédié au défilement — sans ça, une liste plus haute que
l'espace disponible restait simplement coupée par .builderCanvasArea
(overflow:hidden), au lieu de défiler. */