Add (unsynced) in-game timebar + adjust ui pos

This commit is contained in:
Atony committed 2026-08-14 22:29:40 -05:00
1 parent d1d11e2bec
commit 95819cf83a
6 files changed
+410 -4

No files matched your search

+60
View File
@@ -218,6 +218,20 @@ public abstract class AbstractGameMode : IOptionable
/// <returns>True if the sabotage map should be shown.</returns>
public virtual bool ShouldShowSabotageMap(MapBehaviour map) => true;
/// <summary>
/// Gets the <see cref="MapOptions"/> to display on the map.
/// </summary>
/// <returns>The <see cref="MapOptions"/> for the gamemode.</returns>
public virtual MapOptions GetMapOptions()
{
return new MapOptions
{
Mode = PlayerControl.LocalPlayer.Data.Role.IsImpostor && !MeetingHud.Instance
? MapOptions.Modes.Sabotage
: MapOptions.Modes.Normal,
};
}
/// <summary>
/// Can a player vent in this gamemode.
/// </summary>
@@ -226,6 +240,52 @@ public abstract class AbstractGameMode : IOptionable
/// <returns>True if venting is enabled in this mode.</returns>
public virtual bool CanVent(Vent vent, NetworkedPlayerInfo playerInfo) => true;
/// <summary>
/// Does the task bar appear in the gamemode.
/// </summary>
/// <returns>True if the task bar is enabled in this mode.</returns>
public virtual bool ShowTaskBar => true;
public virtual void UpdateTaskPanel(TaskPanelBehaviour instance)
{
instance.background.transform.localScale = (instance.taskText.textBounds.size.x > 0f)
? new Vector3(instance.taskText.textBounds.size.x + 0.2f, instance.taskText.textBounds.size.y + 0.2f, 1f)
: Vector3.zero;
Vector3 vector = instance.background.sprite.bounds.extents;
vector.y = -vector.y;
vector = vector.Mul(instance.background.transform.localScale);
instance.background.transform.localPosition = vector;
Vector3 vector2 = instance.tab.sprite.bounds.extents;
vector2 = vector2.Mul(instance.tab.transform.localScale);
vector2.y = -vector2.y;
vector2.x += vector.x * 2f;
instance.tab.transform.localPosition = vector2;
if (GameManager.Instance == null)
{
return;
}
var yPos = 0.6f;
var xPos = -instance.background.sprite.bounds.size.x * instance.background.transform.localScale.x;
instance.closedPosition = new Vector3(xPos, yPos, instance.closedPosition.z);
instance.openPosition = new Vector3(instance.openPosition.x, yPos, instance.openPosition.z);
if (instance.open)
{
instance.timer = Mathf.Min(1f, instance.timer + Time.deltaTime / instance.animationTimeSeconds);
}
else
{
instance.timer = Mathf.Max(0f, instance.timer - Time.deltaTime / instance.animationTimeSeconds);
}
Vector3 relativePos = new(
Mathf.SmoothStep(instance.closedPosition.x, instance.openPosition.x, instance.timer),
yPos,
instance.openPosition.z);
instance.transform.localPosition =
AspectPosition.ComputePosition(AspectPosition.EdgeAlignments.LeftTop, relativePos);
}
/// <inheritdoc/>
public override string ToString() => Name;
}
+64 -4
View File
@@ -7,6 +7,7 @@ using AmongUs.GameOptions;
using HarmonyLib;
using InnerNet;
using MiraAPI.GameOptions;
using MiraAPI.HnsReimplemented;
using MiraAPI.HnsReimplemented.Options;
using MiraAPI.PluginLoading;
using MiraAPI.Roles;
@@ -39,6 +40,7 @@ public class HideAndSeekMode : AbstractGameMode
public override bool ShowNormalRoleSettings => false;
public override float DefaultImpostorKillCooldown => 1f;
public override bool ShowTaskBar => false;
public static int ImpostorPlayerID()
{
return OptionGroupSingleton<HnsImpostorOptions>.Instance.SelectedSeeker.Value;
@@ -73,7 +75,7 @@ public class HideAndSeekMode : AbstractGameMode
}
IGameOptions currentGameOptions = GameOptionsManager.Instance.CurrentGameOptions;
int adjustedNumImpostors = GameOptionsManager.Instance.CurrentGameOptions.GetAdjustedNumImpostors(list2.Count);
AssignRolesForTeam(list2, currentGameOptions, RoleTeamTypes.Impostor, adjustedNumImpostors, RoleTypes.Impostor);
AssignRolesForTeam(list2, currentGameOptions, RoleTeamTypes.Impostor, Math.Max(adjustedNumImpostors, 1), RoleTypes.Impostor);
AssignRolesForTeam(list2, currentGameOptions, RoleTeamTypes.Crewmate, int.MaxValue, RoleTypes.Engineer);
}
@@ -84,7 +86,7 @@ public class HideAndSeekMode : AbstractGameMode
int teamMax,
RoleTypes defaultRole)
{
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Team: {team}, Max: {teamMax}, Players: {players.Count}, DefaultRole: {defaultRole}");
Error($"Hide And Seek Mode - AssignRolesForTeam: Team: {team}, Max: {teamMax}, Players: {players.Count}, DefaultRole: {defaultRole}");
int num = 0;
IRoleOptionsCollection roleOptions = opts.RoleOptions;
var source = RoleManager.Instance.AllRoles.ToArray()
@@ -400,6 +402,25 @@ public class HideAndSeekMode : AbstractGameMode
deadPlayerCount = 0;
ShipStatus.Instance.BreakEmergencyButton();
PlayerControl.LocalPlayer.SetKillTimer(0.01f);
if (HudManager.InstanceExists)
{
HudManager.Instance.gameObject.AddComponent<HideAndSeekHudHelper>();
}
}
public override MapOptions GetMapOptions()
{
MapOptions mapOptions = new MapOptions
{
Mode = MapOptions.Modes.Normal,
};
if (PlayerControl.LocalPlayer.Data.Role.IsImpostor && HideAndSeekHudHelper.Instance.SeekerAdminMapEnabled(PlayerControl.LocalPlayer))
{
mapOptions.Mode = MapOptions.Modes.CountOverlay;
mapOptions.AllowMovementWhileMapOpen = true;
mapOptions.IncludeDeadBodies = false;
mapOptions.ShowLivePlayerPosition = false;
}
return mapOptions;
}
public override void CheckGameEnd(out bool runOriginal, LogicGameFlowNormal instance)
@@ -411,10 +432,10 @@ public class HideAndSeekMode : AbstractGameMode
}
if (Helpers.GetAlivePlayers().Count(x => !x.Data.Role.IsImpostor) != 0)
{
/*if (instance.AllTimersExpired())
if (HideAndSeekHudHelper.Instance.AllTimersExpired())
{
instance.Manager.RpcEndGame(GameOverReason.HideAndSeek_CrewmatesByTimer, !DataManager.Player.Ads.HasPurchasedAdRemoval);
}*/
}
return;
}
instance.Manager.RpcEndGame(GameOverReason.HideAndSeek_ImpostorsByKills, !DataManager.Player.Ads.HasPurchasedAdRemoval);
@@ -459,6 +480,45 @@ public class HideAndSeekMode : AbstractGameMode
return PlayerBodyTypes.Normal;
}
}
public override void UpdateTaskPanel(TaskPanelBehaviour instance)
{
instance.background.transform.localScale = (instance.taskText.textBounds.size.x > 0f)
? new Vector3(instance.taskText.textBounds.size.x + 0.2f, instance.taskText.textBounds.size.y + 0.2f, 1f)
: Vector3.zero;
Vector3 vector = instance.background.sprite.bounds.extents;
vector.y = -vector.y;
vector = vector.Mul(instance.background.transform.localScale);
instance.background.transform.localPosition = vector;
Vector3 vector2 = instance.tab.sprite.bounds.extents;
vector2 = vector2.Mul(instance.tab.transform.localScale);
vector2.y = -vector2.y;
vector2.x += vector.x * 2f;
instance.tab.transform.localPosition = vector2;
if (GameManager.Instance == null)
{
return;
}
var yPos = 1.6f;
var xPos = -instance.background.sprite.bounds.size.x * instance.background.transform.localScale.x;
instance.closedPosition = new Vector3(xPos, yPos, instance.closedPosition.z);
instance.openPosition = new Vector3(instance.openPosition.x, yPos, instance.openPosition.z);
if (instance.open)
{
instance.timer = Mathf.Min(1f, instance.timer + Time.deltaTime / instance.animationTimeSeconds);
}
else
{
instance.timer = Mathf.Max(0f, instance.timer - Time.deltaTime / instance.animationTimeSeconds);
}
Vector3 relativePos = new(
Mathf.SmoothStep(instance.closedPosition.x, instance.openPosition.x, instance.timer),
yPos,
instance.openPosition.z);
instance.transform.localPosition =
AspectPosition.ComputePosition(AspectPosition.EdgeAlignments.LeftTop, relativePos);
}
private int deadPlayerCount;
public override void OnPlayerDeath(PlayerControl player, bool assignGhostRole)
{
@@ -0,0 +1,227 @@
using System.Collections;
using System.Linq;
using BepInEx.Unity.IL2CPP.Utils.Collections;
using MiraAPI.GameOptions;
using MiraAPI.HnsReimplemented.Options;
using MiraAPI.Utilities;
using Reactor.Utilities.Attributes;
using UnityEngine;
namespace MiraAPI.HnsReimplemented;
[RegisterInIl2Cpp]
public sealed class HideAndSeekHudHelper(nint cppPtr) : MonoBehaviour(cppPtr)
{
public static HideAndSeekHudHelper Instance { get; private set; }
public AudioClip FinalHideAlertSfx;
public AudioClip FinalHideCountdownSfx;
private AudioClip TaskFinishedSound;
private const int SECONDS_TO_BEEP = 10;
private const float SECONDS_TO_SET_DIRTY = 1f;
private HideAndSeekTimerBar timerBar;
private float totalHideTime = float.MaxValue;
private float currentHideTime = float.MaxValue;
private float totalFinalHideTime = float.MaxValue;
private float currentFinalHideTime = float.MaxValue;
private float secondsSinceLastSetDirty;
private Coroutine beepCoroutine;
private void Awake()
{
Instance = this;
}
public bool IsFinalCountdown
{
get { return currentHideTime <= 0f; }
}
public bool SeekerAdminMapEnabled(PlayerControl player)
{
int item = Helpers.GetAlivePlayers().Count(x => !x.Data.Role.IsImpostor);
return !player.inVent && player.Data != null && player.Data.Role != null &&
((!player.inVent && player.Data.Role.IsImpostor && IsFinalCountdown &&
OptionGroupSingleton<HnsFinalHideOptions>.Instance.FinalHideSeekMap.Value) ||
(player.Data.Role.IsImpostor &&
item <=
(GameData.Instance.PlayerCount - 1) / 3));
}
public void OnTaskComplete(float timeDeduction)
{
if (timerBar != null)
{
timerBar.TaskComplete();
}
AdjustEscapeTimer(timeDeduction, true);
}
public float GetTotalRoundTime()
{
float escapeTime = OptionGroupSingleton<HnsCrewmateOptions>.Instance.HidingTime.Value;
float finalCountdownTime = OptionGroupSingleton<HnsFinalHideOptions>.Instance.FinalHideTime.Value;
return escapeTime + finalCountdownTime;
}
public float GetTotalTimeRemaining()
{
return currentHideTime + currentFinalHideTime;
}
public float GetRoundTimeElapsed()
{
return GetTotalRoundTime() - GetTotalTimeRemaining();
}
public void Start()
{
totalHideTime = OptionGroupSingleton<HnsCrewmateOptions>.Instance.HidingTime.Value;
currentHideTime = totalHideTime;
totalFinalHideTime = OptionGroupSingleton<HnsFinalHideOptions>.Instance.FinalHideTime.Value;
currentFinalHideTime = totalFinalHideTime;
if (timerBar != null)
{
Object.Destroy(timerBar);
}
FinalHideAlertSfx = GameManagerCreator.Instance.HideAndSeekManagerPrefab.FinalHideAlertSFX;
FinalHideCountdownSfx = GameManagerCreator.Instance.HideAndSeekManagerPrefab.FinalHideCountdownSFX;
TaskFinishedSound = GameManagerCreator.Instance.HideAndSeekManagerPrefab.TaskFinishedSound;
timerBar = Object.Instantiate<HideAndSeekTimerBar>(
GameManagerCreator.Instance.HideAndSeekManagerPrefab.TimerBarPrefab,
HudManager.Instance.transform.parent);
}
public void OnGameEnd()
{
if (timerBar != null)
{
Object.Destroy(timerBar.gameObject);
}
if (beepCoroutine != null)
{
StopCoroutine(beepCoroutine);
}
beepCoroutine = null!;
}
public void FixedUpdate()
{
secondsSinceLastSetDirty += Time.fixedDeltaTime;
if (IsFinalCountdown)
{
AdjustFinalEscapeTimer(Time.fixedDeltaTime);
return;
}
AdjustEscapeTimer(Time.fixedDeltaTime, false);
}
public void OnDestroy()
{
if (timerBar != null)
{
Object.Destroy(timerBar);
}
}
private void OnFinalCountdownTriggered()
{
foreach (PlayerControl playerControl in PlayerControl.AllPlayerControls)
{
if (!playerControl.Data.Role.IsImpostor && !playerControl.Data.IsDead)
{
playerControl.ClearTasks();
PlayerTask.GetOrCreateTask<ImportantTextTask>(playerControl, 0).Text =
DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.HideActionButton);
}
}
if (!PlayerControl.LocalPlayer.Data.IsDead && Minigame.Instance != null)
{
Minigame instance = Minigame.Instance;
if (instance != null)
{
instance.ForceClose();
}
}
timerBar.StartFinalHide();
SoundManager.Instance.PlaySound(FinalHideAlertSfx, false, 1f, null);
DestroyableSingleton<HudManager>.Instance.SetAlertOverlay(true);
}
private void AdjustEscapeTimer(float timeDeduction, bool forceDirty)
{
float num = currentHideTime;
currentHideTime -= timeDeduction;
currentHideTime = Mathf.Max(currentHideTime, 0f);
if (currentHideTime <= 10f && beepCoroutine == null)
{
beepCoroutine = StartCoroutine(BeepAlmostEverySecond().WrapToIl2Cpp());
}
if (num > 0f && currentHideTime <= 0f)
{
OnFinalCountdownTriggered();
}
timerBar.UpdateTimer(currentHideTime, totalHideTime);
if (forceDirty || secondsSinceLastSetDirty > 1f)
{
secondsSinceLastSetDirty = 0f;
}
}
private IEnumerator BeepAlmostEverySecond()
{
while (!IsFinalCountdown)
{
float num = currentHideTime / 10f;
float pitch = 1.5f - num / 2f;
SoundManager.Instance.PlaySoundImmediate(FinalHideCountdownSfx, false, 1f, pitch, null);
yield return new WaitForSeconds(1f);
}
yield return Effects.Wait(currentFinalHideTime - 10f);
while (currentFinalHideTime > 0f)
{
float num2 = currentFinalHideTime / 10f;
float pitch2 = 1.5f - num2 / 2f;
SoundManager.Instance.PlaySoundImmediate(FinalHideCountdownSfx, false, 1f, pitch2, null);
yield return new WaitForSeconds(1f);
}
yield break;
}
private void AdjustFinalEscapeTimer(float timeDeduction)
{
currentFinalHideTime -= timeDeduction;
currentFinalHideTime = Mathf.Max(currentFinalHideTime, 0f);
timerBar.UpdateTimer(currentFinalHideTime, totalFinalHideTime);
if (secondsSinceLastSetDirty > 1f)
{
secondsSinceLastSetDirty = 0f;
}
}
public bool AllTimersExpired()
{
return currentHideTime <= 0f && currentFinalHideTime <= 0f;
}
}
@@ -28,4 +28,29 @@ internal static class GameManagerPatches
return true;
}
[HarmonyPatch(typeof(NormalGameManager), nameof(NormalGameManager.GetMapOptions))]
[HarmonyPrefix]
public static bool GetMapOptions(ref MapOptions __result)
{
if (CustomGameModeManager.ActiveMode == null)
{
return true;
}
__result = CustomGameModeManager.ActiveMode.GetMapOptions();
return false;
}
}
[HarmonyPatch(typeof(GameStartManager))]
internal static class GameStartManagerPatches
{
[HarmonyPrefix, HarmonyPatch(nameof(GameStartManager.Start))]
public static void StartPatch(GameStartManager __instance)
{
if (MiraApiPlugin.IsDevBuild)
{
__instance.MinPlayers = 1;
}
}
}
@@ -0,0 +1,17 @@
using HarmonyLib;
using MiraAPI.GameModes;
namespace MiraAPI.Patches.GameModes;
[HarmonyPatch(typeof(ProgressTracker), nameof(ProgressTracker.Start))]
internal static class ProgressTrackerPatch
{
public static bool Prefix(ProgressTracker __instance)
{
if (CustomGameModeManager.ActiveMode != null && !CustomGameModeManager.ActiveMode.ShowTaskBar)
{
__instance.gameObject.SetActive(false);
return false;
}
return true;
}
}
@@ -0,0 +1,17 @@
using HarmonyLib;
using MiraAPI.GameModes;
namespace MiraAPI.Patches.GameModes;
[HarmonyPatch(typeof(TaskPanelBehaviour), nameof(TaskPanelBehaviour.Update))]
internal static class TaskPanelPatch
{
public static bool Prefix(TaskPanelBehaviour __instance)
{
if (CustomGameModeManager.ActiveMode != null)
{
CustomGameModeManager.ActiveMode.UpdateTaskPanel(__instance);
return false;
}
return true;
}
}