respawn naturals while chunk loaded
This commit is contained in:
parent
63ca3220d1
commit
c5d89c898a
2 changed files with 114 additions and 33 deletions
|
|
@ -21,7 +21,7 @@
|
||||||
"id": "pickup:forest_herb_01",
|
"id": "pickup:forest_herb_01",
|
||||||
"item": "item:forest_herb",
|
"item": "item:forest_herb",
|
||||||
"pos": [120, 140],
|
"pos": [120, 140],
|
||||||
"respawn_seconds": 5
|
"respawn_seconds": 10
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "pickup:rusty_coin_01",
|
"id": "pickup:rusty_coin_01",
|
||||||
|
|
|
||||||
|
|
@ -158,11 +158,6 @@ public partial class ChunkManager : Node2D
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long NowUnixSeconds()
|
|
||||||
{
|
|
||||||
return DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddChunkMarkers(Node2D chunkNode, ChunkData data)
|
private void AddChunkMarkers(Node2D chunkNode, ChunkData data)
|
||||||
{
|
{
|
||||||
if (_save == null || PickupMarkerScene == null) return;
|
if (_save == null || PickupMarkerScene == null) return;
|
||||||
|
|
@ -173,42 +168,29 @@ public partial class ChunkManager : Node2D
|
||||||
|
|
||||||
foreach (var p in data.pickups)
|
foreach (var p in data.pickups)
|
||||||
{
|
{
|
||||||
if (_save == null || PickupMarkerScene == null)
|
if (_save == null || PickupMarkerScene == null) return;
|
||||||
return;
|
|
||||||
|
|
||||||
|
// Unnatural / one-time pickups
|
||||||
if (p.respawn_seconds <= 0)
|
if (p.respawn_seconds <= 0)
|
||||||
{
|
{
|
||||||
if (_save.State.CollectedPickups.Contains(p.id))
|
if (_save.State.CollectedPickups.Contains(p.id))
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
else
|
SpawnPickupMarker(chunkNode, p);
|
||||||
{
|
continue;
|
||||||
if (_save.State.PickupRespawnAt.TryGetValue(p.id, out var respawnAt) && now < respawnAt)
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var marker = (PickupMarker)PickupMarkerScene.Instantiate();
|
// Natural pickups: check cooldown
|
||||||
marker.Position = new Vector2(p.pos[0], p.pos[1]);
|
//var now = NowUnixSeconds();
|
||||||
marker.PickupId = p.id;
|
if (_save.State.PickupRespawnAt.TryGetValue(p.id, out var respawnAt) && now < respawnAt)
|
||||||
marker.ItemId = p.item;
|
|
||||||
|
|
||||||
marker.PickedUp += (pickupId, itemId) =>
|
|
||||||
{
|
{
|
||||||
GD.Print($"Picked up {itemId} ({pickupId})");
|
// Chunk is loaded but pickup is still cooling down -> schedule its appearance.
|
||||||
|
SchedulePickupRespawn(chunkNode, p.id, p.item, p.respawn_seconds, respawnAt, new int[] { p.pos[0], p.pos[1] });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (p.respawn_seconds <= 0)
|
// Ready now -> spawn immediately
|
||||||
{
|
SpawnPickupMarker(chunkNode, p);
|
||||||
_save.State.CollectedPickups.Add(pickupId);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_save.State.PickupRespawnAt[pickupId] = NowUnixSeconds() + p.respawn_seconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
_save.Save();
|
|
||||||
marker.QueueFree();
|
|
||||||
};
|
|
||||||
chunkNode.AddChild(marker);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -247,4 +229,103 @@ public partial class ChunkManager : Node2D
|
||||||
|
|
||||||
// Collision rects: gray outlines (optional later)
|
// Collision rects: gray outlines (optional later)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static long NowUnixSeconds()
|
||||||
|
{
|
||||||
|
return DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||||
|
}
|
||||||
|
// Timer nodes are attached to the chunk node so they automatically die when the chunk unloads.
|
||||||
|
private const string RespawnTimerPrefix = "RespawnTimer_";
|
||||||
|
private static string RespawnTimerName(string pickupId) => $"{RespawnTimerPrefix}{pickupId.Replace(":", "_")}";
|
||||||
|
// Spawn a pickup marker in world-space (y-sort friendly later).
|
||||||
|
private void SpawnPickupMarker(Node2D chunkNode, ChunkPickup p)
|
||||||
|
{
|
||||||
|
if (_save == null || PickupMarkerScene == null) return;
|
||||||
|
|
||||||
|
var marker = (PickupMarker)PickupMarkerScene.Instantiate();
|
||||||
|
marker.Position = new Vector2(p.pos[0], p.pos[1]);
|
||||||
|
marker.PickupId = p.id;
|
||||||
|
marker.ItemId = p.item;
|
||||||
|
|
||||||
|
// Capture only the values we need (avoid relying on foreach capture semantics).
|
||||||
|
var respawnSeconds = p.respawn_seconds;
|
||||||
|
var pickupId = p.id;
|
||||||
|
var itemId = p.item;
|
||||||
|
var posCopy = new int[] { p.pos[0], p.pos[1] };
|
||||||
|
|
||||||
|
marker.PickedUp += (_, __) =>
|
||||||
|
{
|
||||||
|
if (_save == null) return;
|
||||||
|
|
||||||
|
// Permanent pickups (unnatural): one-time removal.
|
||||||
|
if (respawnSeconds <= 0)
|
||||||
|
{
|
||||||
|
_save.State.CollectedPickups.Add(pickupId);
|
||||||
|
_save.Save();
|
||||||
|
marker.QueueFree();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Natural pickups: schedule respawn by absolute timestamp.
|
||||||
|
var respawnAt = NowUnixSeconds() + respawnSeconds;
|
||||||
|
_save.State.PickupRespawnAt[pickupId] = respawnAt;
|
||||||
|
_save.Save();
|
||||||
|
|
||||||
|
marker.QueueFree();
|
||||||
|
|
||||||
|
// If chunk stays loaded, respawn should still appear.
|
||||||
|
SchedulePickupRespawn(chunkNode, pickupId, itemId, respawnSeconds, respawnAt, posCopy);
|
||||||
|
};
|
||||||
|
|
||||||
|
chunkNode.AddChild(marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule a respawn for a pickup that is currently on cooldown.
|
||||||
|
private void SchedulePickupRespawn(
|
||||||
|
Node2D chunkNode,
|
||||||
|
string pickupId,
|
||||||
|
string itemId,
|
||||||
|
int respawnSeconds,
|
||||||
|
long respawnAtUnix,
|
||||||
|
int[] pos)
|
||||||
|
{
|
||||||
|
// Deduplicate: one timer per pickup per loaded chunk.
|
||||||
|
var timerName = RespawnTimerName(pickupId);
|
||||||
|
if (chunkNode.HasNode(timerName))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var now = NowUnixSeconds();
|
||||||
|
var wait = Math.Max(0.1, respawnAtUnix - now); // remaining time (accounts for time while chunk was unloaded)
|
||||||
|
|
||||||
|
var timer = new Timer
|
||||||
|
{
|
||||||
|
Name = timerName,
|
||||||
|
OneShot = true,
|
||||||
|
WaitTime = wait
|
||||||
|
};
|
||||||
|
|
||||||
|
timer.Timeout += () =>
|
||||||
|
{
|
||||||
|
if (_save == null) return;
|
||||||
|
|
||||||
|
// Re-check at fire time (handles clock changes / save edits).
|
||||||
|
var now2 = NowUnixSeconds();
|
||||||
|
if (_save.State.PickupRespawnAt.TryGetValue(pickupId, out var ra) && now2 < ra)
|
||||||
|
{
|
||||||
|
// Still not ready: reschedule with the updated remaining time.
|
||||||
|
timer.QueueFree();
|
||||||
|
SchedulePickupRespawn(chunkNode, pickupId, itemId, respawnSeconds, ra, pos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ready: remove the timer node name and spawn the pickup.
|
||||||
|
timer.QueueFree();
|
||||||
|
|
||||||
|
var p = new ChunkPickup(pickupId, itemId, pos, respawnSeconds);
|
||||||
|
SpawnPickupMarker(chunkNode, p);
|
||||||
|
};
|
||||||
|
|
||||||
|
chunkNode.AddChild(timer);
|
||||||
|
timer.Start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue