Quêtes/collision : personnages nommés, dialogues qui parlent, boîte de dialogue en jeu
Build and deploy / test-python (push) Successful in 11m32s
Build and deploy / test-js (push) Successful in 1m12s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

- "ℹ️ Informations" (propriétés d'un personnage) : nom éditable, réutilisé
  comme "qui parle" dans l'éditeur de dialogue de quête (menu déroulant,
  plus "Joueur" toujours disponible) — remplace l'ancien choix binaire
  joueur/pnj. db.sanitize_quest_dialogues accepte maintenant un nom libre.

- Nouveau menu "🖥️ Interface" (palette d'objets) avec le premier widget :
  "💬 Boîte de dialogue" (kind="dialogue_box", screens/rendering/
  dialogue_box_style.py) — position/taille comme tout objet de scène,
  panneau "🎨 Style" dédié (police, taille, épaisseur, couleur du texte,
  couleur header/body/footer). Rendu en <div> à 3 zones, jamais soumis à
  la collision ni à l'éditeur de collision (exclu partout : obstacles,
  liste des règles, payload). Rendu côté jeu dans .sceneUI, une couche
  FIXE au viewport (jamais .sceneWorld, qui défile avec la caméra).

- static/js/play/dialogue-box-controller.js : fait le lien moteur entre
  l'action "quete" de l'éditeur de collision et ce widget — affiche la
  réplique en cours du dialogue correspondant au STATUT ACTUEL de la
  quête (gameData.quests, désormais exposé game-wide par
  full_game_payload.py), avance au clic sur "Suivant" (header = qui
  parle, body = texte, footer = bouton), se masque à la dernière réplique.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-03 11:20:58 +02:00
