diff --git a/MiraAPI.Example/Roles/NeutralKillerRole.cs b/MiraAPI.Example/Roles/NeutralKillerRole.cs index 50a8810..d9e4798 100644 --- a/MiraAPI.Example/Roles/NeutralKillerRole.cs +++ b/MiraAPI.Example/Roles/NeutralKillerRole.cs @@ -5,17 +5,18 @@ namespace MiraAPI.Example.Roles; public class NeutralKillerRole : ImpostorRole, ICustomRole { - public string RoleName => "Neutral Killer"; - public string RoleDescription => "Neutral who can kill."; + public string RoleName => "Outcast Killer"; + public string RoleDescription => "Outcast who can kill."; public string RoleLongDescription => RoleDescription; public Color RoleColor => Color.magenta; - public ModdedRoleTeams Team => ModdedRoleTeams.Neutral; + public ModdedRoleTeams Team => ModdedRoleTeams.Custom; - public CustomRoleConfiguration Configuration => new CustomRoleConfiguration(this) + public CustomRoleConfiguration Configuration => new(this) { UseVanillaKillButton = true, CanGetKilled = true, CanUseVent = true, + RoleGroup = new RoleGroup("Outcast", Color.gray), }; public override void SpawnTaskHeader(PlayerControl playerControl) diff --git a/MiraAPI/Patches/IntroCutscenePatches.cs b/MiraAPI/Patches/IntroCutscenePatches.cs index 297ce94..571fd9b 100644 --- a/MiraAPI/Patches/IntroCutscenePatches.cs +++ b/MiraAPI/Patches/IntroCutscenePatches.cs @@ -30,6 +30,32 @@ public static class IntroCutscenePatches MiraEventManager.InvokeEvent(@event); } + [HarmonyPrefix] + [HarmonyPatch(nameof(IntroCutscene.BeginImpostor))] + public static void BeginImpostorPatch(IntroCutscene __instance) + { + if (PlayerControl.LocalPlayer.Data.Role is not ICustomRole customRole) + { + return; + } + + if (customRole.Configuration.IntroTeamColor is { } color) + { + __instance.BackgroundBar.material.SetColor(ShaderID.Color, color); + __instance.TeamTitle.color = color; + } + + if (customRole.Configuration.IntroTeamTitle is { } title) + { + __instance.TeamTitle.text = title; + } + + if (customRole.Configuration.IntroTeamDescription is { } description) + { + __instance.ImpostorText.text = description; + } + } + [HarmonyPrefix] [HarmonyPatch(nameof(IntroCutscene.BeginCrewmate))] public static bool BeginCrewmatePatch(IntroCutscene __instance) @@ -39,7 +65,23 @@ public static class IntroCutscenePatches return true; } - if (customRole.Team is not ModdedRoleTeams.Neutral) + if (customRole.Configuration.IntroTeamColor is { } color) + { + __instance.BackgroundBar.material.SetColor(ShaderID.Color, color); + __instance.TeamTitle.color = color; + } + + if (customRole.Configuration.IntroTeamTitle is { } title) + { + __instance.TeamTitle.text = title; + } + + if (customRole.Configuration.IntroTeamDescription is { } description) + { + __instance.ImpostorText.text = description; + } + + if (customRole.Team is not ModdedRoleTeams.Custom) { return true; } @@ -49,11 +91,7 @@ public static class IntroCutscenePatches position.y -= 0.25f; barTransform.position = position; - __instance.BackgroundBar.material.SetColor(ShaderID.Color, Color.gray); - __instance.TeamTitle.text = "NEUTRAL"; __instance.impostorScale = 1f; - __instance.ImpostorText.text = "You are Neutral. You do not have a team."; - __instance.TeamTitle.color = Color.gray; __instance.ourCrewmate = __instance.CreatePlayer( 0, diff --git a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs index 4461423..ec6a68b 100644 --- a/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs +++ b/MiraAPI/Patches/Options/RoleSettingMenuPatches.cs @@ -1,14 +1,15 @@ -using HarmonyLib; +using System; +using System.Linq; +using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; using MiraAPI.Networking; using MiraAPI.Roles; using MiraAPI.Utilities; using MiraAPI.Utilities.Assets; +using Reactor.Localization.Utilities; using Reactor.Networking.Rpc; using Reactor.Utilities; using Reactor.Utilities.Extensions; -using System; -using System.Linq; using TMPro; using UnityEngine; using UnityEngine.Events; @@ -54,23 +55,43 @@ public static class RoleSettingMenuPatches var num3 = 0; - var crewRoles = GameSettingMenuPatches.SelectedMod?.CustomRoles.Values - .OfType() - .Where(role => role is { Team: ModdedRoleTeams.Crewmate, Configuration.HideSettings: false }) - .ToList() ?? []; + var roleGroups = GameSettingMenuPatches.SelectedMod?.CustomRoles.Values.OfType() + .ToLookup(x => x.Configuration.RoleGroup); - if (crewRoles is { Count: > 0 }) + if (roleGroups is null) { + return true; + } + + // sort the groups by priority + var sortedRoleGroups = roleGroups + .OrderBy(x => x.Key.Priority) + .ThenBy(x => x.Key.Name); + + foreach (var grouping in sortedRoleGroups) + { + if (!grouping.Any()) + { + continue; + } + + var name = grouping.Key.Name switch + { + "Crewmate" => StringNames.CrewmateRolesHeader, + "Impostor" => StringNames.ImpostorRolesHeader, + _ => CustomStringName.CreateAndRegister(grouping.Key.Name), + }; + var categoryHeaderEditRole = Object.Instantiate( __instance.categoryHeaderEditRoleOrigin, Vector3.zero, Quaternion.identity, __instance.RoleChancesSettings.transform); - categoryHeaderEditRole.SetHeader(StringNames.CrewmateRolesHeader, 20); + categoryHeaderEditRole.SetHeader(name, 20); categoryHeaderEditRole.transform.localPosition = new Vector3(4.986f, num, -2f); num -= 0.522f; - foreach (var role in crewRoles) + foreach (var role in grouping) { if (role is RoleBehaviour roleBehaviour) { @@ -78,65 +99,8 @@ public static class RoleSettingMenuPatches } num3++; } - } - - var impRoles = GameSettingMenuPatches.SelectedMod?.CustomRoles.Values - .OfType() - .Where(role => role is { Team: ModdedRoleTeams.Impostor, Configuration.HideSettings: false }) - .ToList() ?? []; - - if (impRoles is { Count: > 0 }) - { num -= 0.4f; - var categoryHeaderEditRole2 = Object.Instantiate( - __instance.categoryHeaderEditRoleOrigin, - Vector3.zero, - Quaternion.identity, - __instance.RoleChancesSettings.transform); - categoryHeaderEditRole2.SetHeader(StringNames.ImpostorRolesHeader, 20); - categoryHeaderEditRole2.transform.localPosition = new Vector3(4.986f, num, -2f); - num -= 0.522f; - - foreach (var role in impRoles) - { - if (role is RoleBehaviour roleBehaviour) - { - CreateQuotaOption(__instance, roleBehaviour, ref num, num3); - } - num3++; - } } - - var neutRoles = GameSettingMenuPatches.SelectedMod?.CustomRoles.Values - .OfType() - .Where(role => role is { Team: ModdedRoleTeams.Neutral, Configuration.HideSettings: false }) - .ToList() ?? []; - - if (neutRoles is { Count: > 0 }) - { - num -= 0.4f; - var categoryHeaderEditRole3 = Object.Instantiate( - __instance.categoryHeaderEditRoleOrigin, - Vector3.zero, - Quaternion.identity, - __instance.RoleChancesSettings.transform); - categoryHeaderEditRole3.SetHeader(StringNames.None, 20); - categoryHeaderEditRole3.Title.text = "Neutral Roles"; - categoryHeaderEditRole3.Background.color = Color.gray; - categoryHeaderEditRole3.Title.color = Color.white; - categoryHeaderEditRole3.transform.localPosition = new Vector3(4.986f, num, -2f); - num -= 0.522f; - - foreach (var role in neutRoles) - { - if (role is RoleBehaviour roleBehaviour) - { - CreateQuotaOption(__instance, roleBehaviour, ref num, num3); - } - num3++; - } - } - return false; } diff --git a/MiraAPI/Patches/Roles/TaskAdderPatch.cs b/MiraAPI/Patches/Roles/TaskAdderPatch.cs index ee3c34d..e371b7d 100644 --- a/MiraAPI/Patches/Roles/TaskAdderPatch.cs +++ b/MiraAPI/Patches/Roles/TaskAdderPatch.cs @@ -90,9 +90,9 @@ public static class TaskAdderPatch [HarmonyPatch(typeof(TaskAddButton), "Role", MethodType.Setter)] public static void RoleGetterPatch(TaskAddButton __instance) { - if (__instance.role is ICustomRole { Team: ModdedRoleTeams.Neutral }) + if (__instance.role is ICustomRole { Team: ModdedRoleTeams.Custom } customRole) { - __instance.FileImage.color = Color.gray; + __instance.FileImage.color = customRole.Configuration.IntroTeamColor ?? Color.gray; } __instance.RolloverHandler.OutColor = __instance.FileImage.color; diff --git a/MiraAPI/Roles/CustomRoleConfiguration.cs b/MiraAPI/Roles/CustomRoleConfiguration.cs index 5bd29bb..cd2220c 100644 --- a/MiraAPI/Roles/CustomRoleConfiguration.cs +++ b/MiraAPI/Roles/CustomRoleConfiguration.cs @@ -38,8 +38,31 @@ public struct CustomRoleConfiguration TasksCountForProgress = role.Team is ModdedRoleTeams.Crewmate; HideSettings = roleBehaviour?.IsDead == true; CanModifyChance = true; + IntroTeamColor = role.Team switch + { + ModdedRoleTeams.Custom => Color.gray, + _ => null, + }; + IntroTeamTitle = role.Team switch + { + ModdedRoleTeams.Custom => "NEUTRAL", + _ => null, + }; + IntroTeamDescription = role.Team switch + { + ModdedRoleTeams.Custom => "You are Neutral. You do not have a team.", + _ => null, + }; + RoleGroup = role.Team switch + { + ModdedRoleTeams.Crewmate => RoleGroup.Crewmate, + ModdedRoleTeams.Impostor => RoleGroup.Impostor, + ModdedRoleTeams.Custom => RoleGroup.Neutral, + }; } + public RoleGroup RoleGroup; + /// /// Gets the hard limit of players that can have this role. This property is used to set a limit in the Role Options menu. If set to 0, the role will not be assigned at start. /// @@ -105,6 +128,21 @@ public struct CustomRoleConfiguration /// public bool HideSettings; + /// + /// Gets the color to show during the "Team" part of the intro cutscene. + /// + public Color? IntroTeamColor; + + /// + /// Gets the title text to show during the "Team" part of the intro cutscene. + /// + public string? IntroTeamTitle; + + /// + /// Gets the description text to show during the "Team" part of the intro cutscene. + /// + public string? IntroTeamDescription; + /// /// Gets the outline color for the KillButton if is true. /// @@ -119,4 +157,4 @@ public struct CustomRoleConfiguration /// Gets the Ghost role that is applied when the player is killed. /// public RoleTypes GhostRole; -} \ No newline at end of file +} diff --git a/MiraAPI/Roles/CustomRoleManager.cs b/MiraAPI/Roles/CustomRoleManager.cs index eb291d8..cc79c02 100644 --- a/MiraAPI/Roles/CustomRoleManager.cs +++ b/MiraAPI/Roles/CustomRoleManager.cs @@ -79,7 +79,7 @@ public static class CustomRoleManager var roleId = RoleIds[roleType]; roleBehaviour.Role = (RoleTypes)roleId; - roleBehaviour.TeamType = customRole.Team == ModdedRoleTeams.Neutral ? RoleTeamTypes.Crewmate : (RoleTeamTypes)customRole.Team; + roleBehaviour.TeamType = customRole.Team == ModdedRoleTeams.Custom ? RoleTeamTypes.Crewmate : (RoleTeamTypes)customRole.Team; roleBehaviour.NameColor = customRole.RoleColor; roleBehaviour.StringName = CustomStringName.CreateAndRegister(customRole.RoleName); roleBehaviour.BlurbName = CustomStringName.CreateAndRegister(customRole.RoleDescription); diff --git a/MiraAPI/Roles/ModdedRoleTeams.cs b/MiraAPI/Roles/ModdedRoleTeams.cs index e599cd7..0d8c6a1 100644 --- a/MiraAPI/Roles/ModdedRoleTeams.cs +++ b/MiraAPI/Roles/ModdedRoleTeams.cs @@ -4,5 +4,5 @@ public enum ModdedRoleTeams { Crewmate, Impostor, - Neutral, + Custom, } diff --git a/MiraAPI/Roles/RoleGroup.cs b/MiraAPI/Roles/RoleGroup.cs new file mode 100644 index 0000000..4de874d --- /dev/null +++ b/MiraAPI/Roles/RoleGroup.cs @@ -0,0 +1,16 @@ +using UnityEngine; + +namespace MiraAPI.Roles; + +/// +/// Represents a group of roles. +/// +/// The group's name. +/// The group's color. +/// The priority to sort by on role setting screen. Negative means first. +public record struct RoleGroup(string Name, Color Color, int Priority = 0) +{ + public static RoleGroup Crewmate { get; } = new("Crewmate", Palette.CrewmateBlue, -2); + public static RoleGroup Impostor { get; } = new("Impostor", Palette.ImpostorRed, -1); + public static RoleGroup Neutral { get; } = new("Neutral", Color.gray); +}