Files
geograsim/App/sims/tourismustal/js/editor-tune.js
T
Adminator cfdfa4bb28 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>
2026-07-11 23:32:55 +02:00

187 lines
6.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Sprite-Editor: Gebäude in allen Ausbaustufen (1×1 und 2×2/Mega) auf echten
// Tiles justieren Sprite-Breite, Boden-Anker und Solarpanel-Position.
// Werte landen live in localStorage (tt_sprite_tuning) und wirken sofort im
// Spiel auf diesem Gerät; der Export wird von Atlas fest in iso.js übernommen.
import { createState, place } from './model.js';
import { IsoRenderer, SPRITE_DEFS, SOLAR_POS } from './iso.js';
import { BUILDINGS } from './data.js';
const $ = id => document.getElementById(id);
// ---------- Test-Welt: flach geräumte Bühne ----------
const state = createState('boom');
state.stageRow = 0;
state.season = 'Sommer';
state.money = 9e9;
state.guestsToday = 0;
for (const row of state.map) {
for (const t of row) {
t.building = null; t.level = null;
delete t.buildingRef; delete t.mega; delete t.eco;
}
}
state.buildings = []; state.pistes = []; state.trails = [];
// Bühne: ebene 2×2-Fläche nahe der Kartenmitte suchen
let SPOT = { c: 18, r: 24 };
outer:
for (let r = 20; r < 34; r++) {
for (let c = 10; c < 28; c++) {
const ok = [[0, 0], [1, 0], [0, 1], [1, 1]].every(([dc, dr]) => {
const t = state.map[r + dr]?.[c + dc];
return t && !t.road && t.terrain !== 'water' && t.terrain !== 'village'
&& t.terrain !== 'rock' && t.elev >= 1 && t.elev <= 3;
});
if (ok) { SPOT = { c, r }; break outer; }
}
}
const renderer = new IsoRenderer(document.getElementById('map'), state, { onTileTap: () => {} });
// ---------- Tuning-Store ----------
let tuned = { sprites: {}, solar: {} };
try { tuned = Object.assign(tuned, JSON.parse(localStorage.getItem('tt_sprite_tuning') || '{}')); } catch {}
function saveTuning() {
localStorage.setItem('tt_sprite_tuning', JSON.stringify(tuned));
$('edOut').value = JSON.stringify(tuned, null, 2);
}
// ---------- Auswahl ----------
const sel = $('edType');
for (const [type, def] of Object.entries(BUILDINGS)) {
if (def.action) continue; // Aufforsten etc.
const o = document.createElement('option');
o.value = type;
o.textContent = `${def.emoji} ${def.name}`;
sel.appendChild(o);
}
let curLevel = 1;
function spriteKey() {
const type = sel.value;
if ($('edMega').checked && SPRITE_DEFS[`mega_${type}`]) return `mega_${type}`;
if (type === 'hotel' && curLevel >= 2) return 'hotel2';
return type;
}
// ---------- Bühne neu bestücken ----------
function rebuild() {
for (const row of state.map) {
for (const t of row) {
t.building = null; t.level = null;
delete t.buildingRef; delete t.mega; delete t.eco;
}
}
state.buildings = []; state.pistes = []; state.trails = [];
const type = sel.value;
const def = BUILDINGS[type];
const mega = $('edMega').checked && (def.size || 1) === 1;
const { c, r } = SPOT;
if (mega) {
// 4 gleiche im Quadrat → verschmilzt automatisch (falls merge-fähig),
// sonst von Hand zum 2×2 zusammensetzen.
place(state, type, c, r); place(state, type, c + 1, r);
place(state, type, c, r + 1); place(state, type, c + 1, r + 1);
let b = state.buildings.find(x => x.mega);
if (!b && state.buildings.length) {
b = state.buildings[0];
state.buildings = [b];
b.size = 2; b.mega = true;
for (let dr = 0; dr < 2; dr++) for (let dc = 0; dc < 2; dc++) {
const t = state.map[r + dr][c + dc];
t.building = type;
if (dr || dc) t.buildingRef = { c, r };
else { delete t.buildingRef; t.mega = true; }
}
}
} else {
place(state, type, c, r);
}
const b = state.buildings[0];
if (b) {
b.level = curLevel;
state.map[b.r][b.c].level = curLevel;
if ($('edEco').checked) { b.eco = true; state.map[b.r][b.c].eco = true; }
}
syncSliders();
}
// ---------- Slider ↔ Werte ----------
function syncSliders() {
const key = spriteKey();
const sd = SPRITE_DEFS[key] || { w: 60, bottom: 8 };
const sp = SOLAR_POS[key]
|| ($('edMega').checked || (BUILDINGS[sel.value].size || 1) > 1
? SOLAR_POS.defaultBig : SOLAR_POS.default);
$('edW').value = sd.w; $('edWv').textContent = sd.w;
$('edB').value = sd.bottom; $('edBv').textContent = sd.bottom;
$('edSX').value = sp.dx ?? 0; $('edSXv').textContent = sp.dx ?? 0;
$('edSY').value = sp.dy ?? -27; $('edSYv').textContent = sp.dy ?? -27;
$('edSS').value = sp.s ?? 1; $('edSSv').textContent = (sp.s ?? 1).toFixed(2);
}
function applySprite() {
const key = spriteKey();
const w = +$('edW').value, bottom = +$('edB').value;
SPRITE_DEFS[key] = Object.assign(SPRITE_DEFS[key] || {}, { w, bottom });
tuned.sprites[key] = { w, bottom };
$('edWv').textContent = w; $('edBv').textContent = bottom;
saveTuning();
}
function applySolar() {
const key = spriteKey();
const v = { dx: +$('edSX').value, dy: +$('edSY').value, s: +$('edSS').value };
SOLAR_POS[key] = v;
tuned.solar[key] = v;
$('edSXv').textContent = v.dx; $('edSYv').textContent = v.dy;
$('edSSv').textContent = v.s.toFixed(2);
saveTuning();
}
['edW', 'edB'].forEach(id => $(id).addEventListener('input', applySprite));
['edSX', 'edSY', 'edSS'].forEach(id => $(id).addEventListener('input', applySolar));
sel.addEventListener('change', rebuild);
$('edMega').addEventListener('change', rebuild);
$('edEco').addEventListener('change', rebuild);
document.querySelectorAll('#edLevels button').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('#edLevels button').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
curLevel = +btn.dataset.lvl;
rebuild();
});
});
$('edReset').addEventListener('click', () => {
const key = spriteKey();
delete tuned.sprites[key];
delete tuned.solar[key];
saveTuning();
location.reload(); // Original-Werte aus dem Code laden
});
$('edCopy').addEventListener('click', async () => {
$('edOut').value = JSON.stringify(tuned, null, 2);
try {
await navigator.clipboard.writeText($('edOut').value);
$('edCopy').textContent = '✅ kopiert!';
} catch {
$('edOut').select();
$('edCopy').textContent = '⬇︎ unten markiert';
}
setTimeout(() => { $('edCopy').textContent = '📋 Export kopieren'; }, 1800);
});
// ---------- Loop ----------
rebuild();
$('edOut').value = JSON.stringify(tuned, null, 2);
let last = performance.now();
function frame(now) {
const dt = now - last; last = now;
renderer.draw(now, dt, 1);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);