✨ Complete resource manager
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
[gd_scene load_steps=6 format=3 uid="uid://c22aprj452aha"]
|
[gd_scene load_steps=7 format=3 uid="uid://c22aprj452aha"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://cudpc3w17mbsw" path="res://Scripts/System/GridManager.cs" id="1_knkkn"]
|
[ext_resource type="Script" uid="uid://cudpc3w17mbsw" path="res://Scripts/System/GridManager.cs" id="1_knkkn"]
|
||||||
|
[ext_resource type="Script" uid="uid://dfi2snip78eq6" path="res://Scripts/System/ResourceManager.cs" id="1_pl8e4"]
|
||||||
[ext_resource type="Script" uid="uid://cfbj72nm0eovg" path="res://Scripts/System/BuildingRegistry.cs" id="1_sxhdm"]
|
[ext_resource type="Script" uid="uid://cfbj72nm0eovg" path="res://Scripts/System/BuildingRegistry.cs" id="1_sxhdm"]
|
||||||
[ext_resource type="Script" uid="uid://cugfbvw70clgd" path="res://Scripts/System/NaturalResourceGenerator.cs" id="2_oss8w"]
|
[ext_resource type="Script" uid="uid://cugfbvw70clgd" path="res://Scripts/System/NaturalResourceGenerator.cs" id="2_oss8w"]
|
||||||
[ext_resource type="Script" uid="uid://bx1wj7gn6vrqe" path="res://Scripts/System/PlacementManager.cs" id="2_sxhdm"]
|
[ext_resource type="Script" uid="uid://bx1wj7gn6vrqe" path="res://Scripts/System/PlacementManager.cs" id="2_sxhdm"]
|
||||||
@@ -8,6 +9,9 @@
|
|||||||
|
|
||||||
[node name="Root" type="Node2D"]
|
[node name="Root" type="Node2D"]
|
||||||
|
|
||||||
|
[node name="ResourceSystem" type="Node" parent="."]
|
||||||
|
script = ExtResource("1_pl8e4")
|
||||||
|
|
||||||
[node name="BuildingRegistry" type="Node" parent="."]
|
[node name="BuildingRegistry" type="Node" parent="."]
|
||||||
script = ExtResource("1_sxhdm")
|
script = ExtResource("1_sxhdm")
|
||||||
|
|
||||||
@@ -19,9 +23,10 @@ Registry = NodePath("../BuildingRegistry")
|
|||||||
[node name="GridSystem" type="Node2D" parent="."]
|
[node name="GridSystem" type="Node2D" parent="."]
|
||||||
script = ExtResource("1_knkkn")
|
script = ExtResource("1_knkkn")
|
||||||
|
|
||||||
[node name="PlacementSystem" type="Node2D" parent="." node_paths=PackedStringArray("Grid", "Registry")]
|
[node name="PlacementSystem" type="Node2D" parent="." node_paths=PackedStringArray("Grid", "Inventory", "Registry")]
|
||||||
script = ExtResource("2_sxhdm")
|
script = ExtResource("2_sxhdm")
|
||||||
Grid = NodePath("../GridSystem")
|
Grid = NodePath("../GridSystem")
|
||||||
|
Inventory = NodePath("../ResourceSystem")
|
||||||
Registry = NodePath("../BuildingRegistry")
|
Registry = NodePath("../BuildingRegistry")
|
||||||
|
|
||||||
[node name="Player" parent="." instance=ExtResource("3_oss8w")]
|
[node name="Player" parent="." instance=ExtResource("3_oss8w")]
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Godot;
|
using Godot;
|
||||||
|
using static System.Int32;
|
||||||
|
|
||||||
namespace AceFieldNewHorizon.Scripts.System;
|
namespace AceFieldNewHorizon.Scripts.System;
|
||||||
|
|
||||||
@@ -101,12 +102,22 @@ public partial class BuildingRegistry : Node
|
|||||||
{
|
{
|
||||||
int val;
|
int val;
|
||||||
var obj = costDict[mat];
|
var obj = costDict[mat];
|
||||||
if (obj.VariantType == Variant.Type.PackedInt64Array)
|
switch (obj.VariantType)
|
||||||
|
{
|
||||||
|
case Variant.Type.PackedInt64Array:
|
||||||
val = (int)obj.AsInt64();
|
val = (int)obj.AsInt64();
|
||||||
else if (obj.VariantType == Variant.Type.PackedInt32Array)
|
break;
|
||||||
|
case Variant.Type.PackedInt32Array:
|
||||||
val = obj.AsInt32();
|
val = obj.AsInt32();
|
||||||
else
|
break;
|
||||||
int.TryParse(obj.ToString(), out val);
|
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;
|
cost[mat] = val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,7 +131,7 @@ public partial class BuildingRegistry : Node
|
|||||||
else if (dObj.VariantType == Variant.Type.PackedInt32Array)
|
else if (dObj.VariantType == Variant.Type.PackedInt32Array)
|
||||||
durability = dObj.AsInt32();
|
durability = dObj.AsInt32();
|
||||||
else
|
else
|
||||||
int.TryParse(dObj.ToString(), out durability);
|
TryParse(dObj.ToString(), out durability);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse buildTime
|
// Parse buildTime
|
||||||
|
@@ -9,11 +9,12 @@ namespace AceFieldNewHorizon.Scripts.System;
|
|||||||
public partial class PlacementManager : Node2D
|
public partial class PlacementManager : Node2D
|
||||||
{
|
{
|
||||||
[Export] public GridManager Grid { get; set; }
|
[Export] public GridManager Grid { get; set; }
|
||||||
|
[Export] public ResourceManager Inventory { get; set; }
|
||||||
[Export] public BuildingRegistry Registry { get; set; }
|
[Export] public BuildingRegistry Registry { get; set; }
|
||||||
[Export] public int MaxConcurrentBuilds { get; set; } = 6; // Make it adjustable in editor
|
[Export] public int MaxConcurrentBuilds { get; set; } = 6; // Make it adjustable in editor
|
||||||
|
|
||||||
private static readonly List<string> BuildableTiles = ["wall", "miner"];
|
private static readonly List<string> BuildableTiles = ["wall", "miner"];
|
||||||
private readonly List<BuildTask> _activeBuilds = new();
|
private readonly Dictionary<Node2D, BuildTask> _buildTasks = new();
|
||||||
private AudioStreamPlayer _completionSound;
|
private AudioStreamPlayer _completionSound;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
@@ -33,10 +34,16 @@ public partial class PlacementManager : Node2D
|
|||||||
private void OnBuildCompleted()
|
private void OnBuildCompleted()
|
||||||
{
|
{
|
||||||
// Remove all completed builds
|
// 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 no builds left, play the completion sound
|
||||||
if (_activeBuilds.Count == 0)
|
if (_buildTasks.Count == 0)
|
||||||
{
|
{
|
||||||
_completionSound.Play();
|
_completionSound.Play();
|
||||||
}
|
}
|
||||||
@@ -46,30 +53,34 @@ public partial class PlacementManager : Node2D
|
|||||||
private bool CanStartNewBuild()
|
private bool CanStartNewBuild()
|
||||||
{
|
{
|
||||||
// Remove completed builds
|
// Remove completed builds
|
||||||
_activeBuilds.RemoveAll(task => task.IsCompleted);
|
var completed = _buildTasks.Where(kvp => kvp.Value.IsCompleted)
|
||||||
return _activeBuilds.Count < MaxConcurrentBuilds;
|
.Select(kvp => kvp.Key)
|
||||||
|
.ToList();
|
||||||
|
foreach (var key in completed)
|
||||||
|
{
|
||||||
|
_buildTasks.Remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BuildTask : IDisposable
|
return _buildTasks.Count < MaxConcurrentBuilds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BuildTask(Action onCompleted) : IDisposable
|
||||||
{
|
{
|
||||||
private readonly Action _onCompleted;
|
|
||||||
public bool IsCompleted { get; private set; }
|
public bool IsCompleted { get; private set; }
|
||||||
|
public bool WasCancelled { get; private set; }
|
||||||
|
|
||||||
public BuildTask(Action onCompleted)
|
public void Complete(bool wasCancelled = false)
|
||||||
{
|
|
||||||
_onCompleted = onCompleted;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Complete()
|
|
||||||
{
|
{
|
||||||
if (IsCompleted) return;
|
if (IsCompleted) return;
|
||||||
|
|
||||||
IsCompleted = true;
|
IsCompleted = true;
|
||||||
_onCompleted?.Invoke();
|
WasCancelled = wasCancelled;
|
||||||
|
onCompleted?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Complete();
|
Complete(true); // Mark as cancelled if disposed before completion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +199,13 @@ public partial class PlacementManager : Node2D
|
|||||||
return;
|
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 scene = building.Scene;
|
||||||
var buildingInstance = (BaseTile)scene.Instantiate();
|
var buildingInstance = (BaseTile)scene.Instantiate();
|
||||||
buildingInstance.RotationDegrees = _currentRotation;
|
buildingInstance.RotationDegrees = _currentRotation;
|
||||||
@@ -195,13 +213,36 @@ public partial class PlacementManager : Node2D
|
|||||||
buildingInstance.Position = _ghostBuilding.Position;
|
buildingInstance.Position = _ghostBuilding.Position;
|
||||||
AddChild(buildingInstance);
|
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);
|
Grid.OccupyArea(_hoveredCell, buildingInstance, building.Size, _currentRotation, building.Layer);
|
||||||
|
|
||||||
if (building.BuildTime > 0f)
|
if (building.BuildTime > 0f)
|
||||||
{
|
{
|
||||||
var buildTask = new BuildTask(OnBuildCompleted);
|
var buildTask = new BuildTask(OnBuildCompleted);
|
||||||
_activeBuilds.Add(buildTask);
|
_buildTasks[buildingInstance] = buildTask;
|
||||||
buildingInstance.StartConstruction(building.BuildTime, buildTask.Complete);
|
|
||||||
|
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()
|
private bool CanPlaceBuilding()
|
||||||
{
|
{
|
||||||
var buildingData = Registry.GetBuilding(_currentBuildingId);
|
var buildingData = Registry.GetBuilding(_currentBuildingId);
|
||||||
if (buildingData == null) return false;
|
if (buildingData == null) return false;
|
||||||
|
|
||||||
// Check if rotation is allowed
|
// Check if we can afford the building
|
||||||
if (!buildingData.IsRotationAllowed(_currentRotation))
|
if (!CanAffordBuilding(_currentBuildingId)) return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
// Check if area is free
|
// Check if area is free
|
||||||
var rotatedSize = buildingData.GetRotatedSize(_currentRotation);
|
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
|
public static class GridManagerExtensions
|
||||||
|
145
Scripts/System/ResourceManager.cs
Normal file
145
Scripts/System/ResourceManager.cs
Normal 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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
Scripts/System/ResourceManager.cs.uid
Normal file
1
Scripts/System/ResourceManager.cs.uid
Normal file
@@ -0,0 +1 @@
|
|||||||
|
uid://dfi2snip78eq6
|
Reference in New Issue
Block a user