Tourismustal: Naturwert aktiv zurueckgewinnen (Oeko-Umbau + Aufforsten)

Antwort auf die Design-Frage 'Naturwert zurueck ausser weniger bauen':
- Oeko-Umbau pro Gebaeude (Button im Gebaeude-Dialog): einmalig, 40 % des
  Bauwerts, dafuer -20 % Betriebskosten, +2 Naturwert, Gebaeude belastet
  die taegliche Natur-Regeneration nicht mehr. Sichtbar: Solardach-Overlay
  (auch auf Megabauwerken, 1,6x). Didaktik: Investition vs. laufende
  Kosten, nachhaltiges Wirtschaften (GW 4.10).
- Aufforsten als wiederholbare Aktion im Baumenue (1.500 Euro): pflanzt
  Wald auf freie Wiese/Alm/Feld, +3 Naturwert, Werkzeug bleibt aktiv fuer
  Serien-Pflanzung. Wissen: Renaturierung + Schutzwald (1/3 des
  oesterreichischen Walds).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-11 18:16:40 +02:00
parent 328d542fdc
commit d195b085d9
6 changed files with 105 additions and 5 deletions
+1
View File
@@ -127,6 +127,7 @@
<div id="biKnowledge" class="bi-knowledge hidden"></div>
<div class="card-buttons">
<button id="biUpgrade" class="primary"></button>
<button id="biEco" class="secondary"></button>
<button id="biClose" class="secondary">Schließen</button>
</div>
<button id="biDemolish" class="bi-demolish">🧨 Abreißen (30 % zurück)</button>
+10 -1
View File
@@ -83,6 +83,11 @@ export const BUILDINGS = {
zone: 'valley', staff: 2, summerCap: 80, satBonus: 4,
desc: 'Wasserspiele & Kletterburg für Familien 80 Sommergäste und viel Zufriedenheit.',
},
forest: {
name: 'Aufforsten', emoji: '🌲', cost: 1500, upkeep: 0,
zone: 'any', staff: 0, action: 'plant',
desc: 'Pflanzt Wald auf eine freie Wiese: +3 Naturwert. Beliebig oft wiederholbar so gestaltest du Natur aktiv.',
},
};
// Start im Sommer: ruhige Aufbauphase, der erste Winter wird zur Bewährungsprobe
@@ -133,7 +138,7 @@ export const SCENARIOS = {
deadlineDay: 600,
goalsTitle: 'Ziele bis Ende 1979',
prebuilt: [],
startUnlocked: ['restaurant'], // kleine Schritte!
startUnlocked: ['restaurant', 'forest'], // kleine Schritte!
stageRow: 8, // Hochlagen anfangs wolkenverhangen
quests: QUESTS_PIONIER,
goals: [
@@ -748,4 +753,8 @@ export const KNOWLEDGE = {
playground: [
{ term: 'Zielgruppe Familien', text: 'Familien sind der Wachstumsmotor des Sommertourismus: Serfaus-Fiss-Ladis hat mit Familienangeboten seine Sommernächtigungen in 10 Jahren fast verdoppelt (+95 %).' },
],
forest: [
{ term: 'Renaturierung', text: 'Aufforstung und Almpflege geben der Natur Fläche zurück. Viele Tourismusregionen investieren gezielt die Landschaft IST ihr Kapital.' },
{ term: 'Schutzwald', text: 'Bergwald schützt Täler vor Lawinen und Muren. In Österreich ist rund ein Drittel des Waldes Schutzwald.' },
],
};
+25
View File
@@ -1757,6 +1757,7 @@ export class IsoRenderer {
ctx.fill();
}
}
if (tile.eco) this._drawSolar(x, y, !!tile.mega || (BUILDINGS[type]?.size || 1) > 1);
return;
}
switch (type) {
@@ -1960,6 +1961,30 @@ export class IsoRenderer {
break;
}
}
if (tile.eco) this._drawSolar(x, y, !!tile.mega);
}
// Solardach: Zeichen des Öko-Umbaus dunkles Panel mit weißen Stegen
// und kleinem Glanzpunkt, stilisiert aufs Dach gesetzt.
_drawSolar(x, y, big = false) {
const ctx = this.ctx;
const s = big ? 1.6 : 1;
ctx.save();
ctx.translate(x - 3 * s, y - (big ? 36 : 27));
ctx.scale(s, s);
ctx.fillStyle = '#2d4a66';
ctx.beginPath();
ctx.moveTo(-9, 3); ctx.lineTo(1, -2); ctx.lineTo(10, 2.5); ctx.lineTo(0, 7.5);
ctx.closePath(); ctx.fill();
ctx.strokeStyle = 'rgba(255,255,255,0.55)';
ctx.lineWidth = 0.7;
ctx.beginPath();
ctx.moveTo(-5.5, 4.6); ctx.lineTo(4.5, -0.4);
ctx.moveTo(-2, 6); ctx.lineTo(8, 1);
ctx.stroke();
ctx.fillStyle = 'rgba(255,255,255,0.85)';
ctx.beginPath(); ctx.arc(6.5, 0.6, 1, 0, 7); ctx.fill();
ctx.restore();
}
// Seil-Endpunkte eines Lifts: a = Talstation, b = Bergstation (Screen-Koordinaten)
+18 -1
View File
@@ -1,7 +1,7 @@
// Einstiegspunkt: Szenenwahl → Spielstart. Verdrahtet Modell, Renderer,
// Audio und UI und treibt die laufende Zeit an (einstellbare Zeitskala).
import { createState, simulateDay, canPlace, place, demolish, upgrade, buildingReport } from './model.js';
import { createState, simulateDay, canPlace, place, demolish, upgrade, ecoUpgrade, buildingReport } from './model.js';
import { BUILDINGS, EVENTS, MILESTONES, DECISIONS, HINTS, ADVISOR, BUILD_INFO } from './data.js';
import { IsoRenderer } from './iso.js';
import { UI } from './ui.js';
@@ -102,6 +102,16 @@ function inspectBuilding(c, r) {
telemetry.log('upgrade', { c, r, cost: res.cost, level: res.level });
ui._closeBuilding(); // Karte schließen → man sieht die Baustelle
},
onEco: () => {
const res = ecoUpgrade(state, c, r);
if (!res.ok) { ui.toast(`${res.reason}`); audio.error(); return; }
audio.build();
renderer.addFloat('🌱 Öko-Umbau!', '#3f8f5e', c + 0.5, r - 0.5, true);
ui.updateKPIs();
ui.toast(`🌱 ${res.name} läuft jetzt als Öko-Betrieb (${res.cost.toLocaleString('de-AT')} €): 20 % Betriebskosten, +2 Naturwert.`, 6000);
telemetry.log('eco_upgrade', { c, r, cost: res.cost });
inspectBuilding(c, r); // Karte mit neuem Status
},
onDemolish: () => {
const res = demolish(state, c, r);
if (!res.ok) { ui.toast(`${res.reason}`); audio.error(); return; }
@@ -130,6 +140,13 @@ function onTileTap(c, r) {
return;
}
const res = place(state, type, c, r);
if (res.planted) {
ui.toast('🌲 Wald gepflanzt: +3 Naturwert. Weiter tippen zum Aufforsten!', 4000);
audio.build();
ui.updateKPIs();
telemetry.log('forest_planted', { c, r, moneyLeft: state.money });
return; // Werkzeug bleibt aktiv Aufforsten in Serie
}
ui.toast(`${BUILDINGS[type].name} gebaut.`);
ui.clearBuildSelection(); // Baumodus verlassen → sicher navigieren
audio.build();
+38 -3
View File
@@ -305,6 +305,9 @@ export function canPlace(state, type, c, r) {
if (type === 'platform' && tile.elev < 5) {
return { ok: false, reason: 'Die Aussichtsplattform braucht Aussicht hoch am Berg bauen (weiter oben)!' };
}
if (type === 'forest' && !['meadow', 'alm', 'field'].includes(tile.terrain)) {
return { ok: false, reason: 'Aufforsten geht nur auf freien Wiesen, Almen oder Feldern.' };
}
if (type === 'access') {
let nearRoad = false;
for (let rr = r - 1; rr <= r + 1 && !nearRoad; rr++) {
@@ -320,6 +323,14 @@ export function canPlace(state, type, c, r) {
export function place(state, type, c, r) {
const def = BUILDINGS[type];
const tile = state.map[r][c];
// Aktion statt Gebäude: Aufforsten pflanzt Wald auf die Kachel
if (def.action === 'plant') {
state.money -= def.cost;
tile.terrain = 'forest';
tile.deco = null;
state.nature = Math.min(100, state.nature + 3);
return { planted: true };
}
const wasForest = tile.terrain === 'forest';
const size = def.size || 1;
state.buildings.push({ type, c, r, alt: tile.elev, size });
@@ -476,7 +487,7 @@ export function buildingReport(state, c, r) {
const def = BUILDINGS[b.type];
const level = b.level || 1;
const sf = seasonFactor(b.type, state.season);
const dailyUpkeep = Math.round(effVal(b, 'upkeep') * sf);
const dailyUpkeep = Math.round(effVal(b, 'upkeep') * sf * (b.eco ? 0.8 : 1));
const dailyWage = Math.round(effVal(b, 'staff') * sf) * state.scenario.wage;
const lines = [];
if (def.cap) lines.push(`Förderleistung: ${effVal(b, 'cap')} Gäste/Tag`);
@@ -489,6 +500,7 @@ export function buildingReport(state, c, r) {
if (def.boost) lines.push(`Verstärker: Bikepark & Panoramaweg im Umkreis +${Math.round(def.boost * 100)} % Gäste`);
if (def.trailLen) lines.push(`Flowtrails: ${def.trailLen} Abschnitte talwärts`);
if (b.mega) lines.push('🏛️ Skaleneffekt: 4-fache Leistung, aber nur 3-fache Betriebskosten');
if (b.eco) lines.push('🌱 Öko-Betrieb: 20 % Betriebskosten, schont die Natur (Solardach)');
// Aktuelle Probleme dieses Gebäudes (erscheinen im Klick-Dialog)
const problems = [];
@@ -513,6 +525,8 @@ export function buildingReport(state, c, r) {
knowledge: KNOWLEDGE[b.type] || [],
canUpgrade: level < MAX_LEVEL && !b.underConstruction,
nextCost: level < MAX_LEVEL ? upgradeCost(b.type, level) : null,
eco: !!b.eco,
ecoCost: b.eco ? null : Math.round(def.cost * 0.4 * (b.mega ? 4 : 1)),
};
}
@@ -521,6 +535,27 @@ export function upgradeCost(type, level) {
return Math.round(BUILDINGS[type].cost * (0.25 + 0.25 * level));
}
// Öko-Umbau: einmalig pro Gebäude. Teuer (40 % des Bauwerts), dafür 20 %
// Betriebskosten (Solar/Wärmepumpe), +2 Naturwert sofort, und das Gebäude
// belastet die tägliche Natur-Regeneration nicht mehr. Sichtbar: Solardach.
export function ecoUpgrade(state, c, r) {
const ref = state.map[r]?.[c]?.buildingRef;
if (ref) ({ c, r } = ref);
const tile = state.map[r]?.[c];
const b = state.buildings.find(x => x.c === c && x.r === r);
if (!tile?.building || !b) return { ok: false, reason: 'Hier steht kein Gebäude.' };
if (b.eco) return { ok: false, reason: 'Läuft schon als Öko-Betrieb.' };
if (b.underConstruction) return { ok: false, reason: 'Wird gerade umgebaut bitte warten.' };
const def = BUILDINGS[b.type];
const cost = Math.round(def.cost * 0.4 * (b.mega ? 4 : 1));
if (state.money < cost) return { ok: false, reason: `Öko-Umbau kostet ${cost.toLocaleString('de-AT')} nicht genug Geld.` };
state.money -= cost;
b.eco = true;
tile.eco = true;
state.nature = Math.min(100, state.nature + 2);
return { ok: true, cost, name: def.name };
}
export function upgrade(state, c, r) {
const ref = state.map[r]?.[c]?.buildingRef;
if (ref) ({ c, r } = ref);
@@ -767,7 +802,7 @@ export function simulateDay(state) {
// Betriebskosten je Gast (Liftstrom, Reinigung, Wäsche …): 55 % vom Umsatz
const opsCost = Math.round(income * 0.58);
const upkeep = Math.round(state.buildings.reduce((a, b) =>
a + effVal(b, 'upkeep') * seasonFactor(b.type, state.season), 0));
a + effVal(b, 'upkeep') * seasonFactor(b.type, state.season) * (b.eco ? 0.8 : 1), 0));
const wages = staffWorking * S.wage;
state.money = Math.round(state.money + income - opsCost - upkeep - wages - cannonCost);
state.lastNet = Math.round(income - opsCost - upkeep - wages - cannonCost);
@@ -791,7 +826,7 @@ export function simulateDay(state) {
// --- Natur erholt sich langsam ---
state.nature = Math.min(100,
state.nature + 0.04 - state.buildings.length * 0.004);
state.nature + 0.04 - state.buildings.filter(b => !b.eco).length * 0.004);
// --- Umbau-Fertigstellung (Fallback) ---
// Primär animiert der Renderer den Umbau (10 s Spielzeit = 5 Tage bei 1×)
+13
View File
@@ -660,6 +660,15 @@ export class UI {
up.disabled = true;
up.classList.remove('hidden');
}
// Öko-Umbau (Solar & Co.): einmalig, 20 % Betriebskosten, +Natur
const eco = $('biEco');
if (report.eco) {
eco.textContent = '🌱 Öko-Betrieb ✓';
eco.disabled = true;
} else {
eco.textContent = `🌱 Öko-Umbau (${report.ecoCost.toLocaleString('de-AT')} €)`;
eco.disabled = handlers.money < report.ecoCost;
}
this.api.setSpeed(0);
this.markSpeed(0);
$('buildingOverlay').classList.remove('hidden');
@@ -677,6 +686,10 @@ export class UI {
audio.click();
this._biHandlers?.onUpgrade?.(this._biReport);
});
$('biEco').addEventListener('click', () => {
audio.click();
this._biHandlers?.onEco?.(this._biReport);
});
$('biDemolish').addEventListener('click', () => {
audio.click();
this._biHandlers?.onDemolish?.(this._biReport);