diff --git a/Assets/_Project/Prefabs/Player/Player.prefab b/Assets/_Project/Prefabs/Player/Player.prefab
index 6e06dfd..08614f3 100644
--- a/Assets/_Project/Prefabs/Player/Player.prefab
+++ b/Assets/_Project/Prefabs/Player/Player.prefab
@@ -18,7 +18,6 @@ GameObject:
- component: {fileID: 2806524246861401801}
- component: {fileID: 5683786710272852339}
- component: {fileID: 6856236869205671849}
- - component: {fileID: -4438158474568445526}
m_Layer: 0
m_Name: Player
m_TagString: Untagged
@@ -174,16 +173,3 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.PlayerSpellLoadout
ShowTopMostFoldoutHeaderGroup: 1
---- !u!114 &-4438158474568445526
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 3493329038866903420}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 65825b3189ae873948659b37b219f5e6, type: 3}
- m_Name:
- m_EditorClassIdentifier: Assembly-CSharp::TD.Dev.DebugCommandRelay
- ShowTopMostFoldoutHeaderGroup: 1
diff --git a/Assets/_Project/Scenes/Levels/9Player.unity b/Assets/_Project/Scenes/Levels/9Player.unity
index 8c21309..0fcac59 100644
--- a/Assets/_Project/Scenes/Levels/9Player.unity
+++ b/Assets/_Project/Scenes/Levels/9Player.unity
@@ -13757,75 +13757,6 @@ Mesh:
- serializedVersion: 1
m_IndexStart: 0
m_IndexCount: 0
---- !u!1 &954434158
-GameObject:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- serializedVersion: 6
- m_Component:
- - component: {fileID: 954434161}
- - component: {fileID: 954434160}
- - component: {fileID: 954434159}
- m_Layer: 0
- m_Name: DebugConsole
- m_TagString: Untagged
- m_Icon: {fileID: 0}
- m_NavMeshLayer: 0
- m_StaticEditorFlags: 0
- m_IsActive: 1
---- !u!114 &954434159
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 954434158}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 7c6a808e8291552d0b64e36a210879c4, type: 3}
- m_Name:
- m_EditorClassIdentifier: Assembly-CSharp::TD.Dev.DebugConsole
- toggleKey: 4
---- !u!114 &954434160
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 954434158}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 19102, guid: 0000000000000000e000000000000000, type: 0}
- m_Name:
- m_EditorClassIdentifier: UnityEngine.dll::UnityEngine.UIElements.UIDocument
- m_PanelSettings: {fileID: 11400000, guid: 6aa0af71585acea4db4995c3931dc946, type: 2}
- m_ParentUI: {fileID: 0}
- sourceAsset: {fileID: 0}
- m_SortingOrder: 1
- m_Position: 0
- m_WorldSpaceSizeMode: 1
- m_WorldSpaceWidth: 1920
- m_WorldSpaceHeight: 1080
- m_PivotReferenceSize: 0
- m_Pivot: 0
- m_WorldSpaceCollider: {fileID: 0}
---- !u!4 &954434161
-Transform:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 954434158}
- serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: -52.51635, y: 0, z: -27.6311}
- m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
- m_Children: []
- m_Father: {fileID: 0}
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &955672358
GameObject:
m_ObjectHideFlags: 0
@@ -29187,4 +29118,3 @@ SceneRoots:
- {fileID: 914832726}
- {fileID: 993870445}
- {fileID: 759470735}
- - {fileID: 954434161}
diff --git a/Assets/_Project/Scripts/Dev/DebugCommandRelay.cs b/Assets/_Project/Scripts/Dev/DebugCommandRelay.cs
deleted file mode 100644
index 3af3076..0000000
--- a/Assets/_Project/Scripts/Dev/DebugCommandRelay.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-// Assets/_Project/Scripts/Dev/DebugCommandRelay.cs
-using System.Collections.Generic;
-using Unity.Netcode;
-using UnityEngine;
-using TD.Gameplay.Draft;
-
-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}.");
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Dev/DebugCommandRelay.cs.meta b/Assets/_Project/Scripts/Dev/DebugCommandRelay.cs.meta
deleted file mode 100644
index d7a2598..0000000
--- a/Assets/_Project/Scripts/Dev/DebugCommandRelay.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 65825b3189ae873948659b37b219f5e6
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/Dev/DebugConsole.cs b/Assets/_Project/Scripts/Dev/DebugConsole.cs
deleted file mode 100644
index 869be74..0000000
--- a/Assets/_Project/Scripts/Dev/DebugConsole.cs
+++ /dev/null
@@ -1,305 +0,0 @@
-// Assets/_Project/Scripts/Dev/DebugConsole.cs
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Text;
-using UnityEngine;
-using UnityEngine.InputSystem;
-using UnityEngine.UIElements;
-using TD.UI;
-using TD.Gameplay.Draft;
-
-namespace TD.Dev
-{
- ///
- /// Standalone debug console overlay: press the toggle key to open a text
- /// input, type a command, Enter to submit, Escape to cancel.
- ///
- ///
- /// Deliberately decoupled from — own
- /// GameObject, own UIDocument/PanelSettings — so it can be dropped out of
- /// a build entirely by not including that GameObject, the same way
- /// is kept separate from shipping systems.
- ///
- /// Supports one command so far: "get <type> <name>" grants a
- /// to the local player via
- /// .
- ///
- [RequireComponent(typeof(UIDocument))]
- public class DebugConsole : MonoBehaviour
- {
- public static DebugConsole Instance { get; private set; }
-
- [Tooltip("Keyboard shortcut that opens and closes the console. Uses the new Input " +
- "System (UnityEngine.InputSystem.Key). Backquote is the physical ` / ~ key.")]
- [SerializeField] private Key toggleKey = Key.Backquote;
-
- private VisualElement consoleContainer;
- private TextField commandInput;
- private bool consoleOpen;
-
- // Frame on which the console was opened or closed. The toggle key and Enter
- // are ignored on that frame and the next so the keypress that opened/closed
- // the console doesn't also get typed into the field or immediately submit it.
- // Mirrors HUDController's chatToggleSuppressFrame.
- private int toggleSuppressFrame = -1;
-
- private void Awake()
- {
- if (Instance != null && Instance != this)
- {
- Debug.LogError("[DebugConsole] Duplicate instance detected. Destroying the new one.");
- Destroy(gameObject);
- return;
- }
- Instance = this;
- }
-
- private void Start()
- {
- var doc = GetComponent();
- if (doc == null)
- {
- Debug.LogError("[DebugConsole] No UIDocument component found.");
- return;
- }
-
- var root = doc.rootVisualElement;
- if (root == null)
- {
- Debug.LogError("[DebugConsole] rootVisualElement is null. " +
- "Check that Panel Settings is assigned.");
- return;
- }
-
- BuildConsoleUi(root);
- }
-
- private void BuildConsoleUi(VisualElement uiRoot)
- {
- // Top-anchored bar, closed by default. Distinct position from the HUD's
- // bottom-left chat panel so the two never overlap.
- consoleContainer = new VisualElement();
- consoleContainer.pickingMode = PickingMode.Ignore;
- consoleContainer.style.position = Position.Absolute;
- consoleContainer.style.left = 0;
- consoleContainer.style.right = 0;
- consoleContainer.style.top = 0;
- consoleContainer.style.paddingTop = 8;
- consoleContainer.style.paddingBottom = 8;
- consoleContainer.style.paddingLeft = 12;
- consoleContainer.style.paddingRight = 12;
- consoleContainer.style.backgroundColor = new Color(0f, 0f, 0f, 0.75f);
- consoleContainer.style.display = DisplayStyle.None;
-
- commandInput = new TextField();
- commandInput.style.minHeight = 28;
- commandInput.style.width = Length.Percent(100);
- commandInput.maxLength = 200;
- commandInput.isDelayed = false;
- StyleConsoleInput(commandInput);
-
- // Focus tracking — contributes to the same flag HUDController's chat
- // input sets, so camera/builder/spell input all stay gated correctly
- // regardless of which text field currently has focus.
- commandInput.RegisterCallback(_ => HUDController.IsTextInputActive = true);
- commandInput.RegisterCallback(_ => HUDController.IsTextInputActive = false);
-
- consoleContainer.Add(commandInput);
- uiRoot.Add(consoleContainer);
- }
-
- // Mirrors HUDController.StyleChatInputDark — dark background, white text,
- // a thin border, and inner padding so ascenders/descenders don't clip.
- // Green border (vs. chat's neutral gray) as a quick visual "this is the
- // debug console, not chat" cue.
- private static void StyleConsoleInput(TextField field)
- {
- var darkBg = new Color(0.05f, 0.05f, 0.05f, 0.95f);
- var borderClr = new Color(0.35f, 0.75f, 0.35f);
-
- field.style.backgroundColor = darkBg;
- field.style.color = Color.white;
- field.style.borderTopWidth = field.style.borderBottomWidth =
- field.style.borderLeftWidth = field.style.borderRightWidth = 1;
- field.style.borderTopColor = field.style.borderBottomColor =
- field.style.borderLeftColor = field.style.borderRightColor = borderClr;
-
- var inner = field.Q("unity-text-input");
- if (inner != null)
- {
- inner.style.backgroundColor = darkBg;
- inner.style.color = Color.white;
- inner.style.paddingTop = 4;
- inner.style.paddingBottom = 4;
- inner.style.paddingLeft = 6;
- inner.style.paddingRight = 6;
- }
- }
-
- private void Update()
- {
- var kb = Keyboard.current;
- if (kb == null) return; // no keyboard connected (e.g. headless server)
-
- if (Time.frameCount <= toggleSuppressFrame) return;
-
- if (toggleKey != Key.None && kb[toggleKey].wasPressedThisFrame)
- {
- if (consoleOpen) CloseConsole();
- else OpenConsole();
- return;
- }
-
- if (!consoleOpen) return;
-
- bool enterDown = kb.enterKey.wasPressedThisFrame || kb.numpadEnterKey.wasPressedThisFrame;
- bool escDown = kb.escapeKey.wasPressedThisFrame;
-
- if (enterDown) SubmitCommand();
- else if (escDown) CloseConsole();
- }
-
- private void OpenConsole()
- {
- if (commandInput == null) return;
- consoleContainer.style.display = DisplayStyle.Flex;
- consoleContainer.pickingMode = PickingMode.Position;
- commandInput.SetValueWithoutNotify(string.Empty);
-
- // Suppress the toggle key and Enter for this frame and the next so the
- // keypress that opened the console doesn't also get typed into the field.
- // Focus is deferred a frame for the same reason — UI Toolkit would
- // otherwise route the open keypress to the freshly-focused TextField.
- toggleSuppressFrame = Time.frameCount + 1;
- consoleOpen = true;
- StartCoroutine(FocusNextFrame());
- }
-
- private IEnumerator FocusNextFrame()
- {
- yield return null;
- if (consoleOpen && commandInput != null)
- {
- commandInput.Focus();
- commandInput.SelectAll();
- }
- }
-
- private void CloseConsole()
- {
- if (commandInput != null)
- {
- commandInput.SetValueWithoutNotify(string.Empty);
- commandInput.Blur();
- }
- if (consoleContainer != null)
- {
- consoleContainer.style.display = DisplayStyle.None;
- consoleContainer.pickingMode = PickingMode.Ignore;
- }
-
- consoleOpen = false;
- toggleSuppressFrame = Time.frameCount + 1;
-
- // The Enter that submitted/cancelled this console also gets polled by
- // HUDController's chat this same frame — suppress its "Enter opens chat"
- // branch so closing the debug console doesn't pop chat open too.
- HUDController.SuppressChatOpenUntilFrame = Time.frameCount + 1;
- }
-
- private void SubmitCommand()
- {
- string text = commandInput?.value ?? string.Empty;
- CloseConsole();
-
- if (string.IsNullOrWhiteSpace(text)) return;
- ExecuteCommand(text.Trim());
- }
-
- // Maps the type token in "get " to the DraftOption subclass it
- // should match against. Add an entry here whenever a new DraftOption subclass
- // should be gettable from the console.
- private static readonly Dictionary DraftTypeAliases =
- new Dictionary(StringComparer.OrdinalIgnoreCase)
- {
- { "spell", typeof(BuilderSpellDraftOption) },
- { "tower", typeof(NewTowerDraftOption) },
- { "buff", typeof(BuilderEffectDraftOption) },
- { "effect", typeof(BuilderEffectDraftOption) },
- };
-
- private void ExecuteCommand(string text)
- {
- string[] tokens = text.Split((char[])null, StringSplitOptions.RemoveEmptyEntries);
- if (tokens.Length < 3 || !string.Equals(tokens[0], "get", StringComparison.OrdinalIgnoreCase))
- {
- Debug.Log($"[DebugConsole] Unrecognized command: \"{text}\". Try: get ");
- return;
- }
-
- string nameToken = string.Join(" ", tokens, 2, tokens.Length - 2);
- ExecuteGet(tokens[1], nameToken);
- }
-
- // "get ": grants any DraftOption of the given type whose DisplayName
- // matches , via the local player's DebugCommandRelay. Reuses DraftOption's
- // existing type-agnostic ServerApply dispatch instead of hardcoding a grant path
- // per option type.
- private void ExecuteGet(string typeToken, string nameToken)
- {
- if (!DraftTypeAliases.TryGetValue(typeToken, out var optionType))
- {
- Debug.LogWarning($"[DebugConsole] Unknown draft option type \"{typeToken}\". " +
- $"Valid types: {string.Join(", ", DraftTypeAliases.Keys)}");
- return;
- }
-
- var pool = DraftPool.Instance;
- if (pool == null)
- {
- Debug.LogWarning("[DebugConsole] No DraftPool in this scene.");
- return;
- }
-
- string normalizedTarget = Normalize(nameToken);
- for (int i = 0; i < pool.Count; i++)
- {
- var option = pool.Get(i);
- if (option == null || !optionType.IsInstanceOfType(option)) continue;
- if (Normalize(option.DisplayName) != normalizedTarget) continue;
-
- var relay = DebugCommandRelay.Local;
- if (relay == null)
- {
- Debug.LogWarning("[DebugConsole] No local DebugCommandRelay found — " +
- "is it on the Player prefab?");
- return;
- }
-
- relay.DebugGrantDraftOptionRpc(i);
- return;
- }
-
- Debug.LogWarning($"[DebugConsole] No {typeToken} option named \"{nameToken}\" found.");
- }
-
- // DisplayName is player-facing draft card text (its spacing/casing is chosen for
- // presentation, not for typing), so command matching strips everything but letters
- // and digits before comparing — "Slow Area", "slow_area", and "SlowArea" all match
- // the same option.
- private static string Normalize(string s)
- {
- if (string.IsNullOrEmpty(s)) return string.Empty;
- var sb = new StringBuilder(s.Length);
- foreach (char c in s)
- if (char.IsLetterOrDigit(c)) sb.Append(char.ToLowerInvariant(c));
- return sb.ToString();
- }
-
- private void OnDestroy()
- {
- if (Instance == this) Instance = null;
- }
- }
-}
diff --git a/Assets/_Project/Scripts/Dev/DebugConsole.cs.meta b/Assets/_Project/Scripts/Dev/DebugConsole.cs.meta
deleted file mode 100644
index c9a3292..0000000
--- a/Assets/_Project/Scripts/Dev/DebugConsole.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 7c6a808e8291552d0b64e36a210879c4
\ No newline at end of file
diff --git a/Assets/_Project/Scripts/UI/HUDController.cs b/Assets/_Project/Scripts/UI/HUDController.cs
index badd2b8..03cb3b0 100644
--- a/Assets/_Project/Scripts/UI/HUDController.cs
+++ b/Assets/_Project/Scripts/UI/HUDController.cs
@@ -157,19 +157,8 @@ namespace TD.UI
// Set true whenever the chat input or any other text field on the HUD has
// keyboard focus. Camera, builder input, and hotkey handlers all gate on
- // this to keep typing from driving gameplay. Setter is internal (not
- // private) so other standalone text-input surfaces — e.g. TD.Dev.DebugConsole,
- // which is not part of HUDController — can contribute to the same flag
- // instead of every gameplay script needing to know about every input widget.
- public static bool IsTextInputActive { get; internal set; }
-
- // Frame until which chat's "Enter opens chat" behavior is suppressed. Other
- // standalone text-input surfaces (e.g. TD.Dev.DebugConsole) set this when their
- // own Enter-driven submit/close already consumed the keypress, so the same
- // Enter press doesn't also get interpreted as "open chat" this frame. Without
- // this, submitting a debug console command with Enter would immediately pop
- // chat open too, since both systems poll the raw key independently.
- internal static int SuppressChatOpenUntilFrame = -1;
+ // this to keep typing from driving gameplay.
+ public static bool IsTextInputActive { get; private set; }
// ----- State ------------------------------------------------------
@@ -2028,7 +2017,7 @@ namespace TD.UI
if (!chatInputOpen)
{
- if (enterDown && Time.frameCount > SuppressChatOpenUntilFrame) OpenChatInput();
+ if (enterDown) OpenChatInput();
return;
}