import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; import { VoxelGrid } from "../grid/VoxelGrid.js"; import { VoxelManager } from "../grid/VoxelManager.js"; import { UnitManager } from "../managers/UnitManager.js"; import { CaveGenerator } from "../generation/CaveGenerator.js"; import { RuinGenerator } from "../generation/RuinGenerator.js"; // import { TurnSystem } from '../systems/TurnSystem.js'; export class GameLoop { constructor() { this.isRunning = false; // 1. Core Systems this.scene = new THREE.Scene(); this.camera = null; this.renderer = null; this.controls = null; this.grid = null; this.voxelManager = null; this.unitManager = null; // 2. State this.runData = null; } init(container) { // Setup Three.js this.camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 ); this.camera.position.set(20, 20, 20); this.camera.lookAt(0, 0, 0); this.renderer = new THREE.WebGLRenderer({ antialias: true }); this.renderer.setSize(window.innerWidth, window.innerHeight); this.renderer.setClearColor(0x111111); // Dark background container.appendChild(this.renderer.domElement); // Setup OrbitControls this.controls = new OrbitControls(this.camera, this.renderer.domElement); this.controls.enableDamping = true; // Smooth camera movement this.controls.dampingFactor = 0.05; this.controls.screenSpacePanning = false; this.controls.minDistance = 5; this.controls.maxDistance = 100; this.controls.maxPolarAngle = Math.PI / 2; // Prevent going below ground // Lighting const ambient = new THREE.AmbientLight(0xffffff, 0.6); const dirLight = new THREE.DirectionalLight(0xffffff, 0.8); dirLight.position.set(10, 20, 10); this.scene.add(ambient); this.scene.add(dirLight); // Handle Resize window.addEventListener("resize", () => { this.camera.aspect = window.innerWidth / window.innerHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(window.innerWidth, window.innerHeight); }); this.animate = this.animate.bind(this); } /** * Starts a Level based on Run Data (New or Loaded). */ async startLevel(runData) { console.log("GameLoop: Starting Level..."); this.runData = runData; this.isRunning = true; // 1. Initialize Grid (20x10x20 for prototype) this.grid = new VoxelGrid(20, 10, 20); // 2. Generate World (Using saved seed) // TODO: Switch generator based on runData.biome_id const generator = new RuinGenerator(this.grid, runData.seed); generator.generate(); // 3. Initialize Visuals this.voxelManager = new VoxelManager(this.grid, this.scene); // Apply textures generated by the biome logic this.voxelManager.updateMaterials(generator.generatedAssets); this.voxelManager.update(); // Center camera using the focus target if (this.controls) { this.voxelManager.focusCamera(this.controls); } // 4. Initialize Units // Mock Registry for Prototype so UnitManager doesn't crash on createUnit const mockRegistry = { get: (id) => { return { type: "EXPLORER", name: id, // Fallback name stats: { hp: 100, attack: 10, speed: 10 }, }; }, }; this.unitManager = new UnitManager(mockRegistry); this.spawnSquad(runData.squad); // Start Loop this.animate(); } spawnSquad(squadManifest) { if (!squadManifest || !this.unitManager) return; // Simple spawn logic: line them up starting at (2, 1, 2) // In a full implementation, the Generator would provide 'StartPoints' let spawnX = 2; let spawnZ = 2; const spawnY = 1; // Assuming flat floor at y=1 for Ruins squadManifest.forEach((member) => { if (!member) return; // Create Unit (this uses the registry to look up stats) const unit = this.unitManager.createUnit(member.classId, "PLAYER"); // Override name if provided in manifest if (member.name) unit.name = member.name; // Find a valid spot (basic collision check) let placed = false; // Try a few spots if the first is taken for (let i = 0; i < 5; i++) { const pos = { x: spawnX + i, y: spawnY, z: spawnZ }; // Ensure we don't spawn inside a wall or off the map if (this.grid.isValidBounds(pos) && !this.grid.isSolid(pos)) { this.grid.placeUnit(unit, pos); console.log( `Spawned ${unit.name} (${unit.id}) at ${pos.x},${pos.y},${pos.z}` ); placed = true; // Update X for next unit to be next to this one spawnX = pos.x + 1; break; } } if (!placed) { console.warn(`Could not find spawn point for ${unit.name}`); } }); } animate() { if (!this.isRunning) return; requestAnimationFrame(this.animate); // Update Logic // TWEEN.update(); if (this.controls) { this.controls.update(); } // Render this.renderer.render(this.scene, this.camera); } stop() { this.isRunning = false; // Cleanup Three.js resources if needed if (this.controls) this.controls.dispose(); } }