// ---------- Éditeur de collision (nouvel onglet "🧩 Collision") ---------- // Une carte par objet ÉLIGIBLE (jamais un "fond", jamais le personnage // "joueur" — voir COLLISION_RULE_OBJECTS, construit par routes/scenes/ // scene_edit_view.py), listant ses règles "déclencheur -> action" déjà // posées + un bouton "+ Action" qui ouvre un assistant pas à pas (jamais // un vrai formulaire à plusieurs champs) : un clic choisit une option, la // carte suivante s'emboîte aussitôt. 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 = { quete: '🗺️ Déclencher une quête', attaque: '⚔️ Attaquer la cible', evenement: '📣 Déclencher un événement', interagir: '🔑 Interagir avec une touche', }; var COLLISION_TRIGGER_LABELS = { collision: '💥 À la collision', perimetre: '📍 Dans un périmètre' }; function collisionRuleObjectLabel(obj) { var kindLabel = obj.kind === 'personnage' ? (obj.role === 'ennemie' ? 'Ennemi' : 'PNJ') : 'Décor'; return (obj.name || kindLabel); } // Un maillon = un segment de la "chaîne de pièces" affichée dans la carte // (déclencheur, puis action, puis éventuellement sous-action) — texte // seul, la logique de lecture vit dans collisionRuleActionSummary/ // collisionRuleTriggerSummary ci-dessous. function collisionRuleTriggerSummary(rule) { if (rule.trigger === 'perimetre') return COLLISION_TRIGGER_LABELS.perimetre + ' (' + rule.perimetre_px + 'px)'; return COLLISION_TRIGGER_LABELS.collision; } function collisionRuleActionSummary(action) { if (action.type === 'quete') return COLLISION_ACTION_LABELS.quete + ' « ' + action.quete_id + ' »'; if (action.type === 'evenement') { var ev = CUSTOM_EVENTS_MAP[action.evenement_id]; return COLLISION_ACTION_LABELS.evenement + ' « ' + (ev ? ev.name : '?') + ' »'; } return COLLISION_ACTION_LABELS[action.type] || action.type; } // Une règle = une chaîne de "maillons" en forme de pièces qui s'emboîtent // (déclencheur -> action -> sous-action éventuelle), pas une liste plate — // chaque maillon porte un data-kind (couleur en CSS) et un délai d'entrée // en cascade (voir @keyframes collisionLinkIn, static/style.css). function collisionRuleChainHtml(rule, ruleIndex, objectId) { var links = [ { kind: 'trigger', text: collisionRuleTriggerSummary(rule) }, { kind: rule.action.type, text: collisionRuleActionSummary(rule.action) }, ]; var action = rule.action; while (action.type === 'interagir' && action.sub_action) { links.push({ kind: action.sub_action.type, text: collisionRuleActionSummary(action.sub_action) }); action = action.sub_action; } var linksHtml = links.map(function (link, i) { return '' + link.text.replace(/'; }).join(''); return ( '
' + '' + '' + '
' ); } function collisionRuleCardHtml(obj) { var rulesHtml = obj.rules.length ? obj.rules.map(function (rule, i) { return collisionRuleChainHtml(rule, i, obj.id); }).join('') : '

Aucune règle pour l\'instant.

'; return ( '
' + '
' + '
' + obj.thumbnail_html + '
' + '

' + collisionRuleObjectLabel(obj).replace(/' + '

' + '
' + rulesHtml + '
' + '' + '
' ); } function renderCollisionRuleCards() { var container = document.getElementById('collisionRulesCards'); if (!container) return; if (!COLLISION_RULE_OBJECTS.length) { container.innerHTML = '

Aucun objet éligible pour l\'instant — un décor, un ennemi ou un PNJ (jamais le joueur lui-même).

'; return; } // Chaque carte entre en cascade (voir @keyframes collisionCardIn, // static/style.css) plutôt que toutes en même temps. container.innerHTML = COLLISION_RULE_OBJECTS.map(function (obj, i) { return collisionRuleCardHtml(obj).replace('
' + '' + '' + '
'; } function collisionWizardChooseTrigger(trigger) { _collisionWizard.trigger = trigger; if (trigger === 'perimetre') { var pxEl = document.getElementById('collisionWizardPerimetrePx'); _collisionWizard.perimetrePx = (pxEl && parseFloat(pxEl.value)) || COLLISION_DEFAULT_PERIMETRE_PX; } 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_ACTION_TYPES.filter(function (t) { return t !== 'interagir'; }) : COLLISION_ACTION_TYPES; var buttons = types.map(function (type, i) { var split = collisionSplitEmojiLabel(COLLISION_ACTION_LABELS[type]); 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); } function collisionWizardSaveRule(action) { var rule = { trigger: _collisionWizard.trigger, action: action }; if (_collisionWizard.trigger === 'perimetre') rule.perimetre_px = _collisionWizard.perimetrePx; var obj = collisionRuleObjectById(_collisionWizard.objectId); obj.rules.push(rule); var objectId = _collisionWizard.objectId; closeCollisionRuleModal(); saveCollisionRules(objectId); } // Chaque type d'action demande éventuellement une précision avant de // pouvoir se finaliser (quête -> son id ; événement -> lequel) — "attaque" // n'a besoin de rien de plus, se finalise directement. function collisionWizardChooseAction(type, nested) { if (type === 'attaque' || type === 'interagir') { collisionWizardFinalizeAction({ type: type }, nested); return; } if (type === 'quete') { collisionWizardBodyEl().innerHTML = '

Identifiant de la quête (l\'éditeur de quête reste à construire — cette règle ne fait que mémoriser la référence pour l\'instant) :

' + '
' + '
' + '
' + '
'; return; } if (type === 'evenement') { var eventIds = Object.keys(CUSTOM_EVENTS_MAP); if (!eventIds.length) { collisionWizardBodyEl().innerHTML = '

Aucun événement personnalisé pour l\'instant — crée-en un dans l\'onglet "📣 Événements", puis reviens ici.

'; return; } var buttons = eventIds.map(function (id, i) { return ''; }).join(''); collisionWizardBodyEl().innerHTML = '

Quel événement ?

' + buttons + '
'; } } function collisionWizardSubmitQuete(nested) { var input = document.getElementById('collisionWizardQueteId'); var queteId = input && input.value.trim(); if (!queteId) { input.focus(); return; } collisionWizardFinalizeAction({ type: 'quete', quete_id: queteId }, nested); } document.addEventListener('DOMContentLoaded', function () { if (typeof COLLISION_RULE_OBJECTS !== 'undefined') renderCollisionRuleCards(); });