Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 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
af36300
Fix bugs and add HuD
Apr 27, 2026
3066720
Rename Default -> Classic
Apr 27, 2026
08c6883
Remove more StringNames duplication
Nix-main Apr 27, 2026
990bac4
Merge branch 'dev' into game-modes
AtonyGit Apr 30, 2026
bf7c800
Add basic Hide n Seek gamemode
AtonyGit Apr 30, 2026
b0ceb12
Re-add Hns options as modded options
AtonyGit Apr 30, 2026
b627006
Move gamemode option to the top, fix registration.
Nix-main May 1, 2026
4f4b880
Reimplement Hide n Seek intro (no music)
AtonyGit May 2, 2026
50cb8ff
Add body type patch for Hide n Seek
AtonyGit May 2, 2026
e095e2d
Merge branch 'dev' into game-modes
AtonyGit May 4, 2026
f7de7f1
Merge branch 'dev' into game-modes
AtonyGit May 13, 2026
ac87003
Fix color not applying on game mode name
AtonyGit May 13, 2026
b33a66c
Fix OnPlayerDeath for HnS
AtonyGit May 13, 2026
ae4b4d8
Add crewmate kill tracker
AtonyGit May 13, 2026
0852f76
Fix game mode option appearing in mod tabs
AtonyGit May 13, 2026
d313089
Register MiraAPI within itself
AtonyGit May 13, 2026
59b8e92
WIP option toggling
AtonyGit May 13, 2026
ad113a4
Hide and add gamemode options
AtonyGit May 13, 2026
8e5028c
Fix scroller issues
AtonyGit May 13, 2026
35b4448
Fix showing/hiding groups
AtonyGit May 13, 2026
390e4c1
Fix toggle title
AtonyGit May 13, 2026
154b50f
Mods can now choose to appear in the settings menu
AtonyGit May 31, 2026
2eac3d0
Add more configuration for hns tasks
AtonyGit May 31, 2026
1d316bb
Merge branch 'dev' into game-modes
AtonyGit May 31, 2026
e9730f0
Check valid gamemode roles to spawn
AtonyGit May 31, 2026
af2cc9a
Renames DefaultMode to ClassicMode among other util changes.
Nix-main May 31, 2026
25a55b1
ColoredName property rather than a method
Nix-main May 31, 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;
}
5 changes: 4 additions & 1 deletion MiraAPI/Events/MiraCancelableEvent.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
namespace MiraAPI.Events;
using MiraAPI.PluginLoading;

namespace MiraAPI.Events;

/// <summary>
/// Abstract class for Mira Events that can be cancelled.
/// </summary>
[MiraIgnore]
public abstract class MiraCancelableEvent : MiraEvent
{
/// <summary>
Expand Down
5 changes: 4 additions & 1 deletion MiraAPI/Events/MiraEvent.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
namespace MiraAPI.Events;
using MiraAPI.PluginLoading;

namespace MiraAPI.Events;

/// <summary>
/// Abstract class for Mira Events.
/// </summary>
[MiraIgnore]
public abstract class MiraEvent;
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
using System.Collections.Generic;
using System.Collections;
using System.Collections.Generic;
using MiraAPI.GameOptions;
using MiraAPI.PluginLoading;
using Reactor.Utilities.Extensions;
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 +24,57 @@ public abstract class CustomGameMode
public abstract string Description { get; }

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

/// <summary>
/// Gets the colored game mode name, using the color and name properties.
/// </summary>
public string ColoredName => $"<color=#{Color.ToHtmlStringRGBA()}>{Name}</color>";

/// <summary>
/// Gets the game mode id.
/// </summary>
public uint ID { get; internal set; }

/// <summary>
/// Gets a value indicating whether a specific body type is used by the game mode.
/// </summary>
public virtual bool GameModeBodyTypeOverride => false;

/// <summary>
/// Gets a value indicating whether the gamemode is based on Hide n Seek.
/// </summary>
public virtual bool IsHideAndSeek => false;

/// <summary>
/// Gets a value indicating whether the gamemode removes the normal game settings.
/// </summary>
public virtual bool ShowNormalGameSettings => true;

/// <summary>
/// Called on GameManager.GetBodyType().
/// </summary>
/// <param name="player">Player to get body type from.</param>
/// <returns>Resulting body type.</returns>
public virtual PlayerBodyTypes GetBodyType(PlayerControl player)
{
if (AprilFoolsMode.ShouldHorseAround())
{
return PlayerBodyTypes.Horse;
}
if (AprilFoolsMode.ShouldLongAround())
{
return PlayerBodyTypes.Long;
}
return PlayerBodyTypes.Normal;
}

/// <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 @@ -49,8 +103,10 @@ public virtual void HudUpdate(HudManager instance)
/// Called when a player is killed.
/// </summary>
/// <param name="player">PlayerControl that was killed.</param>
public virtual void OnDeath(PlayerControl player)
/// <param name="assignGhostRole">Determines whether to assign a ghost role on the player.</param>
public virtual void OnPlayerDeath(PlayerControl player, bool assignGhostRole)
{
GameManager.Instance.LogicRoleSelection.OnPlayerDeath(player, assignGhostRole);
}

/// <summary>
Expand Down Expand Up @@ -85,29 +141,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 +199,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;
}
16 changes: 16 additions & 0 deletions MiraAPI/GameModes/ClassicMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using MiraAPI.PluginLoading;

