Il libro dell'oratorio in primo piano: copertina, frontespizio, una pagina che gira col fruscio e «FINE» (immagini di Codex, testi disegnati dal codice); l'Arcu a scelte (Borgo e la Casa dell'Armando); il cancello dell'oratorio e l'A. Bettini
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ks2K99uqCZjAxvy7Rdj1R
This commit is contained in:
+40
-2
@@ -22,6 +22,9 @@ const SPEED = 95;
|
||||
// Solo profilo, come i cicli di camminata: 'up' e 'down' lasciano il verso com'è.
|
||||
const FACING = { left: -1, right: 1 };
|
||||
const WALK = ['walk_1', 'walk_2', 'walk_3', 'walk_4'], STEP = 15, REACH = 0.45;
|
||||
// Il primo piano di un libro (story.closeups, effetto `closeup`): copertina → frontespizio → una pagina girata →
|
||||
// l'ultima pagina, poi dopo una pausa il commento. Tempi in secondi.
|
||||
export const PAGE_TURN = 0.7, CLOSEUP_PAUSE = 1.4;
|
||||
|
||||
// Condizioni: 'flag', '!flag', 'has:oggetto', '!has:oggetto', oppure un elenco (tutte vere).
|
||||
export function check(cond, state) {
|
||||
@@ -115,15 +118,16 @@ export class Game {
|
||||
this.hurry = false; // doppio clic: si cammina al doppio della velocità
|
||||
this.reach = 0; // secondi di braccio teso quando prende qualcosa
|
||||
this.back = false; // di spalle davanti a una porta o a una bacheca
|
||||
this.closeup = null; // il primo piano aperto: { id, step: 'cover' | 'title' | 'turn' | 'end', t, age }
|
||||
}
|
||||
|
||||
get scene() { return this.story.scenes[this.state.scene]; }
|
||||
// Occupato: c'è qualcosa in corso che il giocatore non può interrompere. La camminata verso un punto
|
||||
// o verso un oggetto si può sempre cambiare con un altro clic: la coda che la seguiva viene scartata.
|
||||
get busy() { return !!this.speech || this.reach > 0 || (this.task ? !this.task.interruptible : this.queue.length > 0); }
|
||||
get busy() { return !!this.speech || !!this.closeup || this.reach > 0 || (this.task ? !this.task.interruptible : this.queue.length > 0); }
|
||||
get idle() { return !this.busy && !this.task && !this.dialogue; }
|
||||
// Fermo: nessuna battuta, nessun effetto in coda, nessuna camminata che fa parte di una sequenza.
|
||||
get steady() { return !this.speech && this.reach <= 0 && !this.queue.length && !(this.task && !this.task.interruptible); }
|
||||
get steady() { return !this.speech && !this.closeup && this.reach <= 0 && !this.queue.length && !(this.task && !this.task.interruptible); }
|
||||
// Lo stato da salvare adesso: quello attuale se è fermo, altrimenti l'ultimo punto fermo.
|
||||
savable() { return this.steady ? this.snapshot() : this.checkpoint; }
|
||||
// A sequenza finita, se qualcosa è cambiato, nuovo punto fermo e salvataggio.
|
||||
@@ -221,6 +225,27 @@ export class Game {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Il primo piano: un tocco sfoglia (copertina → frontespizio → si gira la pagina); all'ultima pagina non fa niente.
|
||||
closeupTap() {
|
||||
const c = this.closeup;
|
||||
if (!c || this.speech) return false;
|
||||
if (c.step === 'cover') Object.assign(c, { step: 'title', t: 0 });
|
||||
else if (c.step === 'title') Object.assign(c, { step: 'turn', t: 0 });
|
||||
else return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Chiuso il libro si torna alla scena, e gli effetti che seguivano il primo piano ripartono.
|
||||
closeupClose() {
|
||||
const c = this.closeup;
|
||||
if (!c) return false;
|
||||
this.closeup = null;
|
||||
this.speech = null;
|
||||
// Chi è arrivato all'ultima pagina, chiudendo, la commenta (`closing`).
|
||||
if (c.step === 'end') this.queue.unshift(...(this.story.closeups?.[c.id]?.closing ?? []));
|
||||
return true;
|
||||
}
|
||||
|
||||
options() {
|
||||
if (!this.dialogue || this.busy) return [];
|
||||
const node = this.story.dialogues[this.dialogue.id].nodes[this.dialogue.node];
|
||||
@@ -271,6 +296,8 @@ export class Game {
|
||||
}
|
||||
|
||||
speakerPoint(who) {
|
||||
// Nel primo piano Andrea parla da sopra il libro, non dalla scena oscurata dietro.
|
||||
if (this.closeup && (!who || who === 'andrea')) return [320, 34];
|
||||
if (!who || who === 'andrea') return [this.state.x, this.state.y - 62 * this.scaleAt(this.state.y)];
|
||||
if (this.scene.speakers?.[who]) return this.scene.speakers[who];
|
||||
const h = this.hotspot(who) ?? (this.scene.hotspots ?? []).find(x => x.id === who);
|
||||
@@ -280,6 +307,7 @@ export class Game {
|
||||
|
||||
update(dt) {
|
||||
this.time += dt;
|
||||
if (this.closeup) this.closeup.age += dt;
|
||||
if (this.speech) {
|
||||
this.speech.left -= dt;
|
||||
if (this.speech.left > 0) return;
|
||||
@@ -310,6 +338,15 @@ export class Game {
|
||||
this.walking = false;
|
||||
this.task = null;
|
||||
}
|
||||
// Nel primo piano il tempo scorre solo per la pagina che gira e per la pausa prima del commento.
|
||||
const c = this.closeup;
|
||||
if (c) {
|
||||
c.t += dt;
|
||||
if (c.step === 'turn' && c.t >= PAGE_TURN) Object.assign(c, { step: 'end', t: 0 });
|
||||
if (c.step === 'end' && !c.lines && c.t >= CLOSEUP_PAUSE) c.lines = [...(this.story.closeups?.[c.id]?.comment ?? [])];
|
||||
if (c.lines?.length) this.run(c.lines.shift());
|
||||
return;
|
||||
}
|
||||
// Esegue gli effetti istantanei di fila e si ferma al primo che richiede tempo.
|
||||
while (!this.speech && !this.walking && this.queue.length) this.run(this.queue.shift());
|
||||
this.commit();
|
||||
@@ -335,6 +372,7 @@ export class Game {
|
||||
else if (e.node) this.dialogue.node = e.node;
|
||||
else if (e.endDialogue) this.dialogue = null;
|
||||
else if (e.end) s.ended = true;
|
||||
else if (e.closeup) this.closeup = { id: e.closeup, step: 'cover', t: 0, age: 0 };
|
||||
else throw new Error('Effetto sconosciuto: ' + JSON.stringify(e));
|
||||
}
|
||||
|
||||
|
||||
+80
-1
@@ -1,6 +1,6 @@
|
||||
// Disegno della scena su un canvas 640×360, ingrandito dal CSS senza sfocatura.
|
||||
// Le forme provvisorie (`art`) verranno sostituite da sfondi e sprite in pixel art.
|
||||
import { check } from './game.js';
|
||||
import { check, PAGE_TURN } from './game.js';
|
||||
import { ambientEffects, ambientHash } from './ambient.js';
|
||||
import { versioned } from '../version.js';
|
||||
export const WIDTH = 640, HEIGHT = 360;
|
||||
@@ -170,6 +170,7 @@ export function render(ctx, game, { hover, spots, debug, time = performance.now(
|
||||
drawLights(ctx, game, time);
|
||||
for (const shape of art) if (shape.top) drawShape(ctx, shape);
|
||||
if (spots) drawSpots(ctx, game);
|
||||
if (game.closeup) drawCloseup(ctx, game, time);
|
||||
if (debug) drawDebug(ctx, game, debug);
|
||||
if (hover) {
|
||||
ctx.strokeStyle = 'rgba(255,255,255,0.35)';
|
||||
@@ -644,3 +645,81 @@ function drawDebug(ctx, game, { mouse, points = [], help }) {
|
||||
label(`${game.state.scene} ${where} ${help ?? ''}`, 2, HEIGHT - 3, '#fff');
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// --- Il primo piano di un libro (story.closeups, game.closeup): la scena si oscura, le mani alzano il libro dal basso.
|
||||
// Le immagini hanno etichetta e pagine vuote: i testi si scrivono qui, nitidi. Una pagina sola gira, a fisarmonica
|
||||
// sulla piega; sotto c'è già l'ultima pagina.
|
||||
const ink = '#3a1f12', FONT = '"Press Start 2P", monospace';
|
||||
function pageText(ctx, lines, [x, y, w, h], size, at = 0.5, color = ink) {
|
||||
ctx.font = `${size}px ${FONT}`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillStyle = color;
|
||||
const gap = size * 1.7, top = y + h * at - (lines.length - 1) * gap / 2;
|
||||
lines.forEach((line, i) => ctx.fillText(line, Math.round(x + w / 2), Math.round(top + i * gap)));
|
||||
}
|
||||
const twoLines = title => { const i = title.lastIndexOf(' '); return i > 0 ? [title.slice(0, i), title.slice(i + 1)] : [title]; };
|
||||
|
||||
function drawCloseup(ctx, game, time) {
|
||||
const c = game.closeup, def = game.story.closeups?.[c.id];
|
||||
if (!def) return;
|
||||
ctx.save();
|
||||
ctx.fillStyle = `rgba(8, 5, 12, ${Math.min(0.78, c.age / 0.35 * 0.78)})`;
|
||||
ctx.fillRect(0, 0, WIDTH, HEIGHT);
|
||||
const src = c.step === 'cover' ? def.closed : def.open, img = backgrounds.get(src);
|
||||
if (!img) { loadBackground(src); loadBackground(c.step === 'cover' ? def.open : def.closed); ctx.restore(); return; }
|
||||
// Il libro sale dal basso (solo all'inizio), con le braccia che entrano dal bordo.
|
||||
const rise = 1 - Math.min(1, c.age / 0.45), ox = Math.round((WIDTH - img.width) / 2);
|
||||
const oy = Math.round(HEIGHT - img.height + 140 * rise * rise);
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
ctx.drawImage(img, ox, oy);
|
||||
ctx.translate(ox, oy);
|
||||
const lines = twoLines(def.title), { left, right, spine } = def.pages;
|
||||
if (c.step === 'cover') pageText(ctx, lines, def.label, 8);
|
||||
else {
|
||||
const flip = c.step === 'turn' ? Math.min(1, c.t / PAGE_TURN) : c.step === 'end' ? 1 : 0;
|
||||
// Sotto la pagina che gira c'è già l'ultima: «FINE», e nient'altro.
|
||||
if (flip > 0) pageText(ctx, [def.last], right, 20, 0.46);
|
||||
else pageText(ctx, lines, right, 8, 0.32);
|
||||
if (flip > 0 && flip < 1) {
|
||||
// La pagina che gira: si stringe sulla piega e passa a sinistra, col dorso un po' più scuro.
|
||||
const e = (1 - Math.cos(Math.PI * flip)) / 2, k = Math.cos(Math.PI * e);
|
||||
const lift = Math.round(Math.sin(Math.PI * e) * 10), [, y, , h] = right, pw = right[0] + right[2] - spine;
|
||||
ctx.save();
|
||||
ctx.translate(spine, 0);
|
||||
ctx.scale(k || 0.001, 1);
|
||||
ctx.fillStyle = k > 0 ? def.paper : '#e6cc9c';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, y); ctx.lineTo(pw, y - lift); ctx.lineTo(pw, y + h - lift); ctx.lineTo(0, y + h);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = 'rgba(58, 31, 18, 0.55)';
|
||||
ctx.lineWidth = 1 / Math.max(0.2, Math.abs(k));
|
||||
ctx.stroke();
|
||||
if (k > 0) pageText(ctx, lines, [right[0] - spine, y - lift, right[2], h], 8, 0.32);
|
||||
ctx.restore();
|
||||
}
|
||||
// Dove toccare per sfogliare: l'angolo della pagina che pulsa.
|
||||
if (c.step === 'title' && !game.speech) {
|
||||
const pulse = 0.5 + 0.5 * Math.sin(time / 260), cx = right[0] + right[2] - 2, cy = right[1] + right[3] - 2, d = 12 + 3 * pulse;
|
||||
ctx.fillStyle = `rgba(232, 194, 90, ${0.55 + 0.4 * pulse})`;
|
||||
ctx.beginPath(); ctx.moveTo(cx, cy); ctx.lineTo(cx - d, cy); ctx.lineTo(cx, cy - d); ctx.closePath(); ctx.fill();
|
||||
}
|
||||
}
|
||||
ctx.restore();
|
||||
// Il suggerimento in alto, finché c'è qualcosa da sfogliare.
|
||||
const hint = c.step === 'cover' ? 'Clic o tocco: apri il libro' : c.step === 'title' ? 'Clic o tocco: gira la pagina' : null;
|
||||
if (hint && !game.speech && c.age > 0.5) {
|
||||
ctx.save();
|
||||
ctx.globalAlpha = 0.65 + 0.35 * Math.sin(time / 300);
|
||||
ctx.font = `7px ${FONT}`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
|
||||
const tw = ctx.measureText(hint).width;
|
||||
ctx.fillRect(WIDTH / 2 - tw / 2 - 6, 8, tw + 12, 14);
|
||||
ctx.fillStyle = '#ffd27a';
|
||||
ctx.fillText(hint, WIDTH / 2, 15);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ export function voiceLines(story) {
|
||||
visit(story.scenes);
|
||||
visit(story.items);
|
||||
visit(story.dialogues);
|
||||
visit(story.closeups);
|
||||
for (const text of ENGINE_LINES) add('andrea', text);
|
||||
return [...seen.values()];
|
||||
}
|
||||
|
||||
+42
-5
@@ -385,11 +385,14 @@ let press = null;
|
||||
canvas.addEventListener('pointerdown', e => {
|
||||
if (debug && e.shiftKey && e.button === 0) { debugPoint(scenePoint(e)); return; }
|
||||
if (!running || e.button > 0) return;
|
||||
// Nel primo piano un tocco sfoglia (o salta la battuta): gli oggetti della scena dietro non si toccano.
|
||||
if (game.closeup) { press = { closeup: true }; return; }
|
||||
const point = scenePoint(e), reach = reachOf(e);
|
||||
press = { point, reach, long: false, timer: setTimeout(() => { press.long = true; command.clickScene(point, { look: true, reach }); }, LONG_PRESS) };
|
||||
});
|
||||
canvas.addEventListener('pointerup', e => {
|
||||
if (!press) return;
|
||||
if (press.closeup) { press = null; if (!game.skip()) game.closeupTap(); return; }
|
||||
clearTimeout(press.timer);
|
||||
// Il secondo clic vicino al primo, entro poco, è un doppio clic: stesso comando, al doppio della velocità.
|
||||
if (!press.long) {
|
||||
@@ -403,7 +406,7 @@ canvas.addEventListener('pointerup', e => {
|
||||
canvas.addEventListener('pointercancel', () => { if (press) clearTimeout(press.timer); press = null; });
|
||||
canvas.addEventListener('contextmenu', e => {
|
||||
e.preventDefault();
|
||||
if (running) command.clickScene(scenePoint(e), { look: true });
|
||||
if (running && !game.closeup) command.clickScene(scenePoint(e), { look: true });
|
||||
});
|
||||
canvas.addEventListener('pointermove', e => {
|
||||
if (!running) return;
|
||||
@@ -489,6 +492,13 @@ window.addEventListener('keydown', e => {
|
||||
if (!$('pause').hidden) { if (e.key === 'Escape') closePause(); return; }
|
||||
if (!$('notebook').hidden) { if (['Escape', 'n', 'N'].includes(e.key)) $('notebook').hidden = true; return; }
|
||||
if (!$('map').hidden) { if (e.key === 'Escape' || e.key === 'p' || e.key === 'P') $('map').hidden = true; return; }
|
||||
// Nel primo piano: Invio, spazio o freccia sfogliano (o saltano la battuta), Esc chiude il libro.
|
||||
if (running && game.closeup) {
|
||||
if (e.key === 'Escape') game.closeupClose();
|
||||
else if ([' ', 'Enter', 'ArrowRight'].includes(e.key)) { e.preventDefault(); if (!game.skip()) game.closeupTap(); }
|
||||
else if (e.key === 'm' || e.key === 'M') toggleMute();
|
||||
return;
|
||||
}
|
||||
if ((e.key === 'p' || e.key === 'P') && running) { openMap(); return; }
|
||||
if ((e.key === 'h' || e.key === 'H') && running) { showSpots(); return; }
|
||||
if ((e.key === 'n' || e.key === 'N') && running) { openNotebook(); return; }
|
||||
@@ -502,6 +512,28 @@ window.addEventListener('keydown', e => {
|
||||
if (game.dialogue && n >= 1 && n <= 9) game.choose(n - 1);
|
||||
});
|
||||
|
||||
// --- Il primo piano: si chiude col pulsante; la pagina che gira fruscia (rumore filtrato, niente file audio).
|
||||
$('closeup-close').addEventListener('click', () => game?.closeupClose());
|
||||
let turning = null, audioCtx = null;
|
||||
function rustle() {
|
||||
if (music.muted || voices.volume <= 0) return;
|
||||
try {
|
||||
audioCtx ??= new AudioContext();
|
||||
const sr = audioCtx.sampleRate, len = Math.floor(sr * 0.42), buf = audioCtx.createBuffer(1, len, sr), d = buf.getChannelData(0);
|
||||
// Un fruscio a strappi: rumore che si accende e si spegne, come la carta che striscia.
|
||||
for (let i = 0; i < len; i++) { const t = i / len; d[i] = (Math.random() * 2 - 1) * Math.sin(Math.PI * t) * (0.55 + 0.45 * Math.sin(t * 38)); }
|
||||
const src = audioCtx.createBufferSource(), filter = audioCtx.createBiquadFilter(), gain = audioCtx.createGain();
|
||||
src.buffer = buf;
|
||||
filter.type = 'bandpass';
|
||||
filter.frequency.setValueAtTime(2400, audioCtx.currentTime);
|
||||
filter.frequency.linearRampToValueAtTime(5200, audioCtx.currentTime + 0.42);
|
||||
filter.Q.value = 0.8;
|
||||
gain.gain.value = 0.5 * voices.volume;
|
||||
src.connect(filter).connect(gain).connect(audioCtx.destination);
|
||||
src.start();
|
||||
} catch { /* senza audio si gira la pagina in silenzio */ }
|
||||
}
|
||||
|
||||
// --- Fumetto sopra chi parla, tenuto dentro i bordi della scena.
|
||||
function updateSpeech() {
|
||||
const box = $('speech'), sp = game.speech;
|
||||
@@ -528,16 +560,21 @@ function updateUi() {
|
||||
// Il bersaglio si ricalcola a ogni fotogramma: la scena cambia anche se il mouse sta fermo.
|
||||
hoverTarget = (game.state.inventory.includes(hoverItem) && hoverItem) || (mouse && game.hotspotAt(mouse)?.id) || null;
|
||||
for (const b of $('verbs').children) b.setAttribute('aria-pressed', String(command.verb === b.dataset.verb));
|
||||
const text = !running || game.speech || game.dialogue ? '' : command.sentence(hoverTarget);
|
||||
const text = !running || game.speech || game.dialogue || game.closeup ? '' : command.sentence(hoverTarget);
|
||||
if ($('sentence').textContent !== text) $('sentence').textContent = text;
|
||||
updateInventory();
|
||||
updateChoices();
|
||||
updateSpeech();
|
||||
// Il pulsante della mappa c'è solo in partita, fuori dai dialoghi, e quando c'è almeno un altro posto dove andare.
|
||||
$('map-button').hidden = !running || !!game.dialogue || game.mapPlaces().length < 2;
|
||||
$('map-button').hidden = !running || !!game.dialogue || !!game.closeup || game.mapPlaces().length < 2;
|
||||
$('pause-button').hidden = !running;
|
||||
$('spots-button').hidden = !running;
|
||||
$('notebook-button').hidden = !running || !story.notebook;
|
||||
$('spots-button').hidden = !running || !!game.closeup;
|
||||
$('notebook-button').hidden = !running || !story.notebook || !!game.closeup;
|
||||
$('closeup-close').hidden = !running || !game.closeup;
|
||||
// La pagina che comincia a girare fa il suo fruscio.
|
||||
const step = game.closeup?.step ?? null;
|
||||
if (step === 'turn' && turning !== 'turn') rustle();
|
||||
turning = step;
|
||||
if (game.state.ended && !game.speech && !game.queue.length) {
|
||||
running = false;
|
||||
$('ending').hidden = false;
|
||||
|
||||
+39
-9
@@ -104,6 +104,23 @@ export const story = {
|
||||
{ role: 'Carattere', names: ['Press Start 2P di CodeMan38 (licenza OFL)'] },
|
||||
{ role: 'Programmazione', names: ['Manzolo, con Claude e Codex'] },
|
||||
],
|
||||
// I primi piani a tutto schermo (effetto `closeup`, disegnati da render.js): per ora il libro dell'oratorio.
|
||||
// Le immagini (Codex, assets/closeups/) hanno l'etichetta e le pagine vuote: titolo e «FINE» li scrive il codice.
|
||||
closeups: {
|
||||
libro: {
|
||||
closed: 'assets/closeups/libro-chiuso.png',
|
||||
open: 'assets/closeups/libro-aperto.png',
|
||||
title: 'La storia infinita',
|
||||
last: 'FINE',
|
||||
comment: [{ say: 'Mi ricordavo un finale diverso.' }],
|
||||
// Chiuso dopo aver visto l'ultima pagina (idea di Andrea, 25 ottobre).
|
||||
closing: [{ say: 'Il Nulla sta viaggiando troppo forte... devo fermarlo!' }],
|
||||
// Dove scrivere, in pixel delle due immagini: l'etichetta della copertina, le due pagine, la piega.
|
||||
label: [78, 48, 104, 54],
|
||||
pages: { left: [56, 26, 96, 194], right: [163, 12, 101, 204], spine: 157 },
|
||||
paper: '#fbe6bc',
|
||||
},
|
||||
},
|
||||
characters: {
|
||||
andrea: { name: 'Andrea', color: '#ffffff', sprite: 'assets/characters/andrea.json' },
|
||||
vecchietta: { name: 'Vecchietta', color: '#ffd27a', sprite: 'assets/characters/vecchietta.json' },
|
||||
@@ -133,12 +150,9 @@ export const story = {
|
||||
actions: {
|
||||
drop: [{ say: 'Una pagina sola, e pure con la fine. Lo lascio qui per il prossimo lettore.' }, { drop: 'libro' }],
|
||||
look: [{ say: '«La storia infinita». Bello spesso: questo mi tiene compagnia fino a settembre.' }],
|
||||
open: [
|
||||
{ say: 'Vediamo da dove comincia...' },
|
||||
{ say: 'C\'è una pagina sola. Con scritto: «FINE».' },
|
||||
{ say: 'Infinita, dicevano.' },
|
||||
],
|
||||
use: [{ say: 'Leggere adesso? Prima il palio. E poi, a quanto pare, si fa presto.' }],
|
||||
// Il primo piano (story.closeups.libro): la battuta la scopre chi guarda, nessuno la racconta.
|
||||
open: [{ closeup: 'libro' }],
|
||||
use: [{ closeup: 'libro' }],
|
||||
},
|
||||
},
|
||||
// La catena del ghiacciolo (idea di Andrea, 2026-10-24): la Tosca dà ghiacciolo e cedrata; il ghiacciolo inzuppato
|
||||
@@ -656,13 +670,17 @@ export const story = {
|
||||
art: [{ rect: [565, 203, 11, 5], fill: '#c46a3a' }, { rect: [565, 207, 11, 1], fill: '#f4efe4' }, { rect: [569, 204, 3, 3], fill: '#e8c25a' }],
|
||||
actions: {
|
||||
look: [{ say: 'Un libro dimenticato sulle tavolate. Sulla copertina c\'è scritto «La storia infinita».' }],
|
||||
take: [{ give: 'libro' }, { set: 'libro_preso' }, { say: 'Me lo prendo. Se è suo, il proprietario lo ritrova... prima o poi.' }],
|
||||
take: [{ give: 'libro' }, { set: 'libro_preso' }, { say: 'Ricordo di aver visto il film da piccolo... era stupendo!' }, { closeup: 'libro' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'cancello', name: 'cancello', box: [477, 166, 23, 52], at: [488, 236], face: 'up',
|
||||
actions: {
|
||||
look: [{ say: 'Il cancello di dietro.' }],
|
||||
// Il vecchio campo «Alberto Bettini» (Andrea, 25 ottobre; www.polisportivasanpieroasieve.it).
|
||||
look: [
|
||||
{ say: 'Il cancello di dietro. Di qui si vede ancora l\'A. Bettini, il campo da calcio di quando eravamo piccoli: lassù ci sono ancora i riflettori.' },
|
||||
{ say: 'Il primo vero campo del paese: inaugurato nel 1964 e intitolato ad Alberto Bettini, un giovane consigliere della società scomparso troppo presto.' },
|
||||
],
|
||||
open: [{ say: 'Chiuso. Si apre solo quando c\'è la festa.' }],
|
||||
},
|
||||
},
|
||||
@@ -1321,7 +1339,7 @@ export const story = {
|
||||
id: 'arcu', name: 'l\'Arcu', box: [400, 216, 38, 76], at: [362, 300], face: 'right', default: 'talk',
|
||||
actions: {
|
||||
look: [{ say: 'L\'Arcu, il mio amico di sempre. Davanti al circolino, come se ci fosse nato.' }],
|
||||
talk: [{ say: 'Arcu! Ma che è sempre là, Borgo?' }, { say: 'E chi lo sposta???', who: 'arcu', pose: 'shrug' }],
|
||||
talk: [{ dialogue: 'arcu' }],
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -1942,6 +1960,18 @@ export const story = {
|
||||
},
|
||||
},
|
||||
},
|
||||
// L'Arcu davanti al circolino (battute di Andrea, 25 ottobre). Lavora alla Casa dell'Armadio: «Armando» è apposta.
|
||||
arcu: {
|
||||
nodes: {
|
||||
start: {
|
||||
options: [
|
||||
{ text: 'Ma che è sempre là, Borgo?', then: [{ say: 'E chi lo sposta???', who: 'arcu', pose: 'shrug' }] },
|
||||
{ text: 'Come va alla Casa dell\'Armando?', then: [{ say: 'Mah... un si fa una sega...', who: 'arcu', pose: 'explain' }] },
|
||||
{ text: 'Ci si vede, Arcu.', next: 'end' },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
// Lo "psicologo": il castello ascolta e risponde con un indizio su quello che manca.
|
||||
castello: {
|
||||
nodes: {
|
||||
|
||||
Reference in New Issue
Block a user