Quiz jouable : budget de récompense, boîte à quiz, widget score
Build and deploy / test-python (push) Failing after 1m34s
Build and deploy / test-js (push) Successful in 1m1s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

- Récompense d'une quête = budget MAXIMUM pour ses questions : la somme
  des points des "❓ Question" ne peut plus dépasser recompense_score
  (routes/quests/quest_dialogues.py à l'enregistrement des dialogues,
  quest_update.py si on abaisse la récompense sous ce qui est déjà
  réparti) — message d'erreur explicite (400) dans les deux sens,
  affiché via alert() côté éditeur (quest-editor.js), jamais enregistré
  silencieusement dans un état incohérent.
- Nombre de choix par question plafonné à 4 (au lieu de 8).

- Deux nouveaux widgets "🖥️ Interface" (mêmes fondations que "💬 Boîte
  de dialogue" — screens/rendering/dialogue_box_style.py, même panneau
  "🎨 Style") :
  - "❓ Boîte à quiz" : affiche la question et ses choix, sans pied (une
    question se résout au clic sur un choix, pas de "Suivant").
  - "🏆 Score" : affiche en continu les points gagnés, TOUJOURS visible
    une fois posé (contrairement aux boîtes de dialogue/quiz, masquées
    par défaut).

- Moteur d'exécution (static/js/play/dialogue-box-controller.js,
  refonte) : une conversation de quête alterne maintenant répliques
  (boîte de dialogue) et questions (boîte à quiz) selon le type de
  chaque ligne. Bonne réponse -> crédite le score (compteur runtime
  dédié, jamais lié au score du moteur document) et avance ; mauvaise
  réponse -> rien, la question reste affichée pour réessayer ; aucune
  boîte à quiz posée -> la question est ignorée plutôt que de bloquer
  la conversation. Le dialogue "en_cours" épuisé (donc, s'il contenait
  des questions, toutes répondues) fait passer la quête "terminee" (en
  mémoire seulement, comme le reste de ce système — voir accepter/
  refuser une offre de quête) : le joueur peut alors quitter la scène
  normalement, ces widgets n'étant jamais des obstacles.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-03 15:42:42 +02:00
