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>
138 lines
8.0 KiB
Python
138 lines
8.0 KiB
Python
import json
|
||
|
||
from flask import render_template, request, abort, g
|
||
|
||
import auth
|
||
import db
|
||
import screens
|
||
|
||
|
||
def render_scene_edit(slug, screen_id, game):
|
||
"""Vue de l'éditeur de scène 2D — appelée par routes/screens/
|
||
screen_edit.py (le dispatcher, selon screen["kind"]), PAS une route
|
||
Flask elle-même : même URL/endpoint "screen_edit" pour les deux types
|
||
d'écran. Réutilise TEL QUEL tout ce qui est déjà générique côté logique
|
||
de flow/animation/variables/événements (voir screens/screens_repo/
|
||
ensure_schema.py — le type d'écran est une propriété PAR ÉCRAN) —
|
||
seuls les objets de scène et la palette de nœuds proposée diffèrent de
|
||
l'éditeur document."""
|
||
screen = screens.get_screen(slug, screen_id)
|
||
if not screen:
|
||
abort(404)
|
||
objects = screens.list_scene_objects(slug, screen_id)
|
||
for o in objects:
|
||
o["rendered_html"] = screens.render_scene_object(o)
|
||
|
||
# "Monde" (voir screens.resolve_scene_world_size, réutilisé aussi par
|
||
# la miniature de "Mes écrans", routes/games/game_dashboard.py) : un
|
||
# "fond" posé à sa taille RÉELLE (souvent plus grande que la scène,
|
||
# voir add_scene_object.py) rend visible/joignable, dans CET éditeur,
|
||
# tout ce qui dépasse la scène nominale plutôt que de le rogner (le
|
||
# glisser-déposer le permet déjà depuis le fix précédent — sans
|
||
# agrandir le canevas ici, cette partie restait invisible/injoignable
|
||
# à la souris). Le rectangle "🎥 Champ de la caméra" (voir
|
||
# templates/scene_edit.html) marque ensuite où s'arrête la scène
|
||
# nominale À L'INTÉRIEUR de ce monde plus grand.
|
||
world_width, world_height = screens.resolve_scene_world_size(screen, objects)
|
||
|
||
selected_id = request.args.get("selected", type=int)
|
||
personnage_data = None
|
||
personnage_commands = None
|
||
personnage_role = None
|
||
collision_settings = None
|
||
dialogue_box_style = None
|
||
selected_obj = next((o for o in objects if o["id"] == selected_id), None) if selected_id else None
|
||
_UI_WIDGET_KINDS = ("dialogue_box", "quiz_box", "score_widget")
|
||
if selected_obj:
|
||
if selected_obj["kind"] not in _UI_WIDGET_KINDS:
|
||
collision_settings = screens.resolve_collision_settings(selected_obj)
|
||
if selected_obj["kind"] == "personnage":
|
||
personnage_data = screens.resolve_personnage_data(selected_obj)
|
||
personnage_commands = screens.resolve_personnage_commands(selected_obj)
|
||
personnage_role = screens.resolve_personnage_role(selected_obj)
|
||
elif selected_obj["kind"] in _UI_WIDGET_KINDS:
|
||
dialogue_box_style = screens.resolve_dialogue_box_style(selected_obj)
|
||
|
||
_DEFAULT_OBJECT_LABELS = {"personnage": "Personnage", "fond": "Fond"}
|
||
object_labels = {
|
||
o["id"]: (o.get("name") or _DEFAULT_OBJECT_LABELS.get(o["kind"], "Décor"))
|
||
for o in objects
|
||
}
|
||
element_animations_map = {
|
||
o["id"]: screens.resolve_personnage_animations(o) for o in objects if o["kind"] == "personnage"
|
||
}
|
||
|
||
flow_blocks = screens.list_flow_blocks(slug, screen_id)
|
||
flow_nodes = screens.list_flow_nodes(slug, screen_id)
|
||
flow_edges = screens.list_flow_edges(slug, screen_id)
|
||
animation_clips = screens.list_animation_clips(slug, screen_id)
|
||
global_variables = db.list_global_variables(slug)
|
||
custom_events = db.list_custom_events(slug)
|
||
custom_event_usages = screens.list_custom_event_usages(slug)
|
||
custom_events_map = {e["id"]: {"name": e["name"]} for e in custom_events}
|
||
all_screens = screens.list_screens(slug)
|
||
screens_map = {s["id"]: s["name"] for s in all_screens}
|
||
blocks_view = screens.blocks_view(flow_blocks, flow_nodes, object_labels)
|
||
|
||
# Sprites animaux CraftPix (screens.ADMIN_SPRITE_LIBRARY) : réservés au
|
||
# rôle admin (licence CraftPix, voir core/sprite_gate.py et le plan
|
||
# "Bibliothèque de sprites animaux CraftPix").
|
||
sprite_library = screens.SPRITE_LIBRARY if g.current_user["role"] == "admin" else screens.PUBLIC_SPRITE_LIBRARY
|
||
sprite_library_families = screens.sprite_gallery_families(sprite_library)
|
||
# Images de fond CraftPix (screens.BACKGROUND_LIBRARY) : même règle
|
||
# d'accès que les sprites animaux — réservées au rôle admin.
|
||
background_library = screens.BACKGROUND_LIBRARY if g.current_user["role"] == "admin" else {}
|
||
# "Mes assets" (voir auth/user_assets_dir.py) : par COMPTE, jamais
|
||
# filtré par rôle contrairement à sprite_library/background_library
|
||
# ci-dessus (rien à voir avec le catalogue Forge réservé à l'admin).
|
||
user_assets = auth.list_user_assets(g.current_user["id"])
|
||
|
||
return render_template(
|
||
"scene_edit.html",
|
||
game=game, screen=screen, all_screens=all_screens,
|
||
world_width=world_width, world_height=world_height,
|
||
objects=objects, selected_id=selected_id, personnage_data=personnage_data,
|
||
personnage_commands=personnage_commands or screens.DEFAULT_PERSONNAGE_COMMANDS,
|
||
personnage_role=personnage_role or screens.DEFAULT_PERSONNAGE_ROLE,
|
||
personnage_role_choices=screens.PERSONNAGE_ROLES, personnage_role_labels=screens.PERSONNAGE_ROLE_LABELS,
|
||
collision_settings=collision_settings or screens.DEFAULT_COLLISION_SETTINGS,
|
||
dialogue_box_style=dialogue_box_style or screens.DEFAULT_DIALOGUE_BOX_STYLE,
|
||
dialogue_box_font_weight_choices=screens.FONT_WEIGHT_CHOICES,
|
||
sprite_library=sprite_library, sprite_library_families=sprite_library_families,
|
||
sprite_library_json=json.dumps(sprite_library),
|
||
background_library=background_library,
|
||
user_assets=user_assets,
|
||
element_animations_map_json=json.dumps(element_animations_map),
|
||
element_orientation_values=screens.ELEMENT_ORIENTATION_VALUES,
|
||
element_orientation_labels_json=json.dumps(screens.ELEMENT_ORIENTATION_LABELS),
|
||
element_visibility_values=screens.ELEMENT_VISIBILITY_VALUES,
|
||
element_visibility_labels_json=json.dumps(screens.ELEMENT_VISIBILITY_LABELS),
|
||
trigger_events=screens.TRIGGER_EVENTS_2D, action_type_labels=screens.ACTION_TYPE_LABELS_2D,
|
||
object_action_properties=screens.OBJECT_ACTION_PROPERTIES,
|
||
element_action_property_labels_json=json.dumps(dict(screens.OBJECT_ACTION_PROPERTIES)),
|
||
condition_operators=screens.CONDITION_OPERATORS,
|
||
data_operations=screens.DATA_OPERATIONS, data_operation_labels=screens.DATA_OPERATION_LABELS,
|
||
score_status_choices=db.SCORE_STATUS_CHOICES,
|
||
score_status_labels_json=json.dumps(db.SCORE_STATUS_LABELS),
|
||
data_operation_labels_json=json.dumps(screens.DATA_OPERATION_LABELS),
|
||
condition_operator_labels_json=json.dumps(screens.CONDITION_OPERATOR_LABELS),
|
||
action_type_labels_json=json.dumps(screens.ACTION_TYPE_LABELS_2D),
|
||
elements_labels_json=json.dumps(object_labels), screens_map_json=json.dumps(screens_map),
|
||
flow_nodes=flow_nodes, flow_edges=flow_edges,
|
||
flow_nodes_json=json.dumps(flow_nodes), flow_edges_json=json.dumps(flow_edges),
|
||
flow_blocks_json=json.dumps(flow_blocks), blocks_view=blocks_view,
|
||
animation_clips=animation_clips, animation_clips_json=json.dumps(animation_clips),
|
||
animate_css_catalog=screens.ANIMATE_CSS_CATALOG, animate_css_labels_json=json.dumps(screens.ANIMATE_CSS_LABELS),
|
||
global_variables=global_variables, global_variable_types=db.GLOBAL_VARIABLE_TYPES,
|
||
custom_events=custom_events, custom_event_usages=custom_event_usages,
|
||
custom_events_map_json=json.dumps(custom_events_map),
|
||
definitions=db.list_definitions(slug),
|
||
definitions_json=json.dumps({str(d["id"]): screens.data_definition_options(slug, d["id"]) for d in db.list_definitions(slug)}),
|
||
definitions_names_json=json.dumps({d["id"]: d["name"] for d in db.list_definitions(slug)}),
|
||
# "qui parle" dans un dialogue de déclencheur (voir "ℹ️
|
||
# Informations", static/js/triggers/trigger-editor.js) : les noms
|
||
# donnés à N'IMPORTE QUEL objet de scène (personnage, décor,
|
||
# fond), toutes scènes du jeu confondues.
|
||
scene_object_names_json=json.dumps(screens.list_named_scene_object_names(slug)),
|
||
)
|