namespace MiraAPI.GameModes;

/// <summary>
/// The classic game mode.
/// </summary>
[MiraIgnore]
public class ClassicMode : AbstractGameMode
{
/// <inheritdoc/>
public override string Name => "Classic";

/// <inheritdoc/>
public override string Description => "The classic Among Us experience";
}
115 changes: 81 additions & 34 deletions MiraAPI/GameModes/CustomGameModeManager.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MiraAPI.PluginLoading;
using Reactor.Utilities;

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

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

/// <summary>
/// Gets the current gamemode.
/// </summary>
public static CustomGameMode? ActiveMode { get; internal set; } = new DefaultMode();
private static uint LastId { get; 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))
{
ActiveMode = gameMode;
Warning($"{gameModeType.Name} does not inherit CustomGameMode!");
return;
}

Error($"No gamemode with id {id} found!");
var instance = Activator.CreateInstance(gameModeType);

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

IdToModeMap.Add(GetNextId(), mode);
pluginInfo.GameModes.Add(LastId, mode);
GameModeOption.AddOption(mode);
mode.ID = LastId;
}

/// <summary>
/// Register gamemode from type.
/// Checks to see if the classic game mode is on.
/// </summary>
/// <returns>True if the classic mode is the current one.</returns>
public static bool IsClassic() => IsActiveGameMode<ClassicMode>();

/// <summary>
/// Checks to see if the HNS game mode is on.
/// </summary>
/// <returns>True if the Hide & Seek mode is the current one.</returns>

Check warning on line 55 in MiraAPI/GameModes/CustomGameModeManager.cs

View workflow job for this annotation

GitHub Actions / build

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 55 in MiraAPI/GameModes/CustomGameModeManager.cs

View workflow job for this annotation

GitHub Actions / build

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'
public static bool IsHideNSeek() => IsActiveGameMode<HideAndSeekMode>();

/// <summary>
/// Checks if a provided GameMode is the current active one.
/// </summary>
/// <typeparam name="T">The AbstractGameMode subclass being checked.</typeparam>
/// <returns>Whether the provided mode is the current active one.</returns>
public static bool IsActiveGameMode<T>() where T : AbstractGameMode => ActiveMode is T;

/// <summary>
/// Gets the current gamemode.
/// </summary>
/// <param name="gameModeType">Type of gamemode class, should inherit from <see cref="CustomGameMode"/>.</param>
internal static void RegisterGameMode(Type gameModeType)
public static AbstractGameMode? ActiveMode { get; internal set; }

/// <summary>
/// Gets a gamemode from an ID.
/// </summary>
/// <param name="id">The ID of the gamemode to fetch.</param>
/// <returns>The gamemode matching that ID.</returns>
public static AbstractGameMode GetMode(uint id) => IdToModeMap[id];

internal static void RegisterDefaultMode()
{
if (!typeof(CustomGameMode).IsAssignableFrom(gameModeType))
var defaultMode = new ClassicMode();
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;
var hnsMode = new HideAndSeekMode();
IdToModeMap.Add(1, hnsMode);
hnsMode.ID = 1;
GameModeOption.AddOption(hnsMode);
LastId++;
}

internal static void SetGameMode(uint id)
{
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}!");
}
}
8 changes: 0 additions & 8 deletions MiraAPI/GameModes/DefaultMode.cs

This file was deleted.

Loading
Loading