Initial commit: 3D Hommie RPG game

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-02-25 01:04:09 +03:00
commit fb5f09212b
34 changed files with 14550 additions and 0 deletions

385
js/game/Game.js Normal file
View File

@@ -0,0 +1,385 @@
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();
}
}