Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
dbacd04
game mode stuff, missing implementation of 4 methods coming soon:tm:
AlchlcDvl Dec 7, 2024
f9f7898
set option title
AlchlcDvl Dec 7, 2024
b6ba8f5
better approach
AlchlcDvl Dec 7, 2024
edca422
finish full implementation of game modes i hope
AlchlcDvl Dec 24, 2024
409503a
read me change to reflect now mode filtering
AlchlcDvl Dec 24, 2024
a4b1868
Merge branch 'optimization' into game-modes
XtraCube Feb 4, 2025
9c15ca4
update to optimization
XtraCube Feb 4, 2025
fa8ea9d
final fix to logic
XtraCube Feb 4, 2025
1b261d7
Merge remote-tracking branch 'origin/dev' into game-modes
XtraCube Feb 17, 2025
47dfa63
improve CustomRoleConfiguration.cs
XtraCube Feb 17, 2025
284a0f5
oops
XtraCube Feb 17, 2025
9177d5b
fix merge stuff
XtraCube Feb 17, 2025
ead2833
Merge branch 'dev' into game-modes
XtraCube Feb 18, 2025
c85cf7e
finish merge
XtraCube Feb 18, 2025
a62c75a
that was stupid
XtraCube Feb 18, 2025
2d59b29
cleanup
XtraCube Feb 18, 2025
7801f81
Merge branch 'dev' into game-modes
XtraCube Feb 18, 2025
65cd177
fix more scrolling issues
XtraCube Feb 18, 2025
8bb1912
Merge branch 'dev' into game-modes
XtraCube Mar 5, 2025
7890ec9
WIP update gamemodes
XtraCube Mar 5, 2025
02f9c20
Add wip string option
AtonyGit Mar 7, 2026
51c7e24
Update Directory.Build.props
AtonyGit Mar 7, 2026
8774c9d
Fix tony's -1 index
Nix-main Mar 8, 2026
205eee9
Merge branch 'dev' into game-modes
AtonyGit Mar 8, 2026
fdfb876
Fix plugin loading
AtonyGit Mar 8, 2026
c29e882
Fix error from float data in ModdedStringOption.cs
Nix-main Mar 8, 2026
8b19271
Merge branch 'runtime-options' into game-modes
Nix-main Mar 8, 2026
cff46eb
Merge branch 'dev' into runtime-options
AtonyGit Mar 10, 2026
7125374
Merge branch 'runtime-options' into game-modes
AtonyGit Mar 10, 2026
4441501
Full gamemode option in vanilla `Game Options` menu.
Nix-main Mar 10, 2026
8f3a137
Merge remote-tracking branch 'origin/game-modes' into game-modes
Nix-main Mar 10, 2026
f59e0af
make patch methods private
Nix-main Mar 10, 2026
bb5c212
Summary tag for the only public part of the option
Nix-main Mar 10, 2026
ba2deb6
Remove from HNS, fix spacing.
Nix-main Mar 11, 2026
00eaa26
Make Gamemode text bigger
Nix-main Mar 12, 2026
77c13be
fix goofy 'works on my machine'
Nix-main Mar 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions MiraAPI.Example/GameModes/ExampleMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using MiraAPI.GameModes;
using UnityEngine;

namespace MiraAPI.Example.GameModes;

