Remplace le système de quêtes par des déclencheurs, ajoute l'action variable et le chaînage
Supprime le concept de "quête" au profit d'un onglet unique "Déclencheurs"
portant toute la logique (dialogue, condition, marquage terminé) directement
sur l'objet de scène. Ajoute une nouvelle action "Modifier une variable"
(réutilisant le vocabulaire du graphe de flow) utilisable après une
collision, une interaction ou une branche de condition, ainsi qu'un
chaînage d'actions ("then") permettant d'enchaîner plusieurs actions à la
suite et d'étendre un déclencheur déjà posé sans le recréer.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
c504ace167
commit
d664ed5637
+1
-1
@@ -1 +1 @@
|
||||
from . import auth, games, screens, flow, uploads, play, animations, global_vars, publish, custom_events, flow_blocks, scenes, onboarding, quests
|
||||
from . import auth, games, screens, flow, uploads, play, animations, global_vars, publish, custom_events, flow_blocks, scenes, onboarding, triggers
|
||||
|
||||
@@ -7,12 +7,13 @@ from core.flask_app import app
|
||||
|
||||
@app.route("/game/<slug>/variables/new", methods=["POST"])
|
||||
def create_global_var(slug):
|
||||
"""Depuis la page de gestion des variables (game_variables.html) —
|
||||
idempotent par nom (voir db.create_global_variable), donc sans risque en
|
||||
cas de double soumission."""
|
||||
"""Depuis l'onglet "Variables" de l'éditeur de scène (screen_edit.html)
|
||||
— idempotent par nom (voir db.create_global_variable), donc sans
|
||||
risque en cas de double soumission."""
|
||||
name = (request.form.get("name") or "").strip()
|
||||
var_type = request.form.get("type") or "texte"
|
||||
default_value = request.form.get("default_value") or ""
|
||||
screen_id = request.form.get("screen_id", type=int)
|
||||
# État par joueur (Phase 1) : coché par défaut (une case DÉCOCHÉE
|
||||
# explicitement rend la variable partagée par tous les joueurs, voir
|
||||
# db/global_vars/create_global_variable.py) — absent du formulaire
|
||||
@@ -20,4 +21,6 @@ def create_global_var(slug):
|
||||
per_player = request.form.get("per_player", "1") == "1"
|
||||
if name:
|
||||
db.create_global_variable(slug, name, var_type, default_value, per_player=per_player)
|
||||
return redirect(url_for("game_dashboard", slug=slug, tab="variables"))
|
||||
if screen_id:
|
||||
return redirect(url_for("screen_edit", slug=slug, screen_id=screen_id, tab="variables"))
|
||||
return redirect(url_for("game_dashboard", slug=slug))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from flask import redirect, url_for
|
||||
from flask import request, redirect, url_for
|
||||
|
||||
import db
|
||||
|
||||
@@ -7,5 +7,8 @@ from core.flask_app import app
|
||||
|
||||
@app.route("/game/<slug>/variables/<int:var_id>/delete", methods=["POST"])
|
||||
def global_var_delete(slug, var_id):
|
||||
screen_id = request.form.get("screen_id", type=int)
|
||||
db.delete_global_variable_by_id(slug, var_id)
|
||||
return redirect(url_for("game_dashboard", slug=slug, tab="variables"))
|
||||
if screen_id:
|
||||
return redirect(url_for("screen_edit", slug=slug, screen_id=screen_id, tab="variables"))
|
||||
return redirect(url_for("game_dashboard", slug=slug))
|
||||
|
||||
@@ -9,5 +9,8 @@ from core.flask_app import app
|
||||
def global_var_edit(slug, var_id):
|
||||
var_type = request.form.get("type") or "texte"
|
||||
value = request.form.get("value") or ""
|
||||
screen_id = request.form.get("screen_id", type=int)
|
||||
db.update_global_variable(slug, var_id, var_type, value)
|
||||
return redirect(url_for("game_dashboard", slug=slug, tab="variables"))
|
||||
if screen_id:
|
||||
return redirect(url_for("screen_edit", slug=slug, screen_id=screen_id, tab="variables"))
|
||||
return redirect(url_for("game_dashboard", slug=slug))
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
from . import quests_list_json, quest_new, quest_update, quest_delete, quest_dialogues
|
||||
@@ -1,11 +0,0 @@
|
||||
from flask import jsonify
|
||||
|
||||
import db
|
||||
|
||||
from core.flask_app import app
|
||||
|
||||
|
||||
@app.route("/game/<slug>/quests/<int:quest_id>/delete", methods=["POST"])
|
||||
def quest_delete(slug, quest_id):
|
||||
db.delete_quest(slug, quest_id)
|
||||
return jsonify({"ok": True})
|
||||
@@ -1,34 +0,0 @@
|
||||
from flask import jsonify, request
|
||||
|
||||
import db
|
||||
|
||||
from core.flask_app import app
|
||||
|
||||
|
||||
@app.route("/game/<slug>/quests/<int:quest_id>/dialogues", methods=["POST"])
|
||||
def quest_dialogues(slug, quest_id):
|
||||
"""Toujours les 3 colonnes ENTIÈRES (voir db.set_quest_dialogues) —
|
||||
le client (renderQuestDialogueColumns, quest-editor.js) poste l'objet
|
||||
complet à chaque modification plutôt qu'une réplique isolée.
|
||||
|
||||
Refusé (400) si la somme des récompenses des questions ("❓ Question")
|
||||
dépasse la récompense totale de la quête (recompense_score) — voir
|
||||
db.quest_dialogues_total_reward. Validé ICI (avant persistance)
|
||||
plutôt que dans db.set_quest_dialogues, pour renvoyer un message
|
||||
d'erreur explicite au lieu d'enregistrer silencieusement un état
|
||||
incohérent."""
|
||||
quest = db.get_quest(slug, quest_id)
|
||||
if quest is None:
|
||||
return jsonify({"error": "quête introuvable"}), 404
|
||||
body = request.get_json(silent=True) or {}
|
||||
sanitized = db.sanitize_quest_dialogues(body.get("dialogues"))
|
||||
allocated = db.quest_dialogues_total_reward(sanitized)
|
||||
if allocated > quest["recompense_score"]:
|
||||
return jsonify({
|
||||
"error": (
|
||||
f"La somme des récompenses des questions ({allocated} pts) dépasse "
|
||||
f"la récompense totale de la quête ({quest['recompense_score']} pts)."
|
||||
),
|
||||
}), 400
|
||||
dialogues = db.set_quest_dialogues(slug, quest_id, body.get("dialogues"))
|
||||
return jsonify({"dialogues": dialogues})
|
||||
@@ -1,12 +0,0 @@
|
||||
from flask import jsonify, request
|
||||
|
||||
import db
|
||||
|
||||
from core.flask_app import app
|
||||
|
||||
|
||||
@app.route("/game/<slug>/quests/new", methods=["POST"])
|
||||
def quest_new(slug):
|
||||
body = request.get_json(silent=True) or {}
|
||||
quest = db.create_quest(slug, body.get("title"))
|
||||
return jsonify({"quest": quest})
|
||||
@@ -1,36 +0,0 @@
|
||||
from flask import jsonify, request
|
||||
|
||||
import db
|
||||
|
||||
from core.flask_app import app
|
||||
|
||||
|
||||
@app.route("/game/<slug>/quests/<int:quest_id>", methods=["POST"])
|
||||
def quest_update(slug, quest_id):
|
||||
"""Refusé (400) si "recompense_score" est abaissé SOUS le total déjà
|
||||
réparti entre les questions ("❓ Question") du dialogue de cette
|
||||
quête — voir db.quest_dialogues_total_reward, même contrainte que
|
||||
routes/quests/quest_dialogues.py mais dans l'autre sens (ici c'est le
|
||||
budget total qui bouge, pas la répartition)."""
|
||||
body = request.get_json(silent=True) or {}
|
||||
if "recompense_score" in body:
|
||||
quest = db.get_quest(slug, quest_id)
|
||||
if quest is None:
|
||||
return jsonify({"error": "quête introuvable"}), 404
|
||||
try:
|
||||
new_score = int(body["recompense_score"])
|
||||
except (TypeError, ValueError):
|
||||
new_score = None
|
||||
if new_score is not None:
|
||||
allocated = db.quest_dialogues_total_reward(quest["dialogues"])
|
||||
if new_score < allocated:
|
||||
return jsonify({
|
||||
"error": (
|
||||
f"La récompense totale ({new_score} pts) est inférieure aux "
|
||||
f"{allocated} pts déjà répartis entre les questions du dialogue."
|
||||
),
|
||||
}), 400
|
||||
quest = db.update_quest(slug, quest_id, body)
|
||||
if quest is None:
|
||||
return jsonify({"error": "quête introuvable"}), 404
|
||||
return jsonify({"quest": quest})
|
||||
@@ -1,15 +0,0 @@
|
||||
from flask import jsonify
|
||||
|
||||
import db
|
||||
|
||||
from core.flask_app import app
|
||||
|
||||
|
||||
@app.route("/game/<slug>/quests/list.json")
|
||||
def quests_list_json(slug):
|
||||
"""Utilisé par l'assistant "+ Action" de l'éditeur de collision (voir
|
||||
collision-rules-editor.js) pour lister les quêtes existantes sans
|
||||
quitter la modale déjà ouverte (l'onglet "🗺️ Quêtes" lui-même reçoit
|
||||
déjà sa liste via QUESTS_JSON, voir routes/scenes/scene_edit_view.py —
|
||||
cette route ne sert qu'à la RAFRAÎCHIR depuis le contexte collision)."""
|
||||
return jsonify({"quests": db.list_quests(slug)})
|
||||
@@ -6,9 +6,7 @@ from . import (
|
||||
scene_object_commands,
|
||||
scene_object_role,
|
||||
scene_object_collision,
|
||||
scene_object_collision_rules,
|
||||
scene_object_name,
|
||||
scene_object_dialogue_box_style,
|
||||
scene_collision_rule_objects,
|
||||
scene_object_names_list,
|
||||
)
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
import screens
|
||||
|
||||
_DEFAULT_OBJECT_LABELS = {"personnage": "Personnage", "fond": "Fond"}
|
||||
_COLLISION_EXCLUDED_KINDS = ("fond", "dialogue_box", "quiz_box", "score_widget")
|
||||
|
||||
|
||||
def build_collision_rule_objects(objects):
|
||||
"""Une carte "🧩 Collision" (voir templates/scene_edit.html) par objet
|
||||
ÉLIGIBLE (jamais un "fond", jamais le personnage "joueur" — ce sont
|
||||
TOUJOURS les AUTRES objets qui réagissent à SA présence, voir
|
||||
screens/rendering/collision_rules.py) — factorisé ici plutôt que
|
||||
dupliqué : utilisé au premier rendu de la page (routes/scenes/
|
||||
scene_edit_view.py) ET par l'endpoint AJAX ci-dessous (appelé après
|
||||
ajout/suppression/changement de rôle ou de nom, voir static/js/scenes/
|
||||
scene-editor.js) pour reconstruire la liste SANS recharger la page.
|
||||
|
||||
`objects` doit déjà porter "rendered_html" (voir
|
||||
screens.render_scene_object) sur chaque élément.
|
||||
|
||||
resolve_personnage_role retombe sur "joueur" pour TOUT personnage sans
|
||||
rôle explicitement enregistré (voir screens/rendering/
|
||||
personnage_role.py — comportement voulu pour une scène à un seul
|
||||
personnage, posé avant l'ajout de ce réglage). Appliqué tel quel,
|
||||
ÇA CASSE dès qu'une scène a plusieurs personnages sans rôle explicite
|
||||
(le cas de toute scène créée avant ce réglage — un personnage ajouté
|
||||
depuis l'éditeur reçoit maintenant TOUJOURS un rôle "pnj" explicite,
|
||||
voir add_scene_object.py) : ils retomberaient TOUS sur "joueur" et
|
||||
disparaîtraient donc TOUS de cette liste. On ne considère donc comme
|
||||
"joueur" implicite QUE LE PREMIER personnage sans rôle explicite (le
|
||||
plus ancien, z_index croissant comme `objects`) — les suivants sans
|
||||
rôle explicite sont traités comme "pnj" ici, sans rien réécrire en
|
||||
base."""
|
||||
implicit_joueur_claimed = any(
|
||||
(o.get("attributes") or {}).get("_personnage_role") == "joueur"
|
||||
for o in objects if o["kind"] == "personnage"
|
||||
)
|
||||
result = []
|
||||
for o in objects:
|
||||
if o["kind"] in _COLLISION_EXCLUDED_KINDS:
|
||||
continue
|
||||
role = None
|
||||
if o["kind"] == "personnage":
|
||||
explicit_role = (o.get("attributes") or {}).get("_personnage_role")
|
||||
if explicit_role in screens.PERSONNAGE_ROLES:
|
||||
role = explicit_role
|
||||
elif not implicit_joueur_claimed:
|
||||
role = "joueur"
|
||||
implicit_joueur_claimed = True
|
||||
else:
|
||||
role = "pnj"
|
||||
if role == "joueur":
|
||||
continue
|
||||
result.append({
|
||||
"id": o["id"],
|
||||
"name": o.get("name") or _DEFAULT_OBJECT_LABELS.get(o["kind"], "Décor"),
|
||||
"kind": o["kind"],
|
||||
"role": role,
|
||||
"thumbnail_html": o["rendered_html"],
|
||||
"rules": screens.resolve_collision_rules(o),
|
||||
})
|
||||
return result
|
||||
@@ -1,24 +0,0 @@
|
||||
from flask import jsonify
|
||||
|
||||
import screens
|
||||
|
||||
from core.flask_app import app
|
||||
|
||||
from .collision_rule_objects import build_collision_rule_objects
|
||||
|
||||
|
||||
@app.route("/game/<slug>/screens/<int:screen_id>/collision-rule-objects", methods=["GET"])
|
||||
def scene_collision_rule_objects(slug, screen_id):
|
||||
"""Liste à jour des cartes "🧩 Collision" (voir COLLISION_RULE_OBJECTS,
|
||||
templates/scene_edit.html) — appelée en AJAX après un ajout/suppression
|
||||
d'objet, ou un changement de rôle/nom (voir static/js/scenes/
|
||||
scene-editor.js) qui peut faire apparaître/disparaître un objet de
|
||||
cette liste (rôle passé à/depuis "joueur", voir
|
||||
build_collision_rule_objects) ou changer son libellé affiché (nom).
|
||||
Recalculée entièrement plutôt que diffée : la liste reste petite (un
|
||||
objet de scène par carte, jamais un "fond"/widget d'UI) et cette même
|
||||
fonction est déjà appelée à chaque rendu complet de la page."""
|
||||
objects = screens.list_scene_objects(slug, screen_id)
|
||||
for o in objects:
|
||||
o["rendered_html"] = screens.render_scene_object(o)
|
||||
return jsonify(build_collision_rule_objects(objects))
|
||||
@@ -5,8 +5,6 @@ from flask import render_template, request, abort, g
|
||||
import db
|
||||
import screens
|
||||
|
||||
from .collision_rule_objects import build_collision_rule_objects
|
||||
|
||||
|
||||
def render_scene_edit(slug, screen_id, game):
|
||||
"""Vue de l'éditeur de scène 2D — appelée par routes/screens/
|
||||
@@ -63,12 +61,6 @@ def render_scene_edit(slug, screen_id, game):
|
||||
o["id"]: screens.resolve_personnage_animations(o) for o in objects if o["kind"] == "personnage"
|
||||
}
|
||||
|
||||
# "🧩 Collision" — voir build_collision_rule_objects() (routes/scenes/
|
||||
# collision_rule_objects.py, factorisé pour être réutilisé par
|
||||
# l'endpoint AJAX scene_collision_rule_objects.py, appelé après ajout/
|
||||
# suppression/changement de rôle ou de nom sans recharger la page).
|
||||
collision_rule_objects = build_collision_rule_objects(objects)
|
||||
|
||||
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)
|
||||
@@ -125,24 +117,15 @@ def render_scene_edit(slug, screen_id, game):
|
||||
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_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)}),
|
||||
collision_rule_objects_json=json.dumps(collision_rule_objects),
|
||||
collision_action_types=screens.ACTION_TYPES, collision_trigger_types=screens.TRIGGER_TYPES,
|
||||
collision_default_perimetre_px=screens.DEFAULT_PERIMETRE_PX,
|
||||
# Éditeur de quêtes (voir db/quests/) : préchargées ici pour que
|
||||
# collisionRuleActionSummary (collision-rules-editor.js) affiche
|
||||
# tout de suite le TITRE de la quête référencée par une règle
|
||||
# existante, sans attendre l'ouverture de l'assistant "+ Action"
|
||||
# (seul moment où questPickerHtml irait la chercher sinon).
|
||||
quests_json=json.dumps(db.list_quests(slug)),
|
||||
# "qui parle" dans le dialogue d'une quête (voir "ℹ️ Informations",
|
||||
# static/js/quests/quest-editor.js) : les noms donnés à N'IMPORTE
|
||||
# QUEL objet de scène (personnage, décor, fond), toutes scènes du
|
||||
# jeu confondues.
|
||||
# "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)),
|
||||
)
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
from flask import request, jsonify
|
||||
|
||||
import screens
|
||||
|
||||
from core.flask_app import app
|
||||
|
||||
|
||||
@app.route("/game/<slug>/scene-objects/<int:object_id>/collision-rules", methods=["POST"])
|
||||
def scene_object_collision_rules(slug, object_id):
|
||||
"""Liste complète des règles "déclencheur -> action" d'un objet (voir
|
||||
"🧩 Collision" dans templates/scene_edit.html) — le client poste
|
||||
toujours la liste ENTIÈRE (ajout/suppression déjà appliqués côté
|
||||
client), jamais une règle isolée, même patron que
|
||||
scene_object_commands.py pour "extra"."""
|
||||
body = request.get_json(silent=True) or {}
|
||||
obj = screens.get_scene_object(slug, object_id)
|
||||
if not obj:
|
||||
return jsonify({"error": "objet introuvable"}), 404
|
||||
rules = screens.sanitize_collision_rules(body.get("rules"))
|
||||
screens.set_scene_object_collision_rules(slug, object_id, rules)
|
||||
return jsonify({"ok": True, "rules": rules})
|
||||
@@ -9,12 +9,13 @@ from core.flask_app import app
|
||||
def scene_object_names_list(slug):
|
||||
"""Noms d'objets de scène à jour, pour repeupler le sélecteur "qui
|
||||
parle" de l'éditeur de dialogue (voir SCENE_OBJECT_NAMES_JSON,
|
||||
templates/scene_edit.html, questSpeakerOptions() dans static/js/
|
||||
quests/quest-editor.js) — cette variable n'est posée qu'une fois au
|
||||
chargement complet de la page (HORS de #builder3, jamais rafraîchie
|
||||
par une sélection d'objet sans rechargement, voir goToSelected()) :
|
||||
sans cet endpoint, renommer un personnage puis rouvrir la modale de
|
||||
dialogue dans la MÊME session ne faisait jamais apparaître son nouveau
|
||||
nom, tant qu'aucun vrai rechargement de page n'avait eu lieu entre
|
||||
temps — appelée en AJAX depuis saveSceneObjectName()."""
|
||||
templates/scene_edit.html, triggerSpeakerOptions() dans static/js/
|
||||
triggers/trigger-editor.js) — cette variable n'est posée qu'une fois
|
||||
au chargement complet de la page (HORS de #builder3, jamais
|
||||
rafraîchie par une sélection d'objet sans rechargement, voir
|
||||
goToSelected()) : sans cet endpoint, renommer un personnage puis
|
||||
rouvrir un dialogue de déclencheur dans la MÊME session ne faisait
|
||||
jamais apparaître son nouveau nom, tant qu'aucun vrai rechargement de
|
||||
page n'avait eu lieu entre temps — appelée en AJAX depuis
|
||||
saveSceneObjectName()."""
|
||||
return jsonify(screens.list_named_scene_object_names(slug))
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
from . import triggers
|
||||
@@ -0,0 +1,112 @@
|
||||
from flask import jsonify, request
|
||||
|
||||
import screens
|
||||
|
||||
from core.flask_app import app
|
||||
|
||||
|
||||
@app.route("/game/<slug>/triggers/objects-catalog", methods=["GET"])
|
||||
def triggers_objects_catalog(slug):
|
||||
"""Catalogue des objets de scène éligibles à un déclencheur, pour la
|
||||
grille "choisis un objet" de la modale d'ajout (voir
|
||||
screens.list_all_collision_objects)."""
|
||||
return jsonify(screens.list_all_collision_objects(slug))
|
||||
|
||||
|
||||
@app.route("/game/<slug>/triggers", methods=["GET"])
|
||||
def triggers_get(slug):
|
||||
"""TOUS les déclencheurs du jeu (voir screens.list_all_triggers) — plus
|
||||
de recherche inversée par quête, la logique est portée directement par
|
||||
chaque déclencheur."""
|
||||
return jsonify(screens.list_all_triggers(slug))
|
||||
|
||||
|
||||
@app.route("/game/<slug>/triggers", methods=["POST"])
|
||||
def triggers_add(slug):
|
||||
"""Ajoute un déclencheur sur l'objet choisi — body {object_id, rule},
|
||||
`rule` construite par l'assistant "déclencheur -> action" (voir
|
||||
openTriggerWizard, static/js/scenes/collision-rules-editor.js) :
|
||||
n'importe quel type de déclencheur/action qu'il propose passe donc
|
||||
déjà tel quel, sans changement à faire ici si de nouveaux types
|
||||
apparaissent un jour. Même contrat "liste ENTIÈRE" que l'éditeur de
|
||||
collision (routes/scenes/scene_object_collision_rules.py) : on
|
||||
recharge les règles existantes de l'objet, on y ajoute la nouvelle, on
|
||||
sauvegarde la liste complète."""
|
||||
body = request.get_json(silent=True) or {}
|
||||
try:
|
||||
object_id = int(body.get("object_id"))
|
||||
except (TypeError, ValueError):
|
||||
return jsonify({"error": "object_id invalide"}), 400
|
||||
obj = screens.get_scene_object(slug, object_id)
|
||||
if obj is None:
|
||||
return jsonify({"error": "objet introuvable"}), 404
|
||||
new_rule = body.get("rule")
|
||||
if not isinstance(new_rule, dict):
|
||||
return jsonify({"error": "rule invalide"}), 400
|
||||
|
||||
rules = screens.resolve_collision_rules(obj)
|
||||
rules.append(new_rule)
|
||||
sanitized = screens.sanitize_collision_rules(rules)
|
||||
screens.set_scene_object_collision_rules(slug, object_id, sanitized)
|
||||
return jsonify({"ok": True})
|
||||
|
||||
|
||||
@app.route("/game/<slug>/triggers/<int:object_id>", methods=["DELETE"])
|
||||
def triggers_delete(slug, object_id):
|
||||
"""Retire TOUTES les règles de collision de CET objet — sans quête
|
||||
pour distinguer "cette règle-ci" d'une autre, un objet ne porte de
|
||||
toute façon en pratique qu'un seul déclencheur à la fois (posé par
|
||||
l'assistant, jamais cumulé)."""
|
||||
obj = screens.get_scene_object(slug, object_id)
|
||||
if obj is None:
|
||||
return jsonify({"error": "objet introuvable"}), 404
|
||||
screens.set_scene_object_collision_rules(slug, object_id, [])
|
||||
return jsonify({"ok": True})
|
||||
|
||||
|
||||
@app.route("/game/<slug>/triggers/<int:object_id>/dialogue", methods=["POST"])
|
||||
def trigger_dialogue_update(slug, object_id):
|
||||
"""Édition EN LIGNE des répliques d'UN dialogue précis (voir
|
||||
screens/rendering/collision_rules.py::"dialogue".dialogue) — body
|
||||
{dialogue_id, lines}. 404 si `dialogue_id` ne correspond à aucun
|
||||
dialogue de CET objet (règle supprimée entre-temps, référence
|
||||
orpheline)."""
|
||||
obj = screens.get_scene_object(slug, object_id)
|
||||
if obj is None:
|
||||
return jsonify({"error": "objet introuvable"}), 404
|
||||
body = request.get_json(silent=True) or {}
|
||||
dialogue_id = body.get("dialogue_id")
|
||||
if not isinstance(dialogue_id, str) or not dialogue_id.strip():
|
||||
return jsonify({"error": "dialogue_id invalide"}), 400
|
||||
|
||||
rules = screens.resolve_collision_rules(obj)
|
||||
if not screens.set_dialogue_lines_in_rules(rules, dialogue_id, body.get("lines")):
|
||||
return jsonify({"error": "dialogue introuvable"}), 404
|
||||
sanitized = screens.sanitize_collision_rules(rules)
|
||||
screens.set_scene_object_collision_rules(slug, object_id, sanitized)
|
||||
return jsonify({"ok": True})
|
||||
|
||||
|
||||
@app.route("/game/<slug>/triggers/<int:object_id>/append-action", methods=["POST"])
|
||||
def trigger_append_action(slug, object_id):
|
||||
""""+ Ajouter une action" en bout de chaîne (voir screens/rendering/
|
||||
collision_rules.py::set_then_action_in_rules, static/js/triggers/
|
||||
trigger-editor.js) — body {after_id, action} : `after_id` est le
|
||||
dialogue.id (feuille "dialogue") ou id (feuille "variable") de la
|
||||
DERNIÈRE feuille d'une chaîne. 404 si `after_id` introuvable ou si
|
||||
cette feuille a déjà un "then" (jamais écrasé par cette route — un
|
||||
nouvel appel doit d'abord recharger la chaîne à jour)."""
|
||||
obj = screens.get_scene_object(slug, object_id)
|
||||
if obj is None:
|
||||
return jsonify({"error": "objet introuvable"}), 404
|
||||
body = request.get_json(silent=True) or {}
|
||||
after_id = body.get("after_id")
|
||||
if not isinstance(after_id, str) or not after_id.strip():
|
||||
return jsonify({"error": "after_id invalide"}), 400
|
||||
|
||||
rules = screens.resolve_collision_rules(obj)
|
||||
if not screens.set_then_action_in_rules(rules, after_id, body.get("action")):
|
||||
return jsonify({"error": "action précédente introuvable ou déjà chaînée"}), 404
|
||||
sanitized = screens.sanitize_collision_rules(rules)
|
||||
screens.set_scene_object_collision_rules(slug, object_id, sanitized)
|
||||
return jsonify({"ok": True})
|
||||
Reference in New Issue
Block a user