Major updates to the HUD and selectable objects
This commit is contained in:
parent
5bc757b385
commit
c100db52e5
23 changed files with 1615 additions and 614 deletions
|
|
@ -4,7 +4,6 @@ using Unity.Netcode;
|
|||
using UnityEngine;
|
||||
using TD.Core;
|
||||
using TD.Towers;
|
||||
using TD.UI;
|
||||
|
||||
namespace TD.Gameplay
|
||||
{
|
||||
|
|
@ -40,7 +39,7 @@ namespace TD.Gameplay
|
|||
/// races with the Builder. See lessons in the project context doc.</para>
|
||||
/// </remarks>
|
||||
[RequireComponent(typeof(NetworkObject))]
|
||||
public class BuildSiteVisual : NetworkBehaviour
|
||||
public class BuildSiteVisual : NetworkBehaviour, ISelectable
|
||||
{
|
||||
// ----- Inspector --------------------------------------------------
|
||||
|
||||
|
|
@ -201,6 +200,36 @@ namespace TD.Gameplay
|
|||
return Mathf.Clamp01((currentRunElapsed + accumulatedConstructionTime.Value) / bt);
|
||||
}
|
||||
|
||||
// ----- ISelectable ------------------------------------------------
|
||||
|
||||
/// <summary>Name shown in the HUD portrait. Pulls from the resolved
|
||||
/// TowerDefinition; falls back to a generic label if the registry hasn't
|
||||
/// resolved the def yet on this client.</summary>
|
||||
public string DisplayName
|
||||
{
|
||||
get
|
||||
{
|
||||
var def = TowerPlacementManager.GetDefinition(towerTypeId.Value);
|
||||
return def != null ? def.DisplayName : "Tower (building)";
|
||||
}
|
||||
}
|
||||
|
||||
public SelectableKind Kind => SelectableKind.BuildSite;
|
||||
|
||||
public Transform SelectionTransform => transform;
|
||||
|
||||
// Match the TowerInstance's radius formula so the selection ring is the
|
||||
// same size before vs. after the build completes — no visual pop on transition.
|
||||
public float SelectionRadius
|
||||
{
|
||||
get
|
||||
{
|
||||
var def = TowerPlacementManager.GetDefinition(towerTypeId.Value);
|
||||
Vector2Int fp = def != null ? def.FootprintSize : new Vector2Int(1, 1);
|
||||
return Mathf.Max(fp.x, fp.y) * 0.5f * GridCoordinates.TILE_SIZE + 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Pre-spawn init data (server) -------------------------------
|
||||
|
||||
private string pendingDefName;
|
||||
|
|
@ -266,12 +295,6 @@ namespace TD.Gameplay
|
|||
|
||||
// Apply initial visual state based on the (now-replicated) values.
|
||||
ApplyStageVisual(currentStage.Value);
|
||||
|
||||
// Attach a local (non-networked) progress bar — each client creates its own.
|
||||
// Destroyed automatically when this NetworkObject is despawned (it's a child).
|
||||
var barHost = new GameObject("ProgressBar");
|
||||
barHost.transform.SetParent(transform, false);
|
||||
barHost.AddComponent<BuildProgressBar>().Initialize(this);
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
|
|
@ -287,6 +310,15 @@ namespace TD.Gameplay
|
|||
{
|
||||
RestoreFootprintGridState();
|
||||
}
|
||||
|
||||
// Local-only selection hygiene. If this visual was the active selection
|
||||
// AND nothing has transferred selection to a replacement (e.g., a
|
||||
// TowerInstance that just spawned at the same anchor), clear so HUD/
|
||||
// visualizer don't hold a destroyed reference. The completion path
|
||||
// spawns TowerInstance BEFORE this despawn arrives on the client; that
|
||||
// spawn already transferred selection, so this check passes through.
|
||||
if (SelectionState.Instance != null && SelectionState.Instance.IsSelected(this))
|
||||
SelectionState.Instance.Clear();
|
||||
}
|
||||
|
||||
// Server-only: restore walkability=true and occupancy=false on this build site's
|
||||
|
|
@ -347,6 +379,82 @@ namespace TD.Gameplay
|
|||
isShelved.Value = false;
|
||||
}
|
||||
|
||||
// ----- Player-initiated cancel (HUD action) -----------------------
|
||||
|
||||
// Server-only guard against double cancel — Cancel RPC could arrive twice
|
||||
// if the player clicks quickly before the visual finishes despawning.
|
||||
private bool serverCancelled;
|
||||
|
||||
/// <summary>
|
||||
/// Owner-only RPC. Routes to <see cref="ServerCancel"/>. Hooked up to the
|
||||
/// Cancel action button in the HUD's action menu.
|
||||
/// </summary>
|
||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Owner)]
|
||||
public void RequestCancelRpc()
|
||||
{
|
||||
ServerCancel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-only: cancel this build. Two paths depending on shelve state:
|
||||
/// - <b>Shelved</b>: this visual is standalone (not in any builder's queue).
|
||||
/// Refund gold ourselves and despawn — <see cref="OnNetworkDespawn"/>
|
||||
/// restores the footprint's grid state because <c>isShelved</c> is true.
|
||||
/// - <b>In a builder's queue</b>: route through the owning builder's
|
||||
/// <see cref="Builder.ServerCancelJobAtAnchor"/>, which already handles
|
||||
/// refund + grid restore + visual despawn through its job-cleanup path.
|
||||
/// </summary>
|
||||
public void ServerCancel()
|
||||
{
|
||||
if (!IsServer) return;
|
||||
if (serverCancelled) return; // idempotent guard
|
||||
serverCancelled = true;
|
||||
|
||||
if (isShelved.Value)
|
||||
{
|
||||
RefundOwner();
|
||||
if (NetworkObject.IsSpawned)
|
||||
NetworkObject.Despawn(destroy: true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Queued / Constructing / Paused — owned by a builder's job queue.
|
||||
var builder = Builder.GetForClient(OwnerClientId);
|
||||
if (builder != null)
|
||||
{
|
||||
bool found = builder.ServerCancelJobAtAnchor(anchor.Value);
|
||||
if (found) return;
|
||||
// Race: visual exists but no matching job (e.g., job just completed
|
||||
// and the visual is mid-despawn). Fall through to manual cleanup.
|
||||
Debug.LogWarning($"[BuildSiteVisual] ServerCancel: no matching job " +
|
||||
$"at anchor {anchor.Value} on builder for client " +
|
||||
$"{OwnerClientId}. Performing manual refund+despawn.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[BuildSiteVisual] ServerCancel: owning builder " +
|
||||
"not found. Performing manual refund+despawn.");
|
||||
}
|
||||
|
||||
// Manual fallback for the race / no-builder cases. Restore grid since
|
||||
// the builder isn't going to do it for us.
|
||||
RefundOwner();
|
||||
if (currentStage.Value == BuildStage.Constructing
|
||||
|| currentStage.Value == BuildStage.Paused)
|
||||
{
|
||||
RestoreFootprintGridState();
|
||||
}
|
||||
if (NetworkObject.IsSpawned)
|
||||
NetworkObject.Despawn(destroy: true);
|
||||
}
|
||||
|
||||
private void RefundOwner()
|
||||
{
|
||||
var goldManager = PlayerGoldManager.GetForClient(OwnerClientId);
|
||||
if (goldManager == null) return;
|
||||
goldManager.AwardGold(goldSpent.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-only: transitions the visual from Queued (or Paused) to Constructing
|
||||
/// and records the server time for stage progression. Caller is responsible
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue