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>
90 lines
3.6 KiB
Python
90 lines
3.6 KiB
Python
_MAX_LINES_PER_COLUMN = 200
|
||
_MAX_SPEAKER_LENGTH = 60
|
||
|
||
# Question à choix (voir "❓ Question", static/js/triggers/trigger-editor.js) :
|
||
# une bulle jaune posée dans le même enchaînement qu'une réplique de
|
||
# dialogue — plusieurs choix, une seule bonne réponse, une récompense.
|
||
# Un seul type de récompense pour l'instant (le score, voir db.SCORE_STATUS_*/
|
||
# db.set_score_value) — liste plutôt qu'un booléen pour pouvoir en ajouter
|
||
# d'autres plus tard sans changer la forme des données.
|
||
QUESTION_REWARD_TYPES = ["score"]
|
||
_MIN_CHOICES = 2
|
||
_MAX_CHOICES = 4
|
||
|
||
|
||
def _sanitize_dialogue_line(raw_line):
|
||
speaker = raw_line.get("speaker")
|
||
text = raw_line.get("text")
|
||
if not isinstance(speaker, str) or not speaker.strip():
|
||
return None
|
||
if not isinstance(text, str) or not text.strip():
|
||
return None
|
||
return {"type": "dialogue", "speaker": speaker.strip()[:_MAX_SPEAKER_LENGTH], "text": text.strip()}
|
||
|
||
|
||
def _sanitize_question_line(raw_line):
|
||
text = raw_line.get("text")
|
||
if not isinstance(text, str) or not text.strip():
|
||
return None
|
||
raw_choices = raw_line.get("choices")
|
||
if not isinstance(raw_choices, list):
|
||
return None
|
||
choices = [c.strip() for c in raw_choices if isinstance(c, str) and c.strip()][:_MAX_CHOICES]
|
||
if len(choices) < _MIN_CHOICES:
|
||
return None
|
||
try:
|
||
correct_index = int(raw_line.get("correct_index"))
|
||
except (TypeError, ValueError):
|
||
return None
|
||
if not (0 <= correct_index < len(choices)):
|
||
return None
|
||
reward_type = raw_line.get("reward_type")
|
||
if reward_type not in QUESTION_REWARD_TYPES:
|
||
reward_type = QUESTION_REWARD_TYPES[0]
|
||
try:
|
||
reward_amount = max(0, int(raw_line.get("reward_amount", 0)))
|
||
except (TypeError, ValueError):
|
||
reward_amount = 0
|
||
return {
|
||
"type": "question", "text": text.strip(), "choices": choices,
|
||
"correct_index": correct_index, "reward_type": reward_type, "reward_amount": reward_amount,
|
||
}
|
||
|
||
|
||
def _sanitize_line(raw_line):
|
||
if not isinstance(raw_line, dict):
|
||
return None
|
||
if raw_line.get("type") == "question":
|
||
return _sanitize_question_line(raw_line)
|
||
return _sanitize_dialogue_line(raw_line)
|
||
|
||
|
||
def sanitize_dialogue_lines(raw_lines):
|
||
"""Sanitize une LISTE de répliques de dialogue ({type: "dialogue",
|
||
speaker, text} — le nom vient du champ "ℹ️ Informations" d'un objet de
|
||
scène, ou "Joueur", voir screens/rendering/scene_object_names.py)
|
||
ET/OU de questions à choix ({type: "question", text, choices,
|
||
correct_index, reward_type, reward_amount}, voir QUESTION_REWARD_TYPES
|
||
ci-dessus) — même esprit que resolve_collision_rules.sanitize_collision_rules :
|
||
rejette tout élément invalide plutôt que de lever. Les lignes d'UN
|
||
dialogue embarqué dans une action "dialogue" (voir
|
||
screens/rendering/collision_rules.py) : chaque occurrence de
|
||
"Déclencher ce dialogue" porte le sien."""
|
||
if not isinstance(raw_lines, list):
|
||
return []
|
||
cleaned = [_sanitize_line(raw_line) for raw_line in raw_lines[:_MAX_LINES_PER_COLUMN]]
|
||
return [line for line in cleaned if line]
|
||
|
||
|
||
def sum_question_rewards(lines):
|
||
"""Somme des récompenses de toutes les questions ("❓ Question" ci-
|
||
dessus) d'UNE liste de répliques/questions — utilisée pour calculer le
|
||
score max possible du quiz à l'export SCORM, sur TOUTES les lignes de
|
||
TOUS les dialogues du jeu (voir screens.collect_all_dialogue_lines,
|
||
publish/build_scorm_package.py)."""
|
||
total = 0
|
||
for line in (lines or []):
|
||
if isinstance(line, dict) and line.get("type") == "question":
|
||
total += line.get("reward_amount", 0) or 0
|
||
return total
|