221 lines
7.1 KiB
C#
221 lines
7.1 KiB
C#
using System;
|
|
using System.Threading.Tasks;
|
|
using AceFieldNewHorizon.Scripts.System;
|
|
using Godot;
|
|
using Vector2 = Godot.Vector2;
|
|
|
|
namespace AceFieldNewHorizon.Scripts.Tiles;
|
|
|
|
public partial class BaseTile : Node2D
|
|
{
|
|
[Export] public string TileId { get; set; }
|
|
|
|
protected GridManager Grid { get; set; }
|
|
protected BuildingRegistry Registry { get; set; }
|
|
|
|
public int MaxDurability { get; private set; }
|
|
public int CurrentDurability { get; private set; }
|
|
public bool IsDestroyed { get; private set; }
|
|
|
|
private CollisionShape2D _collisionShape;
|
|
private Sprite2D _sprite;
|
|
private ColorRect _progressOverlay;
|
|
private Action _onConstructionComplete;
|
|
private Tween _damageTween;
|
|
|
|
public bool IsConstructing;
|
|
public bool IsConstructed;
|
|
|
|
public override void _Ready()
|
|
{
|
|
Grid = DependencyInjection.Container.GetInstance<GridManager>();
|
|
Registry = DependencyInjection.Container.GetInstance<BuildingRegistry>();
|
|
|
|
_collisionShape = GetNodeOrNull<CollisionShape2D>("CollisionShape2D");
|
|
_sprite = GetNodeOrNull<Sprite2D>("Sprite2D");
|
|
_progressOverlay = GetNodeOrNull<ColorRect>("ProgressOverlay");
|
|
if (_progressOverlay != null)
|
|
_progressOverlay.Visible = false;
|
|
|
|
// Get durability from BuildingRegistry
|
|
var buildingData = Registry?.GetBuilding(TileId);
|
|
MaxDurability = buildingData?.Durability ?? 100; // Default to 100 if not found
|
|
CurrentDurability = MaxDurability;
|
|
IsDestroyed = false;
|
|
}
|
|
|
|
public virtual void SetGhostMode(bool canPlace)
|
|
{
|
|
// Don't modify collision for constructing buildings
|
|
if (IsConstructing) return;
|
|
|
|
if (_collisionShape != null)
|
|
_collisionShape.Disabled = true;
|
|
|
|
if (_sprite != null)
|
|
_sprite.Modulate = canPlace
|
|
? new Color(0, 1, 0, 0.5f)
|
|
: new Color(1, 0, 0, 0.5f);
|
|
}
|
|
|
|
public virtual void FinalizePlacement()
|
|
{
|
|
if (_collisionShape != null)
|
|
_collisionShape.Disabled = false;
|
|
if (_sprite != null)
|
|
_sprite.Modulate = Colors.White;
|
|
}
|
|
|
|
public virtual bool TakeDamage(int damage)
|
|
{
|
|
if (IsDestroyed || IsConstructing) return false;
|
|
|
|
GD.Print($"[Tile] {TileId} {GetInstanceId()} took {damage} damage");
|
|
|
|
CurrentDurability = Mathf.Max(0, CurrentDurability - damage);
|
|
|
|
// Visual feedback for taking damage
|
|
ShowDamageEffect();
|
|
|
|
if (CurrentDurability <= 0)
|
|
{
|
|
Destroy();
|
|
return true; // Tile was destroyed
|
|
}
|
|
|
|
return false; // Tile is still alive
|
|
}
|
|
|
|
private void ShowDamageEffect()
|
|
{
|
|
if (_sprite == null) return;
|
|
|
|
// Cancel any existing tween
|
|
_damageTween?.Kill();
|
|
_damageTween = CreateTween();
|
|
|
|
// Flash red briefly
|
|
_damageTween.TweenProperty(_sprite, "modulate", Colors.Red, 0.1f);
|
|
_damageTween.TweenProperty(_sprite, "modulate", Colors.White, 0.1f);
|
|
|
|
// Shake effect
|
|
var originalPosition = _sprite.Position;
|
|
_damageTween.TweenMethod(
|
|
Callable.From<Vector2>(pos => _sprite.Position = pos),
|
|
originalPosition + new Vector2(-2, -2),
|
|
originalPosition + new Vector2(2, 2),
|
|
0.05f
|
|
).SetTrans(Tween.TransitionType.Bounce).SetEase(Tween.EaseType.InOut);
|
|
|
|
_damageTween.TweenMethod(
|
|
Callable.From<Vector2>(pos => _sprite.Position = pos),
|
|
originalPosition + new Vector2(2, 2),
|
|
originalPosition,
|
|
0.05f
|
|
).SetTrans(Tween.TransitionType.Bounce).SetEase(Tween.EaseType.InOut);
|
|
}
|
|
|
|
public virtual void Destroy()
|
|
{
|
|
if (IsDestroyed) return;
|
|
|
|
IsDestroyed = true;
|
|
|
|
// Disable collision using SetDeferred to avoid physics update conflicts
|
|
_collisionShape?.SetDeferred("disabled", true);
|
|
|
|
// Fade out and remove
|
|
var tween = CreateTween();
|
|
tween.TweenProperty(this, "modulate:a", 0f, 0.3f);
|
|
tween.TweenCallback(Callable.From(() => CallDeferred("queue_free")));
|
|
|
|
// Emit signal or call method when tile is destroyed
|
|
CallDeferred(nameof(OnTileDestroyed));
|
|
}
|
|
|
|
protected virtual void OnTileDestroyed()
|
|
{
|
|
// Can be overridden by derived classes for custom destruction behavior
|
|
var cell = GridUtils.WorldToGrid(Position);
|
|
var buildingInfo = Registry.GetBuilding(TileId);
|
|
Grid.FreeArea(cell, buildingInfo.Size, Rotation, buildingInfo.Layer);
|
|
}
|
|
|
|
// Building progress visualization
|
|
public void StartConstruction(float buildTime, Action onComplete = null)
|
|
{
|
|
IsConstructing = true;
|
|
if (_collisionShape != null)
|
|
_collisionShape.Disabled = true;
|
|
|
|
if (_progressOverlay == null || _sprite?.Texture == null)
|
|
{
|
|
IsConstructing = false;
|
|
onComplete?.Invoke();
|
|
return;
|
|
}
|
|
|
|
_onConstructionComplete = onComplete;
|
|
var texSize = new Vector2(GridUtils.TileSize, GridUtils.TileSize);
|
|
|
|
// Set initial transparency for construction
|
|
if (_sprite != null)
|
|
_sprite.Modulate = new Color(1, 1, 1, 0.8f);
|
|
|
|
_progressOverlay.Visible = true;
|
|
_progressOverlay.Modulate = Colors.White;
|
|
_progressOverlay.Color = new Color(0, 0, 1, 0.4f); // semi-transparent blue
|
|
|
|
async void RunProgress()
|
|
{
|
|
var elapsed = 0f;
|
|
while (elapsed < buildTime)
|
|
{
|
|
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
|
|
elapsed += (float)GetProcessDeltaTime();
|
|
|
|
var ratio = Mathf.Clamp(elapsed / buildTime, 0, 1);
|
|
|
|
// Fill from bottom to top, aligned with sprite center pivot
|
|
_progressOverlay.Size = new Vector2(texSize.X, texSize.Y * ratio);
|
|
_progressOverlay.Position = new Vector2(
|
|
-texSize.X / 2, // align left edge
|
|
texSize.Y / 2 - _progressOverlay.Size.Y // move upward
|
|
);
|
|
}
|
|
|
|
// Fade out the overlay
|
|
await FadeOutOverlay(0.5f);
|
|
|
|
// Construction complete - restore full opacity and enable collision
|
|
if (_sprite != null)
|
|
_sprite.Modulate = Colors.White;
|
|
|
|
IsConstructing = false;
|
|
if (_collisionShape != null)
|
|
_collisionShape.Disabled = false;
|
|
|
|
_onConstructionComplete?.Invoke();
|
|
IsConstructed = true;
|
|
}
|
|
|
|
RunProgress();
|
|
}
|
|
|
|
private async Task FadeOutOverlay(float duration)
|
|
{
|
|
var elapsed = 0f;
|
|
var startAlpha = _progressOverlay.Modulate.A;
|
|
|
|
while (elapsed < duration)
|
|
{
|
|
await ToSignal(GetTree(), SceneTree.SignalName.ProcessFrame);
|
|
elapsed += (float)GetProcessDeltaTime();
|
|
var alpha = Mathf.Lerp(startAlpha, 0f, elapsed / duration);
|
|
_progressOverlay.Modulate = new Color(1, 1, 1, alpha);
|
|
}
|
|
|
|
_progressOverlay.Visible = false;
|
|
_progressOverlay.Modulate = Colors.White; // Reset alpha for next use
|
|
}
|
|
} |