co-authored by Claude Sonnet 5
parent e972acf9e6
commit b5546df044
19 changed files with 628 additions and 76 deletions
+18 -11
View File
@@ -17,7 +17,9 @@
.canvasElementInner > img{ position:static !important; left:auto !important; top:auto !important; width:100% !important; height:100% !important; z-index:auto !important; }
/* Même correctif que ci-dessus, pour le widget "💬 Boîte de dialogue"
(screens/scenes/render_scene_object.py, un <div>, pas un <img>). */
.canvasElementInner > .dialogueBoxWidget{ position:static !important; left:auto !important; top:auto !important; width:100% !important; height:100% !important; z-index:auto !important; }
.canvasElementInner > .dialogueBoxWidget, .canvasElementInner > .quizBoxWidget, .canvasElementInner > .scoreWidget{
position:static !important; left:auto !important; top:auto !important; width:100% !important; height:100% !important; z-index:auto !important;
}
</style>
{% endblock %}
{% block content %}
@@ -100,8 +102,10 @@
<details class="propGroup">
<summary>🖥️ Interface</summary>
<div class="propGroupBody">
<button type="button" class="button is-small is-fullwidth" onclick="addSceneObject('dialogue_box')">💬 Boîte de dialogue</button>
<p class="help">Affiche automatiquement le dialogue de la quête en cours (voir l'onglet "🗺️ Quêtes") — position et style réglables une fois posée.</p>
<button type="button" class="button is-small is-fullwidth mb-2" onclick="addSceneObject('dialogue_box')">💬 Boîte de dialogue</button>
<button type="button" class="button is-small is-fullwidth mb-2" onclick="addSceneObject('quiz_box')">❓ Boîte à quiz</button>
<button type="button" class="button is-small is-fullwidth" onclick="addSceneObject('score_widget')">🏆 Score</button>
<p class="help">Boîte de dialogue/quiz : gérées automatiquement par le moteur (voir l'onglet "🗺️ Quêtes") — position et style réglables une fois posées. Score : affiche en continu les points gagnés.</p>
</div>
</details>
</div>
@@ -124,7 +128,7 @@
onmousedown="onSceneObjectMouseDown(event, {{ o.id }})">
<div class="canvasElementInner">{{ o.rendered_html|safe }}</div>
<div class="resizeHandle" onmousedown="onSceneObjectResizeMouseDown(event, {{ o.id }})"></div>
{% if o.id == selected_id and o.kind not in ('fond', 'dialogue_box') %}
{% if o.id == selected_id and o.kind not in ('fond', 'dialogue_box', 'quiz_box', 'score_widget') %}
<!-- Boîte de collision (voir screens/rendering/collision_settings.py) :
aperçu visuel + réglable directement au glisser — seulement pour
l'objet SÉLECTIONNÉ (celui dont le panneau "🧱 Collision" est ouvert),
@@ -261,8 +265,10 @@
{% if not selected_id or not obj %}
<div class="hint" style="padding:20px 0;">Sélectionne un objet (sur la scène ou dans la liste) pour voir et modifier ses propriétés ici.</div>
{% else %}
{% set _WIDGET_ICONS = {'dialogue_box': '💬', 'quiz_box': '❓', 'score_widget': '🏆'} %}
{% set _WIDGET_LABELS = {'dialogue_box': 'Boîte de dialogue', 'quiz_box': 'Boîte à quiz', 'score_widget': 'Score'} %}
<div class="fieldRow" style="align-items:center; justify-content:space-between;">
<h2 class="subtitle is-6" style="margin:0;">{{ '🏃' if obj.kind == 'personnage' else ('💬' if obj.kind == 'dialogue_box' else '🖼️') }} {{ obj.name or ('Personnage' if obj.kind == 'personnage' else ('Boîte de dialogue' if obj.kind == 'dialogue_box' else 'Décor')) }}</h2>
<h2 class="subtitle is-6" style="margin:0;">{{ '🏃' if obj.kind == 'personnage' else _WIDGET_ICONS.get(obj.kind, '🖼️') }} {{ obj.name or ('Personnage' if obj.kind == 'personnage' else _WIDGET_LABELS.get(obj.kind, 'Décor')) }}</h2>
<form method="post" action="{{ url_for('scene_object_delete', slug=game.slug, object_id=obj.id) }}" onsubmit="return confirm('Supprimer cet objet ?');">
<button type="submit" class="button is-small is-danger">🗑️</button>
</form>
@@ -315,11 +321,12 @@
</div>
</details>
{% if obj.kind == 'dialogue_box' %}
<!-- "🎨 Style" (voir screens/rendering/dialogue_box_style.py) : le
widget "💬 Boîte de dialogue" n'a ni collision ni animation,
juste une apparence — police, couleur du texte, couleur de
chaque zone (header/body/footer). -->
{% if obj.kind in ('dialogue_box', 'quiz_box', 'score_widget') %}
<!-- "🎨 Style" (voir screens/rendering/dialogue_box_style.py) —
PARTAGÉ par les 3 widgets d'interface : aucun n'a de collision
ni d'animation, juste une apparence — police, couleur du
texte, couleur de chaque zone (header/body/footer, "header_bg"
sert de fond unique pour "🏆 Score", qui n'a pas de body/footer). -->
<details class="propGroup" open>
<summary>🎨 Style</summary>
<div class="propGroupBody" data-element-id="{{ obj.id }}">
@@ -373,7 +380,7 @@
</details>
{% endif %}
{% if obj.kind not in ('fond', 'dialogue_box') %}
{% if obj.kind not in ('fond', 'dialogue_box', 'quiz_box', 'score_widget') %}
<!-- Boîte de collision (voir screens/rendering/collision_settings.py,
static/js/play/conditions.js::elementsOverlap) : posée AUTOMATIQUEMENT
sur tout objet (= sa boîte visuelle par défaut, tant que rien