time-of-day + creature spawns in spawn zones

This commit is contained in:
anonoe 2026-02-10 17:21:54 +01:00
parent c5d89c898a
commit 246f3c0840
Signed by: anonoe
SSH key fingerprint: SHA256:OnAs6gNQelOnDiY5tBpDYKQiuTgBvnmIdMo5P09cdqg
8 changed files with 148 additions and 2 deletions

View file

@ -0,0 +1,30 @@
using Godot;
public partial class WorldClock : Node
{
public enum TimeOfDay { Morning, Day, Night }
[Export] public TimeOfDay Current { get; private set; } = TimeOfDay.Day;
public override void _Process(double delta)
{
// Debug toggle: press T to cycle
if (Input.IsActionJustPressed("debug_cycle_time"))
{
Current = Current switch
{
TimeOfDay.Morning => TimeOfDay.Day,
TimeOfDay.Day => TimeOfDay.Night,
_ => TimeOfDay.Morning
};
GD.Print($"TimeOfDay -> {Current}");
}
}
public static string ToPackString(TimeOfDay t) => t switch
{
TimeOfDay.Morning => "morning",
TimeOfDay.Day => "day",
_ => "night"
};
}

View file

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

View file

@ -10,6 +10,11 @@ public partial class ChunkManager : Node2D
[Export] public int LoadRadius { get; set; } = 1; // 1 => 3x3
[Export] public PackedScene PickupMarkerScene { get; set; }
[Export] public NodePath SaveManagerPath { get; set; }
[Export] public PackedScene CreatureMarkerScene { get; set; }
[Export] public NodePath WorldClockPath { get; set; }
[Export] public int MaxCreaturesPerChunk { get; set; } = 3;
private WorldClock? _clock;
private WorldClock.TimeOfDay? _lastTime;
private SaveManager? _save;
private CharacterBody2D? _player;
private PackManager? _packs;
@ -36,6 +41,9 @@ public partial class ChunkManager : Node2D
_save = GetNodeOrNull<SaveManager>(SaveManagerPath);
if (_save == null) GD.PrintErr("ChunkManager: SaveManagerPath not set or invalid.");
_clock = GetNodeOrNull<WorldClock>(WorldClockPath);
if (_clock == null) GD.PrintErr("ChunkManager: WorldClockPath not set or invalid.");
LoadWorldIndex();
}
@ -95,6 +103,17 @@ public partial class ChunkManager : Node2D
_loaded[k].QueueFree();
_loaded.Remove(k);
}
if (_clock != null)
{
if (_lastTime == null) _lastTime = _clock.Current;
if (_clock.Current != _lastTime)
{
_lastTime = _clock.Current;
RefreshCreaturesInLoadedChunks();
}
}
}
private void EnsureLoaded(int x, int y)
@ -121,8 +140,10 @@ public partial class ChunkManager : Node2D
var biome = data.biome ?? "biome:unknown";
var node = CreateDebugChunkNode(x, y, _world!.chunk_size_px, biome);
AddChunkMarkers(node, data);
node.SetMeta("chunk_data", data);
// Put the chunk into the scene tree first so Timers can run.
AddChild(node);
AddChunkMarkers(node, data);
_loaded[key] = node;
}
@ -228,6 +249,12 @@ public partial class ChunkManager : Node2D
}
// Collision rects: gray outlines (optional later)
if (data.spawn_zones != null)
{
foreach (var z in data.spawn_zones)
SpawnCreaturesForZone(chunkNode, z);
}
}
private static long NowUnixSeconds()
@ -326,6 +353,63 @@ public partial class ChunkManager : Node2D
};
chunkNode.AddChild(timer);
timer.Start();
// Start after the node is fully in-tree (avoids start-before-tree edge cases).
timer.CallDeferred(Timer.MethodName.Start);
}
private void SpawnCreaturesForZone(Node2D chunkNode, ChunkSpawnZone zone)
{
if (_clock == null || CreatureMarkerScene == null) return;
var timeStr = WorldClock.ToPackString(_clock.Current);
if (zone.time_windows != null && zone.time_windows.Count > 0 && !zone.time_windows.Contains(timeStr))
return;
// Dedup: ensure one spawn container per zone
var containerName = $"Creatures_{zone.id.Replace(":", "_")}";
if (chunkNode.HasNode(containerName))
return;
var container = new Node2D { Name = containerName };
chunkNode.AddChild(container);
var r = zone.rect; // [x,y,w,h]
var rng = new RandomNumberGenerator();
rng.Randomize();
var count = Math.Min(MaxCreaturesPerChunk, 3);
for (int i = 0; i < count; i++)
{
var c = (Node2D)CreatureMarkerScene.Instantiate();
c.Position = new Vector2(
r[0] + rng.RandfRange(0, r[2]),
r[1] + rng.RandfRange(0, r[3])
);
container.AddChild(c);
}
}
private void RefreshCreaturesInLoadedChunks()
{
foreach (var chunk in _loaded.Values)
{
// Remove existing creature containers
foreach (var child in chunk.GetChildren())
{
if (child is Node n && n.Name.StartsWith("Creatures_"))
n.QueueFree();
}
// Re-read spawn zones by re-parsing the chunk json is overkill.
// v0 hack: store ChunkData on the chunk node as metadata.
if (chunk.HasMeta("chunk_data"))
{
var data = (ChunkData)chunk.GetMeta("chunk_data");
if (data.spawn_zones != null)
foreach (var z in data.spawn_zones)
SpawnCreaturesForZone(chunk, z);
}
}
}
}

View file

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

View file

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