/
RaskolnickOFF
/
3D
Обзор
Документация
Войти
/
RaskolnickOFF
/
3D
Код
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
js/source/systems/services/LoadSystem.js
272 строки
14 KB
RaskolnickOFF
Structure update
06 июн 2026, 03:18
06 июн 2026, 03:18
9bc1c25
Код
Авторство
О чём код?
// js/source/systems/services/LoadSystem.js — загрузка сохранения с полной очисткой сцены и рестартом import { Components } from '../../core/Components.js'; import { GameEvents } from '../../core/GameEvents.js'; import { SaveStorage } from '../../core/SaveStorage.js'; import { RenderCleanup } from '../../core/RenderCleanup.js'; import { createEnemyMesh } from '../../scene/Enemy.js'; import { createItemMesh, createBerryMesh } from '../../scene/Items.js'; import { createTreeMesh } from '../../scene/Tree.js'; import * as THREE from 'three'; export class LoadSystem { /** * @param {import('../core/SimulationContext.js').SimulationContext} ctx * @param {import('./ZoneSystem.js').ZoneSystem} zoneSystem * @param {THREE.Scene} scene * @param {import('./BiomeVisualSystem.js').BiomeVisualSystem} [biomeVisualSystem] * @param {import('./InventorySystem.js').InventorySystem} [inventorySystem] * @param {import('../core/HeightmapData.js').HeightmapData} [heightmapData] */ constructor(ctx, zoneSystem, scene, biomeVisualSystem = null, inventorySystem = null, heightmapData = null) { this.engine = ctx.engine; this.eventBus = ctx.eventBus; this.zoneSystem = zoneSystem; this.scene = scene; this.biomeVisualSystem = biomeVisualSystem; this.inventorySystem = inventorySystem; this.heightmapData = heightmapData; this.settings = ctx.settings; this.storage = new SaveStorage(); this.eventBus.on(GameEvents.LOAD_REQUESTED, () => this.load()); } async load() { try { console.log('[LoadSystem] Loading game...'); const saveData = await this.storage.load('autosave'); if (!saveData) { console.warn('[LoadSystem] No save data found.'); this.eventBus.emit(GameEvents.LOAD_FAILED, { reason: 'No save data' }); return; } if (saveData.version !== 1) { console.warn('[LoadSystem] Incompatible save version.'); } // === PHASE 1: CLEAN STATE === console.log('[LoadSystem] Cleaning scene and engine...'); // 1.1 Очищаем игровые THREE.js объекты (земля и свет сохраняются) RenderCleanup.clearScene(this.scene); // 1.2 Очищаем все ECS сущности this.engine.clear(); // 1.3 Восстанавливаем карту высот ★ if (saveData.heightmap && this.heightmapData) { console.log('[LoadSystem] Restoring heightmap...'); this.heightmapData.size = saveData.heightmap.size; this.heightmapData.segments = saveData.heightmap.segments; this.heightmapData.vertexCount = saveData.heightmap.segments + 1; this.heightmapData.cellSize = saveData.heightmap.size / saveData.heightmap.segments; this.heightmapData._vertices = new Float32Array(saveData.heightmap.vertices); console.log('[LoadSystem] Heightmap restored:', this.heightmapData.vertexCount + '×' + this.heightmapData.vertexCount); } // === PHASE 2: RESTORE WORLD STATE === console.log('[LoadSystem] Restoring zones...'); // 2.1 Восстановим состояние зон и таймеры деактивации if (saveData.zones) { this.zoneSystem.importState(saveData.zones); } // === PHASE 3: RESTORE PLAYER === console.log('[LoadSystem] Restoring player...'); let playerEntity = null; if (saveData.entities) { // Ищем игрока в сохраненных сущностях const playerData = saveData.entities.find(e => e.components[Components.PLAYER]); if (playerData) { playerEntity = this.engine.createEntity(); // Добавляем все persistent компоненты из сейва for (const [compName, compValue] of Object.entries(playerData.components)) { playerEntity.add(compName, compValue); } // Добавляем TRANSIENT компоненты (runtime-only) // VELOCITY критична для движения if (!playerEntity.has(Components.VELOCITY)) { playerEntity.add(Components.VELOCITY, { x: 0, y: 0, z: 0 }); } // MESH критична для видимости if (!playerEntity.has(Components.MESH)) { const geometry = new THREE.BoxGeometry(1, 1, 1); const material = new THREE.MeshStandardMaterial({ color: '#ff4400' }); const cubeMesh = new THREE.Mesh(geometry, material); cubeMesh.castShadow = true; playerEntity.add(Components.MESH, cubeMesh); this.scene.add(cubeMesh); } // HUNGER и STAMINA — transient в ComponentMeta, но должны быть у игрока if (!playerEntity.has(Components.HUNGER)) { playerEntity.add(Components.HUNGER, this.settings.player.maxHunger); } if (!playerEntity.has(Components.STAMINA)) { playerEntity.add(Components.STAMINA, this.settings.player.maxStamina); } } else { console.error('[LoadSystem] Player data not found in save. Skipping restore.'); } } // === PHASE 4: RESTORE ENEMIES, TREES & LOOT === console.log('[LoadSystem] Restoring enemies, trees and loot...'); if (saveData.entities) { const nonPlayerEntities = saveData.entities.filter(e => !e.components[Components.PLAYER]); for (const entityData of nonPlayerEntities) { const entity = this.engine.createEntity(); // Добавляем все persistent компоненты for (const [compName, compValue] of Object.entries(entityData.components)) { entity.add(compName, compValue); } // ★ ЗАЩИТА: ORIGIN_POSITION для врагов if (entity.has(Components.ENEMY) && !entity.has(Components.ORIGIN_POSITION)) { const transform = entity.get(Components.TRANSFORM); entity.add(Components.ORIGIN_POSITION, { x: transform ? transform.x : 0, y: transform ? transform.y : 1, z: transform ? transform.z : 0 }); console.warn(`[LoadSystem] Restored missing ORIGIN_POSITION for enemy ${entity.id}`); } // --- Восстановление MESH --- // Приоритет: ENEMY > TREE > ITEM_TYPE (каждая сущность имеет только один маркер) if (entity.has(Components.ENEMY) && !entity.has(Components.MESH)) { // === ВРАГ === try { const mesh = createEnemyMesh(this.settings); entity.add(Components.MESH, mesh); const transform = entity.get(Components.TRANSFORM); if (transform) { mesh.position.set(transform.x, transform.y, transform.z); mesh.rotation.y = transform.rotY; } this.scene.add(mesh); } catch (e) { console.warn('[LoadSystem] Failed to create mesh for enemy:', e); } // Transient для врагов if (!entity.has(Components.VELOCITY)) { entity.add(Components.VELOCITY, { x: 0, y: 0, z: 0 }); } } else if (entity.has(Components.TREE) && !entity.has(Components.MESH)) { // === ДЕРЕВО === try { const treeType = entity.get(Components.TREE_TYPE) || 'oak'; const mesh = createTreeMesh(treeType, this.settings.trees); if (mesh) { // Восстанавливаем scale если есть конфиг const scaleCfg = this.settings.trees?.[treeType]?.scale; if (scaleCfg) { const s = scaleCfg.min + Math.random() * (scaleCfg.max - scaleCfg.min); mesh.scale.setScalar(s); } mesh.castShadow = true; mesh.receiveShadow = true; entity.add(Components.MESH, mesh); const transform = entity.get(Components.TRANSFORM); if (transform) { mesh.position.set(transform.x, transform.y, transform.z); mesh.rotation.y = transform.rotY; } this.scene.add(mesh); } } catch (e) { console.warn('[LoadSystem] Failed to create mesh for tree:', e); } } else if (entity.has(Components.ITEM_TYPE) && !entity.has(Components.MESH)) { const itemType = entity.get(Components.ITEM_TYPE); if (itemType && itemType.startsWith('berry')) { // === КУСТ С ЯГОДАМИ === try { const mesh = createBerryMesh(itemType, this.settings.food); entity.add(Components.MESH, mesh); // Гарантировать наличие компонентов if (!entity.has(Components.BERRY_BUSH)) { entity.add(Components.BERRY_BUSH, { growthProgress: 1, isGrowing: false }); } if (!entity.has(Components.BERRY_BUSH_TYPE)) { entity.add(Components.BERRY_BUSH_TYPE, itemType); } // Применить growthProgress к berryGroup const bush = entity.get(Components.BERRY_BUSH); if (bush) { const berryGroup = mesh.getObjectByName('berryGroup'); if (berryGroup) { berryGroup.scale.setScalar(bush.growthProgress); } } const transform = entity.get(Components.TRANSFORM); if (transform) { mesh.position.set(transform.x, transform.y, transform.z); mesh.rotation.y = transform.rotY; } this.scene.add(mesh); } catch (e) { console.warn('[LoadSystem] Failed to create berry bush:', e); } } else { // === ОБЫЧНЫЙ ЛУТ (синий шар) === try { const mesh = createItemMesh(); entity.add(Components.MESH, mesh); const transform = entity.get(Components.TRANSFORM); if (transform) { mesh.position.set(transform.x, transform.y, transform.z); } this.scene.add(mesh); } catch (e) { console.warn('[LoadSystem] Failed to create mesh for item:', e); } } } } } // === PHASE 5: REBUILD INDICES & STATE === console.log('[LoadSystem] Rebuilding indices...'); // 5.1 Перестраиваем индексы зон this.zoneSystem.rebuildIndex(); // 5.2 Восстанавливаем инвентарь if (this.inventorySystem && saveData.inventory) { this.inventorySystem.items = saveData.inventory; this.eventBus.emit(GameEvents.INVENTORY_CHANGED, { items: saveData.inventory }); } console.log('[LoadSystem] Game loaded successfully. Player ID:', playerEntity?.id); // Передаём playerEntity в WORLD_LOADED — Main.js обновит камеру и биомы this.eventBus.emit(GameEvents.WORLD_LOADED, { playerEntity }); } catch (error) { console.error('[LoadSystem] Failed to load game:', error); this.eventBus.emit(GameEvents.LOAD_FAILED, { error }); } } }