Enrichit les declencheurs/actions de scene (clic/survol/affichage, surbrillance/video/son/visibilite/indication/attendre) et fiabilise la pose d'un fond/decor importe
Build and deploy / test-python (push) Successful in 10m59s
Build and deploy / test-js (push) Successful in 1m27s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

- 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:
william
2026-09-12 00:28:46 +02:00
co-authored by Claude Sonnet 5
parent b07b231a61
commit 559331f9cf
67 changed files with 4739 additions and 306 deletions
+54 -28
View File
@@ -17,36 +17,49 @@ def _game_objects_with_thumbnails(slug, screens_mod):
return result
from .collision_rules import LEAF_ACTION_TYPES
# Champs affichés pour chaque type de feuille (voir screens/rendering/
# collision_rules.py pour la forme exacte de chacune) — un simple filtre
# sur l'action déjà sanitizée, jamais une recopie de valeur : ajouter un
# type de feuille suffit à l'ajouter ici, sans dupliquer sa logique de
# sanitisation.
_LEAF_DISPLAY_FIELDS = {
"dialogue": ("dialogue",),
"variable": ("id", "target_variable", "data_operation", "data_value"),
"surbrillance": ("id", "object_id", "valeur"),
"visibilite": ("id", "object_id", "valeur"),
"son": ("id", "asset_url"),
"video": ("id", "asset_url", "mode"),
"indication": ("id", "object_id", "texte", "duree_ms"),
"attendre": ("id", "data_value"),
}
def _describe_leaf(action):
"""Une seule feuille ("dialogue" ou "variable", voir
screens/rendering/collision_rules.py) décrite pour l'affichage,
SANS son éventuel "then" (voir _describe_chain, qui aplatit la
chaîne complète)."""
if action.get("type") == "dialogue":
return {"type": "dialogue", "dialogue": action.get("dialogue")}
return {
"type": "variable",
"id": action.get("id"),
"target_variable": action.get("target_variable"),
"data_operation": action.get("data_operation"),
"data_value": action.get("data_value"),
}
"""Une seule feuille (voir LEAF_ACTION_TYPES, screens/rendering/
collision_rules.py) décrite pour l'affichage, SANS son éventuel
"then" (voir _describe_chain, qui aplatit la chaîne complète)."""
action_type = action.get("type")
fields = _LEAF_DISPLAY_FIELDS.get(action_type, ())
return {"type": action_type, **{f: action[f] for f in fields if f in action}}
def _describe_chain(action):
"""Aplatit une feuille + ses "then" successifs en une LISTE, dans
l'ordre d'exécution — voir maquette validée ("plusieurs actions
chaînées"). Liste vide si `action` n'est ni "dialogue" ni "variable"."""
chaînées"). Liste vide si `action` n'est pas une feuille reconnue
(voir LEAF_ACTION_TYPES)."""
chain = []
node = action
while isinstance(node, dict) and node.get("type") in ("dialogue", "variable"):
while isinstance(node, dict) and node.get("type") in LEAF_ACTION_TYPES:
chain.append(_describe_leaf(node))
node = node.get("then")
return chain
def _branch_info(branch):
if not isinstance(branch, dict) or branch.get("type") not in ("dialogue", "variable"):
if not isinstance(branch, dict) or branch.get("type") not in LEAF_ACTION_TYPES:
return None
return {"chain": _describe_chain(branch)}
@@ -54,19 +67,19 @@ def _branch_info(branch):
def _resolve_action_trigger(action, has_interagir=False):
"""Décrit ENTIÈREMENT un arbre d'action (voir screens/rendering/
collision_rules.py) pour l'affichage dans l'onglet "Déclencheurs" —
chaque feuille ("dialogue"/"variable") affiche DIRECTEMENT son propre
contenu, où qu'elle soit dans l'arbre (y compris CHAQUE branche d'une
"condition", indépendamment l'une de l'autre), et sa chaîne "then"
complète (voir _describe_chain). Retourne {has_interagir, chain,
condition} — `chain` est une LISTE (jamais None, vide si l'action ne
se termine par aucune feuille reconnue) ; `condition` est None sauf
pour une action "condition", auquel cas il porte {variable, operateur,
valeur, si_vrai, si_faux} où si_vrai/si_faux valent chacun None ("ne
rien faire") ou {chain: [...]}."""
chaque feuille affiche DIRECTEMENT son propre contenu, où qu'elle soit
dans l'arbre (y compris CHAQUE branche d'une "condition",
indépendamment l'une de l'autre), et sa chaîne "then" complète (voir
_describe_chain). Retourne {has_interagir, chain, condition} —
`chain` est une LISTE (jamais None, vide si l'action ne se termine
par aucune feuille reconnue) ; `condition` est None sauf pour une
action "condition", auquel cas il porte {variable, operateur, valeur,
si_vrai, si_faux} où si_vrai/si_faux valent chacun None ("ne rien
faire") ou {chain: [...]}."""
if not isinstance(action, dict):
return {"has_interagir": has_interagir, "chain": [], "condition": None}
action_type = action.get("type")
if action_type in ("dialogue", "variable"):
if action_type in LEAF_ACTION_TYPES:
return {"has_interagir": has_interagir, "chain": _describe_chain(action), "condition": None}
if action_type == "interagir":
return _resolve_action_trigger(action.get("sub_action") or {}, has_interagir=True)
@@ -96,6 +109,15 @@ def list_all_triggers(slug):
result = []
for obj in rule_objects:
# Rétro-remplissage des feuilles posées avant que "id" ne devienne
# systématique (ex. par l'IA, ce champ restant optionnel dans son
# schéma) — sans ça, "+ Ajouter une action"/le retrait/le
# déplacement restent silencieusement indisponibles sur ce bloc
# précis (voir screens/rendering/collision_rules.py::
# backfill_missing_leaf_ids). Persisté dès ce premier affichage,
# jamais régénéré ensuite.
if screens_mod.backfill_missing_leaf_ids(obj["rules"]):
screens_mod.set_scene_object_collision_rules(slug, obj["id"], obj["rules"])
for rule in obj["rules"]:
info = _resolve_action_trigger(rule["action"])
result.append({
@@ -119,7 +141,7 @@ def _collect_dialogue_lines_from_action(action, acc):
if isinstance(dialogue, dict):
acc.append(dialogue.get("lines") or [])
_collect_dialogue_lines_from_action(action.get("then"), acc)
elif action_type == "variable":
elif action_type in LEAF_ACTION_TYPES:
_collect_dialogue_lines_from_action(action.get("then"), acc)
elif action_type == "interagir":
_collect_dialogue_lines_from_action(action.get("sub_action"), acc)
@@ -145,6 +167,8 @@ def collect_all_dialogue_lines(slug):
for obj in screens_mod.list_scene_objects(slug, s["id"]):
for rule in screens_mod.resolve_collision_rules(obj):
_collect_dialogue_lines_from_action(rule.get("action"), acc)
for rule in screens_mod.resolve_screen_triggers(s):
_collect_dialogue_lines_from_action(rule.get("action"), acc)
return acc
@@ -157,7 +181,7 @@ def _collect_completable_ids_from_action(action, acc):
if action.get("mark_completed") and isinstance(dialogue, dict) and dialogue.get("id"):
acc.append(dialogue["id"])
_collect_completable_ids_from_action(action.get("then"), acc)
elif action_type == "variable":
elif action_type in LEAF_ACTION_TYPES:
_collect_completable_ids_from_action(action.get("then"), acc)
elif action_type == "interagir":
_collect_completable_ids_from_action(action.get("sub_action"), acc)
@@ -182,6 +206,8 @@ def list_completable_dialogue_ids(slug):
for obj in screens_mod.list_scene_objects(slug, s["id"]):
for rule in screens_mod.resolve_collision_rules(obj):
_collect_completable_ids_from_action(rule.get("action"), acc)
for rule in screens_mod.resolve_screen_triggers(s):
_collect_completable_ids_from_action(rule.get("action"), acc)
return acc