mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-24 06:46:46 +00:00
Merge pull request #29 from All-Of-Us-Mods/custom-game-over
Custom game over
This commit is contained in:
10 files changed
+332
-17
No files matched your search
@@ -0,0 +1,24 @@
|
||||
using MiraAPI.Example.GameOver;
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.GameEnd;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Buttons;
|
||||
|
||||
public class NeutralKillerButton : CustomActionButton
|
||||
{
|
||||
public override string Name => "Win Game";
|
||||
public override float Cooldown => 0f;
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||
protected override void OnClick()
|
||||
{
|
||||
CustomGameOver.Trigger<NeutralKillerGameOver>([PlayerControl.LocalPlayer.Data]);
|
||||
}
|
||||
|
||||
public override bool Enabled(RoleBehaviour? role)
|
||||
{
|
||||
return role is NeutralKillerRole;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using MiraAPI.GameEnd;
|
||||
using MiraAPI.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.GameOver;
|
||||
|
||||
public class NeutralKillerGameOver : CustomGameOver
|
||||
{
|
||||
public override void AfterEndGameSetup(EndGameManager endGameManager)
|
||||
{
|
||||
endGameManager.WinText.text = "Outcast Killer Wins!";
|
||||
endGameManager.WinText.color = Color.magenta;
|
||||
endGameManager.BackgroundBar.material.SetColor(ShaderID.Color, Color.magenta);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Example.GameOver;
|
||||
using MiraAPI.GameEnd;
|
||||
using MiraAPI.Roles;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Roles;
|
||||
@@ -22,7 +24,7 @@ public class NeutralKillerRole : ImpostorRole, ICustomRole
|
||||
|
||||
public TeamIntroConfiguration? IntroConfiguration { get; } = new(
|
||||
Color.gray,
|
||||
"NEUTRAL",
|
||||
"OUTCAST",
|
||||
"You are an Outcast. You do not have a team.");
|
||||
|
||||
public override void SpawnTaskHeader(PlayerControl playerControl)
|
||||
@@ -32,6 +34,6 @@ public class NeutralKillerRole : ImpostorRole, ICustomRole
|
||||
|
||||
public override bool DidWin(GameOverReason gameOverReason)
|
||||
{
|
||||
return GameManager.Instance.DidHumansWin(gameOverReason);
|
||||
return gameOverReason == CustomGameOver.GameOverReason<NeutralKillerGameOver>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
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>
|
||||
/// Runs before the base game calls EndGameManager.SetEverythingUp.
|
||||
/// </summary>
|
||||
/// <param name="endGameManager">The EndGameManager instance.</param>
|
||||
/// <returns>Return True to use the run the original method, return False to skip it.</returns>
|
||||
public virtual bool BeforeEndGameSetup(EndGameManager endGameManager)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs after the base game calls EndGameManager.SetEverythingUp.
|
||||
/// </summary>
|
||||
/// <param name="endGameManager">The EndGameManager instance.</param>
|
||||
public virtual void AfterEndGameSetup(EndGameManager endGameManager)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the GameOverReason associated with this CustomGameOver.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the custom game over.</typeparam>
|
||||
/// <returns>The GameOverReason associated with the custom game over.</returns>
|
||||
public static GameOverReason GameOverReason<T>() where T : CustomGameOver
|
||||
{
|
||||
return (GameOverReason)GameOverManager.GetGameOverId<T>();
|
||||
}
|
||||
|
||||
/// <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 Trigger<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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Reactor.Utilities;
|
||||
|
||||
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>
|
||||
/// <returns>>True if the game over was registered successfully, false otherwise.</returns>
|
||||
public static bool RegisterGameOver(Type gameOverType)
|
||||
{
|
||||
if (!typeof(CustomGameOver).IsAssignableFrom(gameOverType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gameOverType.IsAbstract)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("The type must not be abstract.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gameOverType.GetConstructor(Type.EmptyTypes) == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("The type must have a parameterless constructor.");
|
||||
return false;
|
||||
}
|
||||
|
||||
GameOverIds.Add(gameOverType, _nextId);
|
||||
GameOverTypes.Add(_nextId, gameOverType);
|
||||
_nextId++;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <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!");
|
||||
}
|
||||
}
|
||||
@@ -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.ReadPackedInt32();
|
||||
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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,4 +34,9 @@ public enum MiraRpc : uint
|
||||
/// Custom Murder RPC.
|
||||
/// </summary>
|
||||
CustomMurder,
|
||||
|
||||
/// <summary>
|
||||
/// Custom Game Over RPC.
|
||||
/// </summary>
|
||||
CustomGameOver,
|
||||
}
|
||||
@@ -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.BeforeEndGameSetup(__instance);
|
||||
return result;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
using MiraAPI.Colors;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.GameEnd;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.GameOptions.Attributes;
|
||||
using MiraAPI.Hud;
|
||||
@@ -9,11 +15,6 @@ using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Networking;
|
||||
using Reactor.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MiraAPI.PluginLoading;
|
||||
|
||||
@@ -69,6 +70,11 @@ public sealed class MiraPluginManager
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterGameOver(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RegisterColorClasses(type);
|
||||
}
|
||||
|
||||
@@ -96,6 +102,19 @@ public sealed class MiraPluginManager
|
||||
return Instance._registeredPlugins.Values.FirstOrDefault(plugin => plugin.PluginId == pluginId);
|
||||
}
|
||||
|
||||
private static bool RegisterGameOver(Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
return GameOverManager.RegisterGameOver(type);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to register game over {type.Name}: {e}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool RegisterOptions(Type type, MiraPluginInfo pluginInfo)
|
||||
{
|
||||
try
|
||||
|
||||
Reference in new issue
Block a user