386 lines
12 KiB
JavaScript
386 lines
12 KiB
JavaScript
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();
|
||
}
|
||
}
|