Merge branch 'dev' into voting

This commit is contained in:
ang-xd committed 2025-03-15 17:34:42 -04:00
commit e25b78705b
18 files changed
+373 -54

No files matched your search

+1 -1
View File
@@ -35,7 +35,7 @@ public abstract class AbstractOptionGroup
/// <summary>
/// Gets the group color. This is used to color the group in the options menu.
/// </summary>
public virtual Color GroupColor => Color.clear;
public virtual Color GroupColor => MiraApiPlugin.DefaultHeaderColor;
/// <summary>
/// Gets the group priority. This is used to determine the order in which groups are displayed in the options menu.
+2 -3
View File
@@ -168,7 +168,7 @@ public abstract class CustomActionButton
/// Allows you to change the button's location.
/// </summary>
/// <param name="location">The new location.</param>
/// <param name="moveButton">Whether the button's position should change ingame.</param>
/// <param name="moveButton">Whether the button's position should change in-game.</param>
public virtual void SetButtonLocation(ButtonLocation location, bool moveButton = true)
{
if (!HudManager.InstanceExists || Button == null)
@@ -221,7 +221,6 @@ public abstract class CustomActionButton
/// A utility function to change the outline color of the button's text.
/// </summary>
/// <param name="color">The new color.</param>
/// <param name="thickness">The thickness of the outline. Set to 0 if you want to remove it.</param>
public virtual void SetTextOutline(Color color)
{
Button?.buttonLabelText.SetOutlineColor(color);
@@ -364,7 +363,7 @@ public abstract class CustomActionButton
/// <returns>A value that represents whether the button should light up or not.</returns>
public virtual bool CanUse()
{
return !LimitedUses || UsesLeft > 0;
return PlayerControl.LocalPlayer.moveable && (!LimitedUses || UsesLeft > 0);
}
/// <summary>
+9 -1
View File
@@ -1,12 +1,19 @@
using BepInEx;
using System;
using BepInEx;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using Il2CppInterop.Runtime;
using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using MonoMod.Utils;
using Reactor;
using Reactor.Networking;
using Reactor.Networking.Attributes;
using Reactor.Utilities;
using TMPro;
using UnityEngine;
using UnityEngine.ProBuilder;
using Delegate = Il2CppSystem.Delegate;
namespace MiraAPI;
@@ -20,6 +27,7 @@ namespace MiraAPI;
public partial class MiraApiPlugin : BasePlugin
{
public static Color MiraColor { get; } = new Color32(238, 154, 112, 255);
public static Color DefaultHeaderColor = new Color32(77, 77, 77, 255);
private static MiraPluginManager? PluginManager { get; set; }
internal Harmony Harmony { get; } = new(Id);
+24
View File
@@ -180,6 +180,30 @@ public static class ModifierExtensions
return player.GetModifierComponent().HasModifier(typeId, predicate);
}
/// <summary>
/// Clears all modifiers from a player.
/// </summary>
/// <param name="plr">The player you want to clear modifiers for.</param>
public static void ClearModifiers(this PlayerControl plr)
{
foreach (var mod in plr.GetModifierComponent().ActiveModifiers)
{
plr.RemoveModifier(mod);
}
}
/// <summary>
/// Clears all modifiers from a player.
/// </summary>
/// <param name="plr">The player you want to clear modifiers for.</param>
public static void RpcClearModifiers(this PlayerControl plr)
{
foreach (var mod in plr.GetModifierComponent().ActiveModifiers)
{
plr.RpcRemoveModifier(mod.UniqueId);
}
}
/// <summary>
/// Checks if the player has a specific modifier by its GUID.
/// </summary>
+33
View File
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace MiraAPI.Modifiers;
/// <summary>
/// Utilities to make handling modifiers in-game easier.
/// </summary>
public static class ModifierUtils
{
/// <summary>
/// Gets a list of all active in-game modifiers.
/// </summary>
/// <param name="predicate">Select if modifier is valid to be added to list.</param>
/// <typeparam name="T">The modifier type.</typeparam>
/// <returns>A list of modifiers.</returns>
public static IEnumerable<T> GetActiveModifiers<T>(Func<T, bool>? predicate = null) where T : BaseModifier
{
return PlayerControl.AllPlayerControls.ToArray().SelectMany(x => x.GetModifiers<T>(predicate)).OfType<T>();
}
/// <summary>
/// Gets all players with a certain modifier.
/// </summary>
/// <param name="predicate">Select if modifier is valid.</param>
/// <typeparam name="T">The modifier type.</typeparam>
/// <returns>A list of players with that modifier.</returns>
public static IEnumerable<PlayerControl> GetPlayersWithModifier<T>(Func<T, bool>? predicate = null) where T : BaseModifier
{
return PlayerControl.AllPlayerControls.ToArray().Where(x => x.HasModifier<T>(predicate));
}
}
+12 -13
View File
@@ -42,7 +42,7 @@ public static class GameOptionsMenuPatch
{
var filteredGroups =
GameSettingMenuPatches.SelectedMod?.OptionGroups
.Where(x => x.OptionableType == null) ?? [];
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu) ?? [];
foreach (var group in filteredGroups)
{
@@ -144,7 +144,7 @@ public static class GameOptionsMenuPatch
}
var filteredGroups = GameSettingMenuPatches.SelectedMod?.OptionGroups
.Where(x => x.OptionableType == null) ?? [];
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu) ?? [];
foreach (var group in filteredGroups)
{
@@ -198,16 +198,16 @@ public static class GameOptionsMenuPatch
menu.settingsContainer);
categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister(group.GroupName), 20);
if (group.GroupColor != Color.clear)
{
categoryHeaderMasked.Background.color = group.GroupColor;
categoryHeaderMasked.Divider.color = group.GroupColor;
categoryHeaderMasked.Title.color = group.GroupColor.FindAlternateColor();
}
categoryHeaderMasked.Background.color = group.GroupColor;
categoryHeaderMasked.Divider.color = group.GroupColor;
categoryHeaderMasked.Title.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) ? Color.white : group.GroupColor.FindAlternateColor();
categoryHeaderMasked.Background.sprite = MiraAssets.CategoryHeader.LoadAsset();
categoryHeaderMasked.Background.transform.localPosition = new Vector3(0.55f, -0.1833f, 0);
categoryHeaderMasked.Background.size = new Vector2(
categoryHeaderMasked.Background.size.x + 1.5f,
categoryHeaderMasked.Background.size.y);
categoryHeaderMasked.gameObject.SetActive(false);
group.Header = categoryHeaderMasked;
@@ -230,7 +230,7 @@ public static class GameOptionsMenuPatch
SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren<SpriteRenderer>(true);
foreach (var renderer in componentsInChildren)
{
if (group.GroupColor != Color.clear)
if (group.GroupColor != MiraApiPlugin.DefaultHeaderColor)
{
renderer.color = group.GroupColor.FindAlternateColor();
if (renderer.transform.parent.TryGetComponent<GameOptionButton>(out var btn))
@@ -245,7 +245,7 @@ public static class GameOptionsMenuPatch
foreach (var textMeshPro in newOpt.GetComponentsInChildren<TextMeshPro>(true))
{
if (group.GroupColor != Color.clear)
if (group.GroupColor != MiraApiPlugin.DefaultHeaderColor)
{
textMeshPro.color = group.GroupColor;
}
@@ -257,12 +257,11 @@ public static class GameOptionsMenuPatch
if (newOpt is ToggleOption toggle)
{
toggle.CheckMark.sprite = MiraAssets.Checkmark.LoadAsset();
toggle.CheckMark.color =
group.GroupColor != Color.clear ? group.GroupColor : MiraAssets.AcceptedTeal;
toggle.CheckMark.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor ? group.GroupColor : MiraAssets.AcceptedTeal;
var rend = toggle.CheckMark.transform.parent.FindChild("ActiveSprite")
.GetComponent<SpriteRenderer>();
rend.sprite = MiraAssets.CheckmarkBox.LoadAsset();
rend.color = group.GroupColor != Color.clear ? group.GroupColor : MiraAssets.AcceptedTeal;
rend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor ? group.GroupColor : MiraAssets.AcceptedTeal;
}
menu.Children.Add(newOpt);
@@ -330,8 +330,8 @@ public static class LobbyViewPanePatches
numPerGame,
chancePerGame,
61,
customRole.OptionsMenuColor,
customRole.Configuration.Icon.LoadAsset(),
customRole.RoleColor,
customRole.Configuration.Icon?.LoadAsset() ?? MiraAssets.Empty.LoadAsset(),
true);
viewSettingsInfoPanelRoleVariant.iconSprite.transform.localScale = new Vector3(0.365f, 0.365f, 1f);
viewSettingsInfoPanelRoleVariant.iconSprite.transform.localPosition = new Vector3(0.7144f, -0.028f, -2);
@@ -340,7 +340,7 @@ public static class LobbyViewPanePatches
viewSettingsInfoPanelRoleVariant.chanceTitle.color =
viewSettingsInfoPanelRoleVariant.chanceBackground.color =
viewSettingsInfoPanelRoleVariant.background.color =
customRole.OptionsMenuColor.FindAlternateColor();
customRole.RoleColor.GetAlternateColor();
instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject);
num -= 0.664f;
}
@@ -418,7 +418,7 @@ public static class LobbyViewPanePatches
role.StringName,
maskLayer,
role.TeamType == RoleTeamTypes.Crewmate,
customRole.Configuration.Icon.LoadAsset());
customRole.Configuration.Icon != null ? customRole.Configuration.Icon.LoadAsset() : MiraAssets.Empty.LoadAsset());
viewPanel.header.icon.transform.localScale = new Vector3(0.465f, 0.465f, 1f);
viewPanel.divider.material.SetInt(PlayerMaterial.MaskLayer, maskLayer);
@@ -127,32 +127,34 @@ public static class RoleSettingMenuPatches
chanceLabel.Destroy();
countLabel.Destroy();
if (name is StringNames.CrewmateRolesHeader)
{
categoryHeaderMasked.Title.color = Palette.CrewmateRoleHeaderTextBlue;
categoryHeaderMasked.Background.color = Palette.CrewmateRoleHeaderBlue;
}
else if (name is StringNames.ImpostorRolesHeader)
{
categoryHeaderMasked.Title.color = Palette.ImpostorRoleHeaderTextRed;
categoryHeaderMasked.Background.color = Palette.ImpostorRoleHeaderRed;
}
else
{
categoryHeaderMasked.Title.color = group.Color;
categoryHeaderMasked.Divider.color = group.Color;
categoryHeaderMasked.Background.color = group.Color;
}
categoryHeaderMasked.Background.sprite = MiraAssets.CategoryHeader.LoadAsset();
categoryHeaderMasked.Background.transform.localPosition = new Vector3(0.55f, -0.1833f, 0);
categoryHeaderMasked.Title.color = categoryHeaderMasked.Title.color.IsColorDark() ?
categoryHeaderMasked.Title.color.LightenColor(0.3f) : categoryHeaderMasked.Title.color.LightenColor(0.1f);
switch (name)
{
case StringNames.CrewmateRolesHeader:
categoryHeaderMasked.Title.color = Palette.CrewmateRoleHeaderTextBlue;
categoryHeaderMasked.Background.color = Palette.CrewmateRoleHeaderBlue;
break;
case StringNames.ImpostorRolesHeader:
categoryHeaderMasked.Title.color = Palette.ImpostorRoleHeaderTextRed;
categoryHeaderMasked.Background.color = Palette.ImpostorRoleHeaderRed;
break;
default:
categoryHeaderMasked.Title.color = group.Color.Equals(MiraApiPlugin.DefaultHeaderColor) ? Color.white : group.Color.FindAlternateColor();
categoryHeaderMasked.Divider.color = group.Color;
categoryHeaderMasked.Background.color = group.Color;
break;
}
categoryHeaderMasked.Title.fontStyle = __instance.categoryHeaderEditRoleOrigin.Title.fontStyle;
categoryHeaderMasked.Title.font = __instance.categoryHeaderEditRoleOrigin.Title.font;
categoryHeaderMasked.Title.fontMaterial = __instance.categoryHeaderEditRoleOrigin.Title.fontMaterial;
categoryHeaderMasked.Divider.color = categoryHeaderMasked.Background.color;
categoryHeaderMasked.Background.transform.localPosition = new Vector3(0.55f, -0.1833f, 0);
categoryHeaderMasked.Background.size = new Vector2(categoryHeaderMasked.Background.size.x + 1.5f, categoryHeaderMasked.Background.size.y);
categoryHeaderMasked.transform.localScale = Vector3.one * 0.63f;
categoryHeaderMasked.transform.localPosition = new Vector3(-0.44f, ScrollerNum, -2f);
categoryHeaderMasked.gameObject.SetActive(true);
+11
View File
@@ -33,4 +33,15 @@ public static class MinigameStubs
{
// nothing needed
}
/// <summary>
/// Fixes the error when creating custom minigames. Credit to Submerged for discovering this fix.
/// </summary>
/// <param name="__instance">The Minigame instance.</param>
[HarmonyPrefix]
[HarmonyPatch(typeof(Minigame), nameof(Minigame.Begin))]
public static void MinigameErrorFixPatch(Minigame __instance)
{
__instance.logger ??= new Logger("Minigame", Logger.Level.Info, Logger.Category.Gameplay);
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

+9 -3
View File
@@ -1,6 +1,8 @@
using AmongUs.GameOptions;
using Il2CppSystem.Web.Util;
using MiraAPI.Utilities.Assets;
using UnityEngine;
using Helpers = MiraAPI.Utilities.Helpers;
namespace MiraAPI.Roles;
@@ -32,7 +34,6 @@ public record struct CustomRoleConfiguration
TasksCountForProgress = role.Team is ModdedRoleTeams.Crewmate;
HideSettings = roleBehaviour?.IsDead == true;
ShowInFreeplay = roleBehaviour?.IsDead == false;
OptionsScreenshot = null;
}
/// <summary>
@@ -58,12 +59,17 @@ public record struct CustomRoleConfiguration
/// <summary>
/// Gets or sets the Sprite used for the Role Options menu screenshot.
/// </summary>
public LoadableAsset<Sprite>? OptionsScreenshot { get; set; }
public LoadableAsset<Sprite>? OptionsScreenshot { get; set; } = null;
/// <summary>
/// Gets or sets the Sprite used for the Role Icon.
/// </summary>
public LoadableAsset<Sprite> Icon { get; set; } = MiraAssets.Empty;
public LoadableAsset<Sprite>? Icon { get; set; } = null;
/// <summary>
/// Gets or sets the Intro sound for the Role.
/// </summary>
public LoadableAsset<AudioClip>? IntroSound { get; set; } = null;
/// <summary>
/// Gets or sets a value indicating whether the role is affected by light affectors on Airship.
+16
View File
@@ -13,6 +13,7 @@ using Reactor.Utilities;
using Reactor.Utilities.Extensions;
using TMPro;
using UnityEngine;
using UnityEngine.TextCore;
using Object = UnityEngine.Object;
namespace MiraAPI.Roles;
@@ -93,6 +94,21 @@ public static class CustomRoleManager
roleBehaviour.MaxCount = customRole.Configuration.MaxRoleCount;
roleBehaviour.RoleScreenshot = customRole.Configuration.OptionsScreenshot?.LoadAsset();
if (customRole.Configuration.Icon != null)
{
var asset = customRole.Configuration.Icon.LoadAsset();
if (asset != null)
{
roleBehaviour.RoleIconSolid = asset;
roleBehaviour.RoleIconWhite = asset;
}
}
if (customRole.Configuration.IntroSound != null)
{
roleBehaviour.IntroSound = customRole.Configuration.IntroSound.LoadAsset();
}
if (roleBehaviour.IsDead)
{
RoleManager.GhostRoles.Add(roleBehaviour.Role);
+49
View File
@@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using AmongUs.GameOptions;
using UnityEngine;
namespace MiraAPI.Roles;
/// <summary>
/// Utilities to make handling roles in-game easier.
/// </summary>
public static class CustomRoleUtils
{
/// <summary>
/// Gets all active in-game roles.
/// </summary>
/// <returns>A list of roles.</returns>
// ReSharper disable once MemberCanBePrivate.Global
public static IEnumerable<RoleBehaviour> GetActiveRoles() => PlayerControl.AllPlayerControls.ToArray().Select(x => x.Data.Role);
/// <summary>
/// Gets all active in-game roles in a certain team.
/// </summary>
/// <param name="team">The team you would like to check for.</param>
/// <returns>A list of roles with the team.</returns>
public static IEnumerable<RoleBehaviour> GetActiveRolesOfTeam(ModdedRoleTeams team) => GetActiveRoles().Where(x => x is ICustomRole customRole && customRole.Team == team);
/// <summary>
/// Gets all active in-game roles of a certain type.
/// </summary>
/// <typeparam name="T">The role Type you would like to check for. Must be a RoleBehaviour.</typeparam>
/// <returns>A list of roles with that specific type.</returns>
public static IEnumerable<T> GetActiveRolesOfType<T>() where T : RoleBehaviour => GetActiveRoles().OfType<T>();
/// <summary>
/// Creates a string builder for the Role Tab.
/// </summary>
/// <param name="role">The ICustomRole object.</param>
/// <returns>A StringBuilder.</returns>
public static StringBuilder CreateForRole(ICustomRole role)
{
var taskStringBuilder = new StringBuilder();
taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleColor.ToTextColor()}You are a <b>{role.RoleName}.</b></color>");
taskStringBuilder.Append("<size=70%>");
taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleLongDescription}");
return taskStringBuilder;
}
}
+1 -1
View File
@@ -206,7 +206,7 @@ public interface ICustomRole : IOptionable
/// Get the custom Role Tab text for this role.
/// </summary>
/// <returns>A StringBuilder with the role tab text.</returns>
StringBuilder SetTabText() => Helpers.CreateForRole(this);
StringBuilder SetTabText() => CustomRoleUtils.CreateForRole(this);
/// <summary>
/// Determine whether a given modifier can be applied to this role.
+1
View File
@@ -10,6 +10,7 @@ public static class MiraAssets
public static readonly LoadableResourceAsset NextButtonActive = new("MiraAPI.Resources.NextButtonActive.png");
public static readonly LoadableResourceAsset Cog = new("MiraAPI.Resources.Cog.png");
public static readonly LoadableResourceAsset Checkmark = new("MiraAPI.Resources.Checkmark.png");
public static readonly LoadableResourceAsset CategoryHeader = new("MiraAPI.Resources.CategoryHeader.png");
public static readonly LoadableResourceAsset CheckmarkBox = new("MiraAPI.Resources.CheckMarkBox.png");
public static readonly Color32 AcceptedTeal = new(43, 233, 198, 255);
}
+5 -1
View File
@@ -1,4 +1,5 @@
using System;
using System.IO;
using System.Reflection;
using Reactor.Utilities.Extensions;
using UnityEngine;
@@ -30,6 +31,9 @@ public static class SpriteTools
throw new ArgumentException($"Resource not found: {resourcePath}");
}
return Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), pixelsPerUnit);
tex.name = resourcePath;
var sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f), pixelsPerUnit);
sprite.name = resourcePath;
return sprite;
}
}
+36
View File
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using HarmonyLib;
using MiraAPI.GameOptions;
using MiraAPI.Networking;
@@ -40,6 +41,30 @@ public static class Extensions
BitConverter.GetBytes(count.Value).AddRangeToArray(BitConverter.GetBytes(chance.Value)));
}
/// <summary>
/// Gets a PlayerControl from their PlayerVoteArea in a meeting.
/// </summary>
/// <param name="state">The vote area.</param>
/// <returns>The player's PlayerControl.</returns>
public static PlayerControl? GetPlayer(this PlayerVoteArea state) => GameData.Instance.GetPlayerById(state.TargetPlayerId)?.Object;
/// <summary>
/// Gets an int representing the amount of tasks a player has left.
/// </summary>
/// <param name="player">The player.</param>
/// <returns>A count of how many tasks the player has left.</returns>
public static int GetTasksLeft(this PlayerControl player) => player.Data.Tasks.ToArray().Count(x => !x.Complete);
/// <summary>
/// Checks if a PlayerControl is the game's host.
/// </summary>
/// <param name="playerControl">The player you're checking for.</param>
/// <returns>If the player is the host, true, else false.</returns>
public static bool IsHost(this PlayerControl playerControl)
{
return TutorialManager.InstanceExists || AmongUsClient.Instance.HostId == playerControl.OwnerId;
}
/// <summary>
/// Determines if a float is an integer.
/// </summary>
@@ -131,6 +156,17 @@ public static class Extensions
.FirstOrDefault();
}
/// <summary>
/// Gets a proper string for an enum. (with spaces).
/// </summary>
/// <param name="enum">The enum you would like to change.</param>
/// <returns>A proper string for the enum.</returns>
public static string ToDisplayString(this Enum @enum)
{
var regex = new Regex(@"([^\^])([A-Z][a-z$])");
return regex.Replace(@enum.ToString(), m => $"{m.Groups[1].Value} {m.Groups[2].Value}");
}
/// <summary>
/// Gets the constructors of a type that match the specified arguments.
/// </summary>
+140 -9
View File
@@ -1,11 +1,17 @@
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using AmongUs.GameOptions;
using HarmonyLib;
using MiraAPI.Roles;
using Reactor.Utilities;
using Reactor.Utilities.Extensions;
using TMPro;
using UnityEngine;
using UnityEngine.TextCore;
using Object = UnityEngine.Object;
namespace MiraAPI.Utilities;
@@ -24,6 +30,123 @@ public static class Helpers
return PlayerControl.AllPlayerControls.ToArray().Where(x => !x.Data.IsDead).ToList();
}
/// <summary>
/// Determines whether a given probability check succeeds.
/// </summary>
/// <param name="probability">An integer value representing the success probability (0-100).</param>
/// <returns>True if the number falls in the range, false if not.</returns>
public static bool CheckChance(int probability)
{
switch (probability)
{
case 0:
return false;
case 100:
return true;
default:
{
var num = Random.RandomRangeInt(1, 101);
return num <= probability;
}
}
}
/// <summary>
/// Retrieves a vent from the ID.
/// </summary>
/// <param name="id">The vent ID.</param>
/// <returns>The vent.</returns>
public static Vent? GetVentById(int id)
{
return ShipStatus.Instance.AllVents.FirstOrDefault(vent => vent.Id == id);
}
/// <summary>
/// Creates an arrow.
/// </summary>
/// <param name="parent">The arrow gameObject's parent.</param>
/// <param name="color">The color of the arrow.</param>
/// <returns>The created ArrowBehaviour.</returns>
public static ArrowBehaviour CreateArrow(Transform parent, Color color)
{
var prefab = Object.FindObjectOfType<ArrowBehaviour>(true);
var arrow = Object.Instantiate(prefab, parent);
arrow.image = arrow.gameObject.GetComponent<SpriteRenderer>();
arrow.image.color = color;
arrow.gameObject.layer = 5;
arrow.gameObject.SetActive(true);
return arrow;
}
/// <summary>
/// Get the closest object of a specific type.
/// </summary>
/// <param name="objectList">A list of all the objects you'd like to check the distance for.</param>
/// <param name="position">The position of where you want to check from. For example: PlayerControl.LocalPlayer.transform.position.</param>
/// <typeparam name="T">The object type.</typeparam>
/// <returns>The closest object.</returns>
public static T? FindClosestObjectOfType<T>(List<T> objectList, Vector3 position) where T : MonoBehaviour
{
T? closest = null;
var closestDistanceSqr = Mathf.Infinity;
foreach (var obj in objectList)
{
if (obj == null)
{
continue;
}
var sqrDistance = (obj.transform.position - position).sqrMagnitude;
if (sqrDistance < closestDistanceSqr)
{
closestDistanceSqr = sqrDistance;
closest = obj;
}
}
return closest;
}
/// <summary>
/// Creates and shows a notification.
/// </summary>
/// <param name="text">The text you want to display.</param>
/// <param name="color">The color of the text and image.</param>
/// <param name="clip">The sound you want to play with the notification.</param>
/// <param name="spr">The sprite beside the notification.</param>
/// <returns>The created notification.</returns>
public static LobbyNotificationMessage CreateAndShowNotification(string text, Color color, AudioClip? clip = null, Sprite? spr = null)
{
return CreateAndShowNotification(text, color, new Vector3(0f, 0f, -2f), clip, spr);
}
/// <summary>
/// Creates and shows a notification.
/// </summary>
/// <param name="text">The text you want to display.</param>
/// <param name="color">The color of the text and image.</param>
/// <param name="localPos">The position of the notification.</param>
/// <param name="clip">The sound you want to play with the notification.</param>
/// <param name="spr">The sprite beside the notification.</param>
/// <returns>The created notification.</returns>
public static LobbyNotificationMessage CreateAndShowNotification(string text, Color color, Vector3 localPos, AudioClip? clip = null, Sprite? spr = null)
{
var popper = HudManager.Instance.Notifier;
var newMessage = Object.Instantiate(popper.notificationMessageOrigin, Vector3.zero, Quaternion.identity, popper.transform);
newMessage.transform.localPosition = localPos;
newMessage.SetUp(text, spr ?? null, color, new System.Action(() => popper.OnMessageDestroy(newMessage)));
popper.lastMessageKey = -1;
popper.ShiftMessages();
popper.AddMessageToQueue(newMessage);
if (clip == null) return newMessage;
SoundManager.Instance.StopSound(clip);
SoundManager.Instance.PlaySound(clip, false, 2f);
return newMessage;
}
/// <summary>
/// Returns an empty coroutine.
/// </summary>
@@ -265,16 +388,24 @@ public static class Helpers
}
/// <summary>
/// Creates a string builder for the Role Tab.
/// Converts the first letter of a string to uppercase.
/// </summary>
/// <param name="role">The ICustomRole object.</param>
/// <returns>A StringBuilder.</returns>
public static StringBuilder CreateForRole(ICustomRole role)
/// <param name="str">The string.</param>
/// <returns>The fixed string.</returns>
public static string FirstLetterToUpper(string str)
{
var taskStringBuilder = new StringBuilder();
taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleColor.ToTextColor()}You are a <b>{role.RoleName}.</b></color>");
taskStringBuilder.Append("<size=70%>");
taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleLongDescription}");
return taskStringBuilder;
return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(str.ToLower(CultureInfo.InvariantCulture));
}
/// <summary>
/// Gets a random string based on characters.
/// </summary>
/// <param name="length">The length of the string.</param>
/// <param name="chars">The characters in the random string.</param>
/// <returns>The random string.</returns>
public static string RandomString(int length, string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
{
return new string(Enumerable.Repeat(chars, length)
.Select(s => s[Random.RandomRangeInt(0, s.Length)]).ToArray());
}
}