Compare commits

..
7 Commits
Author SHA1 Message Date
Az ffecd22176 Merge branch 'dev' into id-builder 2026-10-22 20:59:38 -04:00
Az 26541dc07d clear warnings 2026-10-22 20:54:33 -04:00
Atony 2a7570f022 Adjust code that gets roles and modifiers list 2026-10-22 02:01:05 -05:00
Atony 71138c7bbe Display current role and modifiers at top 2026-10-22 01:17:21 -05:00
Atony 2993ee2bbc Resync options upon clicking start
This is necessary to prevent some pretty common option desync that tends to happen. If anyone else can optimize it further or move it somewhere else, go for it.
2026-10-21 23:37:46 -05:00
Atony 79bbebd12a Allow modifiers and roles to override chance text 2026-10-21 17:48:33 -05:00
Az 9999c6d871 wip translation id builder 2026-09-25 00:08:43 -04:00
21 changed files with 463 additions and 111 deletions

No files matched your search

+5
View File
@@ -77,6 +77,11 @@ public interface IModdedOption
/// </summary>
bool IncludeInPreset { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the option should show a notification, toggled on by an overall option synchronization.
/// </summary>
public bool ShowNotification { get; set; }
/// <summary>
/// Gets or sets the <see cref="BepInEx.Configuration.ConfigDefinition"/> for the option, used for BepInEx configuration.
/// </summary>
+4 -3
View File
@@ -365,11 +365,11 @@ public static class ModdedOptionsManager
while (chunks.Count > 0)
{
Rpc<SyncOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, chunks.Dequeue());
Rpc<SyncOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, new MiraOptionNetData(false, chunks.Dequeue()));
}
}
internal static void HandleSyncOptions(NetData[] data)
internal static void HandleSyncOptions(bool showNotifs, NetData[] data)
{
// necessary to disable then re-enable this setting
// we don't know how other plugins handle their configs
@@ -388,6 +388,7 @@ public static class ModdedOptionsManager
continue;
}
option.ShowNotification = showNotifs;
option.HandleNetData(netData.Data);
}
@@ -397,7 +398,7 @@ public static class ModdedOptionsManager
plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin];
}
if (LobbyInfoPane.Instance)
if (LobbyInfoPane.InstanceExists)
{
LobbyInfoPane.Instance.RefreshPane();
}
@@ -4,6 +4,7 @@ using System.Linq;
using System.Text;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using MiraAPI.Translation;
using UnityEngine;
using Object = UnityEngine.Object;
@@ -36,7 +37,7 @@ public class ModdedEnumOption : ModdedOption<int>
var data = ScriptableObject.CreateInstance<StringGameSetting>();
data.Title = StringName;
data.Type = global::OptionTypes.String;
data.Values = Values.Select(MiraLocaleManager.GetOrCreateLocaleString).ToArray();
data.Values = Values.Select(x => MiraLocaleManager.GetOrCreateLocaleString(ParentMod!.IdBuilder.CreateEnumOptionId(x, enumType))).ToArray();
data.Index = Value;
Data = data;
@@ -93,13 +94,17 @@ public class ModdedEnumOption : ModdedOption<int>
/// <inheritdoc />
protected override void OnValueChanged(int newValue)
{
ModdedOptionsManager.AddSettingsChangeMessage(
HudManager.Instance.Notifier,
StringName,
Data.GetValueString(newValue),
Configuration.PopUpTextColor,
Configuration.PopUpIconTmp,
false);
if (ShowNotification)
{
ModdedOptionsManager.AddSettingsChangeMessage(
HudManager.Instance.Notifier,
StringName,
Data.GetValueString(newValue),
Configuration.PopUpTextColor,
Configuration.PopUpIconTmp,
false);
}
if (!OptionBehaviour)
{
return;
@@ -139,12 +144,18 @@ public class ModdedEnumOption<T> : ModdedOption<T>
var data = ScriptableObject.CreateInstance<StringGameSetting>();
data.Title = StringName;
data.Type = global::OptionTypes.String;
data.Values = Values.Select(MiraLocaleManager.GetOrCreateLocaleString).ToArray();
data.Index = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo);
Data = data;
}
/// <inheritdoc />
protected override void OnParentModSet(IMiraPlugin plugin)
{
base.OnParentModSet(plugin);
((StringGameSetting)Data).Values = Values.Select(x => MiraLocaleManager.GetOrCreateLocaleString(ParentMod!.IdBuilder.CreateEnumOptionId<T>(x))).ToArray();
}
/// <inheritdoc />
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
{
@@ -196,13 +207,17 @@ public class ModdedEnumOption<T> : ModdedOption<T>
/// <inheritdoc />
protected override void OnValueChanged(T newValue)
{
ModdedOptionsManager.AddSettingsChangeMessage(
HudManager.Instance.Notifier,
StringName,
Data.GetValueString(Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo)),
Configuration.PopUpTextColor,
Configuration.PopUpIconTmp,
false);
if (ShowNotification)
{
ModdedOptionsManager.AddSettingsChangeMessage(
HudManager.Instance.Notifier,
StringName,
Data.GetValueString(Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo)),
Configuration.PopUpTextColor,
Configuration.PopUpIconTmp,
false);
}
if (!OptionBehaviour)
{
return;
@@ -199,13 +199,16 @@ public class ModdedNumberOption : ModdedOption<float>
protected override void OnValueChanged(float newValue)
{
Value = Mathf.Clamp(newValue, Min, Max);
ModdedOptionsManager.AddSettingsChangeMessage(
HudManager.Instance.Notifier,
StringName,
Data.GetValueString(newValue),
Configuration.PopUpTextColor,
Configuration.PopUpIconTmp,
false);
if (ShowNotification)
{
ModdedOptionsManager.AddSettingsChangeMessage(
HudManager.Instance.Notifier,
StringName,
Data.GetValueString(newValue),
Configuration.PopUpTextColor,
Configuration.PopUpIconTmp,
false);
}
if (OptionBehaviour is NumberOption opt)
{
+26 -27
View File
@@ -4,7 +4,6 @@ using BepInEx.Configuration;
using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using MiraAPI.Translation;
using MiraAPI.Utilities;
using Reactor.Networking.Rpc;
using UnityEngine;
@@ -14,16 +13,19 @@ namespace MiraAPI.GameOptions.OptionTypes;
/// Represents a modded option.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
public abstract class ModdedOption<T> : IModdedOption
/// <param name="title">The option title.</param>
/// <param name="defaultValue">The default value.</param>
/// <param name="includeInPreset">Whether to include the option in the preset.</param>
public abstract class ModdedOption<T>(string title, T defaultValue, bool includeInPreset = true) : IModdedOption
{
/// <inheritdoc />
public uint Id { get; }
public uint Id { get; } = ModdedOptionsManager.NextId;
/// <inheritdoc />
public string Title { get; set; }
public string Title { get; set; } = title;
/// <inheritdoc />
public StringNames StringName { get; }
public StringNames StringName { get; private set; }
/// <inheritdoc />
public BaseGameSetting Data { get; protected set; } = null!;
@@ -36,6 +38,7 @@ public abstract class ModdedOption<T> : IModdedOption
{
if (field != null || value == null) return;
field = value;
OnParentModSet(value);
var entry = field.GetConfigFile().Bind(ConfigDefinition, DefaultValue);
Value = entry.Value;
@@ -57,12 +60,12 @@ public abstract class ModdedOption<T> : IModdedOption
}
}
private T _value;
private T _value = defaultValue;
/// <summary>
/// Gets the default value of the option.
/// </summary>
public T DefaultValue { get; }
public T DefaultValue { get; } = defaultValue;
/// <inheritdoc cref="IModdedOption.Getter"/>
public Func<T, T>? Getter { get; set; }
@@ -74,10 +77,13 @@ public abstract class ModdedOption<T> : IModdedOption
public Action<T>? ChangedEvent { get; set; }
/// <inheritdoc />
public Func<bool> Visible { get; set; }
public Func<bool> Visible { get; set; } = () => true;
/// <inheritdoc />
public bool IncludeInPreset { get; set; }
public bool IncludeInPreset { get; set; } = includeInPreset;
/// <inheritdoc />
public bool ShowNotification { get; set; } = true;
/// <inheritdoc />
public OptionBehaviour? OptionBehaviour { get; protected set; }
@@ -112,23 +118,6 @@ public abstract class ModdedOption<T> : IModdedOption
set => ChangedEvent = value as Action<T>;
}
/// <summary>
/// Initializes a new instance of the <see cref="ModdedOption{T}"/> class.
/// </summary>
/// <param name="title">The option title.</param>
/// <param name="defaultValue">The default value.</param>
/// <param name="includeInPreset">Whether to include the option in the preset.</param>
protected ModdedOption(string title, T defaultValue, bool includeInPreset = true)
{
Id = ModdedOptionsManager.NextId;
Title = title.Translate();
DefaultValue = defaultValue;
Value = defaultValue;
StringName = MiraLocaleManager.GetOrCreateLocaleString(Title);
Visible = DelegateUtilities.AlwaysTrue;
IncludeInPreset = includeInPreset;
}
internal void ValueChanged(OptionBehaviour optionBehaviour)
{
SetValue(GetValueFromOptionBehaviour(optionBehaviour));
@@ -156,10 +145,11 @@ public abstract class ModdedOption<T> : IModdedOption
entry.Value = _value;
}
Rpc<SyncOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [GetNetData()], true);
Rpc<SyncOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, new MiraOptionNetData(true, [GetNetData()]), true);
}
OnValueChanged(_value);
ShowNotification = true;
}
/// <inheritdoc />
@@ -194,6 +184,15 @@ public abstract class ModdedOption<T> : IModdedOption
}
}
/// <summary>
/// An event that is invoked when the option's parent mod as been assigned to the option.
/// </summary>
/// <param name="plugin">The plugin that was assigned.</param>
protected virtual void OnParentModSet(IMiraPlugin plugin)
{
StringName = MiraLocaleManager.GetOrCreateLocaleString(plugin.IdBuilder.CreateOptionTitleId(Title));
}
/// <inheritdoc />
public Type GetValueType()
{
@@ -4,6 +4,7 @@ using System.Linq;
using System.Text;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using MiraAPI.Translation;
using UnityEngine;
using Object = UnityEngine.Object;
@@ -79,7 +80,7 @@ public class ModdedStringOption : ModdedOption<string>
newVals.Insert(index, value);
Values = [.. newVals];
var newStrVals = StringNameValues.ToList();
newStrVals.Insert(index, MiraLocaleManager.GetOrCreateLocaleString(value));
newStrVals.Insert(index, MiraLocaleManager.GetOrCreateLocaleString(ParentMod!.IdBuilder.CreateStringOptionId(value)));
StringNameValues = [.. newStrVals];
}
@@ -101,9 +102,8 @@ public class ModdedStringOption : ModdedOption<string>
return;
}
var strOpt = OptionBehaviour.TryCast<StringOption>();
Warning("OptionBehaviour is valid");
var strOpt = OptionBehaviour.TryCast<StringOption>();
if (strOpt == null)
{
@@ -114,6 +114,16 @@ public class ModdedStringOption : ModdedOption<string>
strOpt.Values = StringNameValues;
strOpt.Value = GetIndex(Value);
Warning("StringOption is valid");
return;
}
/// <inheritdoc />
protected override void OnParentModSet(IMiraPlugin plugin)
{
base.OnParentModSet(plugin);
StringNameValues = [.. Values.Select(x => MiraLocaleManager.GetOrCreateLocaleString(plugin.IdBuilder.CreateStringOptionId(x)))];
((StringGameSetting)Data).Values = StringNameValues;
}
/// <inheritdoc />
@@ -99,13 +99,17 @@ public class ModdedToggleOption : ModdedOption<bool>
/// <inheritdoc />
protected override void OnValueChanged(bool newValue)
{
ModdedOptionsManager.AddSettingsChangeMessage(
HudManager.Instance.Notifier,
StringName,
(newValue ? OnValue : OffValue).Translate(),
Configuration.PopUpTextColor,
Configuration.PopUpIconTmp,
false);
if (ShowNotification)
{
ModdedOptionsManager.AddSettingsChangeMessage(
HudManager.Instance.Notifier,
StringName,
(newValue ? OnValue : OffValue).Translate(),
Configuration.PopUpTextColor,
Configuration.PopUpIconTmp,
false);
}
if (!OptionBehaviour)
{
return;
+1 -1
View File
@@ -36,7 +36,7 @@ public partial class MiraApiPlugin : BasePlugin, IMiraPlugin
public static CultureInfo Culture { get; internal set; } = new("en-US");
/// <inheritdoc />
public string OptionsTitleText => "MiraAPI";
public string OptionsTitleText { get; } = "MiraAPI";
/// <inheritdoc />
public bool DisplayOnOptionsMenu => false;
+43
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.GameOptions;
using MiraAPI.PluginLoading;
@@ -96,6 +97,48 @@ public abstract class BaseModifier : IOptionable
/// </summary>
public virtual string ModifierCategoryTitle => MiraLocaleManager.Get("Modifier");
/// <summary>
/// Gets a value indicating whether to exclude the "amount" from the displayed amount text within the role guide.
/// </summary>
public virtual bool ShowModifierAsUnique => false;
/// <summary>
/// Gets the displayed amount and chance of the modifier. Used in the role guide.
/// By default, 0 amount or chance results in a complete 0 amount and chance, so override if necessary.
/// </summary>
/// <param name="amount">The amount of the modifier.</param>
/// <param name="chance">The chance of the modifier.</param>
/// <param name="source">The mod source of the modifier.</param>
/// <returns>The <see cref="string"/> result.</returns>
public virtual string GetAmountText(int amount, int chance, string source)
{
if (amount == 0)
{
chance = 0;
}
if (ShowModifierAsUnique)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0}% ({1})",
chance.ToString(CultureInfo.InvariantCulture),
source);
}
if (chance == 0)
{
amount = 0;
}
return string.Format(
CultureInfo.InvariantCulture,
"{0} at {1}% ({2})",
amount.ToString(CultureInfo.InvariantCulture),
chance.ToString(CultureInfo.InvariantCulture),
source);
}
/// <summary>
/// Gets whether the modifier is forcibly shown or disabled in the wiki screen.
/// </summary>
+2 -1
View File
@@ -1,4 +1,5 @@
using MiraAPI.GameModes;
using System.Globalization;
using MiraAPI.GameModes;
using MiraAPI.PluginLoading;
namespace MiraAPI.Modifiers.Types;
+13 -10
View File
@@ -9,29 +9,32 @@ namespace MiraAPI.Networking;
[RegisterCustomRpc((uint)MiraRpc.SyncGameOptions)]
#pragma warning disable SA1515 // Single-line comment should be preceded by blank line (Justification: ReSharper suppression.)
// ReSharper disable once ClassNeverInstantiated.Global (Justification: Instantiated by Reactor.)
internal sealed class SyncOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, NetData[]>(plugin, id)
internal sealed class SyncOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, MiraOptionNetData>(plugin, id)
#pragma warning restore SA1515 // Single-line comment should be preceded by blank line
{
public override RpcLocalHandling LocalHandling => RpcLocalHandling.None;
public override void Write(MessageWriter writer, NetData[]? data)
public override void Write(MessageWriter writer, MiraOptionNetData data)
{
if (data == null)
/*if (data == null)
{
writer.WritePacked(0U);
return;
}
}*/
writer.WritePacked((uint)data.Length);
foreach (var netData in data)
var info = data.Data;
writer.Write(data.ShouldShowNotifications);
writer.WritePacked((uint)info.Length);
foreach (var netData in info)
{
writer.WritePacked(netData.Id);
writer.WriteBytesAndSize(netData.Data);
}
}
public override NetData[] Read(MessageReader reader)
public override MiraOptionNetData Read(MessageReader reader)
{
var shouldShowNotif = reader.ReadBoolean();
var length = reader.ReadPackedUInt32();
var data = new NetData[length];
for (var i = 0; i < length; i++)
@@ -41,16 +44,16 @@ internal sealed class SyncOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCust
data[i] = new NetData(dataId, bytes);
}
return data;
return new MiraOptionNetData(shouldShowNotif, data);
}
public override void Handle(PlayerControl playerControl, NetData[]? data)
public override void Handle(PlayerControl playerControl, MiraOptionNetData data)
{
if (AmongUsClient.Instance.HostId != playerControl.OwnerId)
{
return;
}
ModdedOptionsManager.HandleSyncOptions(data ?? []);
ModdedOptionsManager.HandleSyncOptions(data.ShouldShowNotifications, data.Data ?? []);
}
}
+19 -10
View File
@@ -5,32 +5,41 @@ using Reactor.Networking.Rpc;
namespace MiraAPI.Networking;
internal readonly struct MiraOptionNetData(bool showNotif, NetData[] data)
{
public bool ShouldShowNotifications { get; } = showNotif;
public NetData[] Data { get; } = data;
}
[RegisterCustomRpc((uint)MiraRpc.SyncRoleOptions)]
#pragma warning disable SA1515 // Single-line comment should be preceded by blank line (Justification: ReSharper suppression.)
// ReSharper disable once ClassNeverInstantiated.Global (Justification: Instantiated by Reactor.)
internal sealed class SyncRoleOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, NetData[]>(plugin, id)
internal sealed class SyncRoleOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, MiraOptionNetData>(plugin, id)
#pragma warning restore SA1515 // Single-line comment should be preceded by blank line
{
public override RpcLocalHandling LocalHandling => RpcLocalHandling.None;
public override void Write(MessageWriter writer, NetData[]? data)
public override void Write(MessageWriter writer, MiraOptionNetData data)
{
if (data == null)
/*if (data == null)
{
writer.WritePacked(0U);
return;
}
}*/
writer.WritePacked((uint)data.Length);
foreach (var netData in data)
var info = data.Data;
writer.Write(data.ShouldShowNotifications);
writer.WritePacked((uint)info.Length);
foreach (var netData in info)
{
writer.WritePacked(netData.Id);
writer.WriteBytesAndSize(netData.Data);
}
}
public override NetData[] Read(MessageReader reader)
public override MiraOptionNetData Read(MessageReader reader)
{
var shouldShowNotif = reader.ReadBoolean();
var length = reader.ReadPackedUInt32();
var data = new NetData[length];
for (var i = 0; i < length; i++)
@@ -40,16 +49,16 @@ internal sealed class SyncRoleOptionsRpc(MiraApiPlugin plugin, uint id) : Player
data[i] = new NetData(dataId, bytes);
}
return data;
return new MiraOptionNetData(shouldShowNotif, data);
}
public override void Handle(PlayerControl playerControl, NetData[]? data)
public override void Handle(PlayerControl playerControl, MiraOptionNetData data)
{
if (AmongUsClient.Instance.HostId != playerControl.OwnerId)
{
return;
}
CustomRoleManager.HandleSyncRoleOptions(data ?? []);
CustomRoleManager.HandleSyncRoleOptions(data.ShouldShowNotifications, data.Data ?? []);
}
}
@@ -1,5 +1,7 @@
using HarmonyLib;
using MiraAPI.GameModes;
using MiraAPI.GameOptions;
using MiraAPI.Roles;
namespace MiraAPI.Patches.GameModes;
@@ -55,4 +57,17 @@ internal static class GameStartManagerPatches
__instance.MinPlayers = 1;
}
}
[HarmonyPrefix, HarmonyPatch(nameof(GameStartManager.BeginGame))]
public static void Prefix(GameStartManager __instance)
{
if (!AmongUsClient.Instance.AmHost)
{
return;
}
ModdedOptionsManager.SyncAllOptions();
CustomRoleManager.SyncAllRoleSettings();
GameModeOption.RpcSyncGamemode(PlayerControl.LocalPlayer, GameModeOption.Value);
}
}
+1 -1
View File
@@ -29,7 +29,7 @@ public static class HudManagerPatches
public static IEnumerator CoResizeUI()
{
while (!HudManager.Instance) yield return null;
while (!HudManager.InstanceExists) yield return null;
yield return new WaitForSeconds(0.01f);
ResizeUI(LocalSettingsTabSingleton<MiraApiSettings>.Instance.ButtonUIFactorSlider.Value);
@@ -495,7 +495,7 @@ public static class RoleSettingMenuPatches
if (AmongUsClient.Instance.AmHost)
{
Rpc<SyncRoleOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [role.GetNetData()], true);
Rpc<SyncRoleOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, new MiraOptionNetData(true, [role.GetNetData()]), true);
}
GameOptionsManager.Instance.GameHostOptions = GameOptionsManager.Instance.CurrentGameOptions;
+100 -21
View File
@@ -754,7 +754,8 @@ public static class RoleGuidePatches
modifier.ModifierCategoryTitle,
modifier.ParentMod.MiraPlugin.GetAbbreviatedModName(),
null,
modifier));
modifier,
modifier.TypeId));
}
}
@@ -790,7 +791,8 @@ public static class RoleGuidePatches
roleBehaviour.GetCategoryTitle(),
modId,
roleBehaviour,
null));
null,
(uint)roleBehaviour.Role));
}
_roleEntriesMode = activeModeType;
@@ -928,7 +930,7 @@ public static class RoleGuidePatches
}
}
private sealed class DetailedPanel(string title, string category, string modId, RoleBehaviour? role, GameModifier? modifier)
private sealed class DetailedPanel(string title, string category, string modId, RoleBehaviour? role, GameModifier? modifier, uint typeId)
{
public MatchInfoRolePanel? Panel { get; set; }
public string Title { get; } = title;
@@ -936,6 +938,7 @@ public static class RoleGuidePatches
public string ModId { get; } = modId;
public RoleBehaviour? Role { get; } = role;
public GameModifier? Modifier { get; } = modifier;
public uint TypeId { get; } = typeId;
public int Amount { get; set; }
public int Chance { get; set; }
public float Likelihood { get; set; }
@@ -1157,7 +1160,9 @@ public static class RoleGuidePatches
entry.Visible = entry.CanShow && MatchesSearch(entry, searchText);
if (entry.Panel)
{
SetPanelCount(entry.Panel!, entry.Amount, entry.Chance, entry.ModId);
dynamic entryType = entry.Modifier != null ? entry.Modifier : entry.Role!;
var text = GetPanelCountText(entryType, entry.Amount, entry.Chance, entry.ModId);
SetPanelCount(entry.Panel!, text);
if (!entry.Visible && entry.Panel!.gameObject.activeSelf)
{
entry.Panel.gameObject.SetActive(false);
@@ -1182,16 +1187,58 @@ public static class RoleGuidePatches
return shown;
}
public static IEnumerable<GameModifier> GetActiveModifiers()
{
return PlayerControl.LocalPlayer.GetModifiers<GameModifier>();
}
public static IEnumerable<RoleBehaviour> GetActiveRoles()
{
IEnumerable<RoleBehaviour> comparisons = [];
var curRole = PlayerControl.LocalPlayer.Data.Role;
comparisons = comparisons.AddItem(curRole);
if (PlayerControl.LocalPlayer && PlayerControl.LocalPlayer.Data && PlayerControl.LocalPlayer.Data.IsDead &&
!comparisons.Any(x => x.Role == PlayerControl.LocalPlayer.Data.RoleWhenAlive.Value))
{
comparisons = comparisons.AddItem(RoleManager.Instance.GetRole(PlayerControl.LocalPlayer.Data.RoleWhenAlive.Value));
}
return comparisons;
}
public static List<uint> GetActiveIds()
{
var activeModifiers = GetActiveModifiers();
List<uint> comparisons = activeModifiers.Select(x => x.TypeId).ToList();
comparisons.AddRange(GetActiveRoles().Select(x => (uint)x.Role));
return comparisons;
}
private static List<DetailedPanel> SortEntries(List<DetailedPanel> entries, string searchText)
{
var activeModifiers = PlayerControl.LocalPlayer.GetModifiers<GameModifier>().Select(x => x.TypeId);
var roles = GetActiveRoles().Select(x => (uint)x.Role).ToList();
List<uint> comparisons = activeModifiers.ToList();
comparisons.AddRange(roles);
var comparer = new BasicGuideComparer(comparisons);
var sorted = entries.Where(p => p.Visible)
.OrderByDescending(p => p.Title.Equals(searchText, StringComparison.OrdinalIgnoreCase))
.ThenByDescending(p =>
p.Title.Contains(searchText, StringComparison.OrdinalIgnoreCase) ||
p.Category.Contains(searchText, StringComparison.OrdinalIgnoreCase));
return [.. _sortMethod is SortingMethod.Alphabetical
p.Category.Contains(searchText, StringComparison.OrdinalIgnoreCase))
.ThenBy(x => x.Modifier != null ? x.Modifier.TypeId : (uint)x.Role!.Role, comparer);
return
[
.. _sortMethod is SortingMethod.Alphabetical
? sorted.ThenBy(p => p.GetSortKey(_sortGrouping))
: sorted.ThenByDescending(p => p.GetSortKey(_sortGrouping))];
: sorted.ThenByDescending(p => p.GetSortKey(_sortGrouping))
];
}
private static bool MatchesSearch(DetailedPanel entry, string searchText)
@@ -1651,10 +1698,15 @@ public static class RoleGuidePatches
public static Sprite GetRandomRoleIcon()
{
var aliveRole = PlayerControl.LocalPlayer.Data?.Role;
if (aliveRole != null && aliveRole.RoleIconSolid != null)
var roles = GetActiveRoles().Shuffle().ToList();
foreach (var role in roles)
{
return aliveRole.RoleIconSolid;
if (role.RoleIconSolid == null)
{
continue;
}
return role.RoleIconSolid;
}
return GetRandomFactionIcon();
@@ -1793,21 +1845,22 @@ public static class RoleGuidePatches
return false;
}
__instance.roleCount.text = string.Format(
CultureInfo.InvariantCulture,
"{0} at {1}%",
numPerGame.ToString(CultureInfo.InvariantCulture),
chancePerGame);
if (role is ICustomRole customRole)
{
__instance.roleName.text = customRole.RoleName;
__instance.roleDescription.text =
$"<size=60%>{role.GetCategoryTitle()}</size>\n" + customRole.RoleMedDescription;
__instance.roleIcon.sprite = customRole.Configuration.Icon?.LoadAsset();
__instance.roleCount.text += $" ({customRole.ParentMod.MiraPlugin.GetAbbreviatedModName()})";
var text = GetCustomRolePanelCountText(customRole, numPerGame, chancePerGame, customRole.ParentMod.MiraPlugin.GetAbbreviatedModName());
SetPanelCount(__instance, text);
}
else
{
__instance.roleCount.text = string.Format(
CultureInfo.InvariantCulture,
"{0} at {1}%",
numPerGame.ToString(CultureInfo.InvariantCulture),
chancePerGame);
__instance.roleName.text = role.NiceName;
__instance.roleDescription.text = $"<size=60%>{role.GetCategoryTitle()}</size>\n" + role.BlurbMed;
__instance.roleIcon.sprite = role.RoleIconColor;
@@ -1823,9 +1876,20 @@ public static class RoleGuidePatches
return false;
}
private static void SetPanelCount(MatchInfoRolePanel panel, int amount, int chance, string modId)
// NOTE: This is specifically meant to be a different method name as to not have the game check this method and the GetPanelCountText method made for roles overall.
private static string GetCustomRolePanelCountText(ICustomRole custom, int amount, int chance, string modId)
{
panel.roleCount.text = string.Format(
return custom.GetAmountText(amount, chance, modId);
}
private static string GetPanelCountText(RoleBehaviour role, int amount, int chance, string modId)
{
if (role is ICustomRole custom)
{
return custom.GetAmountText(amount, chance, modId);
}
return string.Format(
CultureInfo.InvariantCulture,
"{0} at {1}% ({2})",
amount.ToString(CultureInfo.InvariantCulture),
@@ -1833,6 +1897,18 @@ public static class RoleGuidePatches
modId);
}
private static string GetPanelCountText(BaseModifier modifier, int amount, int chance, string modId)
{
return modifier.GetAmountText(amount, chance, modId);
}
private static void SetPanelCount(MatchInfoRolePanel panel, string amountChanceText)
{
panel.roleCount.text = string.Format(
CultureInfo.InvariantCulture,
amountChanceText);
}
public static void SetRolePanel(
this MatchInfoRolePanel instance,
RoleBehaviour role,
@@ -1845,14 +1921,16 @@ public static class RoleGuidePatches
instance.roleDescription.text =
$"<size=60%>{role.GetCategoryTitle()}</size>\n" + customRole.RoleMedDescription;
instance.roleIcon.sprite = customRole.Configuration.Icon?.LoadAsset();
SetPanelCount(instance, numPerGame, chancePerGame, customRole.ParentMod.MiraPlugin.GetAbbreviatedModName());
var text = GetCustomRolePanelCountText(customRole, numPerGame, chancePerGame, customRole.ParentMod.MiraPlugin.GetAbbreviatedModName());
SetPanelCount(instance, text);
}
else
{
instance.roleName.text = role.NiceName;
instance.roleDescription.text = $"<size=60%>{role.GetCategoryTitle()}</size>\n" + role.BlurbMed;
instance.roleIcon.sprite = role.RoleIconColor;
SetPanelCount(instance, numPerGame, chancePerGame, "AU");
var text = GetPanelCountText(role, numPerGame, chancePerGame, "AU");
SetPanelCount(instance, text);
}
instance.roleIcon.SetSizeLimit(0.13f);
@@ -1873,7 +1951,8 @@ public static class RoleGuidePatches
instance.roleDescription.text =
$"<size=60%>{modifier.ModifierCategoryTitle}</size>\n" + modifier.ModifierMedDescription;
instance.roleIcon.sprite = modifier.ModifierIcon?.LoadAsset();
SetPanelCount(instance, numPerGame, chancePerGame, modifier.ParentMod.MiraPlugin.GetAbbreviatedModName());
var text = GetPanelCountText(modifier, numPerGame, chancePerGame, modifier.ParentMod.MiraPlugin.GetAbbreviatedModName());
SetPanelCount(instance, text);
instance.roleIcon.SetSizeLimit(0.13f);
instance.roleIcon.material.SetInt(PlayerMaterial.MaskLayer, 50);
+6
View File
@@ -1,6 +1,7 @@
using System.Globalization;
using System.Linq;
using BepInEx.Configuration;
using MiraAPI.Translation;
namespace MiraAPI.PluginLoading;
@@ -58,6 +59,11 @@ public interface IMiraPlugin
/// </summary>
string CustomOptionMenuTwoDescription => "Apply game settings for this mod!";
/// <summary>
/// Gets the translation id builder for this mod.
/// </summary>
TranslationIdBuilder IdBuilder => TranslationIdBuilder.Default;
/// <summary>
/// Gets the <see cref="ConfigFile"/> for the plugin.
/// </summary>
+6 -3
View File
@@ -280,11 +280,11 @@ public static class CustomRoleManager
while (data.Count > 0)
{
Rpc<SyncRoleOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.Dequeue());
Rpc<SyncRoleOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, new MiraOptionNetData(false, data.Dequeue()));
}
}
internal static void HandleSyncRoleOptions(NetData[] data)
internal static void HandleSyncRoleOptions(bool showNotifs, NetData[] data)
{
// necessary to disable then re-enable this setting
// we don't know how other plugins handle their configs
@@ -312,7 +312,10 @@ public static class CustomRoleManager
var num = BitConverter.ToInt32(netData.Data, 0);
var chance = BitConverter.ToInt32(netData.Data, 4);
HudManager.Instance.Notifier.AddRoleSettingsChangeMessage(role.StringName, num, chance, role.TeamType, false);
if (showNotifs)
{
HudManager.Instance.Notifier.AddRoleSettingsChangeMessage(role.StringName, num, chance, role.TeamType, false);
}
try
{
+43
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using BepInEx.Configuration;
using Il2CppInterop.Runtime.Attributes;
@@ -92,6 +93,48 @@ public interface ICustomRole : IOptionable
_ => MiraLocaleManager.Get("MiraApi.RoleTeam.Other"),
};
/// <summary>
/// Gets a value indicating whether to exclude the "amount" from the displayed amount text within the role guide.
/// </summary>
public virtual bool ShowRoleAsUnique => Configuration.MaxRoleCount == 1;
/// <summary>
/// Gets the displayed amount and chance of the role. Used in the role guide.
/// By default, 0 amount or chance results in a complete 0 amount and chance, so override if necessary.
/// </summary>
/// <param name="amount">The amount of the role.</param>
/// <param name="chance">The chance of the role.</param>
/// <param name="source">The mod source of the role.</param>
/// <returns>The <see cref="string"/> result.</returns>
public virtual string GetAmountText(int amount, int chance, string source)
{
if (amount == 0)
{
chance = 0;
}
if (ShowRoleAsUnique)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0}% ({1})",
chance.ToString(CultureInfo.InvariantCulture),
source);
}
if (chance == 0)
{
amount = 0;
}
return string.Format(
CultureInfo.InvariantCulture,
"{0} at {1}% ({2})",
amount.ToString(CultureInfo.InvariantCulture),
chance.ToString(CultureInfo.InvariantCulture),
source);
}
/// <summary>
/// Gets the <see cref="Color"/> of the role.
/// </summary>
@@ -0,0 +1,82 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace MiraAPI.Translation;
/// <summary>
/// A utility class that can build ids based on the provided id part.
/// </summary>
public class TranslationIdBuilder
{
/// <summary>
/// Gets an instance of <see cref="TranslationIdBuilder"/> that has default behaviour.
/// </summary>
public static TranslationIdBuilder Default { get; } = new();
/// <summary>
/// Constructs a translation id for a provided enum option value.
/// </summary>
/// <typeparam name="T">The type of the enum.</typeparam>
/// <param name="value">The enum option value.</param>
/// <returns>The constructed translation id.</returns>
public virtual string CreateEnumOptionId<T>(T value)
where T : Enum
{
return CreateEnumOptionId<T>(value.ToString());
}
/// <summary>
/// Constructs a translation id for a provided enum option value.
/// </summary>
/// <typeparam name="T">The type of the enum.</typeparam>
/// <param name="value">The enum option value.</param>
/// <returns>The constructed translation id.</returns>
public virtual string CreateEnumOptionId<T>(string value)
where T : Enum
{
return CreateEnumOptionId(value, typeof(T));
}
/// <summary>
/// Constructs a translation id for a provided enum option value.
/// </summary>
/// <param name="value">The enum option value.</param>
/// <param name="enumType">The type of the enum.</param>
/// <returns>The constructed translation id.</returns>
public virtual string CreateEnumOptionId(object value, Type enumType)
{
return CreateEnumOptionId(value.ToString()!, enumType);
}
/// <summary>
/// Constructs a translation id for a provided enum option value.
/// </summary>
/// <param name="value">The enum option value.</param>
/// <param name="enumType">The type of the enum.</param>
/// <returns>The constructed translation id.</returns>
public virtual string CreateEnumOptionId(string value, Type enumType)
{
return value;
}
/// <summary>
/// Constructs a translation id for a string option.
/// </summary>
/// <param name="option">The id part.</param>
/// <returns>The constructed translation id.</returns>
[SuppressMessage("Naming", "CA1716:Identifiers should not match keywords", Justification = "Just confusing.")]
public virtual string CreateStringOptionId(string option)
{
return option;
}
/// <summary>
/// Constructs a translation id for an option's title.
/// </summary>
/// <param name="title">The id part.</param>
/// <returns>The constructed translation id.</returns>
public virtual string CreateOptionTitleId(string title)
{
return title;
}
}
+31
View File
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace MiraAPI.Utilities;
/// <summary>
/// A comparer used to sort role/modifier IDs based on which ones are currently active.
/// </summary>
/// <param name="activeIds">The collection of currently active IDs.</param>
public class BasicGuideComparer(IEnumerable<uint> activeIds) : IComparer<uint>
{
/// <inheritdoc/>
public int Compare(uint x, uint y)
{
var xActive = activeIds.Contains(x);
var yActive = activeIds.Contains(y);
if (xActive && !yActive)
{
return -1;
}
if (!xActive && yActive)
{
return 1;
}
return 0;
}
}