Complete resource manager

This commit is contained in:
2025-08-29 00:10:19 +08:00
parent 0d3c84491b
commit 850628ca72
5 changed files with 287 additions and 29 deletions

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using Godot;
using static System.Int32;
namespace AceFieldNewHorizon.Scripts.System;
@@ -101,12 +102,22 @@ public partial class BuildingRegistry : Node
{
int val;
var obj = costDict[mat];
if (obj.VariantType == Variant.Type.PackedInt64Array)
val = (int)obj.AsInt64();
else if (obj.VariantType == Variant.Type.PackedInt32Array)
val = obj.AsInt32();
else
int.TryParse(obj.ToString(), out val);
switch (obj.VariantType)
{
case Variant.Type.PackedInt64Array:
val = (int)obj.AsInt64();
break;
case Variant.Type.PackedInt32Array:
val = obj.AsInt32();
break;
case Variant.Type.Float:
val = (int)obj.AsDouble();
break;
default:
if (!TryParse(obj.ToString(), out val))
GD.PrintErr($"[BuildingRegistry] Failed to parse cost for '{key}': {obj.ToString()}");
break;
}
cost[mat] = val;
}
}
@@ -120,7 +131,7 @@ public partial class BuildingRegistry : Node
else if (dObj.VariantType == Variant.Type.PackedInt32Array)
durability = dObj.AsInt32();
else
int.TryParse(dObj.ToString(), out durability);
TryParse(dObj.ToString(), out durability);
}
// Parse buildTime

View File

