// ---------- Assistant "déclencheur -> action" (fusionné dans l'onglet "Déclencheurs") ---------- // L'onglet "🧩 Collision" séparé a été retiré (redondant une fois la // vue/gestion des déclencheurs intégrée à l'onglet "Déclencheurs", voir // static/js/triggers/trigger-editor.js::openAddTriggerModal) — ce // fichier ne porte plus que le MOTEUR de l'assistant lui-même // (déclencheur -> action, un clic choisit une option, la carte suivante // s'emboîte aussitôt), invoqué exclusivement via openTriggerWizard() // ci-dessous. Voir screens/rendering/collision_rules.py pour la forme // exacte d'une règle (côté serveur, où elle est validée une seconde fois // avant d'être enregistrée). var COLLISION_ACTION_LABELS = { dialogue: 'Déclencher ce dialogue', variable: 'Modifier une variable', interagir: 'Interagir avec une touche', condition: 'Si une condition est vraie', }; var COLLISION_WIZARD_ACTION_TYPES = ['dialogue', 'variable', 'interagir', 'condition']; var COLLISION_TRIGGER_LABELS = { collision: 'À la collision' }; // Opérations qui n'ont besoin d'AUCUNE valeur (voir screens/labels/ // data_operations.py::DATA_OPERATIONS) — le champ valeur de l'étape // "Modifier une variable" (renderCollisionWizardVariableStep) se masque // pour celles-ci. var COLLISION_NO_VALUE_OPERATIONS = ['definir_bool_vrai', 'definir_bool_faux', 'basculer_bool']; // obj.thumbnail_html (screens.render_scene_object) porte un style inline // PENSÉ POUR LE CANEVAS (position:absolute + left/top en dur, voir // screens/scenes/render_scene_object.py) — un personnage déplacé loin // dans la scène (left/top de plusieurs centaines de px) ressortait donc // hors du petit cadre de la miniature : la règle CSS `.questTriggerObjectCard // img`/`.questTriggerObjectThumb img` (static/style.css) est censée le // neutraliser, mais s'appuyer sur un forçage CSS pour annuler un style // inline entier reste fragile. Plus robuste : reconstruire ici une balise // "propre", sans AUCUN style hérité du canevas — seul le src // (l'image elle-même) est repris. Utilisée par static/js/triggers/ // trigger-editor.js (chaîne de bulles + grille de choix d'objet). function collisionRuleThumbHtml(obj) { var match = /\ssrc="([^"]*)"/.exec(obj.thumbnail_html || ''); return match ? '' : ''; } // Id court généré CÔTÉ CLIENT pour une NOUVELLE feuille ("dialogue" ou // "variable", voir screens/rendering/collision_rules.py) — jamais // recalculé/réattribué côté serveur. Pour un dialogue, sert de // dialogue.id (édité en ligne, voir trigger-editor.js) ; pour une // variable, sert d'id à la feuille elle-même — dans les deux cas, // permet d'adresser CETTE feuille précise pour y attacher un "then" // plus tard (voir "+ Ajouter une action", trigger-editor.js). function collisionGenerateActionId() { return 'd_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6); } // ---------- Assistant "déclencheur -> action" ---------- // État du parcours en cours — un seul à la fois (la modale n'autorise // qu'une règle en construction) : l'objet ciblé, le déclencheur choisi, // et l'action en cours de construction (posée directement dans // `action`, ou dans `action.sub_action` au second niveau pour "interagir"). // Plus de "quête cible" à connaître d'avance — la règle est posée sur // l'objet, un déclencheur "dialogue" se suffit à lui-même. var _collisionWizard = null; // Appelée après avoir choisi l'objet dans la modale d'ajout de déclencheur // (voir triggerSelectObject, trigger-editor.js). function openTriggerWizard(objectId, bodyEl) { _collisionWizard = { objectId: objectId, trigger: null, bodyEl: bodyEl }; renderCollisionWizardTriggerStep(); } function collisionWizardBodyEl() { return _collisionWizard.bodyEl; } // Étape 1 — déclencheur (un seul choix pour l'instant, "collision") : la // carte à choix reste affichée telle quelle (plutôt que sautée // automatiquement) pour rester extensible à de futurs types sans rien // changer à la structure de l'assistant. function renderCollisionWizardTriggerStep() { collisionWizardBodyEl().innerHTML = '

Cette règle se déclenche...

' + '
' + '' + '
'; } function collisionWizardChooseTrigger(trigger) { _collisionWizard.trigger = trigger; renderCollisionWizardActionStep(false); } // Étape 2 (et, pour "interagir", étape 3 en second niveau — `nested` // distingue les deux, "interagir" n'étant proposé qu'au premier niveau, // voir screens/rendering/collision_rules.py::_MAX_ACTION_DEPTH) : // quelle action exécuter. function renderCollisionWizardActionStep(nested) { var types = nested ? COLLISION_WIZARD_ACTION_TYPES.filter(function (t) { return t !== 'interagir'; }) : COLLISION_WIZARD_ACTION_TYPES; var buttons = types.map(function (type, i) { return ''; }).join(''); collisionWizardBodyEl().innerHTML = '