public class ExampleMode : AbstractGameMode
{
public override string Name => "Example Mode";
public override string Description => "An example gamemode.";
public override Color Color => Color.red;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
using System.Collections.Generic;
using System.Collections;
using System.Collections.Generic;
using MiraAPI.GameOptions;
using MiraAPI.PluginLoading;
using UnityEngine;

namespace MiraAPI.GameModes;

/// <summary>
/// Base class for custom gamemodes.
/// </summary>
public abstract class CustomGameMode
[MiraIgnore]
public abstract class AbstractGameMode : IOptionable
{
/// <summary>
/// Gets the game mode name.
Expand All @@ -18,9 +23,19 @@ public abstract class CustomGameMode
public abstract string Description { get; }

/// <summary>
/// Gets the game mode ID.
/// Gets the game mode description.
/// </summary>
public virtual Color Color { get; } = Color.white;

/// <summary>
/// Gets or sets the game mode id.
/// </summary>
public abstract int Id { get; }
public uint ID { get; set; }

/// <summary>
/// Gets a value indicating whether a custom intro sequence is implemented by the game mode.
/// </summary>
public virtual bool ShowGameModeIntroCutscene => false;

/// <summary>
/// Called when Intro Cutscene is destroyed.
Expand Down Expand Up @@ -85,29 +100,21 @@ public virtual void CanKill(out bool runOriginal, out bool result, PlayerControl
runOriginal = true;
}

/// <summary>
/// Should Roles Settings be available when this gamemode is selected.
/// </summary>
/// <returns>True if Role Settings are enabled in this game mode.</returns>
public virtual bool AreRoleSettingsEnabled() => true;

/// <summary>
/// Should Game Settings be available when this gamemode is selected.
/// </summary>
/// <returns>True if Game Settings are enabled in this mode.</returns>
public virtual bool AreGameSettingsEnabled() => true;

/// <summary>
/// Custom winner selection.
/// </summary>
/// <returns>List of winners or null.</returns>
public virtual List<NetworkedPlayerInfo>? CalculateWinners() => null;

/// <summary>
/// Show gamemode in Intro Cutscene.
/// The IEnumerator that plays the intro cutscene for this gamemode.
/// </summary>
/// <returns>True if the game mode should be shown in the intro cutscene.</returns>
public virtual bool ShowGameModeIntroCutscene() => false;
/// <param name="__instance">An instance of IntroCutscene.</param>
/// <returns>An IEnumerator to run the intro cutscene instead of the base game one.</returns>
public virtual IEnumerator IntroCutscene(IntroCutscene __instance)
{
yield return new WaitForEndOfFrame();
}

/// <summary>
/// Can Admin be used in this gamemode.
Expand Down Expand Up @@ -151,4 +158,7 @@ public virtual void CanKill(out bool runOriginal, out bool result, PlayerControl
/// <param name="playerInfo">Player attempting to vent.</param>
/// <returns>True if venting is enabled in this mode.</returns>
public virtual bool CanVent(Vent vent, NetworkedPlayerInfo playerInfo) => true;

/// <inheritdoc/>
public override string ToString() => Name;
}
91 changes: 59 additions & 32 deletions MiraAPI/GameModes/CustomGameModeManager.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MiraAPI.PluginLoading;
using Reactor.Utilities;
using Reactor.Utilities.Extensions;

namespace MiraAPI.GameModes;

Expand All @@ -10,62 +11,88 @@ namespace MiraAPI.GameModes;
/// </summary>
public static class CustomGameModeManager
{
/// <summary>
/// List of registered gamemodes.
/// </summary>
internal static readonly Dictionary<int, CustomGameMode> GameModes = [];
public static readonly Dictionary<uint, AbstractGameMode> IdToModeMap = [];

public static bool IsDefault()
private static uint GetNextId()
{
return ActiveMode?.Id == 0;
LastId++;
return LastId;
}

/// <summary>
/// Gets the current gamemode.
/// </summary>
public static CustomGameMode? ActiveMode { get; internal set; } = new DefaultMode();
internal static uint LastId { get; private set; }

/// <summary>
/// Set current gamemode.
/// Register gamemode from type.
/// </summary>
/// <param name="id">gamemode ID.</param>
public static void SetGameMode(int id)
/// <param name="gameModeType">Type of gamemode class, should inherit from <see cref="AbstractGameMode"/>.</param>
/// <param name="pluginInfo">The custom plugin info of the mod.</param>
internal static void RegisterGameMode(Type gameModeType, MiraPluginInfo pluginInfo)
{
if (GameModes.TryGetValue(id, out var gameMode))
if (!typeof(AbstractGameMode).IsAssignableFrom(gameModeType))
{
Warning($"{gameModeType.Name} does not inherit CustomGameMode!");
return;
}

var instance = Activator.CreateInstance(gameModeType);

if (instance is not AbstractGameMode mode)
{
ActiveMode = gameMode;
Error($"Failed to create instance of {gameModeType.Name}");
return;
}

Error($"No gamemode with id {id} found!");
IdToModeMap.Add(GetNextId(), mode);
pluginInfo.GameModes.Add(LastId, mode);
GameModeOption.AddOption($"<color=#{mode.Color.ToHtmlStringRGBA()}>{mode.Name}</color>");
mode.ID = LastId;
}

/// <summary>
/// Register gamemode from type.
/// Checks to see if the default game mode is on.
/// </summary>
/// <param name="gameModeType">Type of gamemode class, should inherit from <see cref="CustomGameMode"/>.</param>
internal static void RegisterGameMode(Type gameModeType)
/// <returns>True if the default mode is one.</returns>
public static bool IsDefault() => (uint) GameModeOption.Value == 0;

/// <summary>
/// Gets the current gamemode.
/// </summary>
public static AbstractGameMode? ActiveMode { get; internal set; }

internal static void RegisterDefaultMode()
{
var defaultMode = new DefaultMode();
IdToModeMap.Add(0, defaultMode);
// no need to add to game mode option as it already contains it
// because we cannot have the option be created with no values
defaultMode.ID = 0;
}

internal static void SetGameMode(uint id)
{
if (!typeof(CustomGameMode).IsAssignableFrom(gameModeType))
if (IdToModeMap.TryGetValue(id, out var mode))
{
Warning($"{gameModeType.Name} does not inherit CustomGameMode!");
return;
ActiveMode = mode;
}

var modeObj = Activator.CreateInstance(gameModeType);

if (modeObj is not CustomGameMode gameMode)
else if (id != 0)
{
Error($"Failed to create instance of {gameModeType.Name}");
return;
ActiveMode = IdToModeMap[0];
GameModeOption.Value = 0;
Logger<MiraApiPlugin>.Warning($"Unable to find game mode of id {id}!");
}
}

internal static void GetAndSetGameMode()
{
var id = (uint)GameModeOption.Value;

if (GameModes.Any(x => x.Key == gameMode.Id))
if (IdToModeMap.TryGetValue(id, out var mode))
{
Error($"ID for gamemode {gameMode.Name} already exists!");
ActiveMode = mode;
return;
}

GameModes.Add(gameMode.Id, gameMode);
ActiveMode = IdToModeMap[0];
Logger<MiraApiPlugin>.Warning($"Unable to find game mode of id {id}!");
}
}
14 changes: 11 additions & 3 deletions MiraAPI/GameModes/DefaultMode.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
namespace MiraAPI.GameModes;
using MiraAPI.PluginLoading;

public class DefaultMode : CustomGameMode
namespace MiraAPI.GameModes;

/// <summary>
/// The default game mode.
/// </summary>
[MiraIgnore]
public class DefaultMode : AbstractGameMode
{
/// <inheritdoc/>
public override string Name => "Default";

/// <inheritdoc/>
public override string Description => "Default Among Us GameMode";
public override int Id => 0;
}
100 changes: 100 additions & 0 deletions MiraAPI/GameModes/GameModeOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System.Collections.Generic;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using Il2CppSystem;
using Reactor.Localization.Utilities;
using UnityEngine;
using UnityEngine.ProBuilder;
using Object = UnityEngine.Object;

namespace MiraAPI.GameModes;

/// <summary>
/// The game mode option.
/// </summary>
[HarmonyPatch]
public static class GameModeOption
{
/// <summary>
/// Gets or Sets the current index of the Game Mode Option
/// For the value as an AbstractGameMode, see CustomGameModeManager.ActiveMode
/// </summary>
public static int Value
{
get =>
OptionBehaviour != null
? OptionBehaviour.GetInt()
: 0;
set
{
if (OptionBehaviour == null)
return;
OptionBehaviour.Value = value;
OptionBehaviour.UpdateValue();
}
}

internal static StringOption OptionBehaviour { get; private set; } = null!;

private static readonly List<string> Queue = [];
// loading takes place before option creation
internal static void AddOption(string opt)
{
if (OptionBehaviour == null)
{
Queue.Add(opt);
return;
}
OptionBehaviour.Values = (Il2CppStructArray<StringNames>)OptionBehaviour.Values.Add(CustomStringName.CreateAndRegister(opt));
}
[HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.CreateSettings))]
[HarmonyPostfix]
private static void CreateSettingsPatch(GameOptionsMenu __instance)
{
if (GameManager.Instance.IsHideAndSeek())
return;
float num = 0.713f;
foreach (RulesCategory rulesCategory in GameManager.Instance.GameSettingsList.AllCategories)
{
num -= 0.63f;
foreach (BaseGameSetting a in rulesCategory.AllGameSettings)
num -= 0.45f;
}
CategoryHeaderMasked categoryHeaderMasked = Object.Instantiate(__instance.categoryHeaderOrigin, Vector3.zero, Quaternion.identity, __instance.settingsContainer);
categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister("Custom"), 20);
categoryHeaderMasked.transform.localScale = Vector3.one * 0.63f;
categoryHeaderMasked.transform.localPosition = new Vector3(-0.903f, num, -2f);
OptionBehaviour = Object.Instantiate(
__instance.stringOptionOrigin,
Vector3.zero,
Quaternion.identity,
__instance.settingsContainer);
num -= 0.63f;
OptionBehaviour.transform.localPosition = new Vector3(0.952f, num, -2f);
OptionBehaviour.SetClickMask(__instance.ButtonClickMask);
StringGameSetting setting = ScriptableObject.CreateInstance<StringGameSetting>();
setting.Type = OptionTypes.MultipleChoice;
setting.Title = CustomStringName.CreateAndRegister("Gamemode");
setting.Index = 0;
setting.Values = new Il2CppStructArray<StringNames>([CustomStringName.CreateAndRegister("Default")]);
OptionBehaviour.SetUpFromData(setting, 20);
OptionBehaviour.TitleText.fontSize = 3;
OptionBehaviour.OnValueChanged = (Action<OptionBehaviour>) ((OptionBehaviour opt) =>
{
CustomGameModeManager.SetGameMode((uint)opt.GetInt());
});
num -= 0.37f; // scrollbar offset
__instance.Children.Add(OptionBehaviour);
__instance.scrollBar.SetYBoundsMax(-num - 1.65f);
foreach (var str in Queue)
AddOption(str);
Queue.Clear();
}

