| |
| import { NPC_SPOTS } from "./map.js"; |
| import { makeChar } from "./sprites.js"; |
| import { zzz } from "./particles.js"; |
|
|
| |
| const MOOD_POSE = { |
| normal: "idle", happy: "idle", energized: "idle", grateful: "idle", |
| smug: "lean", suspicious: "lean", confused: "idle", |
| sad: "slump", tired: "slump", devastated: "headdown", sheepish: "slump", |
| angry: "shake", hiding: "slump", crying: "slump", sleeping: "headdown", |
| }; |
|
|
| export function createNpcs(worldEl) { |
| const npcs = {}; |
| for (const [id, spot] of Object.entries(NPC_SPOTS)) { |
| |
| const opts = { facing: "down", pose: "idle" }; |
| npcs[id] = makeChar(worldEl, id, spot.x, spot.y, opts); |
| npcs[id].bubble = null; |
| npcs[id].mood = "normal"; |
| } |
| |
| npcs.derek.setPose("still"); |
|
|
| |
| setInterval(() => { |
| if (npcs.derek.mood === "normal" && Math.random() < 0.3) |
| zzz(NPC_SPOTS.derek.x + 8, NPC_SPOTS.derek.y - 36); |
| }, 6000); |
|
|
| return { |
| npcs, |
| applyMoods(moods) { |
| for (const [id, mood] of Object.entries(moods || {})) { |
| const npc = npcs[id]; |
| if (!npc || npc.mood === mood) continue; |
| npc.mood = mood; |
| if (id === "derek" && (mood === "normal" || mood === "happy")) |
| npc.setPose("still"); |
| else npc.setPose(MOOD_POSE[mood] || "idle"); |
| } |
| }, |
| showBubble(id, kind = "danger") { |
| this.clearBubbles(); |
| const npc = npcs[id]; |
| const el = document.createElement("div"); |
| el.className = "bubble" + (kind === "amber" ? " amber" : "") + |
| (kind === "heart" ? " heart" : ""); |
| el.textContent = kind === "amber" ? "?" : kind === "heart" ? "♥" : "!"; |
| el.style.left = `${npc.x}px`; |
| el.style.top = `${npc.y - 52}px`; |
| npc.root.parentElement.appendChild(el); |
| npc.bubble = el; |
| if (kind !== "heart") npc.setPose("shake"); |
| setTimeout(() => { if (npc.bubble === el) npc.setPose( |
| MOOD_POSE[npc.mood] || "idle"); }, 600); |
| }, |
| clearBubbles() { |
| for (const npc of Object.values(npcs)) |
| if (npc.bubble) { npc.bubble.remove(); npc.bubble = null; } |
| }, |
| freezeAll(v) { |
| for (const npc of Object.values(npcs)) npc.setDim(false), npc.root |
| .style.filter = v ? "brightness(.8)" : ""; |
| }, |
| |
| sayBubble(id, text, ms = 4200) { |
| const npc = npcs[id]; |
| if (!npc) return; |
| const el = document.createElement("div"); |
| el.className = "say-bubble"; |
| el.textContent = text; |
| el.style.left = `${npc.x}px`; |
| el.style.top = `${npc.y - 58}px`; |
| npc.root.parentElement.appendChild(el); |
| setTimeout(() => el.classList.add("fading"), ms - 600); |
| setTimeout(() => el.remove(), ms); |
| }, |
| saySequence(lines, gap = 3400) { |
| lines.forEach((entry, i) => { |
| setTimeout(() => this.sayBubble(entry.speaker, entry.line, gap - 200), |
| i * gap); |
| }); |
| }, |
| }; |
| } |
|
|