using Godot; using System; using System.Collections.Generic; using System.Text.Json; public partial class ChunkManager : Node2D { [Export] public NodePath PlayerPath { get; set; } [Export] public NodePath PackManagerPath { get; set; } [Export] public int LoadRadius { get; set; } = 1; // 1 => 3x3 private CharacterBody2D? _player; private PackManager? _packs; private WorldIndex? _world; private Dictionary<(int x, int y), string> _chunkMap = new(); private readonly Dictionary<(int x, int y), Node2D> _loaded = new(); public override void _Ready() { _player = GetNodeOrNull(PlayerPath); if (_player == null) { GD.PrintErr("ChunkManager: PlayerPath not set or invalid."); return; } _packs = GetNodeOrNull(PackManagerPath); if (_packs == null) { GD.PrintErr("ChunkManager: PackManagerPath not set or invalid."); return; } LoadWorldIndex(); } private void LoadWorldIndex() { var worldPath = _packs!.Resolve("data/world/world_main.json"); if (worldPath == null) { GD.PrintErr("ChunkManager: Could not resolve data/world/world_main.json from packs."); return; } var json = FileAccess.GetFileAsString(worldPath); _world = JsonSerializer.Deserialize(json); if (_world == null) { GD.PrintErr("ChunkManager: Failed to parse world index."); return; } _chunkMap = _world.BuildChunkMap(); GD.Print($"World loaded: {_world.world_id}, chunks: {_chunkMap.Count}, chunk_size_px={_world.chunk_size_px}"); // After loading world index, set player spawn position if (_player != null && _world.start_pos_px.Length == 2) { _player.GlobalPosition = new Vector2( _world.start_pos_px[0], _world.start_pos_px[1] ); } } public override void _Process(double delta) { if (_player == null || _world == null) return; var (cx, cy) = WorldPosToChunk(_player.GlobalPosition, _world.chunk_size_px); // Load required chunks for (int dy = -LoadRadius; dy <= LoadRadius; dy++) for (int dx = -LoadRadius; dx <= LoadRadius; dx++) { EnsureLoaded(cx + dx, cy + dy); } // Unload chunks too far away var toRemove = new List<(int x, int y)>(); foreach (var kv in _loaded) { var (x, y) = kv.Key; if (Math.Abs(x - cx) > LoadRadius || Math.Abs(y - cy) > LoadRadius) toRemove.Add((x, y)); } foreach (var k in toRemove) { _loaded[k].QueueFree(); _loaded.Remove(k); } } private void EnsureLoaded(int x, int y) { var key = (x, y); if (_loaded.ContainsKey(key)) return; if (!_chunkMap.TryGetValue(key, out var relPath)) return; var chunkPath = _packs!.Resolve(relPath); if (chunkPath == null) { GD.PrintErr($"Chunk missing in packs: {relPath}"); return; } var chunkJson = FileAccess.GetFileAsString(chunkPath); var data = JsonSerializer.Deserialize(chunkJson); if (data == null) { GD.PrintErr($"Failed to parse chunk: {chunkPath}"); return; } var biome = data.biome ?? "biome:unknown"; var node = CreateDebugChunkNode(x, y, _world!.chunk_size_px, biome); AddChunkMarkers(node, data); AddChild(node); _loaded[key] = node; } private static (int cx, int cy) WorldPosToChunk(Vector2 pos, int chunkSizePx) { int cx = Mathf.FloorToInt(pos.X / chunkSizePx); int cy = Mathf.FloorToInt(pos.Y / chunkSizePx); return (cx, cy); } private static Node2D CreateDebugChunkNode(int cx, int cy, int chunkSizePx, string biome) { var n = new Node2D { Name = $"Chunk_{cx}_{cy}", Position = new Vector2(cx * chunkSizePx, cy * chunkSizePx) }; var rect = new ColorRect { Size = new Vector2(chunkSizePx, chunkSizePx), MouseFilter = Control.MouseFilterEnum.Ignore, Color = biome switch { "biome:forest" => new Color(0.1f, 0.35f, 0.1f, 0.25f), "biome:plains" => new Color(0.35f, 0.35f, 0.1f, 0.25f), "biome:town" => new Color(0.25f, 0.25f, 0.25f, 0.25f), _ => new Color(0.2f, 0.2f, 0.2f, 0.25f) } }; n.AddChild(rect); return n; } private static void AddChunkMarkers(Node2D chunkNode, ChunkData data) { // Pickups: small green squares if (data.pickups != null) { foreach (var p in data.pickups) { var r = new ColorRect { Size = new Vector2(10, 10), Color = new Color(0.2f, 1.0f, 0.2f, 0.9f), MouseFilter = Control.MouseFilterEnum.Ignore, Position = new Vector2(p.pos[0] - 5, p.pos[1] - 5) }; chunkNode.AddChild(r); } } // NPCs: blue squares if (data.npcs != null) { foreach (var n in data.npcs) { var r = new ColorRect { Size = new Vector2(12, 12), Color = new Color(0.2f, 0.5f, 1.0f, 0.9f), MouseFilter = Control.MouseFilterEnum.Ignore, Position = new Vector2(n.pos[0] - 6, n.pos[1] - 6) }; chunkNode.AddChild(r); } } // Gates: red rectangles if (data.gates != null) { foreach (var g in data.gates) { var rect = g.rect; // [x,y,w,h] var r = new ColorRect { Size = new Vector2(rect[2], rect[3]), Color = new Color(1.0f, 0.2f, 0.2f, 0.35f), MouseFilter = Control.MouseFilterEnum.Ignore, Position = new Vector2(rect[0], rect[1]) }; chunkNode.AddChild(r); } } // Collision rects: gray outlines (optional later) } }