Add logic for modifiers & roles in hide and seek

This commit is contained in:
Atony committed 2025-11-21 18:23:08 -06:00
1 parent 7e3a279b56
commit 259b9b7097
9 files changed
+402 -4

No files matched your search

+2 -1
View File
@@ -179,6 +179,7 @@ public static class ModifierManager
private static bool IsGameModifierValid(PlayerControl player, GameModifier modifier, uint modifierId)
{
return (player.Data.Role is not ICustomRole role || role.IsModifierApplicable(modifier)) &&
modifier.IsModifierValidOn(player.Data.Role) && !player.HasModifier(modifierId);
modifier.IsModifierValidOn(player.Data.Role) && !player.HasModifier(modifierId) &&
modifier.CanSpawnOnCurrentMode();
}
}
+6
View File
@@ -39,4 +39,10 @@ public abstract class GameModifier : BaseModifier
/// <param name="reason">The reason why the game ended.</param>
/// <returns>True if the player won, false if they lost. Return null to use the player's default win condition.</returns>
public virtual bool? DidWin(GameOverReason reason) => null;
/// <summary>
/// Determines whether the modifier can spawn in general, accounting for gamemodes and everything else.
/// </summary>
/// <returns>True if the modifier is able to spawn, otherwise false.</returns>
public virtual bool CanSpawnOnCurrentMode() => !GameManager.Instance.IsHideAndSeek();
}
@@ -0,0 +1,179 @@
using AmongUs.GameOptions;
using HarmonyLib;
using Il2CppSystem.Collections.Generic;
using System.Linq;
using MiraAPI.Roles;
namespace MiraAPI.Patches.Roles;
[HarmonyPatch(typeof(LogicRoleSelectionHnS))]
public static class LogicRoleSelectionHnsPatch
{
[HarmonyPrefix]
[HarmonyPatch(nameof(LogicRoleSelectionHnS.AssignRolesForTeam))]
public static bool AssignRolesForTeam(
LogicRoleSelectionHnS __instance,
List<NetworkedPlayerInfo> players,
IGameOptions opts,
RoleTeamTypes team,
int teamMax,
Il2CppSystem.Nullable<RoleTypes> defaultRole)
{
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Team: {team}, Max: {teamMax}, Players: {players.Count}, DefaultRole: {defaultRole}");
int num = 0;
IRoleOptionsCollection roleOptions = opts.RoleOptions;
var source = RoleManager.Instance.AllRoles.ToArray()
.Where(role => role.TeamType == team && !RoleManager.IsGhostRole(role.Role) &&
CustomRoleUtils.CanSpawnOnCurrentMode(role));
var assignmentData = source.Where(x => !x.IsDead).Select(role =>
new RoleManager.RoleAssignmentData(
role,
roleOptions.GetNumPerGame(role.Role),
roleOptions.GetChancePerGame(role.Role))).ToList();
var source2 = CustomRoleUtils.GetPossibleRoles(assignmentData, x => x.Chance == 100);
var guaranteedRoles = source.Where(x => source2.Contains(((ushort)x.Role, 100)));
List<RoleTypes> list = new List<RoleTypes>();
if (team == RoleTeamTypes.Crewmate)
{
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: Before Guaranteed Assignment");
foreach (RoleManager.RoleAssignmentData roleAssignmentData in guaranteedRoles.Select((x) =>
new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100)))
{
for (;;)
{
RoleManager.RoleAssignmentData roleAssignmentData2 = roleAssignmentData;
int count = roleAssignmentData2.Count;
roleAssignmentData2.Count = count - 1;
if (count <= 0)
{
break;
}
list.Add(roleAssignmentData.Role.Role);
}
}
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Guaranteed Assignment");
AssignRolesFromList(players, teamMax, list, ref num);
var list2 = source.Where(x => !x.IsDead).Select(role =>
new RoleManager.RoleAssignmentData(
role,
roleOptions.GetNumPerGame(role.Role),
roleOptions.GetChancePerGame(role.Role))).ToList();
list.Clear();
foreach (RoleManager.RoleAssignmentData roleAssignmentData3 in list2)
{
for (int i = 0; i < roleAssignmentData3.Count; i++)
{
if (HashRandom.Next(101) < roleAssignmentData3.Chance)
{
list.Add(roleAssignmentData3.Role.Role);
}
}
}
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Potential Assignment");
AssignRolesFromList(players, teamMax, list, ref num);
var basicRole = RoleTypes.Engineer;
while (list.Count < players.Count && list.Count + num < teamMax)
{
list.Add(basicRole);
}
AssignRolesFromList(players, teamMax, list, ref num);
Error($"MiraAPI.Patches.Roles.LogicRoleSelectionHnsPatch - AssignRolesForTeam: After Fallback Assignment");
}
else if (team == RoleTeamTypes.Impostor)
{
var newImpostors = new List<NetworkedPlayerInfo>();
// Specified Seeker
if (__instance.hnsManager.LogicOptionsHnS.HasImpostorPlayerID() &&
__instance.hnsManager.LogicOptionsHnS.ValidateImpostorPlayerID(players) &&
!AmongUsClient.Instance.IsGamePublic)
{
NetworkedPlayerInfo networkedPlayerInfo = players.ToArray()
.First(p => p.PlayerId == __instance.hnsManager.LogicOptionsHnS.ImpostorPlayerID());
players.Remove(networkedPlayerInfo);
newImpostors.Add(networkedPlayerInfo);
}
// Random Seeker
else
{
int num2 = 0;
while (num2 < teamMax && players.Count > 0)
{
PseudoRandomList<NetworkedPlayerInfo> pseudoRandomList = new PseudoRandomList<NetworkedPlayerInfo>(AmongUsClient.Instance.GameId);
players._items.Do(x => pseudoRandomList.Add(x));
for (int i = 0; i < GameData.RoundsPlayedInSession; i++)
{
pseudoRandomList.PickRandom();
}
NetworkedPlayerInfo networkedPlayerInfo = pseudoRandomList.PickRandom();
players.Remove(networkedPlayerInfo);
newImpostors.Add(networkedPlayerInfo);
num2++;
}
}
foreach (RoleManager.RoleAssignmentData roleAssignmentData in guaranteedRoles.Select((x) =>
new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100)))
{
for (;;)
{
RoleManager.RoleAssignmentData roleAssignmentData2 = roleAssignmentData;
int count = roleAssignmentData2.Count;
roleAssignmentData2.Count = count - 1;
if (count <= 0)
{
break;
}
list.Add(roleAssignmentData.Role.Role);
}
}
AssignRolesFromList(players, teamMax, list, ref num);
var list2 = source.Where(x => !x.IsDead).Select(role =>
new RoleManager.RoleAssignmentData(
role,
roleOptions.GetNumPerGame(role.Role),
roleOptions.GetChancePerGame(role.Role))).ToList();
list.Clear();
foreach (RoleManager.RoleAssignmentData roleAssignmentData3 in list2)
{
for (int i = 0; i < roleAssignmentData3.Count; i++)
{
if (HashRandom.Next(101) < roleAssignmentData3.Chance)
{
list.Add(roleAssignmentData3.Role.Role);
}
}
}
AssignRolesFromList(players, teamMax, list, ref num);
var basicRole = RoleTypes.Impostor;
while (list.Count < players.Count && list.Count + num < teamMax)
{
list.Add(basicRole);
}
AssignRolesFromList(players, teamMax, list, ref num);
}
return false;
}
public static void AssignRolesFromList(List<NetworkedPlayerInfo> players, int teamMax, List<RoleTypes> roleList, ref int rolesAssigned)
{
while (roleList.Count > 0 && players.Count > 0 && rolesAssigned < teamMax)
{
int index = HashRandom.FastNext(roleList.Count);
RoleTypes roleType = roleList[index];
roleList.RemoveAt(index);
int index2 = HashRandom.FastNext(players.Count);
players[index2].Object.RpcSetRole(roleType, false);
players.RemoveAt(index2);
rolesAssigned++;
}
}
}
@@ -0,0 +1,86 @@
using AmongUs.GameOptions;
using HarmonyLib;
using Il2CppSystem.Collections.Generic;
using System.Linq;
using MiraAPI.Roles;
namespace MiraAPI.Patches.Roles;
[HarmonyPatch(typeof(LogicRoleSelectionNormal))]
public static class LogicRoleSelectionNormalPatch
{
[HarmonyPrefix]
[HarmonyPatch(nameof(LogicRoleSelectionNormal.AssignRolesForTeam))]
public static bool AssignRolesForTeam(
LogicRoleSelectionNormal __instance,
List<NetworkedPlayerInfo> players,
IGameOptions opts,
RoleTeamTypes team,
int teamMax,
Il2CppSystem.Nullable<RoleTypes> defaultRole)
{
int num = 0;
var source = RoleManager.Instance.AllRoles.ToArray()
.Where(role => role.TeamType == team && !RoleManager.IsGhostRole(role.Role) &&
CustomRoleUtils.CanSpawnOnCurrentMode(role));
List<RoleTypes> list = new List<RoleTypes>();
IRoleOptionsCollection roleOptions = opts.RoleOptions;
var assignmentData = source.Where(x => !x.IsDead).Select(role =>
new RoleManager.RoleAssignmentData(
role,
roleOptions.GetNumPerGame(role.Role),
roleOptions.GetChancePerGame(role.Role))).ToList();
var source2 = CustomRoleUtils.GetPossibleRoles(assignmentData, x => x.Chance == 100);
var guaranteedRoles = source.Where(x => source2.Contains(((ushort)x.Role, 100)));
foreach (RoleManager.RoleAssignmentData roleAssignmentData in guaranteedRoles.Select((x) =>
new RoleManager.RoleAssignmentData(x, roleOptions.GetNumPerGame(x.Role), 100)))
{
for (;;)
{
RoleManager.RoleAssignmentData roleAssignmentData2 = roleAssignmentData;
int count = roleAssignmentData2.Count;
roleAssignmentData2.Count = count - 1;
if (count <= 0)
{
break;
}
list.Add(roleAssignmentData.Role.Role);
}
}
__instance.AssignRolesFromList(players, teamMax, list, ref num);
var list2 = source.Where(x => !x.IsDead).Select(role =>
new RoleManager.RoleAssignmentData(
role,
roleOptions.GetNumPerGame(role.Role),
roleOptions.GetChancePerGame(role.Role))).ToList();
list.Clear();
foreach (RoleManager.RoleAssignmentData roleAssignmentData3 in list2)
{
for (int i = 0; i < roleAssignmentData3.Count; i++)
{
if (HashRandom.Next(101) < roleAssignmentData3.Chance)
{
list.Add(roleAssignmentData3.Role.Role);
}
}
}
__instance.AssignRolesFromList(players, teamMax, list, ref num);
if (defaultRole != null)
{
while (list.Count < players.Count && list.Count + num < teamMax)
{
list.Add(defaultRole.Value);
}
__instance.AssignRolesFromList(players, teamMax, list, ref num);
}
return false;
}
}
@@ -1,6 +1,7 @@
using System.Linq;
using AmongUs.GameOptions;
using HarmonyLib;
using InnerNet;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
using MiraAPI.Modifiers;
+1 -1
View File
@@ -51,7 +51,7 @@ public class MiraPluginInfo
public IReadOnlyCollection<CustomActionButton> Buttons { get; private set; } = null!;
/// <summary>
/// Gets a read only dictionary of this plugin's custom game modes.
/// Gets a read only dictionary of this plugin's custom presets.
/// </summary>
public IReadOnlyCollection<OptionPreset> Presets { get; internal set; } = null!;
+63 -1
View File
@@ -1,4 +1,5 @@
using System.IO;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using BepInEx.Configuration;
using MiraAPI.PluginLoading;
@@ -18,6 +19,13 @@ public static class PresetManager
/// </summary>
public static string PresetDirectory { get; } = Path.GetFullPath("mira_presets", Application.persistentDataPath);
internal static List<OptionPreset> InternalMasterPresets { get; } = [];
/// <summary>
/// Gets a read only dictionary of this plugin's custom presets.
/// </summary>
public static IReadOnlyCollection<OptionPreset> MasterPresets { get; internal set; } = null!;
internal static void CreateDefaultPreset(MiraPluginInfo plugin)
{
var presetPath = Path.Combine(PresetDirectory, plugin.PluginId);
@@ -93,4 +101,58 @@ public static class PresetManager
}
plugin.Presets = [..plugin.InternalPresets];
}
/// <summary>
/// Loads the master presets by reading the configuration files from the preset directory.
/// </summary>
public static void LoadMasterPreset()
{
/*foreach (var btn in plugin.InternalPresets.Select(x=>x.PresetButton))
{
if (btn != null)
{
Object.DestroyImmediate(btn);
}
}
plugin.InternalPresets.Clear();*/
if (!Directory.Exists(PresetDirectory))
{
Directory.CreateDirectory(PresetDirectory);
}
var pluginPresetPath = Path.Combine(PresetDirectory, "MasterPresets");
if (!Directory.Exists(pluginPresetPath))
{
Directory.CreateDirectory(pluginPresetPath);
}
foreach (var file in Directory.GetFiles(pluginPresetPath, "*.cfg"))
{
var fileName = Path.GetFileName(file);
Info($"Loading preset file {fileName}");
var presetName = Path.GetFileNameWithoutExtension(file);
var presetConfig = new ConfigFile(file, false)
{
SaveOnConfigSet = false,
};
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
{
foreach (var option in plugin.InternalOptions.Where(x=>x.IncludeInPreset))
{
option.Bind(presetConfig);
}
foreach (var role in plugin.InternalRoles.Values.OfType<ICustomRole>().Where(x=>!x.Configuration.HideSettings))
{
role.BindConfig(presetConfig);
}
}
presetConfig.Save();
// InternalMasterPresets.Add(new OptionPreset(presetName, plugin, presetConfig));
}
MasterPresets = [..InternalMasterPresets];
}
}
+58 -1
View File
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
@@ -13,6 +14,62 @@ namespace MiraAPI.Roles;
/// </summary>
public static class CustomRoleUtils
{
/// <summary>
/// Determines whether the specified role can spawn in general, accounting for gamemodes and everything else.
/// </summary>
/// <param name="role">The role you would like to check for.</param>
/// <returns>True if the role is able to spawn, otherwise false.</returns>
public static bool CanSpawnOnCurrentMode(RoleBehaviour role)
{
if (role is ICustomRole custom)
{
return custom.CanSpawnOnCurrentMode();
}
if (GameManager.Instance.IsHideAndSeek())
{
return role.Role is RoleTypes.Engineer || role.Role is RoleTypes.Impostor;
}
return true;
}
public static List<(ushort RoleType, int Chance)> GetPossibleRoles(
List<RoleManager.RoleAssignmentData> assignmentData,
Func<RoleManager.RoleAssignmentData, bool>? predicate = null)
{
var roles = new List<(ushort, int)>();
assignmentData.Where(x => predicate == null || predicate(x)).ToList().ForEach(x =>
{
for (var i = 0; i < x.Count; i++)
{
roles.Add(((ushort)x.Role.Role, x.Chance));
}
});
return roles;
}
public static RoleManager.RoleAssignmentData GetAssignData(RoleTypes roleType)
{
var currentGameOptions = GameOptionsManager.Instance.CurrentGameOptions;
var roleOptions = currentGameOptions.RoleOptions;
var role = GetRegisteredRole(roleType);
var assignmentData = new RoleManager.RoleAssignmentData(role, roleOptions.GetNumPerGame(role!.Role),
roleOptions.GetChancePerGame(role.Role));
return assignmentData;
}
public static RoleBehaviour? GetRegisteredRole(RoleTypes roleType)
{
// we want to prioritize the custom roles because the role has the right RoleColour/TeamColor
var role = CustomRoleManager.AllRoles.FirstOrDefault(x => x.Role == roleType);
return role;
}
/// <summary>
/// Gets all active in-game roles.
/// </summary>
+6
View File
@@ -239,4 +239,10 @@ public interface ICustomRole : IOptionable
{
return true;
}
/// <summary>
/// Determines whether the role can spawn in general, accounting for gamemodes and everything else.
/// </summary>
/// <returns>True if the role is able to spawn, otherwise false.</returns>
public virtual bool CanSpawnOnCurrentMode() => !GameManager.Instance.IsHideAndSeek();
}