Intègre un chat IA capable de manipuler la scène via les mêmes fonctions que l'éditeur manuel (objets, variables, déclencheurs, images générées), avec conversations multiples par écran façon Claude. Corrige au passage le rafraîchissement pjax hors-ordre, l'onglet IA/déclencheurs vide après sélection d'un objet, la comparaison de booléens dans les conditions, et le blocage du glisser-déposer hors du cadre caméra après un redimensionnement de fond par l'IA. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
97 lines
5.0 KiB
Python
97 lines
5.0 KiB
Python
import json
|
||
|
||
import db
|
||
|
||
from .ensure_scene_schema import ensure_scene_schema
|
||
|
||
_DEFAULT_PERSONNAGE_DATA = {"source": "forge", "forge_character": "male-adventurer"}
|
||
_KINDS = {"personnage", "decor", "fond", "dialogue_box", "quiz_box", "score_widget"}
|
||
# Widgets d'interface (voir "🖥️ Interface", screens/rendering/dialogue_box_style.py) :
|
||
# assez grands pour lire une réplique/question sans redimensionner,
|
||
# jamais confondus visuellement avec un personnage/décor (128×128 par
|
||
# défaut). "score_widget" reste petit — juste un nombre.
|
||
_DIALOGUE_BOX_DEFAULT_SIZE = (320, 150)
|
||
_QUIZ_BOX_DEFAULT_SIZE = (340, 240)
|
||
_SCORE_WIDGET_DEFAULT_SIZE = (140, 50)
|
||
|
||
|
||
def add_scene_object(slug, scene_id, kind="personnage", forge_character=None, background_slug=None, image_url=None):
|
||
"""Pose un nouvel objet sur une scène (voir ensure_scene_schema.py).
|
||
kind="personnage" : attributes._personnage_data, MÊME structure/mêmes
|
||
fonctions resolve_personnage_* que le widget "personnage" de l'éditeur
|
||
document (screens/rendering/personnage_data.py) — un objet de scène
|
||
"personnage" partage donc directement la bibliothèque Forge et le
|
||
système d'animation déjà écrits. kind="decor" : une simple image fixe
|
||
(attributes.src), pas d'animation. kind="fond" : même image fixe que
|
||
"decor" (render_scene_object.py ne les distingue pas au rendu), mais
|
||
posée en dessous de tout le reste (z_index très négatif, voir
|
||
static/style.css .sceneBackground) et à la taille RÉELLE de l'image
|
||
choisie (voir screens/labels/background_library.py) par défaut — une
|
||
image plus grande que la scène devient alors le "monde" que la caméra
|
||
suit en défilant (voir static/js/play/personnage-controller.js).
|
||
|
||
image_url (kind "fond"/"decor" uniquement) : une image ARBITRAIRE
|
||
(voir "Mes assets", auth/user_assets/) plutôt qu'une clé de
|
||
BACKGROUND_LIBRARY — prioritaire sur background_slug si les deux sont
|
||
fournis. Pas de largeur/hauteur connues d'avance ici (contrairement à
|
||
BACKGROUND_LIBRARY qui les documente) : retombe sur les colonnes par
|
||
défaut de _scene_objects, exactement comme un "decor" ordinaire."""
|
||
ensure_scene_schema(slug)
|
||
kind = kind if kind in _KINDS else "personnage"
|
||
attributes = {}
|
||
width = height = None
|
||
conn = db.connect(slug)
|
||
if kind == "personnage":
|
||
data = dict(_DEFAULT_PERSONNAGE_DATA)
|
||
if forge_character:
|
||
from ..labels.sprite_library import PUBLIC_SPRITE_LIBRARY
|
||
from ..labels.animal_sprite_library import ADMIN_SPRITE_LIBRARY
|
||
if forge_character in PUBLIC_SPRITE_LIBRARY or forge_character in ADMIN_SPRITE_LIBRARY:
|
||
data["forge_character"] = forge_character
|
||
attributes["_personnage_data"] = json.dumps(data)
|
||
# Rôle "pnj" explicite (demande explicite : un personnage fraîchement
|
||
# posé n'est JAMAIS le joueur par défaut, même le premier de la
|
||
# scène — voir screens/rendering/personnage_role.py, "🏷️ Rôle" dans
|
||
# le panneau de propriétés pour le passer en "Joueur" à la main).
|
||
# Un rôle non défini retomberait sur "joueur" par défaut (voir
|
||
# resolve_personnage_role) : sans cette affectation explicite, un
|
||
# personnage fraîchement posé se ferait donc passer pour le joueur
|
||
# et disparaîtrait à tort de "🧩 Collision" (voir scene_edit_view.py,
|
||
# qui exclut toujours le joueur).
|
||
attributes["_personnage_role"] = "pnj"
|
||
elif kind in ("fond", "decor") and image_url:
|
||
attributes["src"] = image_url
|
||
elif kind == "fond" and background_slug:
|
||
from ..labels.background_library import BACKGROUND_LIBRARY
|
||
background = BACKGROUND_LIBRARY.get(background_slug)
|
||
if background:
|
||
attributes["src"] = background["url"]
|
||
width, height = background["width"], background["height"]
|
||
elif kind == "dialogue_box":
|
||
width, height = _DIALOGUE_BOX_DEFAULT_SIZE
|
||
elif kind == "quiz_box":
|
||
width, height = _QUIZ_BOX_DEFAULT_SIZE
|
||
elif kind == "score_widget":
|
||
width, height = _SCORE_WIDGET_DEFAULT_SIZE
|
||
if kind == "fond":
|
||
z_index = -1000
|
||
else:
|
||
max_z = conn.execute(
|
||
"SELECT MAX(z_index) AS m FROM _scene_objects WHERE scene_id = ?", (scene_id,)
|
||
).fetchone()["m"]
|
||
z_index = (max_z or 0) + 1
|
||
cols = ["scene_id", "kind", "attributes", "z_index"]
|
||
values = [scene_id, kind, json.dumps(attributes), z_index]
|
||
if width is not None:
|
||
# x=0/y=0 (jamais le défaut 100/100, voir ensure_scene_schema.py) :
|
||
# un fond commence au coin haut-gauche de la scène/du monde, pas
|
||
# décalé arbitrairement comme un objet ordinaire.
|
||
cols += ["width", "height", "x", "y"]
|
||
values += [width, height, 0, 0]
|
||
placeholders = ", ".join(["?"] * len(values))
|
||
conn.execute(f"INSERT INTO _scene_objects ({', '.join(cols)}) VALUES ({placeholders})", values)
|
||
object_id = conn.execute("SELECT last_insert_rowid() AS id").fetchone()["id"]
|
||
conn.commit()
|
||
conn.close()
|
||
return object_id
|