Add help modal, changelog, map visits, Dockerfile, CI
- Help modal (? button or keyboard shortcut): how-to-play in Swedish, flavor text, full changelog sourced from src/data/version.ts - src/data/version.ts: versioned changelog entries, in-universe Malmö voice - Map zone clicks: each location on the Leaflet map is now clickable. Unlocked cult areas give resources + log entries. Locked locations show lore hints. 30s cooldown per location so it's a decision, not a spam button. - Dockerfile + nginx.conf + docker-compose.yml (port 5684) - Gitea CI workflow (identical to 2048 — derives image from repo path, needs TKNTKN secret, pushes :latest and :sha tags) - README.md with mechanics summary and docker instructions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -152,6 +152,85 @@ export function tick(state: State): State {
|
||||
return s;
|
||||
}
|
||||
|
||||
interface LocationVisit {
|
||||
text: string;
|
||||
effect: Partial<{ andakt: number; dread: number; kaos: number; ritual: number; hemlighet: number }>;
|
||||
requiresArea?: keyof State['areas'];
|
||||
lockedText: string;
|
||||
}
|
||||
|
||||
const LOCATION_VISITS: Record<string, LocationVisit> = {
|
||||
rosengard: {
|
||||
text: 'Du sitter i källaren. Lamporna flimrar i takt med ditt hjärta. Eller tvärtom. +andakt',
|
||||
effect: { andakt: 15 },
|
||||
requiresArea: 'rosengard',
|
||||
lockedText: 'En fuktig källarlokal i Rosengård. Du kan se ljuset under dörren. Ni är inte redo än.',
|
||||
},
|
||||
varnhem: {
|
||||
text: 'Cirkeln är lite större sedan du var här sist. Du ritade den inte. +dread +ritual',
|
||||
effect: { dread: 8, ritual: 2 },
|
||||
requiresArea: 'varnhem',
|
||||
lockedText: 'Värnhemstorget ser normalt ut. Det är ett dåligt tecken.',
|
||||
},
|
||||
torso: {
|
||||
text: 'Byggnaden pekar. Du tittar dit den pekar. Du tittar bort igen. Det hjälper inte. +dread +ritual',
|
||||
effect: { dread: 15, ritual: 5 },
|
||||
requiresArea: 'torso',
|
||||
lockedText: 'Turning Torso vrider sig i vinden. Inte i vinden. I något annat.',
|
||||
},
|
||||
oresund: {
|
||||
text: 'Vattnet sjunker lite långsammare just nu. Det vet att du stod här. +dread',
|
||||
effect: { dread: 5 },
|
||||
lockedText: 'Öresund. Danskarna på andra sidan verkar inte märka någonting. Det är det mest oroande av allt.',
|
||||
},
|
||||
ribersborg: {
|
||||
text: 'Änderna tittar på dig. En av dem nickar. Du nickar tillbaka. Det var ett misstag. +andakt',
|
||||
effect: { andakt: 8 },
|
||||
lockedText: 'Stranden är tom nu. Änderna sitter stilla. De väntar på rätt tidpunkt.',
|
||||
},
|
||||
davidshall: {
|
||||
text: 'Källarlokalen luktar fortfarande. Du andas djupare än du borde. +andakt',
|
||||
effect: { andakt: 10 },
|
||||
lockedText: 'Davidshallsgatan. Kakel från 1923. Bakom det finns något som väljer att vänta.',
|
||||
},
|
||||
stortorget: {
|
||||
text: 'Du vet inte varför du kom hit. Du vet inte hur länge du stod här. +kaos',
|
||||
effect: { kaos: 8 },
|
||||
lockedText: 'Stortorget är fullt av folk som inte vet vad de gör här heller. Du passar in.',
|
||||
},
|
||||
};
|
||||
|
||||
const VISIT_COOLDOWN_TICKS = 900; // 30s at 30fps
|
||||
|
||||
export function visitLocation(state: State, id: string, cooldowns: Map<string, number>): { state: State; cooldowns: Map<string, number> } {
|
||||
const visit = LOCATION_VISITS[id];
|
||||
if (!visit) return { state, cooldowns };
|
||||
|
||||
const now = state.tick;
|
||||
if ((cooldowns.get(id) ?? 0) > now) return { state, cooldowns };
|
||||
|
||||
const s = { ...state, areas: { ...state.areas }, upgrades: new Set(state.upgrades), log: [...state.log], usedEventIds: new Set(state.usedEventIds) };
|
||||
|
||||
const areaLocked = visit.requiresArea && !s.areas[visit.requiresArea];
|
||||
const text = areaLocked ? visit.lockedText : visit.text;
|
||||
|
||||
if (!areaLocked) {
|
||||
if (visit.effect.andakt) s.andakt += visit.effect.andakt;
|
||||
if (visit.effect.dread) s.dread += visit.effect.dread;
|
||||
if (visit.effect.kaos) s.kaos = Math.min(100, s.kaos + visit.effect.kaos);
|
||||
if (visit.effect.ritual && s.ritualUnlocked) s.ritual = Math.min(100, s.ritual + visit.effect.ritual);
|
||||
if (visit.effect.hemlighet) s.hemlighet = Math.max(0, Math.min(100, s.hemlighet + visit.effect.hemlighet));
|
||||
}
|
||||
|
||||
s.log.unshift({ tick: now, text: `⬡ ${text}`, kind: 'system' });
|
||||
if (s.log.length > 40) s.log = s.log.slice(0, 40);
|
||||
|
||||
const newCooldowns = new Map(cooldowns);
|
||||
newCooldowns.set(id, now + VISIT_COOLDOWN_TICKS);
|
||||
|
||||
return { state: s, cooldowns: newCooldowns };
|
||||
}
|
||||
|
||||
export function saveState(state: State): void {
|
||||
try {
|
||||
const serial = {
|
||||
|
||||
Reference in New Issue
Block a user