import * as THREE from 'three'; export class Interiors { constructor(game) { this.game = game; this.isInside = false; this.currentBuilding = null; this.savedPosition = null; this.interiorObjects = []; this.interiorColliders = []; this.interiorInteractables = []; this.built = false; } buildInteriors() { if (this.built) return; this.built = true; this.buildShop(); this.buildHospital(); this.buildChurch(); } createInteriorNPC(group, x, y, z, bodyColor, hasApron) { // Тело const bodyMat = new THREE.MeshStandardMaterial({ color: bodyColor }); const body = new THREE.Mesh(new THREE.CylinderGeometry(0.28, 0.32, 1.0, 8), bodyMat); body.position.set(x, 0.8, z); body.castShadow = true; group.add(body); // Голова const head = new THREE.Mesh( new THREE.SphereGeometry(0.2, 8, 6), new THREE.MeshStandardMaterial({ color: 0xd4a574 }) ); head.position.set(x, 1.45, z); group.add(head); // Ноги const legMat = new THREE.MeshStandardMaterial({ color: 0x333344 }); [-0.1, 0.1].forEach(side => { const leg = new THREE.Mesh(new THREE.CylinderGeometry(0.07, 0.07, 0.4, 6), legMat); leg.position.set(x + side, 0.2, z); group.add(leg); }); // Фартук (для продавца) if (hasApron) { const apron = new THREE.Mesh( new THREE.BoxGeometry(0.5, 0.6, 0.05), new THREE.MeshStandardMaterial({ color: 0xeeeeee }) ); apron.position.set(x, 0.7, z + 0.28); group.add(apron); } } addDoorFrame(group, ox, oz, d) { const doorMat = new THREE.MeshStandardMaterial({ color: 0x6b3a1f }); // Дверная рама (ширина проёма 2.4) const frameLeft = new THREE.Mesh(new THREE.BoxGeometry(0.12, 2.4, 0.25), doorMat); frameLeft.position.set(ox - 1.2, 1.2, oz + d / 2); group.add(frameLeft); const frameRight = new THREE.Mesh(new THREE.BoxGeometry(0.12, 2.4, 0.25), doorMat); frameRight.position.set(ox + 1.2, 1.2, oz + d / 2); group.add(frameRight); const frameTop = new THREE.Mesh(new THREE.BoxGeometry(2.5, 0.12, 0.25), doorMat); frameTop.position.set(ox, 2.4, oz + d / 2); group.add(frameTop); // Табличка "ВЫХОД" (зелёная, светящаяся) const signMat = new THREE.MeshStandardMaterial({ color: 0x22cc44, emissive: 0x22cc44, emissiveIntensity: 0.8 }); const sign = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.3, 0.05), signMat); sign.position.set(ox, 2.7, oz + d / 2 - 0.05); group.add(sign); // Подсветка у двери const doorLight = new THREE.PointLight(0x44ff66, 0.3, 4); doorLight.position.set(ox, 2.5, oz + d / 2 - 0.5); group.add(doorLight); // Коврик у двери const matFloor = new THREE.Mesh( new THREE.PlaneGeometry(2, 1), new THREE.MeshStandardMaterial({ color: 0x886644 }) ); matFloor.rotation.x = -Math.PI / 2; matFloor.position.set(ox, 0.02, oz + d / 2 - 0.5); group.add(matFloor); } buildShop() { const ox = 500, oz = 0; const w = 10, d = 8, h = 3.5; const group = new THREE.Group(); // Пол const floorMat = new THREE.MeshStandardMaterial({ color: 0xddccaa }); const floor = new THREE.Mesh(new THREE.PlaneGeometry(w, d), floorMat); floor.rotation.x = -Math.PI / 2; floor.position.set(ox, 0.01, oz); floor.receiveShadow = true; group.add(floor); // Потолок const ceilMat = new THREE.MeshStandardMaterial({ color: 0xeeeeee, side: THREE.BackSide }); const ceil = new THREE.Mesh(new THREE.PlaneGeometry(w, d), ceilMat); ceil.rotation.x = -Math.PI / 2; ceil.position.set(ox, h, oz); group.add(ceil); // Стены const wallMat = new THREE.MeshStandardMaterial({ color: 0xf5e6cc }); // Задняя стена const backWall = new THREE.Mesh(new THREE.BoxGeometry(w, h, 0.2), wallMat); backWall.position.set(ox, h / 2, oz - d / 2); backWall.castShadow = true; group.add(backWall); // Левая стена const leftWall = new THREE.Mesh(new THREE.BoxGeometry(0.2, h, d), wallMat); leftWall.position.set(ox - w / 2, h / 2, oz); group.add(leftWall); // Правая стена const rightWall = new THREE.Mesh(new THREE.BoxGeometry(0.2, h, d), wallMat); rightWall.position.set(ox + w / 2, h / 2, oz); group.add(rightWall); // Передняя стена с проёмом (дверь) const frontLeft = new THREE.Mesh(new THREE.BoxGeometry(w / 2 - 1.4, h, 0.2), wallMat); frontLeft.position.set(ox - w / 4 - 0.7, h / 2, oz + d / 2); group.add(frontLeft); const frontRight = new THREE.Mesh(new THREE.BoxGeometry(w / 2 - 1.4, h, 0.2), wallMat); frontRight.position.set(ox + w / 4 + 0.7, h / 2, oz + d / 2); group.add(frontRight); // Прилавок const counterMat = new THREE.MeshStandardMaterial({ color: 0x8b6914 }); const counter = new THREE.Mesh(new THREE.BoxGeometry(5, 1, 0.6), counterMat); counter.position.set(ox, 0.5, oz - 1.5); counter.castShadow = true; group.add(counter); // Продавец за прилавком this.createInteriorNPC(group, ox + 1, 0, oz - 2.2, 0x336633, true); // Полки на стене const shelfMat = new THREE.MeshStandardMaterial({ color: 0x9b7b3c }); for (let i = 0; i < 3; i++) { const shelf = new THREE.Mesh(new THREE.BoxGeometry(2.5, 0.08, 0.5), shelfMat); shelf.position.set(ox - 3 + i * 3, 1.5, oz - d / 2 + 0.35); group.add(shelf); // Товары на полках for (let j = 0; j < 3; j++) { const item = new THREE.Mesh( new THREE.BoxGeometry(0.3, 0.4, 0.3), new THREE.MeshStandardMaterial({ color: [0xe8a030, 0x4488cc, 0xcc4444][j] }) ); item.position.set(ox - 3.5 + i * 3 + j * 0.5, 1.75, oz - d / 2 + 0.35); group.add(item); } } // Дверная рама и табличка this.addDoorFrame(group, ox, oz, d); // Свет const light = new THREE.PointLight(0xffe8c0, 1, 15); light.position.set(ox, h - 0.3, oz); group.add(light); this.game.scene.add(group); this.interiorObjects.push(group); // Коллайдеры this.interiorColliders.push( new THREE.Box3(new THREE.Vector3(ox - w / 2 - 0.1, 0, oz - d / 2 - 0.2), new THREE.Vector3(ox + w / 2 + 0.1, h, oz - d / 2)), new THREE.Box3(new THREE.Vector3(ox - w / 2 - 0.2, 0, oz - d / 2), new THREE.Vector3(ox - w / 2, h, oz + d / 2)), new THREE.Box3(new THREE.Vector3(ox + w / 2, 0, oz - d / 2), new THREE.Vector3(ox + w / 2 + 0.2, h, oz + d / 2)), // Передняя стена левая часть new THREE.Box3(new THREE.Vector3(ox - w / 2, 0, oz + d / 2 - 0.1), new THREE.Vector3(ox - 1.3, h, oz + d / 2 + 0.1)), new THREE.Box3(new THREE.Vector3(ox + 1.3, 0, oz + d / 2 - 0.1), new THREE.Vector3(ox + w / 2, h, oz + d / 2 + 0.1)), // Прилавок коллайдер new THREE.Box3(new THREE.Vector3(ox - 2.5, 0, oz - 1.8), new THREE.Vector3(ox + 2.5, 1, oz - 1.2)), ); // Интерактивные объекты внутри this.interiorInteractables.push( { position: new THREE.Vector3(ox, 0, oz - 1.5), radius: 2.5, type: 'shop_counter', label: 'Купить / Продать', building: 'shop' }, { position: new THREE.Vector3(ox, 0, oz + d / 2 - 0.5), radius: 2, type: 'exit_door', label: 'Выйти на улицу', building: 'shop' } ); } buildHospital() { const ox = 500, oz = 50; const w = 12, d = 10, h = 3.5; const group = new THREE.Group(); // Пол const floorMat = new THREE.MeshStandardMaterial({ color: 0xe8e8e8 }); const floor = new THREE.Mesh(new THREE.PlaneGeometry(w, d), floorMat); floor.rotation.x = -Math.PI / 2; floor.position.set(ox, 0.01, oz); floor.receiveShadow = true; group.add(floor); // Потолок const ceilMat = new THREE.MeshStandardMaterial({ color: 0xffffff, side: THREE.BackSide }); const ceil = new THREE.Mesh(new THREE.PlaneGeometry(w, d), ceilMat); ceil.rotation.x = -Math.PI / 2; ceil.position.set(ox, h, oz); group.add(ceil); // Стены (белые) const wallMat = new THREE.MeshStandardMaterial({ color: 0xf0f0f0 }); const backWall = new THREE.Mesh(new THREE.BoxGeometry(w, h, 0.2), wallMat); backWall.position.set(ox, h / 2, oz - d / 2); group.add(backWall); const leftWall = new THREE.Mesh(new THREE.BoxGeometry(0.2, h, d), wallMat); leftWall.position.set(ox - w / 2, h / 2, oz); group.add(leftWall); const rightWall = new THREE.Mesh(new THREE.BoxGeometry(0.2, h, d), wallMat); rightWall.position.set(ox + w / 2, h / 2, oz); group.add(rightWall); const frontLeft = new THREE.Mesh(new THREE.BoxGeometry(w / 2 - 1.4, h, 0.2), wallMat); frontLeft.position.set(ox - w / 4 - 0.7, h / 2, oz + d / 2); group.add(frontLeft); const frontRight = new THREE.Mesh(new THREE.BoxGeometry(w / 2 - 1.4, h, 0.2), wallMat); frontRight.position.set(ox + w / 4 + 0.7, h / 2, oz + d / 2); group.add(frontRight); // Кровати const bedMat = new THREE.MeshStandardMaterial({ color: 0xffffff }); const frameMat = new THREE.MeshStandardMaterial({ color: 0xaabbcc }); for (let i = 0; i < 3; i++) { const frame = new THREE.Mesh(new THREE.BoxGeometry(1.8, 0.4, 0.9), frameMat); frame.position.set(ox - 4 + i * 3.5, 0.25, oz - 3); frame.castShadow = true; group.add(frame); const mattress = new THREE.Mesh(new THREE.BoxGeometry(1.6, 0.1, 0.7), bedMat); mattress.position.set(ox - 4 + i * 3.5, 0.5, oz - 3); group.add(mattress); } // Стол врача const desk = new THREE.Mesh( new THREE.BoxGeometry(2, 0.8, 1), new THREE.MeshStandardMaterial({ color: 0xccccdd }) ); desk.position.set(ox + 3, 0.4, oz + 2); desk.castShadow = true; group.add(desk); // Врач за столом this.createInteriorNPC(group, ox + 3, 0, oz + 1.2, 0xeeeeee, false); // Красный крест на стене const crossMat = new THREE.MeshStandardMaterial({ color: 0xff3333 }); const crossH = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.2, 0.05), crossMat); crossH.position.set(ox, 2.5, oz - d / 2 + 0.15); group.add(crossH); const crossV = new THREE.Mesh(new THREE.BoxGeometry(0.2, 1.2, 0.05), crossMat); crossV.position.set(ox, 2.5, oz - d / 2 + 0.15); group.add(crossV); // Дверная рама и табличка this.addDoorFrame(group, ox, oz, d); // Свет const light = new THREE.PointLight(0xffffff, 1.2, 18); light.position.set(ox, h - 0.3, oz); group.add(light); this.game.scene.add(group); this.interiorObjects.push(group); this.interiorColliders.push( new THREE.Box3(new THREE.Vector3(ox - w / 2 - 0.1, 0, oz - d / 2 - 0.2), new THREE.Vector3(ox + w / 2 + 0.1, h, oz - d / 2)), new THREE.Box3(new THREE.Vector3(ox - w / 2 - 0.2, 0, oz - d / 2), new THREE.Vector3(ox - w / 2, h, oz + d / 2)), new THREE.Box3(new THREE.Vector3(ox + w / 2, 0, oz - d / 2), new THREE.Vector3(ox + w / 2 + 0.2, h, oz + d / 2)), new THREE.Box3(new THREE.Vector3(ox - w / 2, 0, oz + d / 2 - 0.1), new THREE.Vector3(ox - 0.8, h, oz + d / 2 + 0.1)), new THREE.Box3(new THREE.Vector3(ox + 0.8, 0, oz + d / 2 - 0.1), new THREE.Vector3(ox + w / 2, h, oz + d / 2 + 0.1)), new THREE.Box3(new THREE.Vector3(ox + 2, 0, oz + 1.5), new THREE.Vector3(ox + 4, 0.8, oz + 2.5)), ); this.interiorInteractables.push( { position: new THREE.Vector3(ox + 3, 0, oz + 2), radius: 2.5, type: 'hospital_desk', label: 'Лечение', building: 'hospital' }, { position: new THREE.Vector3(ox, 0, oz + d / 2 - 0.5), radius: 2, type: 'exit_door', label: 'Выйти на улицу', building: 'hospital' } ); } buildChurch() { const ox = 500, oz = 100; const w = 10, d = 14, h = 5; const group = new THREE.Group(); // Пол (деревянный) const floorMat = new THREE.MeshStandardMaterial({ color: 0xb89060 }); const floor = new THREE.Mesh(new THREE.PlaneGeometry(w, d), floorMat); floor.rotation.x = -Math.PI / 2; floor.position.set(ox, 0.01, oz); floor.receiveShadow = true; group.add(floor); // Потолок const ceilMat = new THREE.MeshStandardMaterial({ color: 0xddd8c0, side: THREE.BackSide }); const ceil = new THREE.Mesh(new THREE.PlaneGeometry(w, d), ceilMat); ceil.rotation.x = -Math.PI / 2; ceil.position.set(ox, h, oz); group.add(ceil); // Стены const wallMat = new THREE.MeshStandardMaterial({ color: 0xf0e8d0 }); const backWall = new THREE.Mesh(new THREE.BoxGeometry(w, h, 0.2), wallMat); backWall.position.set(ox, h / 2, oz - d / 2); group.add(backWall); const leftWall = new THREE.Mesh(new THREE.BoxGeometry(0.2, h, d), wallMat); leftWall.position.set(ox - w / 2, h / 2, oz); group.add(leftWall); const rightWall = new THREE.Mesh(new THREE.BoxGeometry(0.2, h, d), wallMat); rightWall.position.set(ox + w / 2, h / 2, oz); group.add(rightWall); const frontLeft = new THREE.Mesh(new THREE.BoxGeometry(w / 2 - 1.4, h, 0.2), wallMat); frontLeft.position.set(ox - w / 4 - 0.7, h / 2, oz + d / 2); group.add(frontLeft); const frontRight = new THREE.Mesh(new THREE.BoxGeometry(w / 2 - 1.4, h, 0.2), wallMat); frontRight.position.set(ox + w / 4 + 0.7, h / 2, oz + d / 2); group.add(frontRight); // Скамьи (ряды) const benchMat = new THREE.MeshStandardMaterial({ color: 0x8b6914 }); for (let row = 0; row < 4; row++) { for (let side = -1; side <= 1; side += 2) { const bench = new THREE.Mesh(new THREE.BoxGeometry(3, 0.4, 0.5), benchMat); bench.position.set(ox + side * 2, 0.25, oz - 3 + row * 2.5); bench.castShadow = true; group.add(bench); } } // Алтарь const altarMat = new THREE.MeshStandardMaterial({ color: 0xf5e6cc }); const altar = new THREE.Mesh(new THREE.BoxGeometry(2, 1, 0.8), altarMat); altar.position.set(ox, 0.5, oz - d / 2 + 1.5); altar.castShadow = true; group.add(altar); // Крест на стене const crossMat = new THREE.MeshStandardMaterial({ color: 0xdaa520 }); const crossH = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.15, 0.05), crossMat); crossH.position.set(ox, 3.5, oz - d / 2 + 0.15); group.add(crossH); const crossV = new THREE.Mesh(new THREE.BoxGeometry(0.15, 2, 0.05), crossMat); crossV.position.set(ox, 3.5, oz - d / 2 + 0.15); group.add(crossV); // Свечи const candleMat = new THREE.MeshStandardMaterial({ color: 0xfff8dc }); for (let i = -1; i <= 1; i++) { const candle = new THREE.Mesh(new THREE.CylinderGeometry(0.03, 0.03, 0.2, 6), candleMat); candle.position.set(ox + i * 0.4, 1.15, oz - d / 2 + 1.5); group.add(candle); } // Отец Михаил у алтаря this.createInteriorNPC(group, ox + 1.5, 0, oz - d / 2 + 2, 0x222244, false); // Борода const beard = new THREE.Mesh( new THREE.BoxGeometry(0.22, 0.2, 0.12), new THREE.MeshStandardMaterial({ color: 0x555555 }) ); beard.position.set(ox + 1.5, 1.3, oz - d / 2 + 2 + 0.15); group.add(beard); // Нагрудный крест const priestCrossH = new THREE.Mesh( new THREE.BoxGeometry(0.12, 0.03, 0.03), new THREE.MeshStandardMaterial({ color: 0xdaa520 }) ); priestCrossH.position.set(ox + 1.5, 1.0, oz - d / 2 + 2 + 0.3); group.add(priestCrossH); const priestCrossV = new THREE.Mesh( new THREE.BoxGeometry(0.03, 0.18, 0.03), new THREE.MeshStandardMaterial({ color: 0xdaa520 }) ); priestCrossV.position.set(ox + 1.5, 1.0, oz - d / 2 + 2 + 0.3); group.add(priestCrossV); // Дверная рама и табличка this.addDoorFrame(group, ox, oz, d); // Тёплый свет const light = new THREE.PointLight(0xffe0a0, 0.8, 20); light.position.set(ox, h - 0.5, oz); group.add(light); const altarLight = new THREE.PointLight(0xffcc66, 0.5, 8); altarLight.position.set(ox, 1.5, oz - d / 2 + 1.5); group.add(altarLight); this.game.scene.add(group); this.interiorObjects.push(group); this.interiorColliders.push( new THREE.Box3(new THREE.Vector3(ox - w / 2 - 0.1, 0, oz - d / 2 - 0.2), new THREE.Vector3(ox + w / 2 + 0.1, h, oz - d / 2)), new THREE.Box3(new THREE.Vector3(ox - w / 2 - 0.2, 0, oz - d / 2), new THREE.Vector3(ox - w / 2, h, oz + d / 2)), new THREE.Box3(new THREE.Vector3(ox + w / 2, 0, oz - d / 2), new THREE.Vector3(ox + w / 2 + 0.2, h, oz + d / 2)), new THREE.Box3(new THREE.Vector3(ox - w / 2, 0, oz + d / 2 - 0.1), new THREE.Vector3(ox - 0.8, h, oz + d / 2 + 0.1)), new THREE.Box3(new THREE.Vector3(ox + 0.8, 0, oz + d / 2 - 0.1), new THREE.Vector3(ox + w / 2, h, oz + d / 2 + 0.1)), new THREE.Box3(new THREE.Vector3(ox - 1, 0, oz - d / 2 + 1.1), new THREE.Vector3(ox + 1, 1, oz - d / 2 + 1.9)), ); this.interiorInteractables.push( { position: new THREE.Vector3(ox, 0, oz - d / 2 + 1.5), radius: 2.5, type: 'church_altar', label: 'Помолиться / Еда', building: 'church' }, { position: new THREE.Vector3(ox, 0, oz + d / 2 - 0.5), radius: 2, type: 'exit_door', label: 'Выйти на улицу', building: 'church' } ); } enterBuilding(type) { if (this.isInside) return; if (!this.built) this.buildInteriors(); this.savedPosition = this.game.player.position.clone(); this.currentBuilding = type; this.isInside = true; // Позиция входа в интерьер const entries = { shop: new THREE.Vector3(500, 0, 3), hospital: new THREE.Vector3(500, 0, 53), church: new THREE.Vector3(500, 0, 106), }; const entry = entries[type]; if (entry) { this.game.player.position.copy(entry); this.game.player.mesh.position.copy(entry); } // Зафиксировать погоду/освещение this.game.scene.fog = null; // Подключить интерьерные коллайдеры и интерактивные объекты this._origColliders = this.game.world.colliders; this._origInteractables = this.game.world.interactables; this.game.world.colliders = this.interiorColliders; this.game.world.interactables = this.interiorInteractables.filter(i => i.building === type); // Скрыть наружные объекты для производительности this.game.player.position.x = THREE.MathUtils.clamp(this.game.player.position.x, 490, 510); this.game.player.position.z = THREE.MathUtils.clamp(this.game.player.position.z, -10, 120); const names = { shop: 'Магазин', hospital: 'Больница', church: 'Церковь' }; this.game.notify(`Вы вошли: ${names[type] || type}`); } exitBuilding() { if (!this.isInside) return; this.isInside = false; // Восстановить позицию if (this.savedPosition) { this.game.player.position.copy(this.savedPosition); this.game.player.mesh.position.copy(this.savedPosition); } // Восстановить туман this.game.scene.fog = new THREE.Fog(0x87CEEB, 80, 200); // Восстановить коллайдеры if (this._origColliders) this.game.world.colliders = this._origColliders; if (this._origInteractables) this.game.world.interactables = this._origInteractables; this.currentBuilding = null; this.game.notify('Вы вышли на улицу.'); } reset() { if (this.isInside) { this.exitBuilding(); } this.interiorObjects.forEach(obj => this.game.scene.remove(obj)); this.interiorObjects = []; this.interiorColliders = []; this.interiorInteractables = []; this.built = false; } }