fixed basic movement, now add world loading

This commit is contained in:
anonoe 2026-02-09 22:44:11 +01:00
parent 34ba37312a
commit 50f37ebc5f
Signed by: anonoe
SSH key fingerprint: SHA256:OnAs6gNQelOnDiY5tBpDYKQiuTgBvnmIdMo5P09cdqg
23 changed files with 482 additions and 27 deletions

View file

@ -1,16 +0,0 @@
using Godot;
public partial class PlayerVisual : Node2D
{
[Export] public float Radius = 8f;
public override void _Draw()
{
DrawCircle(Vector2.Zero, Radius, new Color(0.2f, 0.8f, 1.0f));
}
public override void _Ready()
{
QueueRedraw();
}
}

View file

@ -1 +0,0 @@
uid://b25o2r2lf6nst

View file

@ -0,0 +1,74 @@
using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
public partial class PackManager : Node
{
public record PackManifest(int schema, string id, string name, string version, int priority);
private readonly List<(string Root, PackManifest Manifest)> _packs = new();
public override void _Ready()
{
LoadPacks("res://packs");
GD.Print($"Loaded packs: {string.Join(", ", _packs.Select(p => $"{p.Manifest.id}(prio={p.Manifest.priority})"))}");
}
public void LoadPacks(string packsRoot)
{
_packs.Clear();
if (!DirAccess.DirExistsAbsolute(packsRoot))
{
GD.PrintErr($"Packs root missing: {packsRoot}");
return;
}
using var dir = DirAccess.Open(packsRoot);
dir.ListDirBegin();
while (true)
{
var entry = dir.GetNext();
if (entry == "") break;
if (entry.StartsWith(".")) continue;
var packPath = $"{packsRoot}/{entry}";
if (!dir.CurrentIsDir()) continue;
var manifestPath = $"{packPath}/pack.json";
if (!FileAccess.FileExists(manifestPath))
continue;
var json = FileAccess.GetFileAsString(manifestPath);
var manifest = JsonSerializer.Deserialize<PackManifest>(json);
if (manifest == null)
{
GD.PrintErr($"Invalid pack.json: {manifestPath}");
continue;
}
_packs.Add((packPath, manifest));
}
dir.ListDirEnd();
// Sort bottom->top by priority (low first, high last)
_packs.Sort((a, b) => a.Manifest.priority.CompareTo(b.Manifest.priority));
}
/// Returns the best (topmost) existing file path for a relative path inside a pack.
public string? Resolve(string relativePath)
{
relativePath = relativePath.TrimStart('/');
// Highest priority pack wins -> iterate from end
for (int i = _packs.Count - 1; i >= 0; i--)
{
var candidate = $"{_packs[i].Root}/{relativePath}";
if (FileAccess.FileExists(candidate))
return candidate;
}
return null;
}
}

View file

@ -0,0 +1 @@
uid://db2vbbh5737ke

View file

@ -0,0 +1,146 @@
using Godot;
using System;
using System.Collections.Generic;
using System.Text.Json;
public partial class ChunkManager : Node2D
{
[Export] public NodePath PlayerPath;
[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<CharacterBody2D>(PlayerPath);
if (_player == null)
{
GD.PrintErr("ChunkManager: PlayerPath not set or invalid.");
return;
}
_packs = GetNodeOrNull<PackManager>("/root/Main/PackManager");
if (_packs == null)
{
// fallback: search up the tree for PackManager
_packs = GetParent()?.GetNodeOrNull<PackManager>("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<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);
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;
}
}

View file

@ -0,0 +1 @@
uid://cqdq8fslu7cyp

View file

@ -0,0 +1,15 @@
using System.Collections.Generic;
using System.Text.Json;
public record WorldIndex(int schema, string world_id, string display_name, int chunk_size_px, int tile_size_px, int[] start_pos_px, List<WorldIndexChunk> chunks)
{
public Dictionary<(int x, int y), string> BuildChunkMap()
{
var map = new Dictionary<(int, int), string>();
foreach (var c in chunks)
map[(c.x, c.y)] = c.path;
return map;
}
}
public record WorldIndexChunk(int x, int y, string path);

View file

@ -0,0 +1 @@
uid://bgtmfbm4qg0l5