// Assets/_Project/Scripts/Dev/DebugCommandRelay.cs
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using TD.Gameplay.Draft;
using TD.Gameplay.EnemyUpgrades;
using TD.Gameplay.Waves;
namespace TD.Dev
{
///
/// Debug-only network relay: exposes one RPC that force-applies a
/// by its index, bypassing PlayerDraft's "must currently be
/// offered" check.
///
///
/// Lives entirely separate from PlayerDraft/DraftOption/DraftPool — none of those files
/// know this component exists. Removing this component (and TD.Dev.DebugConsole, which
/// calls it) leaves the shipping draft system completely untouched.
///
/// Added to the Player prefab alongside PlayerDraft/PlayerGoldManager/etc. so it spawns
/// with a per-player OwnerClientId. Static per-client registry mirrors PlayerDraft's
/// GetForClient/Local pattern.
///
public class DebugCommandRelay : NetworkBehaviour
{
private static readonly Dictionary s_byClientId
= new Dictionary();
public static DebugCommandRelay GetForClient(ulong clientId)
{
s_byClientId.TryGetValue(clientId, out var relay);
return relay;
}
public static DebugCommandRelay Local
{
get
{
var nm = NetworkManager.Singleton;
if (nm == null || !nm.IsClient) return null;
return GetForClient(nm.LocalClientId);
}
}
public override void OnNetworkSpawn()
{
s_byClientId[OwnerClientId] = this;
}
public override void OnNetworkDespawn()
{
if (s_byClientId.TryGetValue(OwnerClientId, out var registered) && registered == this)
s_byClientId.Remove(OwnerClientId);
}
///
/// Owning client: force-apply the DraftOption at (its
/// DraftPool index) immediately, skipping the normal "must be currently offered" gate.
/// Debug/testing only — any connected client can grant themselves anything via this
/// RPC, same tradeoff DevWaveControls already accepts for its hotkeys.
///
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Owner)]
public void DebugGrantDraftOptionRpc(int optionId)
{
var option = DraftPool.Instance != null ? DraftPool.Instance.Get(optionId) : null;
if (option == null)
{
Debug.LogWarning($"[DebugCommandRelay] Grant requested for unknown option id {optionId}.");
return;
}
if (option.ServerApply(OwnerClientId))
Debug.Log($"[DebugCommandRelay] Granted '{option.DisplayName}' to client {OwnerClientId}.");
else
Debug.LogWarning($"[DebugCommandRelay] '{option.DisplayName}' could not be applied " +
$"to client {OwnerClientId}.");
}
///
/// Force-apply the at (its
/// index) to the run's current wave slot — the same
/// recording WaveVote.ServerResolve does for a vote winner, skipping the vote
/// entirely. Not player-scoped: the effect lands on the run's current wave slot rather
/// than the caller, so any connected client's relay can trigger it.
///
///
/// Takes effect the next time this wave slot's enemies spawn, not retroactively on
/// enemies already alive — see 's remarks on why
/// applying is "record on the slot," not a direct mutation.
///
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Owner)]
public void DebugGrantEnemyUpgradeRpc(int optionId)
{
var pool = EnemyUpgradePool.Instance;
var option = pool != null ? pool.Get(optionId) : null;
if (option == null)
{
Debug.LogWarning($"[DebugCommandRelay] Grant requested for unknown enemy upgrade id {optionId}.");
return;
}
var run = RunState.Instance;
if (run == null)
{
Debug.LogWarning("[DebugCommandRelay] No RunState in this scene.");
return;
}
int slot = run.CurrentUpgradeSlot;
run.ServerAddUpgrade(slot, optionId);
option.ServerOnApplied(run, slot);
Debug.Log($"[DebugCommandRelay] Granted '{option.DisplayName}' to wave slot {slot}.");
}
}
}