ocker/scripts/world/ChunkManager.cs

202 lines
4.9 KiB
C#

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<CharacterBody2D>(PlayerPath);
if (_player == null)
{
GD.PrintErr("ChunkManager: PlayerPath not set or invalid.");
return;
}
_packs = GetNodeOrNull<PackManager>(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<WorldIndex>(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);
// 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<ChunkData>(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)
}
}