// ---------- 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);
}