// Sprite-Editor v2: Gebäude & Solarpanel direkt auf der Karte ZIEHEN. // - 🏠-Modus: horizontal ziehen = Sprite-Breite, vertikal = Boden-Anker // - ☀️-Modus: Solarpanel frei ziehen (Position wird PRO AUSBAUSTUFE gespeichert) // - ±-Tasten für pixelgenaue Feinjustage // Werte wirken live (localStorage tt_sprite_tuning); der Export wird von // Atlas fest in iso.js übernommen. import { createState, place, MERGEABLE } from './model.js'; import { IsoRenderer, SPRITE_DEFS, SOLAR_POS } from './iso.js'; import { BUILDINGS } from './data.js'; const $ = id => document.getElementById(id); // ---------- Test-Welt ---------- 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; delete t.bikeTrail; } } state.buildings = []; state.pistes = []; state.trails = []; let SPOT = { c: 18, r: 22 }; outer: for (let r = 18; r < 30; r++) { for (let c = 10; c < 28; c++) { const ok = [[0, 0], [1, 0], [0, 1], [1, 1], [0, 2], [0, 3], [0, 4], [0, 5]] .every(([dc, dr]) => { const t = state.map[r + dr]?.[c + dc]; return t && !t.road && !['water', 'village', 'rock'].includes(t.terrain) && t.elev >= 1 && t.elev <= 4; }); 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; const o = document.createElement('option'); o.value = type; o.textContent = `${def.emoji} ${def.name}`; sel.appendChild(o); } let curLevel = 1; let dragMode = 'pan'; 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; } function solarKey() { return `${spriteKey()}@L${curLevel}`; } // Aktuelle Solar-Position (mit Vererbung Stufe → Sprite → Default) function solarPos() { const k = solarKey(); if (!SOLAR_POS[k]) { const base = SOLAR_POS[spriteKey()] || (($('edMega').checked) ? SOLAR_POS.defaultBig : SOLAR_POS.default); SOLAR_POS[k] = { dx: base.dx ?? 0, dy: base.dy ?? -27, s: base.s ?? 1 }; } return SOLAR_POS[k]; } function spriteDef() { const k = spriteKey(); if (!SPRITE_DEFS[k]) SPRITE_DEFS[k] = { w: 60, bottom: 8 }; return SPRITE_DEFS[k]; } // ---------- Bühne ---------- function rebuild() { // Mega gibt es nur für merge-fähige Gebäude (keine Bahnen/Sonderbauwerke) const megaChk = $('edMega'); megaChk.disabled = !MERGEABLE.includes(sel.value); if (megaChk.disabled) megaChk.checked = false; 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; delete t.bikeTrail; } } state.buildings = []; state.pistes = []; state.trails = []; const type = sel.value; const mega = $('edMega').checked && (BUILDINGS[type].size || 1) === 1; const { c, r } = SPOT; if (mega) { 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; } } syncVals(); } // ---------- Werte-Anzeige ---------- function syncVals() { const sd = spriteDef(); const sp = solarPos(); $('vW').textContent = Math.round(sd.w); $('vB').textContent = Math.round(sd.bottom); $('vSX').textContent = Math.round(sp.dx); $('vSY').textContent = Math.round(sp.dy); $('vSS').textContent = (sp.s ?? 1).toFixed(2); $('edKey').textContent = `${spriteKey()} · Solar ${solarKey()}`; } function persist() { const sk = spriteKey(); tuned.sprites[sk] = { w: Math.round(SPRITE_DEFS[sk].w), bottom: Math.round(SPRITE_DEFS[sk].bottom) }; const lk = solarKey(); const sp = SOLAR_POS[lk]; tuned.solar[lk] = { dx: Math.round(sp.dx), dy: Math.round(sp.dy), s: +(sp.s ?? 1).toFixed(2) }; saveTuning(); syncVals(); } // ---------- ±-Feinjustage ---------- document.querySelectorAll('.ed-val .pm button').forEach(btn => { btn.addEventListener('click', () => { const d = +btn.dataset.d; const sd = spriteDef(); const sp = solarPos(); switch (btn.dataset.t) { case 'w': sd.w += d; break; case 'b': sd.bottom += d; break; case 'sx': sp.dx += d; break; case 'sy': sp.dy += d; break; case 'ss': sp.s = Math.max(0.3, +(sp.s + d * 0.05).toFixed(2)); break; } persist(); }); }); // ---------- Zieh-Modi ---------- const dragLayer = $('dragLayer'); document.querySelectorAll('#edModes button').forEach(btn => { btn.addEventListener('click', () => { document.querySelectorAll('#edModes button').forEach(b => b.classList.remove('active')); btn.classList.add('active'); dragMode = btn.dataset.mode; dragLayer.classList.toggle('on', dragMode !== 'pan'); }); }); let dragging = false, lastX = 0, lastY = 0; dragLayer.addEventListener('pointerdown', e => { dragging = true; lastX = e.clientX; lastY = e.clientY; dragLayer.setPointerCapture(e.pointerId); }); dragLayer.addEventListener('pointermove', e => { if (!dragging) return; const dx = (e.clientX - lastX) / renderer.cam.scale; const dy = (e.clientY - lastY) / renderer.cam.scale; lastX = e.clientX; lastY = e.clientY; if (dragMode === 'sprite') { const sd = spriteDef(); sd.w = Math.max(20, sd.w + dx * 1.5); // horizontal = Breite sd.bottom = sd.bottom + dy; // vertikal = Boden-Anker } else if (dragMode === 'solar') { const sp = solarPos(); sp.dx += dx; sp.dy += dy; } persist(); }); dragLayer.addEventListener('pointerup', () => { dragging = false; }); dragLayer.addEventListener('pointercancel', () => { dragging = false; }); // ---------- Auswahl-Events ---------- 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', () => { delete tuned.sprites[spriteKey()]; delete tuned.solar[solarKey()]; saveTuning(); location.reload(); }); $('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); // Anker-Hilfe: Footprint-Raute(n) der Bühne nachziehen const ctx = renderer.ctx; ctx.save(); ctx.translate(renderer.cam.x, renderer.cam.y); ctx.scale(renderer.cam.scale, renderer.cam.scale); const b = state.buildings[0]; if (b) { const size = b.size || 1; ctx.strokeStyle = 'rgba(232,137,43,0.9)'; ctx.lineWidth = 1.6; ctx.setLineDash([5, 4]); for (let dr = 0; dr < size; dr++) { for (let dc = 0; dc < size; dc++) { const t = state.map[b.r + dr][b.c + dc]; const p = renderer.tileToWorld(b.c + dc, b.r + dr, t.elev); renderer._diamond(p.x, p.y); ctx.stroke(); } } ctx.setLineDash([]); ctx.lineWidth = 1; } ctx.restore(); requestAnimationFrame(frame); } requestAnimationFrame(frame);