✨ New sfx and vfx and features in building
This commit is contained in:
@@ -11,7 +11,7 @@ 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
|
||||
[Export] public bool Enabled { get; set; } = true;
|
||||
[Export] public StringName ToggleBuildAction { get; set; } = "toggle_build";
|
||||
@@ -19,33 +19,50 @@ public partial class PlacementManager : Node2D
|
||||
private static readonly List<string> BuildableTiles = ["wall", "miner"];
|
||||
private readonly Dictionary<Node2D, BuildTask> _buildTasks = new();
|
||||
private AudioStreamPlayer _completionSound;
|
||||
private AudioStreamPlayer _buildingSound;
|
||||
private AudioStreamPlayer _canceledSound;
|
||||
private AudioStreamPlayer _cannotDeploySound;
|
||||
private AudioStreamPlayer _notReadySound;
|
||||
private AudioStreamPlayer _insufficientFundsSound;
|
||||
private Node2D _currentGhost; // Keep track of the current ghost building
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
base._Ready();
|
||||
|
||||
|
||||
// Setup completion sound
|
||||
_completionSound = new AudioStreamPlayer();
|
||||
AddChild(_completionSound);
|
||||
var sound = GD.Load<AudioStream>("res://Sounds/Events/ConstructionComplete.wav");
|
||||
_completionSound = CreateAudioPlayer("res://Sounds/Events/ConstructionComplete.wav");
|
||||
_buildingSound = CreateAudioPlayer("res://Sounds/Events/Building.wav");
|
||||
_canceledSound = CreateAudioPlayer("res://Sounds/Events/Canceled.wav");
|
||||
_cannotDeploySound = CreateAudioPlayer("res://Sounds/Events/CannotDeployHere.wav");
|
||||
_notReadySound = CreateAudioPlayer("res://Sounds/Events/NotReady.wav");
|
||||
_insufficientFundsSound = CreateAudioPlayer("res://Sounds/Events/InsufficientFunds.wav");
|
||||
}
|
||||
|
||||
private AudioStreamPlayer CreateAudioPlayer(string path)
|
||||
{
|
||||
var player = new AudioStreamPlayer();
|
||||
AddChild(player);
|
||||
var sound = GD.Load<AudioStream>(path);
|
||||
if (sound != null)
|
||||
{
|
||||
_completionSound.Stream = sound;
|
||||
player.Stream = sound;
|
||||
}
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
private void OnBuildCompleted()
|
||||
{
|
||||
// Remove all completed builds
|
||||
var completed = _buildTasks.Where(kvp => kvp.Value.IsCompleted)
|
||||
.Select(kvp => kvp.Key)
|
||||
.ToList();
|
||||
.Select(kvp => kvp.Key)
|
||||
.ToList();
|
||||
foreach (var key in completed)
|
||||
{
|
||||
_buildTasks.Remove(key);
|
||||
}
|
||||
|
||||
|
||||
// If no builds left, play the completion sound
|
||||
if (_buildTasks.Count == 0)
|
||||
{
|
||||
@@ -58,13 +75,13 @@ public partial class PlacementManager : Node2D
|
||||
{
|
||||
// Remove completed builds
|
||||
var completed = _buildTasks.Where(kvp => kvp.Value.IsCompleted)
|
||||
.Select(kvp => kvp.Key)
|
||||
.ToList();
|
||||
.Select(kvp => kvp.Key)
|
||||
.ToList();
|
||||
foreach (var key in completed)
|
||||
{
|
||||
_buildTasks.Remove(key);
|
||||
}
|
||||
|
||||
|
||||
return _buildTasks.Count < MaxConcurrentBuilds;
|
||||
}
|
||||
|
||||
@@ -76,7 +93,7 @@ public partial class PlacementManager : Node2D
|
||||
public void Complete(bool wasCancelled = false)
|
||||
{
|
||||
if (IsCompleted) return;
|
||||
|
||||
|
||||
IsCompleted = true;
|
||||
WasCancelled = wasCancelled;
|
||||
onCompleted?.Invoke();
|
||||
@@ -158,7 +175,7 @@ public partial class PlacementManager : Node2D
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (!Enabled) return;
|
||||
|
||||
|
||||
// Snap mouse to grid
|
||||
var mousePos = GetGlobalMousePosition();
|
||||
var newHoveredCell = GridUtils.WorldToGrid(mousePos);
|
||||
@@ -194,24 +211,25 @@ public partial class PlacementManager : Node2D
|
||||
_ghostBuilding.SetGhostMode(canPlace);
|
||||
|
||||
// Left click to place
|
||||
if (Input.IsActionPressed("build_tile") && canPlace)
|
||||
if (Input.IsActionPressed("build_tile"))
|
||||
{
|
||||
var building = Registry.GetBuilding(_currentBuildingId);
|
||||
if (building == null) return;
|
||||
|
||||
|
||||
if (!CanStartNewBuild())
|
||||
{
|
||||
// Optionally show feedback to player that build queue is full
|
||||
_notReadySound.Play();
|
||||
return;
|
||||
}
|
||||
|
||||
// Consume resources first
|
||||
if (!ConsumeBuildingResources(_currentBuildingId))
|
||||
{
|
||||
// Optionally show feedback to player that they can't afford this building
|
||||
_insufficientFundsSound.Play();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the building instance first
|
||||
var scene = building.Scene;
|
||||
var buildingInstance = (BaseTile)scene.Instantiate();
|
||||
buildingInstance.RotationDegrees = _currentRotation;
|
||||
@@ -219,23 +237,30 @@ public partial class PlacementManager : Node2D
|
||||
buildingInstance.Position = _ghostBuilding.Position;
|
||||
AddChild(buildingInstance);
|
||||
|
||||
// Check if area is free before placing
|
||||
// First check if area is free
|
||||
if (!IsAreaFree(_hoveredCell, building.Size, _currentRotation, building.Layer))
|
||||
{
|
||||
_cannotDeploySound.Play();
|
||||
RefundBuildingResources(_currentBuildingId);
|
||||
buildingInstance.QueueFree();
|
||||
return;
|
||||
}
|
||||
|
||||
// Occupy the area
|
||||
// If we get here, area is free, so we can safely occupy it
|
||||
Grid.OccupyArea(_hoveredCell, buildingInstance, building.Size, _currentRotation, building.Layer);
|
||||
|
||||
if (building.BuildTime > 0f)
|
||||
{
|
||||
var wasQueueEmpty = _buildTasks.Count == 0;
|
||||
var buildTask = new BuildTask(OnBuildCompleted);
|
||||
_buildTasks[buildingInstance] = buildTask;
|
||||
|
||||
buildingInstance.StartConstruction(building.BuildTime, () => {
|
||||
|
||||
// Play building sound only when adding to an empty queue
|
||||
if (wasQueueEmpty)
|
||||
_buildingSound.Play();
|
||||
|
||||
buildingInstance.StartConstruction(building.BuildTime, () =>
|
||||
{
|
||||
// On construction complete
|
||||
if (_buildTasks.TryGetValue(buildingInstance, out var task))
|
||||
{
|
||||
@@ -245,6 +270,7 @@ public partial class PlacementManager : Node2D
|
||||
Grid.FreeArea(_hoveredCell, building.Size, _currentRotation, building.Layer);
|
||||
buildingInstance.QueueFree();
|
||||
}
|
||||
|
||||
task.Complete();
|
||||
_buildTasks.Remove(buildingInstance);
|
||||
}
|
||||
@@ -258,9 +284,28 @@ public partial class PlacementManager : Node2D
|
||||
// Right click to destroy from current layer
|
||||
var building = Grid.GetBuildingAtCell(_hoveredCell);
|
||||
if (building == null) return;
|
||||
|
||||
// Find all cells occupied by this building
|
||||
var buildingInfo = Grid.GetBuildingInfoAtCell(_hoveredCell, GridLayer.Building);
|
||||
if (buildingInfo == null) return;
|
||||
|
||||
// Check if this building is in the build tasks (under construction)
|
||||
if (_buildTasks.TryGetValue(building, out var buildTask))
|
||||
{
|
||||
var buildingTile = building as BaseTile;
|
||||
// Cancel the build task
|
||||
buildTask.Complete(true); // Mark as cancelled
|
||||
_buildTasks.Remove(building);
|
||||
_canceledSound.Play();
|
||||
if (buildingTile == null) return;
|
||||
|
||||
// Refund resources for canceled build
|
||||
var buildingData = Registry.GetBuilding(buildingTile.TileId);
|
||||
if (buildingData != null)
|
||||
RefundBuildingResources(buildingTile.TileId);
|
||||
}
|
||||
|
||||
// Clean up the building and grid
|
||||
building.QueueFree();
|
||||
Grid.FreeArea(buildingInfo.Value.Position, buildingInfo.Value.Size, buildingInfo.Value.Rotation);
|
||||
}
|
||||
@@ -278,7 +323,7 @@ public partial class PlacementManager : Node2D
|
||||
if (@event.IsActionPressed(ToggleBuildAction))
|
||||
{
|
||||
Enabled = !Enabled;
|
||||
|
||||
|
||||
// Hide ghost building when disabling
|
||||
if (!Enabled && _ghostBuilding != null && _ghostBuilding.IsInsideTree())
|
||||
{
|
||||
@@ -297,41 +342,41 @@ public partial class PlacementManager : Node2D
|
||||
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)
|
||||
{
|
||||
|
Reference in New Issue
Block a user