Tourismustal: Sprite-Editor, Bikepark-Umbau, Animationen, Ambiente, Baumenue-Layout

- Justier-Editor (editor.html): jedes Gebaeude in allen Stufen, 1x1 und
  Mega-2x2, Oeko an/aus - Sprite-Breite, Boden-Anker und Solarpanel-
  Position (dx/dy/s pro Sprite-Key) live auf echten Tiles tunen. Werte
  wirken sofort via localStorage-Override, Export-JSON zum Einbacken.
- Bikepark umgebaut aufs Rodelbahn-Prinzip: 1x1-Station, Flowtrail-
  Strecke 5 Felder talwaerts mit Schwuengen, Platz wird SCHON BEIM BAU
  geprueft und reserviert (tile.bikeTrail blockiert Bebauung), fahrende
  Raeder mit Kurvenneigung (mehr Biker je Ausbaustufe), Ziel-Bogen.
- Animationen: Spielplatz (Schaukel + Rutscher), Hochseilgarten
  (Zipline-Gleiter), Aussichtsplattform (Foto-Blitz), Fahrradverleih
  (drehendes Werkstatt-Rad), Gasthaus (Kamin-Dampf).
- Ambiente: Wolkenschatten ziehen uebers Tal; Schafe auf Almen und
  Kuehe auf Talwiesen (grasen, deterministische Herdenplaetze, nicht
  im Winter).
- Baumenue-Karten: oben Titel+Info links / Preis rechts, darunter
  zweispaltig Icon und Beschreibung.
- Solar-Position pro Gebaeudetyp justierbar (SOLAR_POS).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-11 23:32:55 +02:00
parent d195b085d9
commit cfdfa4bb28
7 changed files with 575 additions and 51 deletions
+22 -9
View File
@@ -260,6 +260,7 @@ export function canPlace(state, type, c, r) {
if (!t) return { ok: false, reason: 'Außerhalb der Karte.' + platz };
if (!inCore(c + dc, r + dr)) return { ok: false, reason: 'Hier ist unwegsames Hochgebirge im Tal bauen.' + platz };
if (t.building) return { ok: false, reason: 'Hier steht schon ein Gebäude.' + platz };
if (t.bikeTrail) return { ok: false, reason: 'Hier verläuft die Bikepark-Strecke.' };
if (t.terrain === 'rock') return { ok: false, reason: 'Auf Fels kann nicht gebaut werden.' + platz };
if (t.terrain === 'water') return { ok: false, reason: 'Im Fluss kann nicht gebaut werden.' + platz };
if (t.terrain === 'village') return { ok: false, reason: 'Hier steht das alte Dorf.' + platz };
@@ -308,6 +309,17 @@ export function canPlace(state, type, c, r) {
if (type === 'forest' && !['meadow', 'alm', 'field'].includes(tile.terrain)) {
return { ok: false, reason: 'Aufforsten geht nur auf freien Wiesen, Almen oder Feldern.' };
}
if (def.trailLen) {
// Die Flowtrail-Strecke braucht ihren Platz SCHON BEIM BAU: trailLen
// freie Felder direkt talwärts unter der Station.
for (let i = 1; i <= def.trailLen; i++) {
const t = state.map[r + i]?.[c];
if (!t || t.building || t.bikeTrail || t.road
|| ['water', 'rock', 'village'].includes(t.terrain)) {
return { ok: false, reason: `${def.name} braucht ${def.trailLen} freie Felder talwärts (direkt unterhalb) für die Strecke.` };
}
}
}
if (type === 'access') {
let nearRoad = false;
for (let rr = r - 1; rr <= r + 1 && !nearRoad; rr++) {
@@ -358,16 +370,11 @@ export function place(state, type, c, r) {
}
}
if (def.trailLen) {
// Bike-Trails ziehen sich hangAUFwärts über der Basisstation gleiche
// Richtung wie die Lift-Pisten (die Bahn bringt die Bikes hinauf, die
// Trails führen zurück herunter). Sie kosten extra Natur.
let cc = c;
// Flowtrail-Strecke talwärts (wie die Rodelbahn): Kacheln werden
// reserviert dort kann nicht mehr gebaut werden. Kostet extra Natur.
for (let i = 1; i <= def.trailLen; i++) {
cc += (i % 2 ? 1 : -1);
const t = state.map[r - i]?.[Math.max(1, Math.min(MAP_COLS - 2, cc))];
if (t && t.terrain !== 'water' && !t.building) {
state.trails.push({ c: t.c, r: t.r, owner: { c, r } });
}
const t = state.map[r + i]?.[c];
if (t) t.bikeTrail = { c, r };
}
state.nature = Math.max(15, state.nature - 2);
}
@@ -391,6 +398,12 @@ export function demolish(state, c, r) {
state.buildings = state.buildings.filter(b => !(b.c === c && b.r === r));
state.pistes = state.pistes.filter(p => !(p.owner.c === c && p.owner.r === r));
state.trails = state.trails.filter(t => !(t.owner.c === c && t.owner.r === r));
if (def.trailLen) {
for (let i = 1; i <= def.trailLen; i++) {
const t = state.map[r + i]?.[c];
if (t?.bikeTrail?.c === c && t?.bikeTrail?.r === r) delete t.bikeTrail;
}
}
const name = def.name;
for (let dr = 0; dr < size; dr++) {
for (let dc = 0; dc < size; dc++) {