Remplace le système de quêtes par des déclencheurs, ajoute l'action variable et le chaînage
Build and deploy / test-python (push) Successful in 8m45s
Build and deploy / test-js (push) Successful in 49s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

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:
william
2026-09-07 22:05:06 +02:00
co-authored by Claude Sonnet 5
parent c504ace167
commit d664ed5637
62 changed files with 3129 additions and 2266 deletions
+1 -1
View File
@@ -145,7 +145,7 @@
// bonnes réponses au quiz requis pour un statut de fin "reussi" plutôt
// que "echoue" (SCORM lesson_status ET xAPI, voir dialogue-box-controller.js).
window.FORGE_SUCCESS_THRESHOLD_PERCENT = {{ success_threshold_percent|tojson }};
// Score max possible du quiz (voir db/quests/reward_budget.py) : permet
// Score max possible du quiz (voir db/dialogue_lines.py::sum_question_rewards) : permet
// au SCORM 1.2 de fournir cmi.core.score.min/max en plus du score brut
// (voir static/js/play/offline/scorm-api.js), pour qu'un LMS calcule un
// vrai pourcentage au lieu d'afficher le score brut à tort étiqueté "%".
+92 -67
View File
@@ -115,7 +115,7 @@
<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>
<p class="help">Boîte de dialogue/quiz : gérées automatiquement par le moteur (voir l'onglet "Déclencheurs") — position et style réglables une fois posées. Score : affiche en continu les points gagnés.</p>
</div>
</details>
</div>
@@ -124,15 +124,16 @@
<div class="builderCanvasArea">
<div class="builderTabs" id="builderTabs">
<button type="button" class="builderTabBtn active" data-tab="screen" onclick="switchBuilderTab('screen')">Scène</button>
<button type="button" class="builderTabBtn" data-tab="collision-rules" onclick="switchBuilderTab('collision-rules')">Collision</button>
<button type="button" class="builderTabBtn" data-tab="quests" onclick="switchBuilderTab('quests')">Quêtes</button>
<button type="button" class="builderTabBtn" data-tab="triggers" onclick="switchBuilderTab('triggers')">Déclencheurs</button>
<button type="button" class="builderTabBtn" data-tab="variables" onclick="switchBuilderTab('variables')">Variables</button>
{% if game.onboarding_type != 'rpg' %}
<!-- Événements personnalisés (voir db.list_custom_events) : aucun
moyen de les DÉCLENCHER depuis un jeu RPG pour l'instant —
l'assistant "+ Action" de l'onglet Collision exclut
délibérément le type "evenement" (voir COLLISION_WIZARD_
ACTION_TYPES, static/js/scenes/collision-rules-editor.js) —
demande explicite : cet onglet n'a donc aucune utilité ici. -->
moyen de les DÉCLENCHER depuis un jeu RPG pour l'instant — le
seul point d'entrée qui le permettait (l'action "evenement" de
l'ancien éditeur de collision) a été retiré (demande explicite,
ne servait à rien). game.onboarding_type est de toute façon
TOUJOURS "rpg" désormais (voir la suppression des types Quiz/
Embranchement) — cet onglet ne s'affiche donc plus jamais. -->
<button type="button" class="builderTabBtn" data-tab="events" onclick="switchBuilderTab('events')">Événements</button>
{% endif %}
</div>
@@ -179,55 +180,82 @@
<p class="hint" style="text-align:center;">Glisse pour déplacer, tire le coin pour redimensionner, clique pour sélectionner. Positions en pixels (scène à taille fixe).</p>
</div>
<!-- Éditeur de collision (voir screens/rendering/collision_rules.py) :
une carte par objet ÉLIGIBLE (jamais un "fond", jamais le
personnage "joueur" — ce sont toujours les AUTRES objets qui
réagissent à SA présence), listant ses règles "déclencheur ->
action" déjà posées + un bouton "+ Action" qui ouvre l'assistant
(modale plus bas). Rendu ENTIÈREMENT en JS (voir
static/js/scenes/collision-rules-editor.js) à partir de
COLLISION_RULE_OBJECTS/CUSTOM_EVENTS_MAP — jamais en Jinja, pour
pouvoir se redessiner à chaud après chaque ajout/suppression de
règle sans recharger la page. -->
<div class="builderTabPanel" id="collisionRulesTabPanel" data-tab-panel="collision-rules">
<div class="dashPanelHeader">
<h2>Collision</h2>
</div>
<p class="hint">Pour chaque objet (jamais le joueur lui-même), choisis ce qui doit se passer à son contact — la carte se construit au fil de tes choix, comme des pièces qui s'emboîtent.</p>
<div id="collisionRulesCards" class="collisionRulesCards"></div>
<!-- Onglet "Déclencheurs" (voir static/js/triggers/trigger-editor.js) :
un graphe de logique lu par déclencheur — TOUS les déclencheurs du
jeu, plus aucune notion de quête pour les regrouper (un créateur
peut faire un simple quiz sans jamais poser de personnage). Chaque
déclencheur (objet -> collision -> action) porte SON PROPRE
dialogue, édité en ligne (voir openTriggerWizard(), static/js/
scenes/collision-rules-editor.js). -->
<div class="builderTabPanel" id="triggersTabPanel" data-tab-panel="triggers">
<div id="triggersTabPanelBody"></div>
</div>
<!-- Assistant "+ Action" — un seul écran affiché à la fois
(#collisionRuleModalBody, reconstruit à chaque étape), jamais un
vrai formulaire à plusieurs pages : chaque clic choisit une
option et avance directement à la suivante. -->
<div class="modal" id="collisionRuleModal">
<div class="modal-background" onclick="closeCollisionRuleModal()"></div>
<div class="modal-content">
<div class="box">
<div class="fieldRow" style="align-items:center; justify-content:space-between;">
<h2 class="title is-5" style="margin:0;">Nouvelle règle</h2>
<button type="button" class="button is-small" onclick="closeCollisionRuleModal()">✕</button>
</div>
<div id="collisionRuleModalBody"></div>
</div>
</div>
</div>
<!-- Éditeur de quêtes (voir db/quests/) : une LISTE (comme l'onglet
Événements juste après), pas une grille de cartes — chaque ligne
résume titre/objectif/récompense/statut/résultat, cliquer dessus
ouvre la modale d'arbre de dialogue (static/js/quests/
quest-editor.js, partagée avec l'assistant "+ Action" de l'onglet
Collision). Rendu ENTIÈREMENT en JS depuis QUESTS_JSON, même
convention que #collisionRulesCards ci-dessus. -->
<div class="builderTabPanel" id="questsTabPanel" data-tab-panel="quests">
<!-- Variables globales (voir db/global_vars/) : accessibles à TOUT le
jeu par leur NOM (immuable après création — condition de
visibilité, action "Modifier une variable", voir
static/js/play/conditions.js::readVariableValue), pas seulement à
un écran. Même patron que l'onglet Événements ci-dessous (table +
formulaires classiques, POST/redirect — pas de rendu JS). -->
<div class="builderTabPanel" id="variablesTabPanel" data-tab-panel="variables">
<div class="dashPanelHeader">
<h2>Quêtes</h2>
<button type="button" class="button is-small primary" onclick="createQuestFromListPage()">+ Créer une quête</button>
<h2>Variables</h2>
<button type="button" class="button is-small primary" onclick="toggleDashCreate('variables')">+ Créer une variable</button>
</div>
<p class="hint">Titre, objectif, récompense, statut, résultat — clique sur une ligne pour écrire son enchaînement de dialogue.</p>
<div id="questsListWrap"></div>
<p class="hint">Accessibles depuis n'importe quelle scène (conditions, actions "Modifier une variable"). Le nom ne peut plus changer une fois créé.</p>
<form method="post" action="{{ url_for('create_global_var', slug=game.slug) }}" class="dashCreateBar" id="dashCreate-variables" style="display:none;">
<input type="hidden" name="screen_id" value="{{ screen.id }}">
<input type="text" name="name" placeholder="NOM (ex. score_total)" required autocomplete="off">
<select name="type">
{% for key, t in global_variable_types.items() %}
<option value="{{ key }}">{{ t.label }}</option>
{% endfor %}
</select>
<input type="text" name="default_value" placeholder="Valeur par défaut">
<label class="checkbox" style="display:flex; align-items:center; gap:4px; white-space:nowrap;" title="Chaque joueur a sa propre valeur (décoché = valeur unique partagée par tous)">
<input type="checkbox" name="per_player" value="1" checked> Par joueur
</label>
<button type="submit" class="primary">Créer</button>
</form>
{% if global_variables %}
{% for v in global_variables %}
<form method="post" action="{{ url_for('global_var_edit', slug=game.slug, var_id=v.id) }}" id="varEditForm{{ v.id }}">
<input type="hidden" name="screen_id" value="{{ screen.id }}">
</form>
{% endfor %}
<div class="fieldsTableWrap">
<table class="table is-fullwidth is-hoverable fieldsTable">
<thead><tr><th>Nom</th><th>Type</th><th>Valeur</th><th>Par joueur</th><th></th></tr></thead>
<tbody>
{% for v in global_variables %}
<tr>
<td><code>{{ v.name }}</code></td>
<td>
<select form="varEditForm{{ v.id }}" name="type" aria-label="Type de « {{ v.name }} »">
{% for key, t in global_variable_types.items() %}
<option value="{{ key }}" {{ 'selected' if v.type == key else '' }}>{{ t.label }}</option>
{% endfor %}
</select>
</td>
<td><input type="text" form="varEditForm{{ v.id }}" name="value" value="{{ v.value or '' }}" placeholder="Valeur" aria-label="Valeur de « {{ v.name }} »"></td>
<td><span class="hint">{{ 'Oui' if v.per_player else 'Non (partagée)' }}</span></td>
<td class="fieldsTableActions">
<button type="submit" form="varEditForm{{ v.id }}" class="button is-small" title="Enregistrer">💾</button>
<form method="post" action="{{ url_for('global_var_delete', slug=game.slug, var_id=v.id) }}"
onsubmit="return confirm('Supprimer la variable « {{ v.name }} » ? Toute condition/action qui la référence encore cessera de fonctionner.');">
<input type="hidden" name="screen_id" value="{{ screen.id }}">
<button type="submit" class="button is-small is-danger" title="Supprimer">🗑️</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="hint">Aucune variable pour l'instant — crée la première ci-dessus.</p>
{% endif %}
</div>
{% if game.onboarding_type != 'rpg' %}
@@ -311,9 +339,9 @@
screens/scenes/set_scene_object_name.py) — n'importe quel kind
(personnage, décor, fond), pas seulement un personnage — 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) : un dialogue peut mettre en scène n'importe
quel objet, pas seulement un personnage. -->
de dialogue d'un déclencheur (voir "Déclencheurs", static/js/
triggers/trigger-editor.js) : un dialogue peut mettre en scène
n'importe quel objet, pas seulement un personnage. -->
<details class="propGroup" open>
<summary>Informations</summary>
<div class="propGroupBody">
@@ -602,18 +630,15 @@
var ANIMATE_CSS_LABELS = {{ animate_css_labels_json|safe }};
var SPRITE_LIBRARY = {{ sprite_library_json|safe }};
var ELEMENT_ANIMATIONS_MAP = {{ element_animations_map_json|safe }};
var COLLISION_RULE_OBJECTS = {{ collision_rule_objects_json|safe }};
var COLLISION_ACTION_TYPES = {{ collision_action_types|tojson }};
var COLLISION_TRIGGER_TYPES = {{ collision_trigger_types|tojson }};
var COLLISION_DEFAULT_PERIMETRE_PX = {{ collision_default_perimetre_px }};
// Éditeur de quêtes (voir static/js/quests/quest-editor.js) — chargé
// 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) — n'importe
// quel objet de scène nommé, pas seulement un personnage.
// static/js/triggers/trigger-editor.js::triggerSpeakerOptions) —
// n'importe quel objet de scène nommé, pas seulement un personnage.
var SCENE_OBJECT_NAMES_JSON = {{ scene_object_names_json|safe }};
// Assistant "condition" d'un déclencheur (voir static/js/scenes/
// collision-rules-editor.js::renderCollisionWizardConditionStep) — les
// noms des variables globales du jeu (voir l'onglet "Variables"
// ci-dessus), pour peupler son sélecteur de variable.
var GLOBAL_VARIABLE_NAMES = {{ global_variables | map(attribute='name') | list | tojson }};
</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>
@@ -621,6 +646,6 @@
<script src="{{ url_for('static', filename='js/screen_edit/builder-tabs.js') }}"></script>
<script src="{{ url_for('static', filename='js/screen_edit/personnage-preview.js') }}"></script>
<script src="{{ url_for('static', filename='js/scenes/scene-editor.js') }}"></script>
<script src="{{ url_for('static', filename='js/quests/quest-editor.js') }}"></script>
<script src="{{ url_for('static', filename='js/triggers/trigger-editor.js') }}"></script>
<script src="{{ url_for('static', filename='js/scenes/collision-rules-editor.js') }}"></script>
{% endblock %}