[HarmonyPatch(typeof(GameOptionsMenu), nameof(GameOptionsMenu.ValueChanged))]
[HarmonyPrefix]
private static bool ValueChanged(GameOptionsMenu __instance, OptionBehaviour option)
{
return !OptionBehaviour.Equals(option);
}
}
8 changes: 7 additions & 1 deletion MiraAPI/GameOptions/Attributes/ModdedOptionAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ namespace MiraAPI.GameOptions.Attributes;
/// </summary>
/// <param name="title">The option title.</param>
/// <param name="roleType">Optional parameter to specify a role Type.</param>
/// <param name="modeType">Optional parameter to specify a game mode Type.</param>
[AttributeUsage(AttributeTargets.Property)]
public abstract class ModdedOptionAttribute(string title, Type? roleType = null) : Attribute
public abstract class ModdedOptionAttribute(string title, Type? roleType = null, Type? modeType = null) : Attribute
{
internal IModdedOption? HolderOption { get; set; }

Expand All @@ -23,6 +24,11 @@ public abstract class ModdedOptionAttribute(string title, Type? roleType = null)
/// </summary>
protected Type? RoleType { get; private set; } = roleType;

/// <summary>
/// Gets the game mode type of the option.
/// </summary>
protected Type? ModeType { get; private set; } = modeType;

/// <summary>
/// Sets the value of the option.
/// </summary>
Expand Down
Loading
Loading