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

386 lines
12 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';
import { World } from './World.js';
import { Player } from './Player.js';
import { CameraController } from './Camera.js';
import { NPCManager } from './NPC.js';
import { Inventory } from './Inventory.js';
import { QuestSystem } from './QuestSystem.js';
import { UI } from './UI.js';
import { ParticleSystem } from './Particles.js';
import { Weather } from './Weather.js';
import { SaveSystem } from './SaveSystem.js';
import { SoundManager } from './SoundManager.js';
import { EventSystem } from './Events.js';
import { Dog } from './Dog.js';
import { Skills } from './Skills.js';
import { Reputation } from './Reputation.js';
import { JobSystem } from './JobSystem.js';
import { Seasons } from './Seasons.js';
import { Dangers } from './Dangers.js';
import { Equipment } from './Equipment.js';
import { Achievements } from './Achievements.js';
import { Housing } from './Housing.js';
import { Police } from './Police.js';
import { Interiors } from './Interiors.js';
export class Game {
constructor(canvas) {
this.canvas = canvas;
this.clock = new THREE.Clock();
this.running = false;
this.paused = false;
// Время
this.gameTime = 8 * 60;
this.gameDay = 1;
this.timeSpeed = 1.5;
// Статистика
this.totalJobsCompleted = 0;
this.totalBottlesSold = 0;
this.totalCrafted = 0;
this.talkedNPCs = new Set();
this.visitedLocations = new Set();
this.enemiesDefeated = 0;
this.consecutiveFights = 0;
// Renderer
this.renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
this.renderer.toneMappingExposure = 1.0;
// Scene
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0x87CEEB);
this.scene.fog = new THREE.Fog(0x87CEEB, 80, 200);
// Camera
this.camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 300);
this.camera.layers.enable(0);
this.camera.layers.disable(1);
// Core systems
this.world = new World(this);
this.player = new Player(this);
this.cameraController = new CameraController(this);
this.npcManager = new NPCManager(this);
this.inventory = new Inventory(this);
this.questSystem = new QuestSystem(this);
this.ui = new UI(this);
// Extended systems
this.particles = new ParticleSystem(this);
this.weather = new Weather(this);
this.saveSystem = new SaveSystem(this);
this.sound = new SoundManager(this);
this.events = new EventSystem(this);
this.dog = new Dog(this);
this.skills = new Skills(this);
this.reputation = new Reputation(this);
this.jobSystem = new JobSystem(this);
this.seasons = new Seasons(this);
this.dangers = new Dangers(this);
// New systems
this.equipment = new Equipment(this);
this.achievements = new Achievements(this);
this.housing = new Housing(this);
this.police = new Police(this);
this.interiors = new Interiors(this);
// Night sky
this.stars = null;
this.moon = null;
// Input
this.keys = {};
this.setupInput();
window.addEventListener('resize', () => this.onResize());
}
async start() {
this.running = true;
this.paused = true;
this.sound.init();
await this.world.build();
this.housing.initFromConfig();
this.player.spawn();
this.npcManager.spawnNPCs();
this.questSystem.initQuests();
this.weather.init();
this.ui.init();
this.initParticles();
this.createNightSky();
this.dog.spawn();
this.npcManager.spawnPassersby();
this.jobSystem.init();
this.world.createVehicles();
this.police.spawnPatrols();
this.animate();
// Показать интро
this.ui.showIntro(() => {
this.paused = false;
this.canvas.requestPointerLock();
});
}
async startFromSave() {
this.running = true;
this.sound.init();
await this.world.build();
this.housing.initFromConfig();
this.player.spawn();
this.npcManager.spawnNPCs();
this.questSystem.initQuests();
this.weather.init();
this.ui.init();
this.initParticles();
this.createNightSky();
this.dog.spawn();
this.npcManager.spawnPassersby();
this.jobSystem.init();
this.world.createVehicles();
this.police.spawnPatrols();
this.saveSystem.load();
this.animate();
}
initParticles() {
const shelterCfg = this.world.mapConfig?.structures?.shelter || {};
const fireX = (shelterCfg.x ?? -35) - 2;
const fireZ = shelterCfg.z ?? 35;
this.particles.createFire(new THREE.Vector3(fireX, 0.3, fireZ));
this.particles.createRain();
this.particles.createSnow();
}
createNightSky() {
// Звёзды
const starCount = 800;
const starGeo = new THREE.BufferGeometry();
const starPos = new Float32Array(starCount * 3);
for (let i = 0; i < starCount; i++) {
const theta = Math.random() * Math.PI * 2;
const phi = Math.random() * Math.PI * 0.5;
const r = 250;
starPos[i * 3] = Math.cos(theta) * Math.sin(phi) * r;
starPos[i * 3 + 1] = Math.cos(phi) * r;
starPos[i * 3 + 2] = Math.sin(theta) * Math.sin(phi) * r;
}
starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3));
const starMat = new THREE.PointsMaterial({
color: 0xffffff,
size: 0.8,
transparent: true,
opacity: 0,
depthWrite: false
});
this.stars = new THREE.Points(starGeo, starMat);
this.scene.add(this.stars);
// Луна
const moonGeo = new THREE.SphereGeometry(5, 16, 16);
const moonMat = new THREE.MeshBasicMaterial({
color: 0xffffee,
transparent: true,
opacity: 0
});
this.moon = new THREE.Mesh(moonGeo, moonMat);
this.moon.position.set(-100, 120, -80);
this.scene.add(this.moon);
}
animate() {
if (!this.running) return;
requestAnimationFrame(() => this.animate());
const dt = Math.min(this.clock.getDelta(), 0.1);
if (this.paused) return;
// Игровое время
this.gameTime += this.timeSpeed * dt;
if (this.gameTime >= 24 * 60) {
this.gameTime -= 24 * 60;
this.gameDay++;
}
// Все системы
this.world.updateLighting(this.gameTime);
this.world.updateVehicles(dt);
this.player.update(dt);
this.cameraController.update(dt);
this.npcManager.update(dt);
this.questSystem.update(dt);
this.weather.update(dt);
this.particles.update(dt);
this.events.update(dt);
this.dog.update(dt);
this.seasons.update();
this.jobSystem.update(dt);
this.dangers.update(dt);
this.police.update(dt);
this.achievements.updateChecks();
this.ui.update(dt);
// Ночное небо
this.updateNightSky();
// Амбиент
this.updateAmbientSound();
// Рендер
this.renderer.render(this.scene, this.camera);
}
updateNightSky() {
const hour = this.gameTime / 60;
let nightFactor = 0;
if (hour < 5) nightFactor = 1;
else if (hour < 7) nightFactor = 1 - (hour - 5) / 2;
else if (hour > 19 && hour < 21) nightFactor = (hour - 19) / 2;
else if (hour >= 21) nightFactor = 1;
if (this.stars) {
this.stars.material.opacity = nightFactor * 0.8;
this.stars.rotation.y += 0.00005;
}
if (this.moon) {
this.moon.material.opacity = nightFactor * 0.9;
const moonAngle = ((hour - 18) / 12) * Math.PI;
this.moon.position.set(
Math.cos(moonAngle) * 100,
Math.sin(moonAngle) * 100 + 40,
-80
);
}
}
updateAmbientSound() {
if (this.weather.current === 'rain') {
this.sound.playAmbient('rain');
} else if (this.isNight()) {
this.sound.playAmbient('night');
} else {
this.sound.stopAmbient();
}
}
setupInput() {
document.addEventListener('keydown', (e) => {
if (!this.running) return;
this.keys[e.code] = true;
if (e.code === 'KeyE') this.player.interact();
if (e.code === 'KeyI') this.ui.toggleInventory();
if (e.code === 'KeyQ') this.ui.toggleQuests();
if (e.code === 'KeyJ') this.ui.toggleSkills();
if (e.code === 'KeyU') this.ui.toggleAchievements();
if (e.code === 'KeyF') this.player.startBegging();
if (e.code === 'KeyG') this.player.startBusking();
if (e.code === 'KeyM') {
const on = this.sound.toggle();
this.notify(on ? 'Звук включён' : 'Звук выключен');
}
if (e.code === 'Space') {
this.dangers.playerFightBack();
}
if (e.code === 'KeyH') {
this.jobSystem.cancelJob();
}
if (e.code === 'F5') {
e.preventDefault();
this.saveSystem.save();
}
});
document.addEventListener('keyup', (e) => {
this.keys[e.code] = false;
if (e.code === 'KeyF') this.player.stopBegging();
if (e.code === 'KeyG') this.player.stopBusking();
});
}
onResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
getTimeString() {
const h = Math.floor(this.gameTime / 60) % 24;
const m = Math.floor(this.gameTime % 60);
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
}
isNight() {
const h = this.gameTime / 60;
return h < 6 || h > 21;
}
notify(text, type) {
this.ui.showNotification(text, type);
}
gameOver(reason) {
this.running = false;
this.sound.stopAmbient();
this.sound.playHurt();
this.dangers.reset();
this.ui.showDeathScreen(reason, this.gameDay);
document.exitPointerLock();
}
async restart() {
this.scene.clear();
this.particles.clear();
this.gameTime = 8 * 60;
this.gameDay = 1;
this.totalJobsCompleted = 0;
this.totalBottlesSold = 0;
this.totalCrafted = 0;
this.talkedNPCs = new Set();
this.visitedLocations = new Set();
this.enemiesDefeated = 0;
this.consecutiveFights = 0;
this.player.reset();
this.inventory.reset();
this.questSystem.reset();
this.weather.reset();
this.events.reset();
this.dog.reset();
this.skills.reset();
this.reputation.reset();
this.jobSystem.reset();
this.seasons.reset();
this.dangers.reset();
this.police.reset();
this.interiors.reset();
this.equipment.reset();
this.achievements.reset();
this.housing.reset();
await this.world.build();
this.housing.initFromConfig();
this.world.createVehicles();
this.player.spawn();
this.npcManager.spawnNPCs();
this.npcManager.spawnPassersby();
this.questSystem.initQuests();
this.initParticles();
this.createNightSky();
this.dog.spawn();
this.police.spawnPatrols();
this.ui.hideDeathScreen();
this.running = true;
this.clock.getDelta();
this.animate();
}
}