Tourismustal: Megabauwerke + Figuren-Feinschliff
- Merge-Mechanik: 4 gleiche 1x1-Gebaeude im Quadrat verschmelzen zum 2x2-Megabauwerk. Skaleneffekt als Lernmoment: 4-fache Leistung, nur 3-fache Betriebskosten (Erklaerkarte beim ersten Mal). Level = Maximum der Teile, Abriss-Erloes auf 4-fachem Bauwert, Bahnen/Bus/Bikepark ausgenommen. Rendering zentriert und 1.75x skaliert. - Gaeste stehen nicht mehr im Wasser (dwell auf Wasser -> sofort ans Ufer). - Skifahrer schimpft "#*!", wenn ihm der Schnee unterm Ski ausgeht. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -448,15 +448,23 @@ export class IsoRenderer {
|
||||
if (!ct || ct.terrain === 'water' || !liftSkiable(s, ct.elev)) {
|
||||
g.mode = 'walk'; g.route = []; g.nextLift = null; g.longDwell = false;
|
||||
g.tx = g.x + (Math.random() - 0.5) * 2; g.ty = g.y + 1.5;
|
||||
this._say(g, '#*!', 4000); // schimpft: Schnee zu Ende, Ski getragen!
|
||||
}
|
||||
}
|
||||
if (g.mode === 'crashed') { g.moving = false; continue; } // liegt, wartet auf Bergung
|
||||
|
||||
if (g.mode === 'dwell') { // verweilen (essen, schauen)
|
||||
g.wait -= dt;
|
||||
g.moving = false;
|
||||
if (g.wait <= 0) this._nextActivity(g);
|
||||
continue;
|
||||
// Niemand steht im Wasser herum: sofort ans Ufer weitergehen
|
||||
const wt = s.map[Math.round(g.y)]?.[Math.round(g.x)];
|
||||
if (wt?.terrain === 'water') {
|
||||
const d = this._randomDestination();
|
||||
g.mode = 'walk'; g.longDwell = false; g.tx = d.c; g.ty = d.r;
|
||||
} else {
|
||||
g.wait -= dt;
|
||||
g.moving = false;
|
||||
if (g.wait <= 0) this._nextActivity(g);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (g.mode === 'ride') { // sitzt sichtbar im Sessel
|
||||
@@ -1664,17 +1672,18 @@ export class IsoRenderer {
|
||||
_drawBuilding(type, x, y, tile) {
|
||||
const ctx = this.ctx;
|
||||
const level = tile.level || 1;
|
||||
// 2×2-Gebäude: zentriert über dem Areal zeichnen (Anker = links oben)
|
||||
if ((BUILDINGS[type]?.size || 1) > 1) {
|
||||
const half = (BUILDINGS[type].size - 1) / 2;
|
||||
const ctr = this.tileToWorld(tile.c + half, tile.r + half, tile.elev);
|
||||
// 2×2-Gebäude (Bikepark oder Megabauwerk): zentriert über dem Areal
|
||||
const isBig = (BUILDINGS[type]?.size || 1) > 1 || tile.mega;
|
||||
if (isBig) {
|
||||
const ctr = this.tileToWorld(tile.c + 0.5, tile.r + 0.5, tile.elev);
|
||||
x = ctr.x; y = ctr.y;
|
||||
}
|
||||
// KI-Sprites für Gebäude (Bahnen & Rodelbahn bleiben Vektor: dynamisch)
|
||||
const spriteName = type === 'hotel' && level >= 2 ? 'hotel2' : type;
|
||||
const spriteScale = type === 'hotel'
|
||||
let spriteScale = type === 'hotel'
|
||||
? (level >= 3 ? 1.15 : 1)
|
||||
: [1, 1.15, 1.3][Math.min(2, level - 1)];
|
||||
if (tile.mega) spriteScale *= 1.75; // Megabauwerk füllt sein 2×2-Areal
|
||||
if (type !== 'lift' && type !== 'gondola' && type !== 'sled'
|
||||
&& this._sprite(spriteName, x, y, spriteScale)) {
|
||||
this._badge(x + 18, y - 34, level);
|
||||
|
||||
@@ -128,12 +128,29 @@ function onTileTap(c, r) {
|
||||
telemetry.log('build_rejected', { type, c, r, reason: check.reason });
|
||||
return;
|
||||
}
|
||||
place(state, type, c, r);
|
||||
const res = place(state, type, c, r);
|
||||
ui.toast(`✅ ${BUILDINGS[type].name} gebaut.`);
|
||||
ui.clearBuildSelection(); // Baumodus verlassen → sicher navigieren
|
||||
audio.build();
|
||||
ui.updateKPIs();
|
||||
telemetry.log('build_placed', { type, c, r, moneyLeft: state.money });
|
||||
// 4 gleiche im Quadrat → Megabauwerk (Skaleneffekt-Lernmoment)
|
||||
if (res.merged) {
|
||||
audio.milestone?.();
|
||||
renderer.addFloat('🏛️ Megabauwerk!', '#e8892b', res.merged.c + 1, res.merged.r - 0.5, true);
|
||||
const first = !state.firedEvents.has('mega-info');
|
||||
state.firedEvents.add('mega-info');
|
||||
ui.showCard({
|
||||
id: `mega-${res.merged.c}-${res.merged.r}`, icon: '🏛️', sound: 'milestone',
|
||||
title: `Megabauwerk: ${res.merged.name}!`,
|
||||
text: `Vier gleiche Gebäude im Quadrat sind zu EINEM großen ${res.merged.name} verschmolzen.`
|
||||
+ '\n\nDas ist ein Skaleneffekt: Das Megabauwerk leistet so viel wie '
|
||||
+ 'alle vier zusammen – braucht aber nur die Betriebskosten von DREI. '
|
||||
+ 'Große Betriebe arbeiten pro Gast günstiger als kleine.'
|
||||
+ (first ? '\n\nTipp: Das klappt mit vielen Gebäudetypen – probiere es aus!' : ''),
|
||||
});
|
||||
telemetry.log('mega_merge', { type, c: res.merged.c, r: res.merged.r });
|
||||
}
|
||||
// Lernprogramm: beim ERSTEN Bau jedes Typs erklärt eine Karte die Wirkung
|
||||
const infoId = `bi-${type}`;
|
||||
if (BUILD_INFO[type] && !state.firedEvents.has(infoId)) {
|
||||
|
||||
@@ -345,7 +345,9 @@ export function place(state, type, c, r) {
|
||||
}
|
||||
state.nature = Math.max(15, state.nature - 2);
|
||||
}
|
||||
return { wasForest };
|
||||
// 4 gleiche im Quadrat? → Megabauwerk (Skaleneffekt)
|
||||
const merged = tryMerge(state, c, r);
|
||||
return { wasForest, merged };
|
||||
}
|
||||
|
||||
export function demolish(state, c, r) {
|
||||
@@ -354,8 +356,10 @@ export function demolish(state, c, r) {
|
||||
// Klick auf eine Teilfläche eines großen Gebäudes → auf den Anker umlenken
|
||||
if (tile.buildingRef) { ({ c, r } = tile.buildingRef); tile = state.map[r][c]; }
|
||||
const def = BUILDINGS[tile.building];
|
||||
const size = def.size || 1;
|
||||
const refund = Math.round(def.cost * 0.3);
|
||||
const entry = state.buildings.find(b => b.c === c && b.r === r);
|
||||
const size = entry?.size || def.size || 1;
|
||||
// Mega = 4 verschmolzene Gebäude → 4-facher Bauwert als Abriss-Basis
|
||||
const refund = Math.round(def.cost * (entry?.mega ? 4 : 1) * 0.3);
|
||||
state.money += refund;
|
||||
state.nature = Math.min(100, state.nature + 1);
|
||||
state.buildings = state.buildings.filter(b => !(b.c === c && b.r === r));
|
||||
@@ -369,6 +373,7 @@ export function demolish(state, c, r) {
|
||||
t.building = null;
|
||||
t.level = null;
|
||||
delete t.buildingRef;
|
||||
delete t.mega;
|
||||
}
|
||||
}
|
||||
return { ok: true, refund, name };
|
||||
@@ -380,7 +385,44 @@ export const UPGRADABLE = Object.keys(BUILDINGS);
|
||||
|
||||
function effVal(b, key) {
|
||||
const v = BUILDINGS[b.type][key] || 0;
|
||||
return Math.round(v * (1 + 0.5 * ((b.level || 1) - 1)));
|
||||
let val = v * (1 + 0.5 * ((b.level || 1) - 1));
|
||||
// Megabauwerk (4 verschmolzene Gebäude): 4-fache Leistung, aber nur
|
||||
// 3-fache Betriebskosten – Skaleneffekt („economies of scale").
|
||||
if (b.mega) val *= (key === 'upkeep' || key === 'staff') ? 3 : 4;
|
||||
return Math.round(val);
|
||||
}
|
||||
|
||||
// Verschmelzen: liegen 4 gleiche 1×1-Gebäude im 2×2-Quadrat, werden sie zu
|
||||
// EINEM Megabauwerk (size 2, mega). Bahnen, Bus & Bikepark ausgenommen.
|
||||
const MERGEABLE = ['restaurant', 'hotel', 'staffhouse', 'snow',
|
||||
'badesee', 'sled', 'ropes', 'playground', 'bikerental', 'trail'];
|
||||
function tryMerge(state, c, r) {
|
||||
const type = state.map[r]?.[c]?.building;
|
||||
if (!type || !MERGEABLE.includes(type)) return null;
|
||||
const at = (cc, rr) => state.buildings.find(b =>
|
||||
b.c === cc && b.r === rr && b.type === type
|
||||
&& (b.size || 1) === 1 && !b.mega && !b.underConstruction);
|
||||
// alle 4 möglichen Quadrate, die (c,r) enthalten
|
||||
for (const [ac, ar] of [[c, r], [c - 1, r], [c, r - 1], [c - 1, r - 1]]) {
|
||||
const parts = [at(ac, ar), at(ac + 1, ar), at(ac, ar + 1), at(ac + 1, ar + 1)];
|
||||
if (parts.some(p => !p)) continue;
|
||||
const level = Math.max(...parts.map(p => p.level || 1));
|
||||
// drei Teile entfernen, Anker wird Mega
|
||||
const anchor = parts[0];
|
||||
state.buildings = state.buildings.filter(b => b === anchor || !parts.includes(b));
|
||||
anchor.size = 2; anchor.mega = true; anchor.level = level;
|
||||
for (let dr = 0; dr < 2; dr++) {
|
||||
for (let dc = 0; dc < 2; dc++) {
|
||||
const t = state.map[ar + dr][ac + dc];
|
||||
t.building = type;
|
||||
t.level = level;
|
||||
if (dr || dc) t.buildingRef = { c: ac, r: ar };
|
||||
else { delete t.buildingRef; t.mega = true; }
|
||||
}
|
||||
}
|
||||
return { type, name: BUILDINGS[type].name, c: ac, r: ar, level };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function dist(a, b) {
|
||||
@@ -431,6 +473,7 @@ export function buildingReport(state, c, r) {
|
||||
if (def.snowPower) lines.push(`Beschneiung: +${effVal(b, 'snowPower').toFixed(1)} cm/Tag`);
|
||||
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');
|
||||
|
||||
// Aktuelle Probleme dieses Gebäudes (erscheinen im Klick-Dialog)
|
||||
const problems = [];
|
||||
@@ -445,7 +488,7 @@ export function buildingReport(state, c, r) {
|
||||
problems.push('💤 Nebensaison: ruht gerade – die laufenden Kosten laufen (reduziert) weiter.');
|
||||
|
||||
return {
|
||||
type: b.type, name: def.name, emoji: def.emoji, level,
|
||||
type: b.type, name: b.mega ? `Mega-${def.name}` : def.name, emoji: def.emoji, level,
|
||||
alt: b.alt ?? 3,
|
||||
dailyUpkeep, dailyWage,
|
||||
dailyCost: dailyUpkeep + dailyWage,
|
||||
|
||||
Reference in New Issue
Block a user