Files
Forge-Engine/db/quests/sanitize_quest_dialogues.py
T
williamandClaude Sonnet 5 e972acf9e6
Build and deploy / test-python (push) Successful in 6m25s
Build and deploy / test-js (push) Successful in 58s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped
Quêtes : bulle "❓ Question" à choix multiples, à côté de "+ Réplique"
Nouveau type de ligne dans un dialogue de quête (voir
db/quests/sanitize_quest_dialogues.py) : une bulle JAUNE, dans la même
chaîne verticale que les répliques (même alignement, reliée par un
trait) — header : nombre de choix (2 à 8) + récompense (score, montant
en points) ; body : la question, ses choix et un bouton radio pour
désigner la bonne réponse. Aucune limite au nombre de questions par
colonne, comme pour une réplique.

Les lignes de dialogue portent maintenant explicitement
{"type": "dialogue", ...} (au lieu d'un objet sans type) pour
distinguer les deux formes — migration de forme, tests mis à jour.

Portée de ce commit : l'ÉDITEUR (créer/modifier une question). Le jeu
lui-même (afficher la question au joueur, vérifier la réponse,
attribuer la récompense) reste à construire — le widget "💬 Boîte de
dialogue" ne sait aujourd'hui afficher qu'une réplique.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 14:48:14 +02:00

83 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from .constants import QUEST_STATUS_CHOICES
_MAX_LINES_PER_COLUMN = 200
_MAX_SPEAKER_LENGTH = 60
# Question à choix (voir "❓ Question", static/js/quests/quest-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 = 8
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_quest_dialogues(raw_dialogues):
"""Une colonne (voir QUEST_STATUS_CHOICES) contient une suite 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."""
result = {status: [] for status in QUEST_STATUS_CHOICES}
if not isinstance(raw_dialogues, dict):
return result
for status in QUEST_STATUS_CHOICES:
raw_lines = raw_dialogues.get(status)
if not isinstance(raw_lines, list):
continue
cleaned = [_sanitize_line(raw_line) for raw_line in raw_lines[:_MAX_LINES_PER_COLUMN]]
result[status] = [line for line in cleaned if line]
return result