using Godot; using System; using System.Collections.Generic; using System.Text.Json; public partial class ChunkManager : Node2D { [Export] public NodePath PlayerPath; [Export] public NodePath PackManagerPath; [Export] public int LoadRadius = 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) { // fallback: search up the tree for PackManager _packs = GetParent()?.GetNodeOrNull("PackManager"); } if (_packs == null) { GD.PrintErr("ChunkManager: PackManager not found. Add it to your Main scene."); 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}"); } public override void _Process(double delta) { if (_player == null || _world == null) return; var (cx, cy) = WorldPosToChunk(_player.GlobalPosition, _world.chunk_size_px); for (int dy = -LoadRadius; dy <= LoadRadius; dy++) for (int dx = -LoadRadius; dx <= LoadRadius; dx++) { var key = (cx + dx, cy + dy); EnsureLoaded(key.x, key.y); } // Unload chunks that are too far var keysToRemove = 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) keysToRemove.Add((x, y)); } foreach (var k in keysToRemove) { _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; // Use pack resolve for the chunk file itself var chunkPath = _packs!.Resolve(relPath); if (chunkPath == null) { GD.PrintErr($"Chunk missing in packs: {relPath}"); return; } // For now, we don't parse chunk contents yet; we just visualize existence + biome by file naming/placeholder. // Next step: parse biome from JSON and use it to color. var chunkJson = FileAccess.GetFileAsString(chunkPath); using var doc = JsonDocument.Parse(chunkJson); var biome = doc.RootElement.TryGetProperty("biome", out var b) ? b.GetString() : "biome:unknown"; var node = CreateDebugChunkNode(x, y, _world!.chunk_size_px, biome ?? "biome:unknown"); 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(); n.Name = $"Chunk_{cx}_{cy}"; n.Position = new Vector2(cx * chunkSizePx, cy * chunkSizePx); var rect = new ColorRect(); rect.Size = new Vector2(chunkSizePx, chunkSizePx); rect.MouseFilter = Control.MouseFilterEnum.Ignore; rect.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) }; // Add an outline using a Panel (cheap) or just rely on transparency for now n.AddChild(rect); return n; } }