Enrichit les declencheurs/actions de scene (clic/survol/affichage, surbrillance/video/son/visibilite/indication/attendre) et fiabilise la pose d'un fond/decor importe
- Ajoute clic/survol/affichage-ecran comme declencheurs, et surbrillance, video, son, visibilite, indication, attendre comme actions, utilisables aussi bien par l'editeur manuel (menu lateral Objets/Ecran) que par Ruby (IA), avec blocs deplacables/supprimables dans une chaine. - Corrige plusieurs variantes du bug "impossible de poser un objet hors du champ de la camera" (troncature du chainage d'actions a 4 maillons, fond importe pose a 128x128 au lieu de sa taille reelle, decalage du fond au vrai glisser-depose, redimensionnement manuel jamais propage au monde). - Ajoute un vrai glisser-depose depuis la galerie vers la scene, la gestion complete de "Mes assets" (sous-sections Fonds/Decors/Sons/ Videos, suppression, reclassement fond<->decor sans re-upload). - Ajoute l'upload de son (limite 3 min) et de video (MP4 uniquement, limite 5 min), avec validation de la duree reelle du fichier, et une replique audio optionnelle dans une bulle de dialogue. - Fixe la taille de pose d'un objet/decor importe a 200x200 avec une boite de collision de 150x150. - Filtre le selecteur de fichier des actions son/video par type reel. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
b07b231a61
commit
559331f9cf
@@ -112,6 +112,42 @@ function forgeHideCollisionInteractBubble(objectId, objectEl) {
|
||||
if (bubble) bubble.style.display = 'none';
|
||||
}
|
||||
|
||||
// ---------- Action "indication" — bulle de texte libre, temporisée ----------
|
||||
// Distincte de la bulle "Appuie sur X" ci-dessus (sa propre classe CSS,
|
||||
// sa propre minuterie) : ne dépend jamais du cycle "actif tant que la
|
||||
// collision dure" d'un déclencheur "interagir", peut être posée sur
|
||||
// N'IMPORTE QUEL objet de l'écran (voir action.object_id, pas
|
||||
// nécessairement l'objet qui porte le déclencheur).
|
||||
var FORGE_INDICATION_DEFAULT_DURATION_MS = 3000;
|
||||
var forgeIndicationTimeouts = {};
|
||||
|
||||
function forgeIndicationBubbleEl(objectId, parentEl) {
|
||||
var bubble = parentEl.querySelector('.collisionIndicationBubble[data-for-object-id="' + objectId + '"]');
|
||||
if (!bubble) {
|
||||
bubble = document.createElement('div');
|
||||
bubble.className = 'collisionIndicationBubble';
|
||||
bubble.setAttribute('data-for-object-id', objectId);
|
||||
parentEl.appendChild(bubble);
|
||||
}
|
||||
return bubble;
|
||||
}
|
||||
|
||||
function forgeShowIndicationBubble(objectId, texte, dureeMs) {
|
||||
var targetEl = document.querySelector('[data-object-id="' + objectId + '"]');
|
||||
var parentEl = targetEl && targetEl.parentElement;
|
||||
if (!targetEl || !parentEl || !texte) return;
|
||||
var objRect = forgeElementBoxRect(targetEl);
|
||||
var bubble = forgeIndicationBubbleEl(objectId, parentEl);
|
||||
bubble.textContent = texte;
|
||||
bubble.style.display = 'block';
|
||||
bubble.style.left = (objRect.left + objRect.width / 2) + 'px';
|
||||
bubble.style.top = objRect.top + 'px';
|
||||
if (forgeIndicationTimeouts[objectId]) clearTimeout(forgeIndicationTimeouts[objectId]);
|
||||
forgeIndicationTimeouts[objectId] = setTimeout(function () {
|
||||
bubble.style.display = 'none';
|
||||
}, dureeMs || FORGE_INDICATION_DEFAULT_DURATION_MS);
|
||||
}
|
||||
|
||||
// Exécute une action (top-level, sub_action d'un "interagir", ou branche
|
||||
// si_vrai/si_faux d'une "condition") — jamais appelée pour un "interagir"
|
||||
// lui-même à ce niveau, voir forgeCollisionRuleTick qui le gère à part
|
||||
@@ -156,6 +192,40 @@ function forgeRunCollisionRuleAction(action, targetEl, obj) {
|
||||
});
|
||||
var branch = isTrue ? action.si_vrai : action.si_faux;
|
||||
if (branch) forgeRunCollisionRuleAction(branch, targetEl, obj);
|
||||
} else if (action.type === 'surbrillance' || action.type === 'visibilite') {
|
||||
// Cible N'IMPORTE QUEL objet de l'écran (action.object_id, pas
|
||||
// forcément celui qui porte le déclencheur) — réutilise TEL QUEL
|
||||
// applyObjectProperty (static/js/play/actions.js, équivalent scène 2D
|
||||
// d'applyElementProperty), plutôt que de dupliquer cette logique.
|
||||
var targetObjEl = document.querySelector('[data-object-id="' + action.object_id + '"]');
|
||||
if (targetObjEl) applyObjectProperty(targetObjEl, action.type, action.valeur);
|
||||
if (action.then) forgeRunCollisionRuleAction(action.then, targetEl, obj);
|
||||
} else if (action.type === 'son') {
|
||||
// Effet sonore ponctuel, non bouclé — même comportement que l'action
|
||||
// "Jouer un son" du Flow (static/js/play/actions.js), instantané :
|
||||
// `then` s'exécute tout de suite, sans attendre la fin du son.
|
||||
if (action.asset_url) new Audio(action.asset_url).play().catch(function () {});
|
||||
if (action.then) forgeRunCollisionRuleAction(action.then, targetEl, obj);
|
||||
} else if (action.type === 'video') {
|
||||
// Bloque la suite de la chaîne "then" jusqu'à la fin de la vidéo (ou
|
||||
// "Passer") — voir static/js/play/video-overlay.js, même esprit
|
||||
// qu'un dialogue ci-dessus.
|
||||
forgeShowVideoOverlay(action.asset_url, action.mode, function () {
|
||||
if (action.then) forgeRunCollisionRuleAction(action.then, targetEl, obj);
|
||||
});
|
||||
} else if (action.type === 'indication') {
|
||||
forgeShowIndicationBubble(action.object_id, action.texte, action.duree_ms);
|
||||
if (action.then) forgeRunCollisionRuleAction(action.then, targetEl, obj);
|
||||
} else if (action.type === 'attendre') {
|
||||
// Suspend la chaîne "then" pendant data_value SECONDES — même
|
||||
// vocabulaire que l'action "attendre" du Flow (static/js/play/
|
||||
// actions.js), utile ex. pour laisser un temps de lecture après une
|
||||
// narration à l'affichage de l'écran.
|
||||
var seconds = parseFloat(action.data_value);
|
||||
var ms = isNaN(seconds) ? 0 : Math.max(0, seconds) * 1000;
|
||||
setTimeout(function () {
|
||||
if (action.then) forgeRunCollisionRuleAction(action.then, targetEl, obj);
|
||||
}, ms);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,6 +244,11 @@ function forgeCollisionRuleTick(screenData) {
|
||||
|
||||
var anyInteractActive = false;
|
||||
rules.forEach(function (rule, ruleIndex) {
|
||||
// "clic"/"survol" sont liés à part (voir forgeBindClicSurvolRules,
|
||||
// écouteurs DOM directs) — jamais évalués ici, sans quoi ils
|
||||
// s'exécuteraient EN PLUS, à la proximité du joueur, en plus de
|
||||
// leur propre déclencheur.
|
||||
if (rule.trigger !== 'collision') return;
|
||||
var stateKey = forgeCollisionRuleStateKey(obj.id, ruleIndex);
|
||||
var isActive = forgeCollisionRuleTriggerActive(rule, objRect, playerRect);
|
||||
var wasActive = !!forgeCollisionRuleActiveState[stateKey];
|
||||
@@ -217,6 +292,36 @@ function forgeStartCollisionRuleInteractListener(screenData) {
|
||||
window.addEventListener('keydown', activeCollisionRuleInteractListener);
|
||||
}
|
||||
|
||||
// ---------- Déclencheurs "clic"/"survol" — écouteurs DOM directs ----------
|
||||
// Contrairement à "collision" (tick + proximité du joueur), ces deux
|
||||
// déclencheurs n'ont besoin NI d'un joueur NI d'un tick : un écouteur
|
||||
// posé une fois par objet suffit (voir screens/rendering/
|
||||
// collision_rules.py). Re-attachés à chaque affichage d'écran, comme le
|
||||
// reste de ce fichier.
|
||||
var activeClicSurvolListeners = [];
|
||||
|
||||
function forgeUnbindClicSurvolRules() {
|
||||
activeClicSurvolListeners.forEach(function (l) { l.el.removeEventListener(l.event, l.handler); });
|
||||
activeClicSurvolListeners = [];
|
||||
}
|
||||
|
||||
function forgeBindClicSurvolRules(screenData) {
|
||||
forgeUnbindClicSurvolRules();
|
||||
(screenData.elements || []).forEach(function (obj) {
|
||||
var rules = obj.collision_rules;
|
||||
if (!rules || !rules.length) return;
|
||||
var targetEl = document.querySelector('[data-object-id="' + obj.id + '"]');
|
||||
if (!targetEl) return;
|
||||
rules.forEach(function (rule) {
|
||||
if (rule.trigger !== 'clic' && rule.trigger !== 'survol') return;
|
||||
var eventName = rule.trigger === 'clic' ? 'click' : 'mouseenter';
|
||||
var handler = function () { forgeRunCollisionRuleAction(rule.action, targetEl, obj); };
|
||||
targetEl.addEventListener(eventName, handler);
|
||||
activeClicSurvolListeners.push({ el: targetEl, event: eventName, handler: handler });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function forgeStopCollisionRuleControllers() {
|
||||
if (activeCollisionRuleInterval) {
|
||||
clearInterval(activeCollisionRuleInterval);
|
||||
@@ -226,16 +331,20 @@ function forgeStopCollisionRuleControllers() {
|
||||
window.removeEventListener('keydown', activeCollisionRuleInteractListener);
|
||||
activeCollisionRuleInteractListener = null;
|
||||
}
|
||||
forgeUnbindClicSurvolRules();
|
||||
forgeCollisionRuleActiveState = {};
|
||||
}
|
||||
|
||||
// Même patron par écran que forgeStartPersonnageControllers — appelé
|
||||
// juste après lui (voir static/js/play/screens.js), n'a d'effet que si
|
||||
// l'écran a un joueur ET au moins un objet avec des règles.
|
||||
// juste après lui (voir static/js/play/screens.js). "clic"/"survol" sont
|
||||
// liés inconditionnellement (aucun joueur requis) ; le tick de collision
|
||||
// + l'écouteur "interagir" ne démarrent, eux, que si l'écran a un joueur
|
||||
// ET au moins un objet avec des règles.
|
||||
function forgeStartCollisionRuleControllers(screenData) {
|
||||
forgeStopCollisionRuleControllers();
|
||||
if (!screenData || screenData.kind !== 'jeu_2d') return;
|
||||
var elements = screenData.elements || [];
|
||||
forgeBindClicSurvolRules(screenData);
|
||||
var hasPlayer = elements.some(function (o) { return o.kind === 'personnage' && o.personnage_role === 'joueur'; });
|
||||
var hasRules = elements.some(function (o) { return o.collision_rules && o.collision_rules.length; });
|
||||
if (!hasPlayer || !hasRules) return;
|
||||
@@ -249,6 +358,7 @@ if (typeof module !== 'undefined' && module.exports) {
|
||||
forgeCollisionRuleTriggerActive, forgeRunCollisionRuleAction, forgeCollisionRuleTick,
|
||||
forgeStartCollisionRuleControllers, forgeStopCollisionRuleControllers,
|
||||
forgeCollisionRuleStateKey, forgeElementBoxRect, forgeExpandRect,
|
||||
forgeBindClicSurvolRules, forgeUnbindClicSurvolRules, forgeShowIndicationBubble,
|
||||
};
|
||||
const conditionsMod = require('./conditions.js');
|
||||
global.forgeCollisionRectFromBox = global.forgeCollisionRectFromBox || conditionsMod.forgeCollisionRectFromBox;
|
||||
@@ -259,4 +369,8 @@ if (typeof module !== 'undefined' && module.exports) {
|
||||
global.FORGE_PERSONNAGE_FPS = global.FORGE_PERSONNAGE_FPS || 8;
|
||||
const dialogueBoxMod = require('./dialogue-box-controller.js');
|
||||
global.forgeShowDialogueBox = global.forgeShowDialogueBox || dialogueBoxMod.forgeShowDialogueBox;
|
||||
const actionsMod = require('./actions.js');
|
||||
global.applyObjectProperty = global.applyObjectProperty || actionsMod.applyObjectProperty;
|
||||
const videoOverlayMod = require('./video-overlay.js');
|
||||
global.forgeShowVideoOverlay = global.forgeShowVideoOverlay || videoOverlayMod.forgeShowVideoOverlay;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user