import * as THREE from 'three'; export class Housing { constructor(game) { this.game = game; this.built = false; this.mesh = null; this.position = new THREE.Vector3(-20, 0, 38); // Улучшения this.upgrades = { roof: { built: false, name: 'Крыша', icon: '🏚️', desc: 'Защита от дождя и снега', cost: { scrap: 3, rope: 1 }, effect: 'weather_protection' }, bed: { built: false, name: 'Лежанка', icon: '🛏️', desc: 'Лучший отдых при сне (+10 здоровье)', cost: { clothing: 2, newspaper: 3 }, effect: 'better_sleep' }, stove: { built: false, name: 'Печка', icon: '🔥', desc: 'Обогрев и готовка', cost: { scrap: 4, candle: 2 }, effect: 'heating' }, door: { built: false, name: 'Дверь', icon: '🚪', desc: 'Защита от врагов в лагере', cost: { scrap: 2, rope: 2 }, effect: 'safety' }, storage: { built: false, name: 'Тайник', icon: '📦', desc: '+10 слотов инвентаря', cost: { scrap: 3, rope: 1 }, effect: 'extra_storage' }, }; } initFromConfig() { const cfg = this.game.world.mapConfig?.structures?.campSpot || {}; this.position.set(cfg.x ?? -20, 0, cfg.z ?? 38); } buildShelter() { if (this.built) return; // Нужно 5 хлама и 2 верёвки const inv = this.game.inventory; if (inv.getCount('scrap') < 5 || inv.getCount('rope') < 2) { this.game.notify('Нужно: 5x Хлам, 2x Верёвка', 'bad'); return; } inv.removeItem('scrap', 5); inv.removeItem('rope', 2); this.built = true; this.createMesh(); this.addInteractable(); this.game.notify('Вы построили своё укрытие!', 'good'); this.game.sound.playQuestComplete(); this.game.questSystem.onEvent('build_shelter'); this.game.achievements.check('shelter_built'); this.game.skills.addXP('survival', 5); } createMesh() { if (this.mesh) { this.game.scene.remove(this.mesh); } const group = new THREE.Group(); // Базовый каркас — палатка из палок const frameMat = new THREE.MeshStandardMaterial({ color: 0x5c3a1e }); // Основание const base = new THREE.Mesh( new THREE.BoxGeometry(4, 0.15, 3), new THREE.MeshStandardMaterial({ color: 0x4a3a2a }) ); base.position.y = 0.075; base.receiveShadow = true; group.add(base); // Стойки const stickGeo = new THREE.CylinderGeometry(0.06, 0.06, 2.2, 6); [[-1.8, 1.1, -1.3], [1.8, 1.1, -1.3], [-1.8, 1.1, 1.3], [1.8, 1.1, 1.3]].forEach(([x, y, z]) => { const stick = new THREE.Mesh(stickGeo, frameMat); stick.position.set(x, y, z); stick.castShadow = true; group.add(stick); }); // Перекладины const barGeo = new THREE.CylinderGeometry(0.05, 0.05, 3.8, 6); const bar1 = new THREE.Mesh(barGeo, frameMat); bar1.position.set(0, 2.2, -1.3); bar1.rotation.z = Math.PI / 2; group.add(bar1); const bar2 = new THREE.Mesh(barGeo, frameMat); bar2.position.set(0, 2.2, 1.3); bar2.rotation.z = Math.PI / 2; group.add(bar2); // Крыша (если построена) if (this.upgrades.roof.built) { const roofMat = new THREE.MeshStandardMaterial({ color: 0x3a5a3a, side: THREE.DoubleSide }); const roofGeo = new THREE.PlaneGeometry(4.2, 3.2); const roof = new THREE.Mesh(roofGeo, roofMat); roof.position.set(0, 2.3, 0); roof.rotation.x = -Math.PI / 2; roof.castShadow = true; roof.receiveShadow = true; group.add(roof); } // Лежанка if (this.upgrades.bed.built) { const bedMat = new THREE.MeshStandardMaterial({ color: 0x6b5b4b }); const bed = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.2, 2.2), bedMat); bed.position.set(-0.5, 0.25, 0); group.add(bed); const pillowMat = new THREE.MeshStandardMaterial({ color: 0x7b6b5b }); const pillow = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.15, 0.4), pillowMat); pillow.position.set(-0.5, 0.4, -0.8); group.add(pillow); } // Печка if (this.upgrades.stove.built) { const stoveMat = new THREE.MeshStandardMaterial({ color: 0x555555 }); const stove = new THREE.Mesh(new THREE.CylinderGeometry(0.35, 0.4, 0.5, 8), stoveMat); stove.position.set(1.2, 0.25, 0.5); stove.castShadow = true; group.add(stove); // Огонь const fireMat = new THREE.MeshStandardMaterial({ color: 0xff6600, emissive: 0xff4400, emissiveIntensity: 0.8 }); const fire = new THREE.Mesh(new THREE.SphereGeometry(0.15, 6, 4), fireMat); fire.position.set(1.2, 0.55, 0.5); group.add(fire); } // Дверь if (this.upgrades.door.built) { const doorMat = new THREE.MeshStandardMaterial({ color: 0x5c4a3a }); const door = new THREE.Mesh(new THREE.BoxGeometry(1.2, 2, 0.1), doorMat); door.position.set(0, 1, 1.35); door.castShadow = true; group.add(door); } // Тайник if (this.upgrades.storage.built) { const boxMat = new THREE.MeshStandardMaterial({ color: 0x4a4a3a }); const box = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.6, 0.6), boxMat); box.position.set(1.2, 0.3, -0.7); box.castShadow = true; group.add(box); } group.position.copy(this.position); this.game.scene.add(group); this.mesh = group; } addInteractable() { // Убираем старое если есть this.game.world.interactables = this.game.world.interactables.filter(o => o.type !== 'player_shelter'); this.game.world.interactables.push({ position: this.position.clone(), radius: 4, type: 'player_shelter', label: 'Ваше укрытие' }); } showMenu() { if (!this.built) { // Предложить построить const hasRes = this.game.inventory.getCount('scrap') >= 5 && this.game.inventory.getCount('rope') >= 2; this.game.ui.showDialog('Место для лагеря', 'Здесь можно поставить укрытие. Нужно: 5x Хлам, 2x Верёвка.', [ hasRes ? 'Построить укрытие' : 'Не хватает материалов', 'Уйти' ], (i) => { if (i === 0 && hasRes) { this.buildShelter(); } this.game.ui.hideDialog(); }); return; } const choices = []; const actions = []; // Спать (если есть лежанка — лучше) choices.push(this.upgrades.bed.built ? 'Поспать (улучшенный отдых)' : 'Поспать'); actions.push('sleep'); // Погреться (если есть печка) if (this.upgrades.stove.built) { choices.push('Погреться у печки'); actions.push('warm'); } // Доступные улучшения for (const [key, upg] of Object.entries(this.upgrades)) { if (upg.built) continue; const canBuild = this.canBuildUpgrade(key); const costStr = Object.entries(upg.cost).map(([item, count]) => { const name = this.game.inventory.itemData[item]?.name || item; return `${count}x ${name}`; }).join(', '); choices.push(`${upg.icon} ${upg.name} (${costStr})${canBuild ? '' : ' [не хватает]'}`); actions.push('upgrade_' + key); } choices.push('Уйти'); actions.push('leave'); this.game.sound.playDialogOpen(); this.game.ui.showDialog('Ваше укрытие', 'Что хотите сделать?', choices, (index) => { const action = actions[index]; if (action === 'sleep') { this.sleepHere(); } else if (action === 'warm') { this.warmHere(); } else if (action && action.startsWith('upgrade_')) { const key = action.replace('upgrade_', ''); this.buildUpgrade(key); } this.game.ui.hideDialog(); }); } canBuildUpgrade(key) { const upg = this.upgrades[key]; if (!upg || upg.built) return false; for (const [item, count] of Object.entries(upg.cost)) { if (this.game.inventory.getCount(item) < count) return false; } return true; } buildUpgrade(key) { if (!this.canBuildUpgrade(key)) { this.game.notify('Не хватает материалов!', 'bad'); return; } const upg = this.upgrades[key]; for (const [item, count] of Object.entries(upg.cost)) { this.game.inventory.removeItem(item, count); } upg.built = true; this.game.notify(`Построено: ${upg.name}!`, 'good'); this.game.sound.playQuestComplete(); this.game.skills.addXP('survival', 3); // Применить эффект тайника if (key === 'storage') { this.game.inventory.maxSlots += 10; this.game.notify('+10 слотов инвентаря!', 'good'); } // Перестроить меш this.createMesh(); } sleepHere() { const player = this.game.player; if (player.stats.hunger < 10) { this.game.notify('Слишком голодно, не уснуть...', 'bad'); return; } player.isSleeping = true; player.sleepTimer = 0; player._shelterSleep = true; // Помечаем что сон в укрытии this.game.notify('Вы легли спать в укрытии...'); } warmHere() { this.game.player.stats.warmth = Math.min(100, this.game.player.stats.warmth + 30); this.game.player.stats.mood = Math.min(100, this.game.player.stats.mood + 5); this.game.notify('+30 Тепло, +5 Настроение', 'good'); } // Модификатор сна в укрытии getSleepBonus() { let bonus = { health: 15, mood: 10, warmth: 20 }; if (this.upgrades.bed.built) { bonus.health += 10; bonus.mood += 5; } if (this.upgrades.roof.built) { bonus.warmth += 15; } if (this.upgrades.stove.built) { bonus.warmth += 10; } return bonus; } // Враги не атакуют рядом с укрытием если есть дверь isSafeZone(pos) { if (!this.built || !this.upgrades.door.built) return false; return pos.distanceTo(this.position) < 6; } // Крыша защищает от погоды hasRoof() { return this.built && this.upgrades.roof.built; } isPlayerInShelter() { if (!this.built) return false; return this.game.player.position.distanceTo(this.position) < 5; } getSaveData() { const upgradeData = {}; for (const [key, upg] of Object.entries(this.upgrades)) { upgradeData[key] = upg.built; } return { built: this.built, upgrades: upgradeData }; } loadSaveData(data) { if (!data) return; this.built = data.built || false; if (data.upgrades) { for (const [key, built] of Object.entries(data.upgrades)) { if (this.upgrades[key]) { this.upgrades[key].built = built; } } } if (this.built) { this.createMesh(); this.addInteractable(); if (this.upgrades.storage.built) { this.game.inventory.maxSlots = 30; } } } reset() { if (this.mesh) { this.game.scene.remove(this.mesh); this.mesh = null; } this.built = false; for (const upg of Object.values(this.upgrades)) { upg.built = false; } } }