Files
Hommie_RPG_Game/js/game/Interiors.js
Maxim Dolgolyov fb5f09212b Initial commit: 3D Hommie RPG game
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 01:04:09 +03:00

524 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
}
}