' + (nested ? 'Une fois la touche pressée...' : 'Alors...') + '

' + '
' + buttons + '
'; } function collisionWizardFinalizeAction(action, nested) { if (nested) { // Deuxième niveau ("interagir" -> sous-action) : referme la maille // sur l'action de premier niveau, déjà mémorisée sur le wizard. _collisionWizard.pendingInteragir.sub_action = action; collisionWizardSaveRule(_collisionWizard.pendingInteragir); return; } if (action.type === 'interagir') { // "interagir" n'est jamais final : une carte supplémentaire s'emboîte // pour choisir CE QUI se passe une fois la touche pressée. _collisionWizard.pendingInteragir = action; renderCollisionWizardActionStep(true); return; } collisionWizardSaveRule(action); } // Enregistre via la route /game//triggers (routes/triggers/triggers.py) // — même contrat "liste ENTIÈRE" que l'ancien éditeur de collision // (recharge les règles existantes de l'objet, y ajoute la nouvelle, // sauvegarde tout), géré côté serveur : le client ne poste que la // nouvelle règle + l'id d'objet. function collisionWizardSaveRule(action) { var rule = { trigger: _collisionWizard.trigger, action: action }; closeTriggerModal(); fetch('/game/' + GAME_SLUG + '/triggers', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ object_id: _collisionWizard.objectId, rule: rule }), }).then(function () { triggerLoadAll(); }); } // "dialogue"/"variable" (des FEUILLES, voir screens/rendering/ // collision_rules.py) enchaînent sur "Ensuite ?" (voir // renderCollisionWizardChainStep — Marquer terminé + chaîner une autre // action, demande explicite : "pouvoir modifier un déclencheur pour lui // ajouter des actions") ; "interagir" enchaîne sur une carte de plus // (voir collisionWizardFinalizeAction) ; "condition" enchaîne sur son // propre mini-formulaire (voir renderCollisionWizardConditionStep). function collisionWizardChooseAction(type, nested) { if (type === 'interagir') { collisionWizardFinalizeAction({ type: type }, nested); return; } if (type === 'dialogue') { var leaf = { type: 'dialogue', dialogue: { id: collisionGenerateActionId(), lines: [] } }; renderCollisionWizardChainStep(leaf, function (finalLeaf) { collisionWizardFinalizeAction(finalLeaf, nested); }); return; } if (type === 'variable') { renderCollisionWizardVariableStep(function (action) { renderCollisionWizardChainStep(action, function (finalLeaf) { collisionWizardFinalizeAction(finalLeaf, nested); }); }); return; } if (type === 'condition') { renderCollisionWizardConditionStep(nested); } } // ---------- Étape "Modifier une variable" ---------- // Même vocabulaire EXACT que l'action "Modifier une variable" du graphe // de logique document (GLOBAL_VARIABLE_NAMES et DATA_OPERATION_LABELS_MAP // déjà posés par templates/scene_edit.html — ce dernier n'était utilisé // nulle part avant cette fonctionnalité) : variable ciblée, opération, // valeur (masquée pour les opérations qui n'en ont pas besoin, voir // COLLISION_NO_VALUE_OPERATIONS). `onChoose(action)`. function renderCollisionWizardVariableStep(onChoose) { var variableOptions = (typeof GLOBAL_VARIABLE_NAMES !== 'undefined' ? GLOBAL_VARIABLE_NAMES : []) .map(function (name) { return ''; }).join(''); var operationOptions = Object.keys(DATA_OPERATION_LABELS_MAP).map(function (key) { return ''; }).join(''); collisionWizardBodyEl().innerHTML = '

Modifier...

' + '
' + '' + '' + '' + '
' + ''; _collisionWizard.onVariableChosen = onChoose; collisionWizardVariableOperationChanged(); } function collisionWizardVariableOperationChanged() { var operation = document.getElementById('varWizOperation').value; var valueInput = document.getElementById('varWizValue'); valueInput.style.display = COLLISION_NO_VALUE_OPERATIONS.indexOf(operation) === -1 ? '' : 'none'; } function collisionWizardVariableNext() { var operation = document.getElementById('varWizOperation').value; var action = { type: 'variable', id: collisionGenerateActionId(), target_variable: document.getElementById('varWizVariable').value, data_operation: operation, }; if (COLLISION_NO_VALUE_OPERATIONS.indexOf(operation) === -1) { action.data_value = document.getElementById('varWizValue').value; } var onChoose = _collisionWizard.onVariableChosen; _collisionWizard.onVariableChosen = null; onChoose(action); } // ---------- Étape "Ensuite ?" (chaînage + Marquer terminé) ---------- // Après CHAQUE feuille construite (dialogue ou variable, au premier // niveau, sous "interagir", ou dans une branche de condition) : la case // "Marquer terminé" (seulement pour un dialogue) PUIS le choix de // chaîner une autre action à la suite (voir screens/rendering/ // collision_rules.py::"then", demande explicite "pouvoir modifier un // déclencheur pour lui ajouter des actions") — jamais bloqué à une seule // action par déclencheur. `onDone(leafAction)` reçoit la PREMIÈRE // feuille de la chaîne (les suivantes sont attachées via `.then`). function renderCollisionWizardChainStep(leafAction, onDone) { var completionHtml = leafAction.type === 'dialogue' ? '' : ''; collisionWizardBodyEl().innerHTML = completionHtml + '

Ensuite ?

' + '
' + '' + '' + '' + '
'; _collisionWizard.chainLeaf = leafAction; _collisionWizard.onChainDone = onDone; } function collisionWizardChainDone(choice) { var leafAction = _collisionWizard.chainLeaf; var onDone = _collisionWizard.onChainDone; if (leafAction.type === 'dialogue') { if (document.getElementById('condWizMarkCompleted').checked) leafAction.mark_completed = true; else delete leafAction.mark_completed; } if (choice === 'none') { onDone(leafAction); return; } if (choice === 'dialogue') { var nextLeaf = { type: 'dialogue', dialogue: { id: collisionGenerateActionId(), lines: [] } }; leafAction.then = nextLeaf; renderCollisionWizardChainStep(nextLeaf, onDone); return; } // 'variable' renderCollisionWizardVariableStep(function (action) { leafAction.then = action; renderCollisionWizardChainStep(action, onDone); }); } // Étape "condition" — variable/opérateur/valeur (même vocabulaire que le // nœud Condition de l'éditeur de logique, voir screens/flow/constants.py // ::CONDITION_OPERATORS ; GLOBAL_VARIABLE_NAMES et // CONDITION_OPERATOR_LABELS_MAP posés par templates/scene_edit.html). // `nested` (interagir -> condition) est reporté jusqu'à la finalisation, // une fois les deux branches Vrai/Faux choisies ci-dessous. function renderCollisionWizardConditionStep(nested) { var variableOptions = (typeof GLOBAL_VARIABLE_NAMES !== 'undefined' ? GLOBAL_VARIABLE_NAMES : []) .map(function (name) { return ''; }).join(''); var operatorOptions = Object.keys(CONDITION_OPERATOR_LABELS_MAP).map(function (key) { return ''; }).join(''); collisionWizardBodyEl().innerHTML = '

Si...

' + '
' + '' + '' + '' + '
' + ''; } function collisionWizardConditionNext(nested) { _collisionWizard.pendingCondition = { type: 'condition', variable: document.getElementById('condWizVariable').value, operateur: document.getElementById('condWizOperator').value, valeur: document.getElementById('condWizValue').value, si_vrai: null, si_faux: null, }; _collisionWizard.pendingConditionNested = nested; renderCollisionWizardBranchStep('si_vrai'); } // Étape branche (Vrai puis Faux) — chaque branche est soit "Ne rien // faire", soit une FEUILLE ("Déclencher un dialogue" ou "Modifier une // variable", voir screens/rendering/collision_rules.py:: // _sanitize_condition_branch : une branche reste une feuille, jamais une // nouvelle "interagir"/"condition" imbriquée) — les DEUX branches // peuvent chacune déclencher LEUR PROPRE feuille (et sa propre chaîne), // indépendamment l'une de l'autre. function renderCollisionWizardBranchStep(branchKey) { var label = branchKey === 'si_vrai' ? '✓ Si VRAI...' : '✕ Si FAUX...'; collisionWizardBodyEl().innerHTML = '

' + label + '

' + '
' + '' + '' + '' + '
'; } function collisionWizardChooseBranch(branchKey, choice) { if (choice === 'none') { _collisionWizard.pendingCondition[branchKey] = null; collisionWizardBranchDone(branchKey); return; } if (choice === 'dialogue') { var leaf = { type: 'dialogue', dialogue: { id: collisionGenerateActionId(), lines: [] } }; renderCollisionWizardChainStep(leaf, function (finalLeaf) { _collisionWizard.pendingCondition[branchKey] = finalLeaf; collisionWizardBranchDone(branchKey); }); return; } // 'variable' renderCollisionWizardVariableStep(function (action) { renderCollisionWizardChainStep(action, function (finalLeaf) { _collisionWizard.pendingCondition[branchKey] = finalLeaf; collisionWizardBranchDone(branchKey); }); }); } function collisionWizardBranchDone(branchKey) { if (branchKey === 'si_vrai') { renderCollisionWizardBranchStep('si_faux'); return; } var nested = _collisionWizard.pendingConditionNested; var condition = _collisionWizard.pendingCondition; _collisionWizard.pendingCondition = null; collisionWizardFinalizeAction(condition, nested); }