Add tower Sell action with refund + coin VFX/SFX

Owner-validated sell RPC refunds SellRefundPercent of invested gold (Wall refunds 100% while un-upgraded), despawns the tower, and broadcasts a coin-burst VFX + rustle SFX. Refunds do not count as per-wave income. Investment/upgrade tracking added as the seam for the future upgrade system.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Matt F 2026-07-14 20:05:06 -07:00
parent 2429efbf6a
commit 9be31b1645
7 changed files with 361 additions and 12 deletions

View file

@ -1178,19 +1178,30 @@ namespace TD.UI
return btn;
}
// Sell action for a completed tower. Refund is computed on the TowerInstance (single
// source of truth shared with the server) so the badge shows exactly what the player
// gets back. Hotkey is Key.None: the bottom-right slot's letter (B) is already the
// global buff-menu toggle — see CreateBuffMenuButton — so binding it here would fire
// both. The button stays click-only.
private VisualElement CreateSellButton(TowerInstance tower, Key hotkey)
{
int sellValue = tower.Definition != null
? Mathf.RoundToInt(tower.Definition.GoldCost * 0.7f)
: 0;
// Only the tower's owner can sell it (the server enforces this too). A non-owner
// may have this tower view-selected — show the slot disabled with no refund badge
// so the action reads as unavailable rather than misleading.
bool ownedByLocal = tower != null
&& PlayerMatchState.Local != null
&& tower.Owner == PlayerMatchState.Local.Slot;
int sellValue = ownedByLocal ? tower.ComputeSellRefund() : 0;
var btn = CreateActionButton(
costText: sellValue > 0 ? $"+{sellValue}g" : "",
hotkey: hotkey,
hotkey: Key.None,
onClick: () =>
{
/* TODO: sell flow */
if (tower != null)
tower.RequestSellServerRpc();
});
btn.SetEnabled(false);
btn.SetEnabled(ownedByLocal);
return btn;
}
@ -1437,7 +1448,11 @@ namespace TD.UI
ttStats.text = "(stats pending)";
}
int sellValue = Mathf.RoundToInt(def.GoldCost * 0.7f);
// Sell preview for a freshly placed (un-upgraded) tower: mirrors
// TowerInstance.ComputeSellRefund for invested == GoldCost, upgradeCount == 0.
int sellValue = def.FullRefundIfUnupgraded
? def.GoldCost
: Mathf.RoundToInt(def.GoldCost * def.SellRefundPercent);
ttCost.text = $"Cost: {def.GoldCost}g · Sell: {sellValue}g";
}