Files
Hommie_RPG_Game/js/game/Housing.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

353 lines
13 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 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;
}
}
}