co-authored by Claude Sonnet 5
parent 390ee831ca
commit 2347a70c45
27 changed files with 774 additions and 41 deletions
+8 -1
View File
@@ -84,7 +84,13 @@
uniquement si un objet kind="fond" plus grand que la scène est posé
(caméra qui suit le personnage). -->
<div class="sceneWorld" data-scene-width="{{ s.scene_width }}" data-scene-height="{{ s.scene_height }}">
{% for obj in s.elements %}{{ obj.rendered_html|safe }}{% endfor %}
{% for obj in s.elements if obj.kind != 'dialogue_box' %}{{ obj.rendered_html|safe }}{% endfor %}
</div>
<!-- .sceneUI : widgets d'interface FIXES au viewport, jamais dans
.sceneWorld ci-dessus (voir static/style.css) — pour l'instant
seulement "💬 Boîte de dialogue" (screens/rendering/dialogue_box_style.py). -->
<div class="sceneUI">
{% for obj in s.elements if obj.kind == 'dialogue_box' %}{{ obj.rendered_html|safe }}{% endfor %}
</div>
</div>
{% else %}
@@ -155,6 +161,7 @@
<script src="{{ asset_url('js/play/actions.js') }}"></script>
<script src="{{ asset_url('js/play/triggers.js') }}"></script>
<script src="{{ asset_url('js/play/personnage-controller.js') }}"></script>
<script src="{{ asset_url('js/play/dialogue-box-controller.js') }}"></script>
<script src="{{ asset_url('js/play/collision-rules-controller.js') }}"></script>
<script src="{{ asset_url('js/play/bindings.js') }}"></script>
<script src="{{ asset_url('js/play/flow-engine.js') }}"></script>
+97 -3
View File
@@ -15,6 +15,9 @@
#canvas (son ancêtre positionné le plus proche), en double du
décalage déjà appliqué par .canvasElement. */
.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; }
</style>
{% endblock %}
{% block content %}
@@ -87,6 +90,20 @@
</div>
</details>
{% endif %}
<!-- "🖥️ Interface" — l'UI du jeu (voir screens/rendering/
dialogue_box_style.py) : pour l'instant un seul widget, "💬 Boîte
de dialogue", gérée AUTOMATIQUEMENT par le moteur (affiche la
réplique en cours de la quête déclenchée par une règle de
collision "quete", voir static/js/play/dialogue-box-controller.js) —
l'auteur ne fait que la poser où il veut et la styliser (panneau
de propriétés de droite, une fois sélectionnée). -->
<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>
</div>
</details>
</div>
</div>
@@ -107,7 +124,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 != 'fond' %}
{% if o.id == selected_id and o.kind not in ('fond', 'dialogue_box') %}
<!-- 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),
@@ -245,12 +262,28 @@
<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 %}
<div class="fieldRow" style="align-items:center; justify-content:space-between;">
<h2 class="subtitle is-6" style="margin:0;">{{ '🏃' if obj.kind == 'personnage' else '🖼️' }} {{ obj.name or ('Personnage' if obj.kind == 'personnage' else 'Décor') }}</h2>
<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>
<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>
</div>
{% if obj.kind == 'personnage' %}
<!-- "ℹ️ Informations" : le nom donné au personnage (voir
screens/scenes/set_scene_object_name.py) — c'est CE nom qui est
ensuite proposé comme "qui parle" dans l'éditeur de dialogue de
quête (voir "🗺️ Quêtes", static/js/quests/quest-editor.js). -->
<details class="propGroup" open>
<summary>ℹ️ Informations</summary>
<div class="propGroupBody">
<div class="field">
<label class="label is-small">Nom du personnage</label>
<div class="control"><input class="input is-small" type="text" id="scene-obj-name" value="{{ obj.name or '' }}" placeholder="ex. Le Sorcier" onchange="saveSceneObjectName({{ obj.id }}, this.value)"></div>
</div>
</div>
</details>
{% endif %}
<details class="propGroup" open>
<summary>Position &amp; taille (pixels)</summary>
<div class="propGroupBody">
@@ -279,7 +312,65 @@
</div>
</details>
{% if obj.kind != 'fond' %}
{% 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). -->
<details class="propGroup" open>
<summary>🎨 Style</summary>
<div class="propGroupBody" data-element-id="{{ obj.id }}">
<div class="field">
<label class="label is-small">Police</label>
<div class="control">
<input class="input is-small" type="text" id="dbox-font-family" value="{{ dialogue_box_style.font_family }}" placeholder="ex. Georgia, serif" onchange="saveDialogueBoxStyle({{ obj.id }})">
</div>
</div>
<div class="columns is-multiline posGrid">
<div class="column is-half">
<div class="field"><label class="label is-small">Taille (px)</label>
<div class="control"><input class="input is-small" type="number" id="dbox-font-size" min="8" max="48" value="{{ dialogue_box_style.font_size }}" onchange="saveDialogueBoxStyle({{ obj.id }})"></div>
</div>
</div>
<div class="column is-half">
<div class="field"><label class="label is-small">Épaisseur</label>
<div class="control">
<select class="input is-small" id="dbox-font-weight" onchange="saveDialogueBoxStyle({{ obj.id }})">
{% for w in dialogue_box_font_weight_choices %}
<option value="{{ w }}" {{ 'selected' if dialogue_box_style.font_weight == w else '' }}>{{ 'Gras' if w == 'bold' else 'Normal' }}</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
<div class="columns is-multiline posGrid">
<div class="column is-half">
<div class="field"><label class="label is-small">Texte</label>
<div class="control"><input class="input is-small" type="color" id="dbox-text-color" value="{{ dialogue_box_style.text_color }}" onchange="saveDialogueBoxStyle({{ obj.id }})"></div>
</div>
</div>
<div class="column is-half">
<div class="field"><label class="label is-small">Header</label>
<div class="control"><input class="input is-small" type="color" id="dbox-header-bg" value="{{ dialogue_box_style.header_bg }}" onchange="saveDialogueBoxStyle({{ obj.id }})"></div>
</div>
</div>
<div class="column is-half">
<div class="field"><label class="label is-small">Corps</label>
<div class="control"><input class="input is-small" type="color" id="dbox-body-bg" value="{{ dialogue_box_style.body_bg }}" onchange="saveDialogueBoxStyle({{ obj.id }})"></div>
</div>
</div>
<div class="column is-half">
<div class="field"><label class="label is-small">Pied</label>
<div class="control"><input class="input is-small" type="color" id="dbox-footer-bg" value="{{ dialogue_box_style.footer_bg }}" onchange="saveDialogueBoxStyle({{ obj.id }})"></div>
</div>
</div>
</div>
</div>
</details>
{% endif %}
{% if obj.kind not in ('fond', 'dialogue_box') %}
<!-- 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
@@ -464,6 +555,9 @@
// avant collision-rules-editor.js : l'action "quete" de l'assistant
// "+ Action" ouvre directement cet éditeur (renderQuestPickerStep).
var QUESTS_JSON = {{ quests_json|safe }};
// "qui parle" dans l'éditeur de dialogue (voir "ℹ️ Informations" +
// static/js/quests/quest-editor.js::questSpeakerOptions).
var PERSONNAGE_NAMES_JSON = {{ personnage_names_json|safe }};
</script>
<script src="{{ url_for('static', filename='js/screen_edit/tree-panels.js') }}"></script>
<script src="{{ url_for('static', filename='js/screen_edit/panel-init.js') }}"></script>