diff --git a/Assets/Scripts/Components/Controller/FreeCamController.cs b/Assets/Scripts/Components/Controller/FreeCamController.cs
index 37e1a69..cdcef79 100644
--- a/Assets/Scripts/Components/Controller/FreeCamController.cs
+++ b/Assets/Scripts/Components/Controller/FreeCamController.cs
@@ -6,7 +6,7 @@ using UnityEngine.InputSystem;
///
/// Free-flying spectator camera on its own dedicated : WASD flight relative
-/// to the view, Q/E (or Space/Left Ctrl) for down/up, mouse look. A
+/// to the view, Space/Left Ctrl for up/down, mouse look. A
/// capsule makes it slide along world geometry instead of clipping. Driven by
/// via ; while inactive this component does nothing.
///
@@ -170,14 +170,15 @@ public class FreeCamController : MonoBehaviour
if (kb.sKey.isPressed) dir -= transform.forward;
if (kb.dKey.isPressed) dir += transform.right;
if (kb.aKey.isPressed) dir -= transform.right;
- if (kb.eKey.isPressed || kb.spaceKey.isPressed) dir += Vector3.up;
- if (kb.qKey.isPressed || kb.leftCtrlKey.isPressed) dir -= Vector3.up;
+ // Q/E intentionally unbound: E is the taunt key and must work in free cam.
+ if (kb.spaceKey.isPressed) dir += Vector3.up;
+ if (kb.leftCtrlKey.isPressed) dir -= Vector3.up;
}
#else
dir += transform.forward * Input.GetAxisRaw("Vertical");
dir += transform.right * Input.GetAxisRaw("Horizontal");
- if (Input.GetKey(KeyCode.E) || Input.GetKey(KeyCode.Space)) dir += Vector3.up;
- if (Input.GetKey(KeyCode.Q) || Input.GetKey(KeyCode.LeftControl)) dir -= Vector3.up;
+ if (Input.GetKey(KeyCode.Space)) dir += Vector3.up;
+ if (Input.GetKey(KeyCode.LeftControl)) dir -= Vector3.up;
#endif
return dir;
}
diff --git a/Assets/Scripts/Components/UI/OverviewMenu.cs b/Assets/Scripts/Components/UI/OverviewMenu.cs
index 734313a..782b299 100644
--- a/Assets/Scripts/Components/UI/OverviewMenu.cs
+++ b/Assets/Scripts/Components/UI/OverviewMenu.cs
@@ -6,7 +6,8 @@ using UnityEngine.InputSystem;
#endif
///
-/// The Esc overview menu (the "Overview UI" canvas in Main). Owns ALL Escape handling: Esc toggles
+/// The Esc overview menu (the "Overview UI" canvas in Main). Owns ALL Escape handling (Tab acts as an
+/// alternative to Esc): Esc toggles
/// this panel as a modal pause overlay (gameplay suspended via );
/// while the session browser or match results overlay is open instead, Esc closes that, and while paint
/// mode is active Esc exits to the camera mode (Player Control, or First Person for the hunter). Buttons
@@ -41,6 +42,9 @@ public class OverviewMenu : MonoBehaviour
[SerializeField] private Button paintButton;
[SerializeField] private Button firstPersonButton;
[SerializeField] private Button freeCamButton;
+ [Tooltip("Triggers the hider's manual radar ping (same as the E key) and closes the menu. Only shown " +
+ "for a hider during Seek.")]
+ [SerializeField] private Button tauntButton;
private bool _open;
@@ -60,6 +64,7 @@ public class OverviewMenu : MonoBehaviour
if (paintButton) paintButton.onClick.AddListener(OnPaint);
if (firstPersonButton) firstPersonButton.onClick.AddListener(OnFirstPerson);
if (freeCamButton) freeCamButton.onClick.AddListener(OnFreeCam);
+ if (tauntButton) tauntButton.onClick.AddListener(OnTaunt);
}
private void OnDisable()
@@ -70,6 +75,7 @@ public class OverviewMenu : MonoBehaviour
if (paintButton) paintButton.onClick.RemoveListener(OnPaint);
if (firstPersonButton) firstPersonButton.onClick.RemoveListener(OnFirstPerson);
if (freeCamButton) freeCamButton.onClick.RemoveListener(OnFreeCam);
+ if (tauntButton) tauntButton.onClick.RemoveListener(OnTaunt);
}
private void Update()
@@ -147,6 +153,12 @@ public class OverviewMenu : MonoBehaviour
gameMode.SetPaused(false);
}
+ private void OnTaunt()
+ {
+ Close(resume: true);
+ if (MatchManager.Instance) MatchManager.Instance.RequestManualPing();
+ }
+
private void OnPlayerControl() => SwitchMode();
private void OnPaint() => SwitchMode();
private void OnFirstPerson() => SwitchMode();
@@ -174,6 +186,12 @@ public class OverviewMenu : MonoBehaviour
if (matchResultButton)
matchResultButton.gameObject.SetActive(matchResultScreen && matchResultScreen.HasResults);
+ // Taunt/ping only does something for a hider during Seek (the server re-gates found hiders) -
+ // hide it otherwise, same pattern as the role-scoped mode buttons.
+ if (tauntButton)
+ tauntButton.gameObject.SetActive(gameMode.LocalPlayerIsHider && MatchManager.Instance &&
+ MatchManager.Instance.LocalSubPhase == MatchManager.MatchSubPhase.Seek);
+
SetModeButton(playerControlButton, modes, active);
SetModeButton(paintButton, modes, active);
SetModeButton(firstPersonButton, modes, active);
@@ -191,13 +209,14 @@ public class OverviewMenu : MonoBehaviour
button.interactable = !(active is T);
}
- // NOT TypingGuard-gated (GameplayKeyPressed): Esc must work even while a text field has focus.
+ // Esc is NOT TypingGuard-gated: it must work even while a text field has focus.
+ // Tab (alternative binding) IS gated so it stays usable for typing/field navigation.
private static bool EscapePressed()
{
#if ENABLE_INPUT_SYSTEM
- return InputUtil.KeyPressedThisFrame(Key.Escape);
+ return InputUtil.KeyPressedThisFrame(Key.Escape) || InputUtil.GameplayKeyPressed(Key.Tab);
#else
- return InputUtil.KeyPressedThisFrame(KeyCode.Escape);
+ return InputUtil.KeyPressedThisFrame(KeyCode.Escape) || InputUtil.GameplayKeyPressed(KeyCode.Tab);
#endif
}
}
diff --git a/Assets/Scripts/GameMode/GameModeManager.cs b/Assets/Scripts/GameMode/GameModeManager.cs
index 56bca5b..83a2364 100644
--- a/Assets/Scripts/GameMode/GameModeManager.cs
+++ b/Assets/Scripts/GameMode/GameModeManager.cs
@@ -98,6 +98,9 @@ public class GameModeManager : MonoBehaviour
/// True while gameplay is suspended for a modal overlay (see ).
public bool IsPaused => _paused;
+ /// True while the bound local avatar is a hider (drives the overview menu's taunt button).
+ public bool LocalPlayerIsHider => _localRole == PlayerRole.Hider;
+
/// The active mode, or - while paused - the mode that will resume. Null in the no-player state.
public GameMode EffectiveMode =>
ActiveMode ?? ((_resumeIndex >= 0 && _resumeIndex < _modes.Count) ? _modes[_resumeIndex] : null);
@@ -413,9 +416,9 @@ public class GameModeManager : MonoBehaviour
}
// A hider manually pings itself onto every hunter's radar (same blip as the periodic sweep), on demand.
- // Excluded in Free Cam whose fly controls already use this key; the phase/role gate lives in MatchManager
- // (RequestManualPing no-ops outside Seek / for a found hider), so a stray press is harmless.
- if (_localRole == PlayerRole.Hider && !(ActiveMode is FreeCamMode) &&
+ // Works in every mode incl. Free Cam (its fly controls no longer use Q/E); the phase/role gate lives in
+ // MatchManager (RequestManualPing no-ops outside Seek / for a found hider), so a stray press is harmless.
+ if (_localRole == PlayerRole.Hider &&
InputUtil.GameplayKeyPressed(pingKey) && MatchManager.Instance)
MatchManager.Instance.RequestManualPing();
diff --git a/Assets/Textures/Icons/Icon_firstperson_paint.png b/Assets/Textures/Icons/Icon_firstperson_paint.png
new file mode 100644
index 0000000..53e3ad4
Binary files /dev/null and b/Assets/Textures/Icons/Icon_firstperson_paint.png differ
diff --git a/Assets/Textures/Icons/Icon_firstperson_paint.png.meta b/Assets/Textures/Icons/Icon_firstperson_paint.png.meta
new file mode 100644
index 0000000..91e7b7e
--- /dev/null
+++ b/Assets/Textures/Icons/Icon_firstperson_paint.png.meta
@@ -0,0 +1,117 @@
+fileFormatVersion: 2
+guid: e1ee44a22edb1ce47930b5ad143075a4
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 1
+ wrapV: 1
+ wrapW: 1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 1
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 0
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 256
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 0
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID: 5e97eb03825dee720800000000000000
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Textures/Icons/Icon_taunt.png b/Assets/Textures/Icons/Icon_taunt.png
new file mode 100644
index 0000000..cc2a5b0
Binary files /dev/null and b/Assets/Textures/Icons/Icon_taunt.png differ
diff --git a/Assets/Textures/Icons/Icon_taunt.png.meta b/Assets/Textures/Icons/Icon_taunt.png.meta
new file mode 100644
index 0000000..fc794e1
--- /dev/null
+++ b/Assets/Textures/Icons/Icon_taunt.png.meta
@@ -0,0 +1,117 @@
+fileFormatVersion: 2
+guid: f7a95cb24662ee042ae5bba5593ca55a
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 1
+ wrapV: 1
+ wrapW: 1
+ nPOTScale: 0
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 1
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 0
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 8
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 256
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 0
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID: 5e97eb03825dee720800000000000000
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant: