Tourismustal: Editor v2 (Ziehen statt Slider, Solar pro Stufe) + Bikepark-Vorschau
- Editor: Zieh-Modi direkt auf der Karte (Gebaeude: horizontal=Breite, vertikal=Boden-Anker; Solar: frei ziehen) + Pixel-Feinjustage per Plus/Minus-Tasten, grosse Live-Wertanzeige, Footprint-Hilfslinien. - Solar-Position wird PRO AUSBAUSTUFE gespeichert (<key>@L<stufe>, Vererbung Stufe -> Sprite -> Default) - Renderer-Lookup erweitert. - Bikepark zeigt beim Bauen die komplette Strecke talwaerts (5 Felder, pro Kachel gruen/rot je nach Belegbarkeit) - wie die Lift-Piste. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,37 +8,51 @@
|
|||||||
<style>
|
<style>
|
||||||
#edPanel {
|
#edPanel {
|
||||||
position: fixed; top: 10px; left: 10px; z-index: 90;
|
position: fixed; top: 10px; left: 10px; z-index: 90;
|
||||||
width: min(340px, 92vw); max-height: calc(100dvh - 20px); overflow-y: auto;
|
width: min(320px, 92vw); max-height: calc(100dvh - 20px); overflow-y: auto;
|
||||||
padding: 14px 16px; font-size: 14px;
|
padding: 12px 14px; font-size: 14px;
|
||||||
}
|
}
|
||||||
#edPanel h2 { margin: 0 0 10px; font-size: 16px; }
|
#edPanel h2 { margin: 0 0 8px; font-size: 16px; }
|
||||||
#edPanel label { display: block; font-weight: 700; font-size: 12px; margin: 10px 0 3px; color: #4a4f52; }
|
#edPanel label { display: block; font-weight: 700; font-size: 11px; margin: 8px 0 3px; color: #4a4f52; text-transform: uppercase; letter-spacing: .04em; }
|
||||||
#edPanel select, #edPanel textarea { width: 100%; font-size: 14px; padding: 6px; border-radius: 8px; border: 1px solid #ccc; }
|
#edPanel select, #edPanel textarea { width: 100%; font-size: 14px; padding: 7px; border-radius: 8px; border: 1px solid #ccc; }
|
||||||
#edPanel textarea { height: 110px; font-family: monospace; font-size: 11px; }
|
#edPanel textarea { height: 90px; font-family: monospace; font-size: 11px; }
|
||||||
.ed-row { display: flex; align-items: center; gap: 8px; }
|
.ed-lvl, .ed-mode { display: flex; gap: 4px; }
|
||||||
.ed-row input[type=range] { flex: 1; }
|
.ed-lvl button, .ed-mode button {
|
||||||
.ed-row .val { width: 44px; text-align: right; font-family: monospace; font-size: 12px; }
|
flex: 1; padding: 9px 0; border-radius: 8px; border: 1px solid #bbb;
|
||||||
.ed-btns { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 10px; }
|
background: #fff; cursor: pointer; font-size: 13px;
|
||||||
.ed-btns button { flex: 1; padding: 8px 6px; border-radius: 8px; border: 1px solid #bbb; background: #f4f1e6; cursor: pointer; font-size: 13px; }
|
}
|
||||||
.ed-btns button.primary { background: #e8892b; color: #fff; border-color: #d07a20; font-weight: 700; }
|
.ed-lvl button.active, .ed-mode button.active { background: #e8892b; color: #fff; border-color: #d07a20; font-weight: 700; }
|
||||||
.ed-lvl { display: flex; gap: 4px; }
|
|
||||||
.ed-lvl button { flex: 1; padding: 6px 0; border-radius: 8px; border: 1px solid #bbb; background: #fff; cursor: pointer; }
|
|
||||||
.ed-lvl button.active { background: #e8892b; color: #fff; border-color: #d07a20; font-weight: 700; }
|
|
||||||
.ed-check { display: flex; gap: 14px; margin-top: 8px; font-size: 13px; }
|
.ed-check { display: flex; gap: 14px; margin-top: 8px; font-size: 13px; }
|
||||||
.ed-hint { font-size: 11px; color: #8a8a8a; margin-top: 4px; line-height: 1.4; }
|
.ed-vals { display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; margin-top: 8px; }
|
||||||
|
.ed-val {
|
||||||
|
background: #f4f1e6; border-radius: 10px; padding: 6px 8px; text-align: center;
|
||||||
|
}
|
||||||
|
.ed-val .k { font-size: 10px; color: #8a8a8a; text-transform: uppercase; letter-spacing: .04em; }
|
||||||
|
.ed-val .v { font-size: 18px; font-weight: 800; font-family: monospace; }
|
||||||
|
.ed-val .pm { display: flex; justify-content: center; gap: 6px; margin-top: 3px; }
|
||||||
|
.ed-val .pm button {
|
||||||
|
width: 34px; height: 30px; border-radius: 8px; border: 1px solid #bbb;
|
||||||
|
background: #fff; font-size: 16px; font-weight: 800; cursor: pointer;
|
||||||
|
}
|
||||||
|
.ed-btns { display: flex; gap: 6px; margin-top: 10px; }
|
||||||
|
.ed-btns button { flex: 1; padding: 9px 6px; border-radius: 8px; border: 1px solid #bbb; background: #f4f1e6; cursor: pointer; font-size: 13px; }
|
||||||
|
.ed-btns button.primary { background: #e8892b; color: #fff; border-color: #d07a20; font-weight: 700; }
|
||||||
|
.ed-hint { font-size: 11px; color: #8a8a8a; margin-top: 6px; line-height: 1.45; }
|
||||||
|
#dragLayer { position: fixed; inset: 0; z-index: 50; display: none; touch-action: none; }
|
||||||
|
#dragLayer.on { display: block; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<canvas id="map"></canvas>
|
<canvas id="map"></canvas>
|
||||||
|
<div id="dragLayer"></div>
|
||||||
|
|
||||||
<div id="edPanel" class="panel">
|
<div id="edPanel" class="panel">
|
||||||
<h2>🛠️ Sprite-Editor</h2>
|
<h2>🛠️ Sprite-Editor <span id="edKey" style="font-size:11px;color:#8a8a8a;font-family:monospace"></span></h2>
|
||||||
|
|
||||||
<label>Gebäude</label>
|
<label>Gebäude</label>
|
||||||
<select id="edType"></select>
|
<select id="edType"></select>
|
||||||
|
|
||||||
<label>Ausbaustufe</label>
|
<label>Ausbaustufe (Solar wird PRO STUFE gespeichert)</label>
|
||||||
<div class="ed-lvl" id="edLevels">
|
<div class="ed-lvl" id="edLevels">
|
||||||
<button data-lvl="1" class="active">I</button>
|
<button data-lvl="1" class="active">I</button>
|
||||||
<button data-lvl="2">II</button>
|
<button data-lvl="2">II</button>
|
||||||
@@ -47,35 +61,39 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="ed-check">
|
<div class="ed-check">
|
||||||
<label style="margin:0;font-weight:400"><input type="checkbox" id="edMega"> 🏛️ Mega (2×2)</label>
|
<label style="margin:0;font-weight:400;text-transform:none"><input type="checkbox" id="edMega"> 🏛️ Mega</label>
|
||||||
<label style="margin:0;font-weight:400"><input type="checkbox" id="edEco" checked> 🌱 Öko (Solar)</label>
|
<label style="margin:0;font-weight:400;text-transform:none"><input type="checkbox" id="edEco" checked> 🌱 Öko</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label>Sprite-Breite <span style="color:#8a8a8a">(w)</span></label>
|
<label>Zieh-Modus (direkt auf der Karte ziehen)</label>
|
||||||
<div class="ed-row"><input type="range" id="edW" min="30" max="180" step="1"><span class="val" id="edWv"></span></div>
|
<div class="ed-mode" id="edModes">
|
||||||
|
<button data-mode="pan" class="active">✋ Karte</button>
|
||||||
|
<button data-mode="sprite">🏠 Gebäude</button>
|
||||||
|
<button data-mode="solar">☀️ Solar</button>
|
||||||
|
</div>
|
||||||
|
<div class="ed-hint">🏠: horizontal ziehen = Breite, vertikal = Boden-Anker.
|
||||||
|
☀️: Panel frei ziehen. Feinjustage: ±-Tasten.</div>
|
||||||
|
|
||||||
<label>Boden-Anker <span style="color:#8a8a8a">(bottom · größer = tiefer)</span></label>
|
<div class="ed-vals">
|
||||||
<div class="ed-row"><input type="range" id="edB" min="-20" max="60" step="1"><span class="val" id="edBv"></span></div>
|
<div class="ed-val"><div class="k">Breite w</div><div class="v" id="vW">–</div>
|
||||||
|
<div class="pm"><button data-t="w" data-d="-1">−</button><button data-t="w" data-d="1">+</button></div></div>
|
||||||
<label>☀️ Solar: horizontal (dx)</label>
|
<div class="ed-val"><div class="k">Anker bottom</div><div class="v" id="vB">–</div>
|
||||||
<div class="ed-row"><input type="range" id="edSX" min="-60" max="60" step="1"><span class="val" id="edSXv"></span></div>
|
<div class="pm"><button data-t="b" data-d="-1">−</button><button data-t="b" data-d="1">+</button></div></div>
|
||||||
|
<div class="ed-val"><div class="k">Solar dx</div><div class="v" id="vSX">–</div>
|
||||||
<label>☀️ Solar: vertikal (dy)</label>
|
<div class="pm"><button data-t="sx" data-d="-1">−</button><button data-t="sx" data-d="1">+</button></div></div>
|
||||||
<div class="ed-row"><input type="range" id="edSY" min="-80" max="20" step="1"><span class="val" id="edSYv"></span></div>
|
<div class="ed-val"><div class="k">Solar dy</div><div class="v" id="vSY">–</div>
|
||||||
|
<div class="pm"><button data-t="sy" data-d="-1">−</button><button data-t="sy" data-d="1">+</button></div></div>
|
||||||
<label>☀️ Solar: Größe (s)</label>
|
<div class="ed-val" style="grid-column:1/3"><div class="k">Solar Größe s</div><div class="v" id="vSS">–</div>
|
||||||
<div class="ed-row"><input type="range" id="edSS" min="0.4" max="3" step="0.05"><span class="val" id="edSSv"></span></div>
|
<div class="pm"><button data-t="ss" data-d="-1">−</button><button data-t="ss" data-d="1">+</button></div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="ed-btns">
|
<div class="ed-btns">
|
||||||
<button id="edReset">↩︎ Typ zurücksetzen</button>
|
<button id="edReset">↩︎ Zurücksetzen</button>
|
||||||
<button id="edCopy" class="primary">📋 Export kopieren</button>
|
<button id="edCopy" class="primary">📋 Export kopieren</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label>Export (an Atlas schicken → wird fest eingebaut)</label>
|
<label>Export (an Atlas → wird fest eingebaut)</label>
|
||||||
<textarea id="edOut" readonly></textarea>
|
<textarea id="edOut" readonly></textarea>
|
||||||
<div class="ed-hint">Werte wirken sofort – auch im echten Spiel auf DIESEM Gerät
|
|
||||||
(localStorage). Endgültig werden sie erst, wenn Atlas den Export in den Code übernimmt.
|
|
||||||
Karte: ziehen/zoomen wie im Spiel.</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="module" src="js/editor-tune.js"></script>
|
<script type="module" src="js/editor-tune.js"></script>
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
// Sprite-Editor: Gebäude in allen Ausbaustufen (1×1 und 2×2/Mega) auf echten
|
// Sprite-Editor v2: Gebäude & Solarpanel direkt auf der Karte ZIEHEN.
|
||||||
// Tiles justieren – Sprite-Breite, Boden-Anker und Solarpanel-Position.
|
// - 🏠-Modus: horizontal ziehen = Sprite-Breite, vertikal = Boden-Anker
|
||||||
// Werte landen live in localStorage (tt_sprite_tuning) und wirken sofort im
|
// - ☀️-Modus: Solarpanel frei ziehen (Position wird PRO AUSBAUSTUFE gespeichert)
|
||||||
// Spiel auf diesem Gerät; der Export wird von Atlas fest in iso.js übernommen.
|
// - ±-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 } from './model.js';
|
import { createState, place } from './model.js';
|
||||||
import { IsoRenderer, SPRITE_DEFS, SOLAR_POS } from './iso.js';
|
import { IsoRenderer, SPRITE_DEFS, SOLAR_POS } from './iso.js';
|
||||||
@@ -9,7 +11,7 @@ import { BUILDINGS } from './data.js';
|
|||||||
|
|
||||||
const $ = id => document.getElementById(id);
|
const $ = id => document.getElementById(id);
|
||||||
|
|
||||||
// ---------- Test-Welt: flach geräumte Bühne ----------
|
// ---------- Test-Welt ----------
|
||||||
const state = createState('boom');
|
const state = createState('boom');
|
||||||
state.stageRow = 0;
|
state.stageRow = 0;
|
||||||
state.season = 'Sommer';
|
state.season = 'Sommer';
|
||||||
@@ -18,21 +20,21 @@ state.guestsToday = 0;
|
|||||||
for (const row of state.map) {
|
for (const row of state.map) {
|
||||||
for (const t of row) {
|
for (const t of row) {
|
||||||
t.building = null; t.level = null;
|
t.building = null; t.level = null;
|
||||||
delete t.buildingRef; delete t.mega; delete t.eco;
|
delete t.buildingRef; delete t.mega; delete t.eco; delete t.bikeTrail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.buildings = []; state.pistes = []; state.trails = [];
|
state.buildings = []; state.pistes = []; state.trails = [];
|
||||||
|
|
||||||
// Bühne: ebene 2×2-Fläche nahe der Kartenmitte suchen
|
let SPOT = { c: 18, r: 22 };
|
||||||
let SPOT = { c: 18, r: 24 };
|
|
||||||
outer:
|
outer:
|
||||||
for (let r = 20; r < 34; r++) {
|
for (let r = 18; r < 30; r++) {
|
||||||
for (let c = 10; c < 28; c++) {
|
for (let c = 10; c < 28; c++) {
|
||||||
const ok = [[0, 0], [1, 0], [0, 1], [1, 1]].every(([dc, dr]) => {
|
const ok = [[0, 0], [1, 0], [0, 1], [1, 1], [0, 2], [0, 3], [0, 4], [0, 5]]
|
||||||
const t = state.map[r + dr]?.[c + dc];
|
.every(([dc, dr]) => {
|
||||||
return t && !t.road && t.terrain !== 'water' && t.terrain !== 'village'
|
const t = state.map[r + dr]?.[c + dc];
|
||||||
&& t.terrain !== 'rock' && t.elev >= 1 && t.elev <= 3;
|
return t && !t.road && !['water', 'village', 'rock'].includes(t.terrain)
|
||||||
});
|
&& t.elev >= 1 && t.elev <= 4;
|
||||||
|
});
|
||||||
if (ok) { SPOT = { c, r }; break outer; }
|
if (ok) { SPOT = { c, r }; break outer; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,13 +52,14 @@ function saveTuning() {
|
|||||||
// ---------- Auswahl ----------
|
// ---------- Auswahl ----------
|
||||||
const sel = $('edType');
|
const sel = $('edType');
|
||||||
for (const [type, def] of Object.entries(BUILDINGS)) {
|
for (const [type, def] of Object.entries(BUILDINGS)) {
|
||||||
if (def.action) continue; // Aufforsten etc.
|
if (def.action) continue;
|
||||||
const o = document.createElement('option');
|
const o = document.createElement('option');
|
||||||
o.value = type;
|
o.value = type;
|
||||||
o.textContent = `${def.emoji} ${def.name}`;
|
o.textContent = `${def.emoji} ${def.name}`;
|
||||||
sel.appendChild(o);
|
sel.appendChild(o);
|
||||||
}
|
}
|
||||||
let curLevel = 1;
|
let curLevel = 1;
|
||||||
|
let dragMode = 'pan';
|
||||||
|
|
||||||
function spriteKey() {
|
function spriteKey() {
|
||||||
const type = sel.value;
|
const type = sel.value;
|
||||||
@@ -64,23 +67,37 @@ function spriteKey() {
|
|||||||
if (type === 'hotel' && curLevel >= 2) return 'hotel2';
|
if (type === 'hotel' && curLevel >= 2) return 'hotel2';
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
function solarKey() { return `${spriteKey()}@L${curLevel}`; }
|
||||||
|
|
||||||
// ---------- Bühne neu bestücken ----------
|
// 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() {
|
function rebuild() {
|
||||||
for (const row of state.map) {
|
for (const row of state.map) {
|
||||||
for (const t of row) {
|
for (const t of row) {
|
||||||
t.building = null; t.level = null;
|
t.building = null; t.level = null;
|
||||||
delete t.buildingRef; delete t.mega; delete t.eco;
|
delete t.buildingRef; delete t.mega; delete t.eco; delete t.bikeTrail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.buildings = []; state.pistes = []; state.trails = [];
|
state.buildings = []; state.pistes = []; state.trails = [];
|
||||||
const type = sel.value;
|
const type = sel.value;
|
||||||
const def = BUILDINGS[type];
|
const mega = $('edMega').checked && (BUILDINGS[type].size || 1) === 1;
|
||||||
const mega = $('edMega').checked && (def.size || 1) === 1;
|
|
||||||
const { c, r } = SPOT;
|
const { c, r } = SPOT;
|
||||||
if (mega) {
|
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); place(state, type, c + 1, r);
|
||||||
place(state, type, c, r + 1); place(state, type, c + 1, r + 1);
|
place(state, type, c, r + 1); place(state, type, c + 1, r + 1);
|
||||||
let b = state.buildings.find(x => x.mega);
|
let b = state.buildings.find(x => x.mega);
|
||||||
@@ -104,44 +121,80 @@ function rebuild() {
|
|||||||
state.map[b.r][b.c].level = curLevel;
|
state.map[b.r][b.c].level = curLevel;
|
||||||
if ($('edEco').checked) { b.eco = true; state.map[b.r][b.c].eco = true; }
|
if ($('edEco').checked) { b.eco = true; state.map[b.r][b.c].eco = true; }
|
||||||
}
|
}
|
||||||
syncSliders();
|
syncVals();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Slider ↔ Werte ----------
|
// ---------- Werte-Anzeige ----------
|
||||||
function syncSliders() {
|
function syncVals() {
|
||||||
const key = spriteKey();
|
const sd = spriteDef();
|
||||||
const sd = SPRITE_DEFS[key] || { w: 60, bottom: 8 };
|
const sp = solarPos();
|
||||||
const sp = SOLAR_POS[key]
|
$('vW').textContent = Math.round(sd.w);
|
||||||
|| ($('edMega').checked || (BUILDINGS[sel.value].size || 1) > 1
|
$('vB').textContent = Math.round(sd.bottom);
|
||||||
? SOLAR_POS.defaultBig : SOLAR_POS.default);
|
$('vSX').textContent = Math.round(sp.dx);
|
||||||
$('edW').value = sd.w; $('edWv').textContent = sd.w;
|
$('vSY').textContent = Math.round(sp.dy);
|
||||||
$('edB').value = sd.bottom; $('edBv').textContent = sd.bottom;
|
$('vSS').textContent = (sp.s ?? 1).toFixed(2);
|
||||||
$('edSX').value = sp.dx ?? 0; $('edSXv').textContent = sp.dx ?? 0;
|
$('edKey').textContent = `${spriteKey()} · Solar ${solarKey()}`;
|
||||||
$('edSY').value = sp.dy ?? -27; $('edSYv').textContent = sp.dy ?? -27;
|
|
||||||
$('edSS').value = sp.s ?? 1; $('edSSv').textContent = (sp.s ?? 1).toFixed(2);
|
|
||||||
}
|
}
|
||||||
|
function persist() {
|
||||||
function applySprite() {
|
const sk = spriteKey();
|
||||||
const key = spriteKey();
|
tuned.sprites[sk] = { w: Math.round(SPRITE_DEFS[sk].w), bottom: Math.round(SPRITE_DEFS[sk].bottom) };
|
||||||
const w = +$('edW').value, bottom = +$('edB').value;
|
const lk = solarKey();
|
||||||
SPRITE_DEFS[key] = Object.assign(SPRITE_DEFS[key] || {}, { w, bottom });
|
const sp = SOLAR_POS[lk];
|
||||||
tuned.sprites[key] = { w, bottom };
|
tuned.solar[lk] = { dx: Math.round(sp.dx), dy: Math.round(sp.dy), s: +(sp.s ?? 1).toFixed(2) };
|
||||||
$('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();
|
saveTuning();
|
||||||
|
syncVals();
|
||||||
}
|
}
|
||||||
|
|
||||||
['edW', 'edB'].forEach(id => $(id).addEventListener('input', applySprite));
|
// ---------- ±-Feinjustage ----------
|
||||||
['edSX', 'edSY', 'edSS'].forEach(id => $(id).addEventListener('input', applySolar));
|
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);
|
sel.addEventListener('change', rebuild);
|
||||||
$('edMega').addEventListener('change', rebuild);
|
$('edMega').addEventListener('change', rebuild);
|
||||||
$('edEco').addEventListener('change', rebuild);
|
$('edEco').addEventListener('change', rebuild);
|
||||||
@@ -155,11 +208,10 @@ document.querySelectorAll('#edLevels button').forEach(btn => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$('edReset').addEventListener('click', () => {
|
$('edReset').addEventListener('click', () => {
|
||||||
const key = spriteKey();
|
delete tuned.sprites[spriteKey()];
|
||||||
delete tuned.sprites[key];
|
delete tuned.solar[solarKey()];
|
||||||
delete tuned.solar[key];
|
|
||||||
saveTuning();
|
saveTuning();
|
||||||
location.reload(); // Original-Werte aus dem Code laden
|
location.reload();
|
||||||
});
|
});
|
||||||
|
|
||||||
$('edCopy').addEventListener('click', async () => {
|
$('edCopy').addEventListener('click', async () => {
|
||||||
@@ -181,6 +233,29 @@ let last = performance.now();
|
|||||||
function frame(now) {
|
function frame(now) {
|
||||||
const dt = now - last; last = now;
|
const dt = now - last; last = now;
|
||||||
renderer.draw(now, dt, 1);
|
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);
|
||||||
}
|
}
|
||||||
requestAnimationFrame(frame);
|
requestAnimationFrame(frame);
|
||||||
|
|||||||
@@ -1995,7 +1995,7 @@ export class IsoRenderer {
|
|||||||
ctx.fill();
|
ctx.fill();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tile.eco) this._drawSolar(spriteName, x, y, !!tile.mega || (BUILDINGS[type]?.size || 1) > 1);
|
if (tile.eco) this._drawSolar(spriteName, x, y, !!tile.mega || (BUILDINGS[type]?.size || 1) > 1, level);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -2199,14 +2199,16 @@ export class IsoRenderer {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (tile.eco) this._drawSolar(type, x, y, !!tile.mega);
|
if (tile.eco) this._drawSolar(type, x, y, !!tile.mega, level);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Solardach: Zeichen des Öko-Umbaus – dunkles Panel mit weißen Stegen
|
// Solardach: Zeichen des Öko-Umbaus – dunkles Panel mit weißen Stegen
|
||||||
// und kleinem Glanzpunkt. Position pro Sprite-Key justierbar (SOLAR_POS).
|
// und kleinem Glanzpunkt. Position pro Sprite-Key UND Ausbaustufe
|
||||||
_drawSolar(key, x, y, big = false) {
|
// justierbar: erst "<key>@L<stufe>", dann "<key>", dann Default.
|
||||||
|
_drawSolar(key, x, y, big = false, level = 1) {
|
||||||
const ctx = this.ctx;
|
const ctx = this.ctx;
|
||||||
const pos = SOLAR_POS[key] || (big ? SOLAR_POS.defaultBig : SOLAR_POS.default);
|
const pos = SOLAR_POS[`${key}@L${level}`] || SOLAR_POS[key]
|
||||||
|
|| (big ? SOLAR_POS.defaultBig : SOLAR_POS.default);
|
||||||
const s = pos.s ?? 1;
|
const s = pos.s ?? 1;
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.translate(x + (pos.dx ?? 0), y + (pos.dy ?? 0));
|
ctx.translate(x + (pos.dx ?? 0), y + (pos.dy ?? 0));
|
||||||
@@ -2371,6 +2373,19 @@ export class IsoRenderer {
|
|||||||
this.ctx.fillStyle = fill;
|
this.ctx.fillStyle = fill;
|
||||||
this.ctx.fill();
|
this.ctx.fill();
|
||||||
}
|
}
|
||||||
|
} else if (def?.trailLen) {
|
||||||
|
// Bikepark: die ganze reservierte Strecke talwärts zeigen –
|
||||||
|
// pro Kachel grün/rot, je nachdem ob sie frei ist
|
||||||
|
for (let i = 1; i <= def.trailLen; i++) {
|
||||||
|
const tt = this.state.map[t.r + i]?.[t.c];
|
||||||
|
if (!tt) break;
|
||||||
|
const free = !tt.building && !tt.bikeTrail && !tt.road
|
||||||
|
&& !['water', 'rock', 'village'].includes(tt.terrain);
|
||||||
|
const p = this.tileToWorld(t.c, t.r + i, tt.elev);
|
||||||
|
this._diamond(p.x, p.y);
|
||||||
|
this.ctx.fillStyle = free ? fill : 'rgba(200,60,50,0.4)';
|
||||||
|
this.ctx.fill();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._diamond(x, y);
|
this._diamond(x, y);
|
||||||
|
|||||||
Reference in New Issue
Block a user