@@ -9,11 +9,12 @@ namespace AceFieldNewHorizon.Scripts.System;
public partial class PlacementManager : Node2D
{
[Export] public GridManager Grid { get; set; }
[Export] public ResourceManager Inventory { get; set; }
[Export] public BuildingRegistry Registry { get; set; }
[Export] public int MaxConcurrentBuilds { get; set; } = 6; // Make it adjustable in editor
private static readonly List<string> BuildableTiles = ["wall", "miner"];
private readonly List<BuildTask> _activeBuilds = new();
private readonly Dictionary<Node2D, BuildTask> _buildTasks = new();
private AudioStreamPlayer _completionSound;
public override void _Ready()
@@ -33,10 +34,16 @@ public partial class PlacementManager : Node2D
private void OnBuildCompleted()
{
// Remove all completed builds
_activeBuilds.RemoveAll(task => task.IsCompleted);
var completed = _buildTasks.Where(kvp => kvp.Value.IsCompleted)
.Select(kvp => kvp.Key)
.ToList();
foreach (var key in completed)
{
_buildTasks.Remove(key);
}
// If no builds left, play the completion sound
if (_activeBuilds.Count == 0)
if (_buildTasks.Count == 0)
{
_completionSound.Play();
}
@@ -46,30 +53,34 @@ public partial class PlacementManager : Node2D
private bool CanStartNewBuild()
{
// Remove completed builds
_activeBuilds.RemoveAll(task => task.IsCompleted);
return _activeBuilds.Count < MaxConcurrentBuilds;
var completed = _buildTasks.Where(kvp => kvp.Value.IsCompleted)
.Select(kvp => kvp.Key)
.ToList();
foreach (var key in completed)
{
_buildTasks.Remove(key);
}
return _buildTasks.Count < MaxConcurrentBuilds;
}
private class BuildTask : IDisposable
private class BuildTask(Action onCompleted) : IDisposable
{
private readonly Action _onCompleted;
public bool IsCompleted { get; private set; }
public bool WasCancelled { get; private set; }
public BuildTask(Action onCompleted)
{
_onCompleted = onCompleted;
}
public void Complete()
public void Complete(bool wasCancelled = false)
{
if (IsCompleted) return;
IsCompleted = true;
_onCompleted?.Invoke();
WasCancelled = wasCancelled;
onCompleted?.Invoke();
}
public void Dispose()
{
Complete();
Complete(true); // Mark as cancelled if disposed before completion
}
}
@@ -188,6 +199,13 @@ public partial class PlacementManager : Node2D
return;
}
// Consume resources first
if (!ConsumeBuildingResources(_currentBuildingId))
{
// Optionally show feedback to player that they can't afford this building
return;
}
var scene = building.Scene;
var buildingInstance = (BaseTile)scene.Instantiate();
buildingInstance.RotationDegrees = _currentRotation;
@@ -195,13 +213,36 @@ public partial class PlacementManager : Node2D
buildingInstance.Position = _ghostBuilding.Position;
AddChild(buildingInstance);
// Check if area is free before placing
if (!IsAreaFree(_hoveredCell, building.Size, _currentRotation, building.Layer))
{
RefundBuildingResources(_currentBuildingId);
buildingInstance.QueueFree();
return;
}
// Occupy the area
Grid.OccupyArea(_hoveredCell, buildingInstance, building.Size, _currentRotation, building.Layer);
if (building.BuildTime > 0f)
{
var buildTask = new BuildTask(OnBuildCompleted);
_activeBuilds.Add(buildTask);
buildingInstance.StartConstruction(building.BuildTime, buildTask.Complete);
_buildTasks[buildingInstance] = buildTask;
buildingInstance.StartConstruction(building.BuildTime, () => {
// On construction complete
if (_buildTasks.TryGetValue(buildingInstance, out var task))
{
if (task.WasCancelled)
{
RefundBuildingResources(_currentBuildingId);
Grid.FreeArea(_hoveredCell, building.Size, _currentRotation, building.Layer);
buildingInstance.QueueFree();
}
task.Complete();
_buildTasks.Remove(buildingInstance);
}
});
}
}
@@ -226,14 +267,64 @@ public partial class PlacementManager : Node2D
}
}
public override void _ExitTree()
{
base._ExitTree();
_buildTasks.Clear();
}
private bool CanAffordBuilding(string buildingId)
{
if (Inventory == null) return false;
var buildingData = Registry.GetBuilding(buildingId);
if (buildingData == null) return false;
// Check if we have enough of each required resource
foreach (var (resourceId, amount) in buildingData.Cost)
if (!Inventory.HasResource(resourceId, amount))
return false;
return true;
}
private bool ConsumeBuildingResources(string buildingId)
{
if (Inventory == null) return false;
var buildingData = Registry.GetBuilding(buildingId);
if (buildingData == null) return false;
// First verify we can afford it
if (!CanAffordBuilding(buildingId)) return false;
// Then consume each resource
foreach (var (resourceId, amount) in buildingData.Cost)
Inventory.RemoveResource(resourceId, amount);
return true;
}
private void RefundBuildingResources(string buildingId)
{
if (Inventory == null) return;
var buildingData = Registry.GetBuilding(buildingId);
if (buildingData == null) return;
// Refund each resource
foreach (var (resourceId, amount) in buildingData.Cost)
{
Inventory.AddResource(resourceId, amount);
}
}
private bool CanPlaceBuilding()
{
var buildingData = Registry.GetBuilding(_currentBuildingId);
if (buildingData == null) return false;
// Check if rotation is allowed
if (!buildingData.IsRotationAllowed(_currentRotation))
return false;
// Check if we can afford the building
if (!CanAffordBuilding(_currentBuildingId)) return false;
// Check if area is free
var rotatedSize = buildingData.GetRotatedSize(_currentRotation);
@@ -250,6 +341,11 @@ public partial class PlacementManager : Node2D
_ => []
};
}
private bool IsAreaFree(Vector2I topLeft, Vector2I size, float rotation, GridLayer layer)
{
return !Grid.IsAreaOccupied(topLeft, size, rotation, GetBlockingLayers(layer));
}
}
public static class GridManagerExtensions

View File

