Quêtes/collision : personnages nommés, dialogues qui parlent, boîte de dialogue en jeu
- "ℹ️ 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:
co-authored by
Claude Sonnet 5
parent
390ee831ca
commit
2347a70c45
@@ -71,10 +71,13 @@ test('forgeRunCollisionRuleAction — "evenement" appelle window.dispatchGameEve
|
||||
assert.deepEqual(calls, [42]);
|
||||
});
|
||||
|
||||
test('forgeRunCollisionRuleAction — "quete" mémorise l\'id dans window.forgeQuestLog (pas d\'éditeur de quête pour le moment)', () => {
|
||||
window.forgeQuestLog = [];
|
||||
forgeRunCollisionRuleAction({ type: 'quete', quete_id: 'q-1' }, fakeEl(), { id: 1 });
|
||||
assert.deepEqual(window.forgeQuestLog, ['q-1']);
|
||||
test('forgeRunCollisionRuleAction — "quete" délègue à forgeShowDialogueBox (voir dialogue-box-controller.js)', () => {
|
||||
const calls = [];
|
||||
const previousShow = global.forgeShowDialogueBox;
|
||||
global.forgeShowDialogueBox = id => calls.push(id);
|
||||
forgeRunCollisionRuleAction({ type: 'quete', quete_id: 42 }, fakeEl(), { id: 1 });
|
||||
global.forgeShowDialogueBox = previousShow;
|
||||
assert.deepEqual(calls, [42]);
|
||||
});
|
||||
|
||||
test('tick — la collision entre le joueur et un ennemi déclenche "attaque" une seule fois tant que le contact persiste', () => {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
// Widget "💬 Boîte de dialogue" — moteur d'exécution (voir
|
||||
// static/js/play/dialogue-box-controller.js) : affiche la réplique en
|
||||
// cours du dialogue d'une quête (gameData.quests) correspondant à son
|
||||
// STATUT ACTUEL, avance au clic sur "Suivant", se masque à la fin.
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
|
||||
global.window = global.window || {};
|
||||
global.gameData = {};
|
||||
|
||||
const {
|
||||
forgeQuestById, forgeShowDialogueBox, forgeDialogueBoxAdvance,
|
||||
} = require('../dialogue-box-controller.js');
|
||||
|
||||
function fakeWidget(objectId) {
|
||||
const roles = {
|
||||
header: { textContent: '' },
|
||||
body: { textContent: '' },
|
||||
};
|
||||
const classes = new Set();
|
||||
return {
|
||||
getAttribute: attr => (attr === 'data-object-id' ? String(objectId) : null),
|
||||
classList: { add: c => classes.add(c), remove: c => classes.delete(c), contains: c => classes.has(c) },
|
||||
querySelector: sel => {
|
||||
if (sel === '[data-dialogue-role="header"]') return roles.header;
|
||||
if (sel === '[data-dialogue-role="body"]') return roles.body;
|
||||
return null;
|
||||
},
|
||||
_roles: roles, _classes: classes,
|
||||
};
|
||||
}
|
||||
|
||||
test.beforeEach(() => {
|
||||
global.window.currentScreenId = 1;
|
||||
global.gameData = {
|
||||
quests: [
|
||||
{ id: 5, status: 'nouvelle', dialogues: { nouvelle: [{ speaker: 'Le Sorcier', text: 'Bonjour !' }, { speaker: 'Joueur', text: 'Salut.' }], en_cours: [], terminee: [] } },
|
||||
{ id: 6, status: 'en_cours', dialogues: { nouvelle: [{ speaker: 'PNJ', text: 'x' }], en_cours: [], terminee: [] } },
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
test('forgeQuestById retrouve une quête par id', () => {
|
||||
assert.equal(forgeQuestById(5).id, 5);
|
||||
assert.equal(forgeQuestById(999), undefined);
|
||||
});
|
||||
|
||||
test('forgeShowDialogueBox — quête introuvable : aucun effet (pas de plantage)', () => {
|
||||
const previousDoc = global.document;
|
||||
global.document = { querySelector: () => { throw new Error('ne devrait jamais chercher de widget'); } };
|
||||
try {
|
||||
assert.doesNotThrow(() => forgeShowDialogueBox(999));
|
||||
} finally { global.document = previousDoc; }
|
||||
});
|
||||
|
||||
test('forgeShowDialogueBox — quête sans réplique pour son statut actuel : aucun effet', () => {
|
||||
const previousDoc = global.document;
|
||||
global.document = { querySelector: () => { throw new Error('ne devrait jamais chercher de widget'); } };
|
||||
try {
|
||||
assert.doesNotThrow(() => forgeShowDialogueBox(6)); // "en_cours" -> dialogues.en_cours est vide
|
||||
} finally { global.document = previousDoc; }
|
||||
});
|
||||
|
||||
test('forgeShowDialogueBox — aucun widget posé sur l\'écran : aucun effet', () => {
|
||||
const previousDoc = global.document;
|
||||
global.document = { querySelector: () => null };
|
||||
try {
|
||||
assert.doesNotThrow(() => forgeShowDialogueBox(5));
|
||||
} finally { global.document = previousDoc; }
|
||||
});
|
||||
|
||||
test('forgeShowDialogueBox puis forgeDialogueBoxAdvance — cycle toutes les répliques puis se masque', () => {
|
||||
const widget = fakeWidget(42);
|
||||
const previousDoc = global.document;
|
||||
global.document = { querySelector: sel => (sel.indexOf('data-object-id="42"') !== -1 ? widget : (sel.indexOf('dialogueBoxWidget') !== -1 ? widget : null)) };
|
||||
try {
|
||||
forgeShowDialogueBox(5);
|
||||
assert.equal(widget._roles.header.textContent, 'Le Sorcier');
|
||||
assert.equal(widget._roles.body.textContent, 'Bonjour !');
|
||||
assert.equal(widget._classes.has('is-active'), true);
|
||||
|
||||
forgeDialogueBoxAdvance(42);
|
||||
assert.equal(widget._roles.header.textContent, 'Joueur');
|
||||
assert.equal(widget._roles.body.textContent, 'Salut.');
|
||||
assert.equal(widget._classes.has('is-active'), true); // encore une réplique visible
|
||||
|
||||
forgeDialogueBoxAdvance(42); // dernière réplique consommée -> se masque
|
||||
assert.equal(widget._classes.has('is-active'), false);
|
||||
} finally { global.document = previousDoc; }
|
||||
});
|
||||
|
||||
test('forgeDialogueBoxAdvance — objet inconnu (jamais affiché) : aucun effet', () => {
|
||||
const previousDoc = global.document;
|
||||
global.document = { querySelector: () => null };
|
||||
try {
|
||||
assert.doesNotThrow(() => forgeDialogueBoxAdvance(999));
|
||||
} finally { global.document = previousDoc; }
|
||||
});
|
||||
Reference in New Issue
Block a user