custom game over

This commit is contained in:
XtraCube committed 2025-02-27 19:33:14 -05:00
1 parent 6f4eff7b38
commit 530cb79981
6 files changed
+242 -8

No files matched your search

+64
View File
@@ -0,0 +1,64 @@
using System.Collections.Generic;
using MiraAPI.Networking;
using Reactor.Networking.Rpc;
using Reactor.Utilities;
namespace MiraAPI.GameEnd;
/// <summary>
/// Custom Game Over.
/// </summary>
public abstract class CustomGameOver
{
/// <summary>
/// Gets the current CustomGameOver.
/// </summary>
public static CustomGameOver? Instance { get; internal set; }
/// <summary>
/// Run before setting up the game over.
/// </summary>
/// <param name="endGameManager">The EndGameManager instance.</param>
/// <returns>Return True to use the default implementation, return False to skip it.</returns>
public virtual bool BeforeSetEverythingUp(EndGameManager endGameManager)
{
return true;
}
/// <summary>
/// Run after setting up the game over.
/// </summary>
/// <param name="endGameManager">The EndGameManager instance.</param>
public virtual void AfterSetEverythingUp(EndGameManager endGameManager)
{
}
/// <summary>
/// Send a custom game over.
/// </summary>
/// <param name="winners">A collection of winners.</param>
/// <typeparam name="T">Type of the custom game over.</typeparam>
public static void Send<T>(IEnumerable<NetworkedPlayerInfo> winners) where T : CustomGameOver
{
if (!AmongUsClient.Instance.AmHost)
{
Logger<MiraApiPlugin>.Error("Only the host can send a custom game over.");
return;
}
var reason = GameOverManager.GetGameOverId<T>();
var data = new GameOverData(reason, [.. winners]);
Rpc<CustomGameOverRpc>.Instance.Send(PlayerControl.LocalPlayer, data, true);
}
/// <summary>
/// Implicitly convert a CustomGameOver to a GameOverReason.
/// </summary>
/// <param name="gameOver">The CustomGameOver instance.</param>
/// <returns>The GameOverReason associated with the CustomGameOver.</returns>
public static implicit operator GameOverReason(CustomGameOver gameOver)
{
return (GameOverReason)GameOverManager.GetGameOverId(gameOver.GetType());
}
}
+10
View File
@@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace MiraAPI.GameEnd;
/// <summary>
/// Data for a game over.
/// </summary>
/// <param name="Reason">Reason for the game over.</param>
/// <param name="Winners">A collection of winners.</param>
public record struct GameOverData(int Reason, List<NetworkedPlayerInfo> Winners);
+78
View File
@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
namespace MiraAPI.GameEnd;
/// <summary>
/// Manage custom game overs.
/// </summary>
public static class GameOverManager
{
private static readonly Dictionary<Type, int> GameOverIds = [];
private static readonly Dictionary<int, Type> GameOverTypes = [];
private static int _nextId = Enum.GetNames<GameOverReason>().Length;
/// <summary>
/// Register a custom game over.
/// </summary>
/// <param name="gameOverType">Type of the custom game over.</param>
/// <exception cref="ArgumentException">Thrown when the type is not a subclass of CustomGameOver, is abstract, or does not have a parameterless constructor.</exception>
public static void RegisterGameOver(Type gameOverType)
{
if (!typeof(CustomGameOver).IsAssignableFrom(gameOverType))
throw new ArgumentException("The type must be a subclass of CustomGameOver.");
if (gameOverType.IsAbstract)
throw new ArgumentException("The type must not be abstract.");
if (gameOverType.GetConstructor(Type.EmptyTypes) == null)
throw new ArgumentException("The type must have a parameterless constructor.");
GameOverIds.Add(gameOverType, _nextId);
GameOverTypes.Add(_nextId, gameOverType);
_nextId++;
}
/// <summary>
/// Create an instance of a custom game over.
/// </summary>
/// <param name="id">ID of the custom game over.</param>
/// <param name="customGameOver">The created instance of the custom game over.</param>
/// <returns>An instance of the custom game over.</returns>
public static bool TryGetGameOver(int id, out CustomGameOver? customGameOver)
{
if (GameOverTypes.TryGetValue(id, out var gameOverType))
{
customGameOver = Activator.CreateInstance(gameOverType) as CustomGameOver;
return customGameOver != null;
}
customGameOver = null;
return false;
}
/// <summary>
/// Get the ID of a custom game over.
/// </summary>
/// <typeparam name="T">Type of the custom game over.</typeparam>
/// <returns>The ID of the custom game over.</returns>
public static int GetGameOverId<T>() where T : CustomGameOver
{
return GetGameOverId(typeof(T));
}
/// <summary>
/// Get the ID of a custom game over.
/// </summary>
/// <param name="gameOverType">Type of the custom game over.</param>
/// <returns>The ID of the custom game over.</returns>
/// <exception cref="ArgumentException">Thrown when the type is not registered.</exception>
public static int GetGameOverId(Type gameOverType)
{
if (GameOverIds.TryGetValue(gameOverType, out var id))
return id;
throw new ArgumentException($"{gameOverType.FullName} is not a registered custom game over!");
}
}
+64
View File
@@ -0,0 +1,64 @@
using System.Collections.Generic;
using Hazel;
using InnerNet;
using MiraAPI.GameEnd;
using Reactor.Networking.Attributes;
using Reactor.Networking.Rpc;
using Reactor.Utilities;
namespace MiraAPI.Networking;
/// <summary>
/// Custom Game Over RPC.
/// </summary>
/// <inheritdoc />
[RegisterCustomRpc((uint)MiraRpc.CustomGameOver)]
public class CustomGameOverRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, GameOverData>(plugin, id)
{
/// <inheritdoc />
public override RpcLocalHandling LocalHandling => RpcLocalHandling.After;
/// <inheritdoc />
public override void Write(MessageWriter writer, GameOverData data)
{
writer.WritePacked(data.Reason);
writer.WritePacked(data.Winners.Count);
foreach (var player in data.Winners)
{
writer.WriteNetObject(player);
}
}
/// <inheritdoc />
public override GameOverData Read(MessageReader reader)
{
var reason = reader.ReadPackedInt32();
var count = reader.ReadInt32();
var data = new List<NetworkedPlayerInfo>(count);
for (var i = 0; i < count; i++)
{
data.Add(reader.ReadNetObject<NetworkedPlayerInfo>());
}
return new GameOverData(reason, data);
}
/// <inheritdoc />
public override void Handle(PlayerControl innerNetObject, GameOverData data)
{
if (GameOverManager.TryGetGameOver(data.Reason, out var gameOver))
{
CustomGameOver.Instance = gameOver;
if (AmongUsClient.Instance.AmHost)
{
GameManager.Instance.RpcEndGame(gameOver!, false);
}
}
else
{
CustomGameOver.Instance = null;
Logger<MiraApiPlugin>.Error($"Unknown game over reason: {data.Reason}");
}
}
}
+5
View File
@@ -34,4 +34,9 @@ public enum MiraRpc : uint
/// Custom Murder RPC.
/// </summary>
CustomMurder,
/// <summary>
/// Custom Game Over RPC.
/// </summary>
CustomGameOver,
}
+21 -8
View File
@@ -1,19 +1,32 @@
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
using MiraAPI.GameEnd;
namespace MiraAPI.Patches.Events;
/// <summary>
/// Patch for trigging the GameEndEvent.
/// </summary>
[HarmonyPatch]
public static class EndGamePatches
[HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))]
internal static class EndGamePatches
{
[HarmonyPostfix]
[HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))]
public static void SetEverythingUpPatch(EndGameManager __instance)
public static bool Prefix(EndGameManager __instance)
{
if (CustomGameOver.Instance is not { } gameOver)
{
return true;
}
var result = gameOver.BeforeSetEverythingUp(__instance);
return result;
}
public static void Postfix(EndGameManager __instance)
{
if (CustomGameOver.Instance is { } gameOver)
{
gameOver.AfterSetEverythingUp(__instance);
CustomGameOver.Instance = null;
}
var @event = new GameEndEvent(__instance);
MiraEventManager.InvokeEvent(@event);
}