diff --git a/MiraAPI/Events/Vanilla/Gameplay/BeforeGameEndEvent.cs b/MiraAPI/Events/Vanilla/Gameplay/BeforeGameEndEvent.cs
new file mode 100644
index 0000000..0a70a1d
--- /dev/null
+++ b/MiraAPI/Events/Vanilla/Gameplay/BeforeGameEndEvent.cs
@@ -0,0 +1,21 @@
+namespace MiraAPI.Events.Vanilla.Gameplay;
+
+///
+/// Invoked before RpcEndGame is called, allowing cancellation of the game end entirely.
+///
+public class BeforeGameEndEvent : MiraCancelableEvent
+{
+ ///
+ /// Gets the reason for the game end.
+ ///
+ public GameOverReason Reason { get; }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The reason for the game end.
+ public BeforeGameEndEvent(GameOverReason reason)
+ {
+ Reason = reason;
+ }
+}
diff --git a/MiraAPI/GameOptions/ModdedOptionsManager.cs b/MiraAPI/GameOptions/ModdedOptionsManager.cs
index 94c1659..0adc461 100644
--- a/MiraAPI/GameOptions/ModdedOptionsManager.cs
+++ b/MiraAPI/GameOptions/ModdedOptionsManager.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using BepInEx.Configuration;
@@ -175,7 +176,10 @@ public static class ModdedOptionsManager
///
/// The original setter method.
/// The new object value.
+#pragma warning disable CA1707
+ [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony naming convention")]
public static void PropertySetterPatch(MethodBase __originalMethod, object value)
+#pragma warning restore CA1707
{
var attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod() == __originalMethod).Value;
attribute.SetValue(value);
@@ -187,7 +191,10 @@ public static class ModdedOptionsManager
/// The original getter method.
/// The result of the property getter.
/// False so the original getter gets skipped.
+#pragma warning disable CA1707
+ [SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony naming convention")]
public static bool PropertyGetterPatch(MethodBase __originalMethod, ref object __result)
+#pragma warning restore CA1707
{
var attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod() == __originalMethod).Value;
__result = attribute.GetValue();
diff --git a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs
index 919db15..3c98d24 100644
--- a/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs
+++ b/MiraAPI/GameOptions/OptionTypes/ModdedOption.cs
@@ -113,7 +113,7 @@ public abstract class ModdedOption : IModdedOption
entry.Value = Value;
}
- Rpc.Instance.Send(PlayerControl.LocalPlayer, [GetNetData()]);
+ Rpc.Instance.Send(PlayerControl.LocalPlayer, [GetNetData()], true);
}
OnValueChanged(newValue);
diff --git a/MiraAPI/Hud/CustomActionButton.cs b/MiraAPI/Hud/CustomActionButton.cs
index 7e0c0e2..42edf39 100644
--- a/MiraAPI/Hud/CustomActionButton.cs
+++ b/MiraAPI/Hud/CustomActionButton.cs
@@ -3,6 +3,7 @@ using System.Globalization;
using MiraAPI.Events;
using MiraAPI.Events.Mira;
using MiraAPI.Patches;
+using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
using UnityEngine.Events;
@@ -36,6 +37,11 @@ public abstract class CustomActionButton
///
public abstract LoadableAsset Sprite { get; }
+ ///
+ /// Gets the format string for the cooldown timer.
+ ///
+ public virtual string CooldownTimerFormatString => "0";
+
///
/// Gets the button's effect duration in seconds. If the button has no effect, set to 0.
///
@@ -446,12 +452,12 @@ public abstract class CustomActionButton
{
Button.SetFillUp(Timer, EffectDuration);
- Button.cooldownTimerText.text = Mathf.CeilToInt(Timer).ToString(NumberFormatInfo.InvariantInfo);
+ Button.cooldownTimerText.text = Timer.ToString(CooldownTimerFormatString, NumberFormatInfo.InvariantInfo);
Button.cooldownTimerText.gameObject.SetActive(true);
}
else
{
- Button.SetCoolDown(Timer, Cooldown);
+ Button.SetCooldownFormat(Timer, Cooldown, CooldownTimerFormatString);
}
}
diff --git a/MiraAPI/Patches/Events/EndGamePatches.cs b/MiraAPI/Patches/Events/EndGamePatches.cs
index 2c04d6b..558c468 100644
--- a/MiraAPI/Patches/Events/EndGamePatches.cs
+++ b/MiraAPI/Patches/Events/EndGamePatches.cs
@@ -5,29 +5,44 @@ using MiraAPI.GameEnd;
namespace MiraAPI.Patches.Events;
-[HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))]
internal static class EndGamePatches
{
- public static bool Prefix(EndGameManager __instance)
+ [HarmonyPatch(typeof(GameManager), nameof(GameManager.RpcEndGame))]
+ public static class GameManagerPatches
{
- if (CustomGameOver.Instance is not { } gameOver)
+ public static bool Prefix(GameOverReason endReason)
{
- return true;
- }
+ var @event = new BeforeGameEndEvent(endReason);
+ MiraEventManager.InvokeEvent(@event);
- var result = gameOver.BeforeEndGameSetup(__instance);
- return result;
+ return !@event.IsCancelled;
+ }
}
- public static void Postfix(EndGameManager __instance)
+ [HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))]
+ public static class EndGameManagerPatches
{
- if (CustomGameOver.Instance is { } gameOver)
+ public static bool Prefix(EndGameManager __instance)
{
- gameOver.AfterEndGameSetup(__instance);
- CustomGameOver.Instance = null;
+ if (CustomGameOver.Instance is not { } gameOver)
+ {
+ return true;
+ }
+
+ var result = gameOver.BeforeEndGameSetup(__instance);
+ return result;
}
- var @event = new GameEndEvent(__instance);
- MiraEventManager.InvokeEvent(@event);
+ public static void Postfix(EndGameManager __instance)
+ {
+ if (CustomGameOver.Instance is { } gameOver)
+ {
+ gameOver.AfterEndGameSetup(__instance);
+ CustomGameOver.Instance = null;
+ }
+
+ var @event = new GameEndEvent(__instance);
+ MiraEventManager.InvokeEvent(@event);
+ }
}
}
diff --git a/MiraAPI/Patches/Options/OptionsPatches.cs b/MiraAPI/Patches/Options/OptionsPatches.cs
index 8560a77..c61613c 100644
--- a/MiraAPI/Patches/Options/OptionsPatches.cs
+++ b/MiraAPI/Patches/Options/OptionsPatches.cs
@@ -37,6 +37,22 @@ public static class OptionsPatches
return false;
}
+ [HarmonyPrefix]
+ [HarmonyPatch(typeof(StringOption), nameof(StringOption.UpdateValue))]
+ [HarmonyPatch(typeof(NumberOption), nameof(NumberOption.UpdateValue))]
+ public static bool UpdateValuePrefix(OptionBehaviour __instance)
+ {
+ return !__instance.IsCustom();
+ }
+
+ [HarmonyPrefix]
+ [HarmonyPatch(typeof(NumberOption), nameof(NumberOption.AdjustButtonsActiveState))]
+ [HarmonyPatch(typeof(StringOption), nameof(StringOption.AdjustButtonsActiveState))]
+ public static bool AdjustButtonsPrefix(OptionBehaviour __instance)
+ {
+ return !__instance.IsCustom();
+ }
+
[HarmonyPrefix]
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.Initialize))]
public static bool NumberInit(NumberOption __instance)
@@ -46,17 +62,84 @@ public static class OptionsPatches
return true;
}
- __instance.TitleText.text =
- TranslationController.Instance.GetString(__instance.Title, Array.Empty