@@ -0,0 +1,145 @@
using Godot;
using System.Collections.Generic;
using System.Linq;
namespace AceFieldNewHorizon.Scripts.System;
public partial class ResourceManager : Node
{
// Dictionary to store resources with their IDs and quantities
private readonly Dictionary<string, int> _resources = new();
private static readonly StringName LoggerName = "ResourceManager";
// Event for when resource amounts change
[Signal]
public delegate void OnResourceChangedEventHandler(string resourceId, int newAmount);
public override void _Ready()
{
base._Ready();
GD.Print($"[{LoggerName}] ResourceManager initialized");
}
// Add resources of a specific type
public void AddResource(string resourceId, int amount = 1)
{
if (amount <= 0)
{
GD.PushWarning($"[{LoggerName}] Attempted to add non-positive amount ({amount}) for resource: {resourceId}");
return;
}
bool isNew = !_resources.ContainsKey(resourceId);
if (!_resources.TryAdd(resourceId, amount))
{
var oldAmount = _resources[resourceId];
_resources[resourceId] += amount;
GD.Print($"[{LoggerName}] Added {amount} {resourceId}. New total: {_resources[resourceId]} (was {oldAmount})");
}
else
{
GD.Print($"[{LoggerName}] Added new resource: {resourceId} with amount: {amount}");
}
EmitSignal(nameof(OnResourceChanged), resourceId, _resources[resourceId]);
}
// Remove resources of a specific type
public bool RemoveResource(string resourceId, int amount = 1)
{
if (amount <= 0)
{
GD.PushWarning($"[{LoggerName}] Attempted to remove non-positive amount ({amount}) for resource: {resourceId}");
return false;
}
if (!_resources.ContainsKey(resourceId))
{
GD.Print($"[{LoggerName}] Failed to remove {amount} {resourceId}: Resource not found");
return false;
}
if (_resources[resourceId] < amount)
{
GD.Print($"[{LoggerName}] Insufficient {resourceId}. Requested: {amount}, Available: {_resources[resourceId]}");
return false;
}
var oldAmount = _resources[resourceId];
_resources[resourceId] -= amount;
GD.Print($"[{LoggerName}] Removed {amount} {resourceId}. New total: {_resources[resourceId]} (was {oldAmount})");
// Remove the entry if quantity reaches zero
if (_resources[resourceId] <= 0)
{
_resources.Remove(resourceId);
GD.Print($"[{LoggerName}] Removed resource entry for {resourceId} (reached zero)");
EmitSignal(nameof(OnResourceChanged), resourceId, 0);
}
else
{
EmitSignal(nameof(OnResourceChanged), resourceId, _resources[resourceId]);
}
return true;
}
// Get the current amount of a specific resource
public int GetResourceAmount(string resourceId)
{
// Silently return 0 for non-existent resources
return _resources.GetValueOrDefault(resourceId, 0);
}
// Check if there are at least 'amount' of a specific resource
public bool HasResource(string resourceId, int amount = 1)
{
if (amount <= 0) return true; // Always true for zero or negative amounts
var exists = _resources.TryGetValue(resourceId, out var currentAmount);
var hasEnough = exists && currentAmount >= amount;
return hasEnough;
}
// Get all resources as a read-only dictionary
public IReadOnlyDictionary<string, int> GetAllResources()
{
GD.Print($"[{LoggerName}] Getting all resources. Total types: {_resources.Count}");
return new Dictionary<string, int>(_resources);
}
// Clear all resources
public void Clear()
{
GD.Print($"[{LoggerName}] Clearing all resources. Total types before clear: {_resources.Count}");
var keys = new List<string>(_resources.Keys);
foreach (var resourceId in keys)
{
_resources[resourceId] = 0;
EmitSignal(nameof(OnResourceChanged), resourceId, 0);
}
_resources.Clear();
GD.Print($"[{LoggerName}] All resources cleared");
}
// Helper method to log current resource state
public void LogResourceState()
{
if (_resources.Count == 0)
{
GD.Print($"[{LoggerName}] No resources currently stored");
return;
}
GD.Print($"[{LoggerName}] Current resource state:");
foreach (var (id, amount) in _resources.OrderBy(x => x.Key))
{
GD.Print($" - {id}: {amount}");
}
}
}

View File

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