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:
@@ -127,6 +127,7 @@
|
|||||||
<div id="biKnowledge" class="bi-knowledge hidden"></div>
|
<div id="biKnowledge" class="bi-knowledge hidden"></div>
|
||||||
<div class="card-buttons">
|
<div class="card-buttons">
|
||||||
<button id="biUpgrade" class="primary"></button>
|
<button id="biUpgrade" class="primary"></button>
|
||||||
|
<button id="biEco" class="secondary"></button>
|
||||||
<button id="biClose" class="secondary">Schließen</button>
|
<button id="biClose" class="secondary">Schließen</button>
|
||||||
</div>
|
</div>
|
||||||
<button id="biDemolish" class="bi-demolish">🧨 Abreißen (30 % zurück)</button>
|
<button id="biDemolish" class="bi-demolish">🧨 Abreißen (30 % zurück)</button>
|
||||||
|
|||||||
@@ -83,6 +83,11 @@ export const BUILDINGS = {
|
|||||||
zone: 'valley', staff: 2, summerCap: 80, satBonus: 4,
|
zone: 'valley', staff: 2, summerCap: 80, satBonus: 4,
|
||||||
desc: 'Wasserspiele & Kletterburg für Familien – 80 Sommergäste und viel Zufriedenheit.',
|
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
|
// Start im Sommer: ruhige Aufbauphase, der erste Winter wird zur Bewährungsprobe
|
||||||
@@ -133,7 +138,7 @@ export const SCENARIOS = {
|
|||||||
deadlineDay: 600,
|
deadlineDay: 600,
|
||||||
goalsTitle: 'Ziele bis Ende 1979',
|
goalsTitle: 'Ziele bis Ende 1979',
|
||||||
prebuilt: [],
|
prebuilt: [],
|
||||||
startUnlocked: ['restaurant'], // kleine Schritte!
|
startUnlocked: ['restaurant', 'forest'], // kleine Schritte!
|
||||||
stageRow: 8, // Hochlagen anfangs wolkenverhangen
|
stageRow: 8, // Hochlagen anfangs wolkenverhangen
|
||||||
quests: QUESTS_PIONIER,
|
quests: QUESTS_PIONIER,
|
||||||
goals: [
|
goals: [
|
||||||
@@ -748,4 +753,8 @@ export const KNOWLEDGE = {
|
|||||||
playground: [
|
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 %).' },
|
{ 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.' },
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1757,6 +1757,7 @@ export class IsoRenderer {
|
|||||||
ctx.fill();
|
ctx.fill();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (tile.eco) this._drawSolar(x, y, !!tile.mega || (BUILDINGS[type]?.size || 1) > 1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@@ -1960,6 +1961,30 @@ export class IsoRenderer {
|
|||||||
break;
|
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)
|
// Seil-Endpunkte eines Lifts: a = Talstation, b = Bergstation (Screen-Koordinaten)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Einstiegspunkt: Szenenwahl → Spielstart. Verdrahtet Modell, Renderer,
|
// Einstiegspunkt: Szenenwahl → Spielstart. Verdrahtet Modell, Renderer,
|
||||||
// Audio und UI und treibt die laufende Zeit an (einstellbare Zeitskala).
|
// 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 { BUILDINGS, EVENTS, MILESTONES, DECISIONS, HINTS, ADVISOR, BUILD_INFO } from './data.js';
|
||||||
import { IsoRenderer } from './iso.js';
|
import { IsoRenderer } from './iso.js';
|
||||||
import { UI } from './ui.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 });
|
telemetry.log('upgrade', { c, r, cost: res.cost, level: res.level });
|
||||||
ui._closeBuilding(); // Karte schließen → man sieht die Baustelle
|
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: () => {
|
onDemolish: () => {
|
||||||
const res = demolish(state, c, r);
|
const res = demolish(state, c, r);
|
||||||
if (!res.ok) { ui.toast(`❌ ${res.reason}`); audio.error(); return; }
|
if (!res.ok) { ui.toast(`❌ ${res.reason}`); audio.error(); return; }
|
||||||
@@ -130,6 +140,13 @@ function onTileTap(c, r) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const res = place(state, type, c, r);
|
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.toast(`✅ ${BUILDINGS[type].name} gebaut.`);
|
||||||
ui.clearBuildSelection(); // Baumodus verlassen → sicher navigieren
|
ui.clearBuildSelection(); // Baumodus verlassen → sicher navigieren
|
||||||
audio.build();
|
audio.build();
|
||||||
|
|||||||
@@ -305,6 +305,9 @@ export function canPlace(state, type, c, r) {
|
|||||||
if (type === 'platform' && tile.elev < 5) {
|
if (type === 'platform' && tile.elev < 5) {
|
||||||
return { ok: false, reason: 'Die Aussichtsplattform braucht Aussicht – hoch am Berg bauen (weiter oben)!' };
|
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') {
|
if (type === 'access') {
|
||||||
let nearRoad = false;
|
let nearRoad = false;
|
||||||
for (let rr = r - 1; rr <= r + 1 && !nearRoad; rr++) {
|
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) {
|
export function place(state, type, c, r) {
|
||||||
const def = BUILDINGS[type];
|
const def = BUILDINGS[type];
|
||||||
const tile = state.map[r][c];
|
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 wasForest = tile.terrain === 'forest';
|
||||||
const size = def.size || 1;
|
const size = def.size || 1;
|
||||||
state.buildings.push({ type, c, r, alt: tile.elev, size });
|
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 def = BUILDINGS[b.type];
|
||||||
const level = b.level || 1;
|
const level = b.level || 1;
|
||||||
const sf = seasonFactor(b.type, state.season);
|
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 dailyWage = Math.round(effVal(b, 'staff') * sf) * state.scenario.wage;
|
||||||
const lines = [];
|
const lines = [];
|
||||||
if (def.cap) lines.push(`Förderleistung: ${effVal(b, 'cap')} Gäste/Tag`);
|
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.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 (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.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)
|
// Aktuelle Probleme dieses Gebäudes (erscheinen im Klick-Dialog)
|
||||||
const problems = [];
|
const problems = [];
|
||||||
@@ -513,6 +525,8 @@ export function buildingReport(state, c, r) {
|
|||||||
knowledge: KNOWLEDGE[b.type] || [],
|
knowledge: KNOWLEDGE[b.type] || [],
|
||||||
canUpgrade: level < MAX_LEVEL && !b.underConstruction,
|
canUpgrade: level < MAX_LEVEL && !b.underConstruction,
|
||||||
nextCost: level < MAX_LEVEL ? upgradeCost(b.type, level) : null,
|
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));
|
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) {
|
export function upgrade(state, c, r) {
|
||||||
const ref = state.map[r]?.[c]?.buildingRef;
|
const ref = state.map[r]?.[c]?.buildingRef;
|
||||||
if (ref) ({ c, r } = ref);
|
if (ref) ({ c, r } = ref);
|
||||||
@@ -767,7 +802,7 @@ export function simulateDay(state) {
|
|||||||
// Betriebskosten je Gast (Liftstrom, Reinigung, Wäsche …): 55 % vom Umsatz
|
// Betriebskosten je Gast (Liftstrom, Reinigung, Wäsche …): 55 % vom Umsatz
|
||||||
const opsCost = Math.round(income * 0.58);
|
const opsCost = Math.round(income * 0.58);
|
||||||
const upkeep = Math.round(state.buildings.reduce((a, b) =>
|
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;
|
const wages = staffWorking * S.wage;
|
||||||
state.money = Math.round(state.money + income - opsCost - upkeep - wages - cannonCost);
|
state.money = Math.round(state.money + income - opsCost - upkeep - wages - cannonCost);
|
||||||
state.lastNet = Math.round(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 ---
|
// --- Natur erholt sich langsam ---
|
||||||
state.nature = Math.min(100,
|
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) ---
|
// --- Umbau-Fertigstellung (Fallback) ---
|
||||||
// Primär animiert der Renderer den Umbau (10 s Spielzeit = 5 Tage bei 1×)
|
// Primär animiert der Renderer den Umbau (10 s Spielzeit = 5 Tage bei 1×)
|
||||||
|
|||||||
@@ -660,6 +660,15 @@ export class UI {
|
|||||||
up.disabled = true;
|
up.disabled = true;
|
||||||
up.classList.remove('hidden');
|
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.api.setSpeed(0);
|
||||||
this.markSpeed(0);
|
this.markSpeed(0);
|
||||||
$('buildingOverlay').classList.remove('hidden');
|
$('buildingOverlay').classList.remove('hidden');
|
||||||
@@ -677,6 +686,10 @@ export class UI {
|
|||||||
audio.click();
|
audio.click();
|
||||||
this._biHandlers?.onUpgrade?.(this._biReport);
|
this._biHandlers?.onUpgrade?.(this._biReport);
|
||||||
});
|
});
|
||||||
|
$('biEco').addEventListener('click', () => {
|
||||||
|
audio.click();
|
||||||
|
this._biHandlers?.onEco?.(this._biReport);
|
||||||
|
});
|
||||||
$('biDemolish').addEventListener('click', () => {
|
$('biDemolish').addEventListener('click', () => {
|
||||||
audio.click();
|
audio.click();
|
||||||
this._biHandlers?.onDemolish?.(this._biReport);
|
this._biHandlers?.onDemolish?.(this._biReport);
|
||||||
|
|||||||
Reference in New Issue
Block a user