Merge branch 'master' into cleanup

This commit is contained in:
Az committed 2026-09-04 15:06:03 -04:00
commit 7099e377ac
26 files changed
+681 -192

No files matched your search

+1 -1
View File
@@ -8,7 +8,7 @@
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<VersionPrefix>0.4.3</VersionPrefix>
<VersionPrefix>0.5.0</VersionPrefix>
<VersionSuffix>dev</VersionSuffix>
<Authors>All Of Us, XtraCube, Angxl</Authors>
@@ -0,0 +1,30 @@
using UnityEngine;
namespace MiraAPI.Events.Mira;
/// <summary>
/// Event fired by Mira API to adjust the UI buttons at the top right of the screen.
/// </summary>
public class UiButtonPostResetEvent : MiraEvent
{
/// <summary>
/// Gets the <see cref="GameObject"/> parent for the very top UI row.
/// </summary>
public GameObject MainTopUiRow { get; }
/// <summary>
/// Gets the <see cref="GameObject"/> parent for the second top UI row.
/// </summary>
public GameObject SecondTopUiRow { get; }
/// <summary>
/// Initializes a new instance of the <see cref="UiButtonPostResetEvent"/> class.
/// </summary>
/// <param name="topUiRow">The <see cref="GameObject"/> for the top UI row.</param>
/// <param name="secondUiRow">The <see cref="GameObject"/> for the second UI row.</param>
public UiButtonPostResetEvent(GameObject topUiRow, GameObject secondUiRow)
{
MainTopUiRow = topUiRow;
SecondTopUiRow = secondUiRow;
}
}
+15
View File
@@ -0,0 +1,15 @@
namespace MiraAPI.Events.Mira;
/// <summary>
/// Event fired by Mira API prior to adjusting the UI buttons at the top right of the screen.
/// </summary>
public class UiButtonResetEvent : MiraEvent
{
/// <summary>
/// Initializes a new instance of the <see cref="UiButtonResetEvent"/> class.
/// </summary>
public UiButtonResetEvent()
{
// nothing!
}
}
@@ -1,6 +1,7 @@
using System.Collections;
using System.Linq;
using BepInEx.Unity.IL2CPP.Utils.Collections;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.GameOptions;
using MiraAPI.HnsReimplemented.Options;
using MiraAPI.Utilities;
@@ -245,6 +246,7 @@ public sealed class HideAndSeekHudHelper(nint cppPtr) : MonoBehaviour(cppPtr)
}
}
[HideFromIl2Cpp]
private IEnumerator BeepAlmostEverySecond()
{
while (!IsFinalCountdown)
+1 -1
View File
@@ -186,7 +186,7 @@ public sealed class MiraHudHelper(nint cppPtr) : MonoBehaviour(cppPtr)
{
ExtraUiTopRight = new GameObject("ExtraUiTopRight")
{
layer = UiTopRight.layer
layer = UiTopRight.layer,
};
ExtraUiTopRight.transform.SetParent(instance.MapButton.transform.parent.parent, false);
@@ -8,7 +8,7 @@ namespace MiraAPI.Hud;
/// <see cref="CustomActionButton"/> that has multiple target objects.
/// </summary>
/// <typeparam name="T">The type of the target objects.</typeparam>
public abstract class MultiTargetMeetingButton<T> : CustomActionButton where T : MonoBehaviour
public abstract class MultiTargetButton<T> : CustomActionButton where T : MonoBehaviour
{
/// <summary>
/// Gets or sets the list of targets of the button.
@@ -1,4 +1,5 @@
using System.Globalization;
using Il2CppInterop.Runtime.Attributes;
using Reactor.Utilities.Attributes;
using Reactor.Utilities.Extensions;
using TMPro;
@@ -52,6 +53,7 @@ public class MeetingAbilityBehaviour : MonoBehaviour
/// </summary>
/// <param name="button">The <see cref="TargetedMeetingButton"/> to initialize this component for.</param>
/// <param name="voteArea">The parent <see cref="PlayerVoteArea"/>.</param>
[HideFromIl2Cpp]
public void Initialize(TargetedMeetingButton button, PlayerVoteArea voteArea)
{
if (_init) return;
@@ -67,6 +67,26 @@ public abstract class MeetingActionButton
/// </summary>
public bool TimerPaused { get; set; }
/// <summary>
/// Gets or sets a value indicating how many times the ability was used in the current meeting.
/// </summary>
public int UsesSoFar { get; set; }
/// <summary>
/// Gets a value determining if the abiity is disabled upon being used.
/// </summary>
public virtual bool DisableUponUse => false;
/// <summary>
/// Gets a value determining if the abiity is disabled upon voting.
/// </summary>
public virtual bool DisableUponVoting => false;
/// <summary>
/// Gets a value determining if the abiity is hidden when votes wrapping up.
/// </summary>
public virtual bool HideUponWrapUp => false;
/// <summary>
/// Gets or sets the amount of uses left.
/// </summary>
@@ -103,6 +123,7 @@ public abstract class MeetingActionButton
Button.graphic.sprite = Sprite.LoadAsset();
Button.SetUsesRemaining(UsesLeft);
UsesSoFar = 0;
if (MaxUses <= 0)
{
Button.SetInfiniteUses();
@@ -260,7 +281,9 @@ public abstract class MeetingActionButton
/// <returns>A value that represents whether the button should light up or not.</returns>
public virtual bool CanUse()
{
return !LimitedUses || UsesLeft > 0;
return (!DisableUponUse || UsesSoFar < 1) &&
(!DisableUponVoting || MeetingHud.Instance.CurrentState is not MeetingHud.MeetingStates.Voted) &&
(!LimitedUses || UsesLeft > 0);
}
/// <summary>
@@ -283,6 +306,11 @@ public abstract class MeetingActionButton
Timer = Cooldown;
OnClick();
UsesSoFar++;
if (DisableUponUse && Button)
{
Button!.SetDisabled();
}
}
/// <summary>
@@ -99,8 +99,9 @@ public static class MeetingButtonManager
{
var btn = ability.CreateButton(playerVoteArea);
btn.transform.SetParent(playerVoteArea.Buttons.transform);
playerVoteArea.CancelButton.transform.SetAsFirstSibling();
}
var btn2 = ability.CreateButton(meetingHud.SkipVoteButton);
btn2.transform.SetParent(meetingHud.SkipVoteButton.Buttons.transform);
if (ability.UsesMode == MeetingButtonUsesMode.PerMeeting) ability.UsesLeft = ability.MaxUses;
if (ability is MultiTargetMeetingButton multiAbility) multiAbility.Targets = new();
@@ -110,5 +111,10 @@ public static class MeetingButtonManager
Error($"Failed to create targeted meeting button {ability.Name}: {e.Message}");
}
}
foreach (var playerVoteArea in meetingHud.playerStates)
{
playerVoteArea.CancelButton.transform.SetAsFirstSibling();
}
meetingHud.SkipVoteButton.CancelButton.transform.SetAsFirstSibling();
}
}
@@ -149,6 +149,6 @@ public abstract class TargetedMeetingButton
/// <returns>Whether the target is valid or not.</returns>
public virtual bool IsTargetValid(PlayerVoteArea playerVoteArea)
{
return playerVoteArea != null && playerVoteArea.PlayerId.Value != PlayerControl.LocalPlayer.Data.PlayerId;
return playerVoteArea != null && playerVoteArea.PlayerId.Value != PlayerControl.LocalPlayer.Data.PlayerId && playerVoteArea.PlayerId.Value != PlayerVoteArea.SkippedVote;
}
}
+1
View File
@@ -71,6 +71,7 @@ public partial class MiraApiPlugin : BasePlugin, IMiraPlugin
public override void Load()
{
Harmony.PatchAll();
UiResetEvents.Initialize();
JudgeEvents.Initialize();
ReactorCredits.Register("Mira API", Version, IsDevBuild, ReactorCredits.AlwaysShow);
+7 -44
View File
@@ -1,5 +1,7 @@
using System.Collections;
using BepInEx.Configuration;
using MiraAPI.Events;
using MiraAPI.Events.Mira;
using MiraAPI.Hud;
using MiraAPI.LocalSettings;
using MiraAPI.LocalSettings.Attributes;
@@ -82,57 +84,18 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
{
var topUi = MiraHudHelper.UiTopRight;
var extraTopUi = MiraHudHelper.ExtraUiTopRight;
var wikiButton = MiraHudHelper.VanillaMatchInfoButton;
var subButton = MiraHudHelper.SubmergedFloorButton;
var modDisplay = MiraHudHelper.ModifierDisplayOnRight ? MiraHudHelper.ModifierDisplayObject : null!;
ResetButtonPositions();
if (topUi && extraTopUi)
{
var opts = LocalSettingsTabSingleton<MiraApiSettings>.Instance;
if (wikiButton)
{
wikiButton.transform.SetParent(opts.WikiOnBottomRow.Value ? extraTopUi.transform : topUi.transform);
}
if (subButton)
{
subButton.transform.SetParent(extraTopUi.transform);
}
if (modDisplay)
{
modDisplay.transform.SetParent(extraTopUi.transform);
}
var genericEvent = new UiButtonResetEvent();
MiraEventManager.InvokeEvent(genericEvent);
var genericEvent2 = new UiButtonPostResetEvent(topUi, extraTopUi);
MiraEventManager.InvokeEvent(genericEvent2);
MiraHudHelper.UiGrid.ArrangeChilds();
MiraHudHelper.ExtraUiGrid.ArrangeChilds();
}
}
/// <summary>
/// Resets the button positions in the UI.
/// </summary>
public static void ResetButtonPositions()
{
var topUi = MiraHudHelper.UiTopRight;
var extraTopUi = MiraHudHelper.ExtraUiTopRight;
var subButton = MiraHudHelper.SubmergedFloorButton;
var modDisplay = MiraHudHelper.ModifierDisplayOnRight ? MiraHudHelper.ModifierDisplayObject : null!;
if (topUi && extraTopUi)
{
var wikiButton = MiraHudHelper.VanillaMatchInfoButton;
if (wikiButton)
{
wikiButton.transform.SetParent(null);
}
if (subButton)
{
subButton.transform.SetParent(null);
}
if (modDisplay)
{
modDisplay.transform.SetParent(null);
}
}
}
/// <summary>
/// Resizes the UI as a coroutine.
/// </summary>
+112 -2
View File
@@ -1,10 +1,14 @@
using System.Globalization;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using AmongUs.GameOptions;
using HarmonyLib;
using Innersloth.Assets;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using UnityEngine;
using Object = UnityEngine.Object;
namespace MiraAPI.Patches;
@@ -77,7 +81,29 @@ public static class RoleGuidePatches
{
__instance.numOfTabs = 3;
__instance.TabButtons[0].SelectButton(true);
__instance.CreateNormalModeSettings();
DisplayNormalRoleSettings(__instance, true);
__instance.MatchInfoRoleMaskArea.transform.localPosition = new Vector3(-0.0184f, 0.15f, -0.1f);
__instance.matchInfoPlayersMaskArea.transform.localPosition =
__instance.matchInfoSettingsMaskArea.transform.localPosition = new Vector3(1.22f, -0.335f, -0.1f);
__instance.matchInfoPlayersMaskArea.size =
__instance.matchInfoSettingsMaskArea.size =
__instance.MatchInfoRoleMaskArea.size = new Vector2(-6, 1.8f);
__instance.matchInfoPlayersMaskArea.transform.parent.GetAllChildren().First(x => x.name.Contains("BG_Gradient"))
.GetComponent<SpriteRenderer>()
.maskInteraction =
__instance.matchInfoSettingsMaskArea.transform.parent.GetAllChildren()
.First(x => x.name.Contains("BG_Gradient")).GetComponent<SpriteRenderer>()
.maskInteraction =
__instance.MatchInfoRoleMaskArea.transform.parent.GetAllChildren()
.First(x => x.name.Contains("BG_Gradient")).GetComponent<SpriteRenderer>()
.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
}
else
{
DisplayNormalRoleSettings(__instance, false);
}
}
else if (__instance.HnSModeSettings.Count == 0)
@@ -98,6 +124,90 @@ public static class RoleGuidePatches
__instance.SetActiveTab(0);
return false;
}
private static Dictionary<RoleBehaviour, MatchInfoRolePanel> _rolePanels = [];
public static void DisplayNormalRoleSettings(MatchInfoGuide instance, bool reset)
{
if (reset)
{
_rolePanels.Clear();
instance.CreateSettingsEntry(
StringNames.GameNumImpostors,
GameManager.Instance.AllGameSettingData[StringNames.GameNumImpostors]
.GetValueString(GameManager.Instance.LogicOptions.NumImpostors));
instance.CreateSettingsEntry(
StringNames.GameKillCooldown,
GameManager.Instance.AllGameSettingData[StringNames.GameKillCooldown]
.GetValueString(GameManager.Instance.LogicOptions.GetKillCooldown()));
instance.CreateSettingsEntry(
StringNames.GameEmergencyCooldown,
GameManager.Instance.AllGameSettingData[StringNames.GameEmergencyCooldown]
.GetValueString((float)GameManager.Instance.LogicOptions.GetEmergencyCooldown()));
instance.CreateSettingsEntry(
StringNames.GameVisualTasks,
instance.GetBoolString(GameManager.Instance.LogicOptions.GetVisualTasks()));
instance.CreateSettingsEntry(
StringNames.GameAnonymousVotes,
instance.GetBoolString(GameManager.Instance.LogicOptions.GetAnonymousVotes()));
instance.CreateSettingsEntry(
StringNames.GameConfirmImpostor,
instance.GetBoolString(GameManager.Instance.LogicOptions.GetConfirmImpostor()));
instance.CreateSettingsEntry(
StringNames.GameTaskBarMode,
GameManager.Instance.LogicOptions.GetTaskBarMode().ToString());
foreach (RoleBehaviour roleBehaviour in RoleManager.Instance.AllRoles)
{
if (roleBehaviour.Role != RoleTypes.Crewmate && roleBehaviour.Role != RoleTypes.Impostor &&
roleBehaviour.Role is not RoleTypes.CrewmateGhost &&
roleBehaviour.Role is not RoleTypes.ImpostorGhost)
{
var panel = Object.Instantiate(
instance.MatchInfoRolePanelPrefab,
instance.settingsTabs[2].GetComponent<Scroller>().Inner);
_rolePanels.Add(roleBehaviour, panel);
}
}
}
int num = 0;
foreach (var pair in _rolePanels)
{
var role = pair.Key;
var panel = pair.Value;
var amount = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(role.Role);
var chance = GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(role.Role);
var forciblyShow = role is ICustomRole custom ? custom.ForceShowRoleOnWiki : null;
if (amount == 0 || chance == 0 || (Enum.IsDefined(role.Role) && role.IsRoleBlacklisted()) ||
(role is ICustomRole custom2 && ((!custom2.CanSpawnOnCurrentMode() && forciblyShow == null) ||
(forciblyShow.HasValue && !forciblyShow.Value))))
{
panel.gameObject.SetActive(false);
continue;
}
panel.gameObject.SetActive(true);
panel.SetPanel(
role,
amount,
chance);
num++;
}
if (num == 0)
{
instance.rolesEnabledMessage.SetActive(true);
}
instance.MatchInfoRoleScroller.SetYBoundsMax(Mathf.Clamp(Mathf.Ceil((float)num / 2f) * 1.3f - 1.5f, 0f, 999f));
instance.MatchInfoRoleMaskArea.material.SetInt(PlayerMaterial.MaskLayer, 50);
instance.matchInfoSettingsMaskArea.material.SetInt(PlayerMaterial.MaskLayer, 50);
if (reset)
{
instance.CreatePlayerEntries();
}
}
[HarmonyPrefix]
[HarmonyPriority(Priority.First)]
[HarmonyPatch(typeof(MatchInfoGuide), nameof(MatchInfoGuide.CreatePlayerEntries))]
@@ -112,6 +112,15 @@ internal static class MeetingHudPatches
[HarmonyPatch(nameof(MeetingHud.VotingComplete))]
public static void VotingCompletePatch(MeetingHud __instance)
{
foreach (var meetingAbility in MeetingButtonManager.UntargetedButtons.Where(x => x.Enabled(PlayerControl.LocalPlayer.Data.Role) &&
(x.DisableUponVoting || x.HideUponWrapUp)))
{
meetingAbility.UpdateHandler(__instance);
if (meetingAbility.Button)
{
meetingAbility.Button!.SetDisabled();
}
}
MiraEventManager.InvokeEvent(new VotingCompleteEvent(__instance));
}
+40 -11
View File
@@ -1,14 +1,43 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="MiraApi.TopRightButtonScaleFactor">Escala De Botón</string>
<string name="MiraApi.WikiOnBottomRow">Botón de Wiki debajo de la IU principal</string>
<string name="Roles">Roles</string>
<string name="Modifier">Modificador</string>
<string name="Modifiers">Modificadores</string>
<string name="MiraApi.RoleTeam.Crewmate">Tripulante</string>
<string name="MiraApi.RoleTeam.Crewmate.Short">Trip</string>
<string name="MiraApi.RoleTeam.Impostor">Impostor</string>
<string name="MiraApi.RoleTeam.Impostor.Short">Imp</string>
<string name="MiraApi.RoleTeam.Neutral">Neutral</string>
<string name="MiraApi.RoleTeam.Neutral.Short">Neut</string>
<string name="MiraApi.TopRightButtonScaleFactor">Factor de Escala de la IU superior derecha</string>
<string name="MiraApi.WikiOnBottomRow">Botón de Wiki debajo de la IU principal</string>
<string name="Roles">Roles</string>
<string name="Modifier">Modificador</string>
<string name="Modifiers">Modificadores</string>
<string name="MiraApi.RoleTeam.Crewmate">Tripulante</string>
<string name="MiraApi.RoleTeam.Crewmate.Short">Trip</string>
<string name="MiraApi.RoleTeam.Impostor">Impostor</string>
<string name="MiraApi.RoleTeam.Impostor.Short">Imp</string>
<string name="MiraApi.RoleTeam.Neutral">Neutral</string>
<string name="MiraApi.RoleTeam.Neutral.Short">Neut</string>
<string name="MiraApi">API Mira</string>
<string name="MiraApi.VisualsUi">Visuales/IU</string>
<string name="MiraApi.Accessibility">Accesibilidad</string>
<string name="MiraApi.MiraApiBrand">MiraAPI</string>
<string name="MiraApi.ShowModifiersHudOnLeftSide">Mostrar Modificadores al Lado Izq</string>
<string name="MiraApi.ShowKeybindsOnButtons">Mostrar Atajos en Botones</string>
<string name="MiraApi.ButtonScaleFactor">Escala de Botón</string>
<string name="MiraApi.MaxFps">FPS Máx</string>
<string name="MiraApi.EnableSabotageFlashes">Activar Visuales de Sabotaje</string>
<string name="MiraApi.EnableSabotageBlare">Activar Alarma de Sabotaje</string>
<string name="MiraApi.ReturnToRoleSettings">REGRESAR A AJUSTES DE ROL</string>
<string name="MiraApi.CustomCategory1">Categoría Personalizada 1</string>
<string name="MiraApi.CustomCategory2">Categoría Personalizada 2</string>
<string name="MiraApi.ModifierMenuDescription">¡Configura modificadores y sus ajustes aquí!</string>
<string name="MiraApi.CustomOptionMenuDescription">¡Aplica opciones de partida para este mod!</string>
<string name="MiraApi.Gamemode.Classic">Clásico</string>
<string name="MiraApi.Gamemode.Classic.Description">La experiencia clásica de Among Us
Encuentra a los Impostores o engaña a la Tripulación.</string>
<string name="MiraApi.Gamemode.HideAndSeek">Escondidas</string>
<string name="MiraApi.Gamemode.HideAndSeek.Description">¡Puedes correr, pero no esconderte!
Sobrevive al temporizador o elimina a la Tripulación.</string>
<string name="MiraApi.GameSetting.Global.ClickToClose">Click para cerrar</string>
<string name="MiraApi.GameSetting.Global.ClickToOpen">Click para abrir</string>
<string name="MiraApi.GameSetting.Preset.Hint">Selecciona o crea un ajuste predefinido para esta partida</string>
<string name="MiraApi.GameSetting.Preset.Save">Guardar</string>
<string name="MiraApi.RoleTeam.Crewmate.Group">Roles de Tripulante</string>
<string name="MiraApi.RoleTeam.Impostor.Group">Roles de Impostor</string>
<string name="MiraApi.RoleTeam.Neutral.Group">Roles Neutrales</string>
<string name="MiraApi.RoleTeam.Other">Otros</string>
</resources>
+12 -9
View File
@@ -1,12 +1,15 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="Roles">Roles</string>
<string name="Modifier">Modificador</string>
<string name="Modifiers">Modificadores</string>
<string name="MiraApi.RoleTeam.Crewmate">Tripulante</string>
<string name="MiraApi.RoleTeam.Crewmate.Short">Tripulante</string>
<string name="MiraApi.RoleTeam.Impostor">Impostor</string>
<string name="MiraApi.RoleTeam.Impostor.Short">Impostor</string>
<string name="MiraApi.RoleTeam.Neutral">Neutro</string>
<string name="MiraApi.RoleTeam.Neutral.Short">Neutro</string>
<string name="Roles">Roles</string>
<string name="Modifier">Modificador</string>
<string name="Modifiers">Modificadores</string>
<string name="MiraApi.RoleTeam.Crewmate">Tripulante</string>
<string name="MiraApi.RoleTeam.Crewmate.Short">Tripulante</string>
<string name="MiraApi.RoleTeam.Impostor">Impostor</string>
<string name="MiraApi.RoleTeam.Impostor.Short">Impostor</string>
<string name="MiraApi.RoleTeam.Neutral">Neutro</string>
<string name="MiraApi.RoleTeam.Neutral.Short">Neutro</string>
<string name="MiraApi">Mira API</string>
<string name="MiraApi.Accessibility">Accesibilidad</string>
<string name="MiraApi.MiraApiBrand">MiraAPI</string>
</resources>
+29 -11
View File
@@ -1,14 +1,32 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="MiraApi.TopRightButtonScaleFactor">Skala Przycisków</string>
<string name="MiraApi.WikiOnBottomRow">Przycisk Wiki Poniżej Głównego UI</string>
<string name="Roles">Role</string>
<string name="Modifier">Modyfikator</string>
<string name="Modifiers">Modyfikatory</string>
<string name="MiraApi.RoleTeam.Crewmate">Załogant</string>
<string name="MiraApi.RoleTeam.Crewmate.Short">Zał</string>
<string name="MiraApi.RoleTeam.Impostor">Intruz</string>
<string name="MiraApi.RoleTeam.Impostor.Short">Int</string>
<string name="MiraApi.RoleTeam.Neutral">Neutralny</string>
<string name="MiraApi.RoleTeam.Neutral.Short">Neut</string>
<string name="MiraApi.TopRightButtonScaleFactor">Skala Przycisków w Prawym Górnym</string>
<string name="MiraApi.WikiOnBottomRow">Przycisk Wiki Poniżej Głównego UI</string>
<string name="Roles">Role</string>
<string name="Modifier">Modyfikator</string>
<string name="Modifiers">Modyfikatory</string>
<string name="MiraApi.RoleTeam.Crewmate">Załogant</string>
<string name="MiraApi.RoleTeam.Crewmate.Short">Zał</string>
<string name="MiraApi.RoleTeam.Impostor">Intruz</string>
<string name="MiraApi.RoleTeam.Impostor.Short">Int</string>
<string name="MiraApi.RoleTeam.Neutral">Neutralny</string>
<string name="MiraApi.RoleTeam.Neutral.Short">Neut</string>
<string name="MiraApi.VisualsUi">Wizualne/UI</string>
<string name="MiraApi.Accessibility">Dostępności</string>
<string name="MiraApi.ShowModifiersHudOnLeftSide">Pokaż HUD Modyfikatorów Po Lewo</string>
<string name="MiraApi.ShowKeybindsOnButtons">Pokaż Przypisane Klawisze na Przyciskach</string>
<string name="MiraApi.ButtonScaleFactor">Skala Przycisków</string>
<string name="MiraApi.MaxFps">Limit FPS</string>
<string name="MiraApi.EnableSabotageFlashes">Włącz Błyski Sabotaży</string>
<string name="MiraApi.EnableSabotageBlare">Włącz Dźwięk Sabotaży</string>
<string name="MiraApi.ReturnToRoleSettings">WRÓĆ DO USTAWIEŃ RÓL</string>
<string name="MiraApi.CustomCategory1">Niestandardowa Kategoria 1</string>
<string name="MiraApi.CustomCategory2">Niestandardowa Kategoria 2</string>
<string name="MiraApi.ModifierMenuDescription">Skonfiguruj modyfikatory i ich ustawienia tutaj!</string>
<string name="MiraApi.Gamemode.Classic">Klasyczny</string>
<string name="MiraApi.Gamemode.Classic.Description">Klasyczna rozgrywka w Among Us!
Znajdź Intruzów, bądź oszukaj Załogę.</string>
<string name="MiraApi.Gamemode.HideAndSeek">Chowany</string>
<string name="MiraApi.Gamemode.HideAndSeek.Description">Możesz uciekać, ale nie możesz się schować!
Przeżyj przez wymagany czas, albo zabij chowających.</string>
</resources>
+40 -11
View File
@@ -1,14 +1,43 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources>
<string name="MiraApi.TopRightButtonScaleFactor">Fator do Tamanho do Botão</string>
<string name="MiraApi.WikiOnBottomRow">Botão Debaixo do Interface Principal</string>
<string name="Roles">Funções</string>
<string name="Modifier">Modificador</string>
<string name="Modifiers">Modificadores</string>
<string name="MiraApi.RoleTeam.Crewmate">Tripulante</string>
<string name="MiraApi.RoleTeam.Crewmate.Short">Tripulante</string>
<string name="MiraApi.RoleTeam.Impostor">Impostor</string>
<string name="MiraApi.RoleTeam.Impostor.Short">Impostor</string>
<string name="MiraApi.RoleTeam.Neutral">Neutro</string>
<string name="MiraApi.RoleTeam.Neutral.Short">Neutro</string>
<string name="MiraApi.TopRightButtonScaleFactor">Fator do Tamanho do Botão</string>
<string name="MiraApi.WikiOnBottomRow">Botão Debaixo do Interface Principal</string>
<string name="Roles">Funções</string>
<string name="Modifier">Modificador</string>
<string name="Modifiers">Modificadores</string>
<string name="MiraApi.RoleTeam.Crewmate">Tripulante</string>
<string name="MiraApi.RoleTeam.Crewmate.Short">Tripulante</string>
<string name="MiraApi.RoleTeam.Impostor">Impostor</string>
<string name="MiraApi.RoleTeam.Impostor.Short">Impostor</string>
<string name="MiraApi.RoleTeam.Neutral">Neutro</string>
<string name="MiraApi.RoleTeam.Neutral.Short">Neutro</string>
<string name="MiraApi">Mira API</string>
<string name="MiraApi.VisualsUi">Visuais/UI</string>
<string name="MiraApi.Accessibility">Acessibilidade</string>
<string name="MiraApi.MiraApiBrand">MiraAPI</string>
<string name="MiraApi.ShowModifiersHudOnLeftSide">Mostrar Modificadores HUD no Lado Esquerdo</string>
<string name="MiraApi.ShowKeybindsOnButtons">Mostrar Teclas nos Botões</string>
<string name="MiraApi.ButtonScaleFactor">Fator De Escala De Botão</string>
<string name="MiraApi.MaxFps">FPS Máximo</string>
<string name="MiraApi.EnableSabotageFlashes">Ativar Brilho De Sabotagem</string>
<string name="MiraApi.EnableSabotageBlare">Ativar Som De Sabotagem</string>
<string name="MiraApi.ReturnToRoleSettings">VOLTAR PARA DEFINIÇÕES DE DEFINIÇÕES</string>
<string name="MiraApi.CustomCategory1">Categoria Personalizada 1</string>
<string name="MiraApi.CustomCategory2">Categoria Personalizada 2</string>
<string name="MiraApi.ModifierMenuDescription">Configura modificadores e as suas definições aqui!</string>
<string name="MiraApi.CustomOptionMenuDescription">Aplica definições de jogo para este modo!</string>
<string name="MiraApi.Gamemode.Classic">Clássico</string>
<string name="MiraApi.Gamemode.Classic.Description">A experiencia clássica do Among Us!
Encontra os Impostores ou engana a Tripulação.</string>
<string name="MiraApi.Gamemode.HideAndSeek">Escondidinhas</string>
<string name="MiraApi.Gamemode.HideAndSeek.Description">Podes fugir, mas não podes esconder!
Vive até o tempo acabar ou elimina a tripulação.</string>
<string name="MiraApi.GameSetting.Global.ClickToClose">Clica para fechar</string>
<string name="MiraApi.GameSetting.Global.ClickToOpen">Clica para abrir</string>
<string name="MiraApi.GameSetting.Preset.Hint">Seleciona ou cria uma predefinição para este jogo</string>
<string name="MiraApi.GameSetting.Preset.Save">Salvar</string>
<string name="MiraApi.RoleTeam.Crewmate.Group">Funções Dos Tripulantes</string>
<string name="MiraApi.RoleTeam.Impostor.Group">Funções Dos Impostores</string>
<string name="MiraApi.RoleTeam.Neutral.Group">Funções Dos Neutros</string>
<string name="MiraApi.RoleTeam.Other">Outro</string>
</resources>
+9 -2
View File
@@ -1,6 +1,7 @@
using System;
using System.Text;
using BepInEx.Configuration;
using MiraAPI.GameModes;
using MiraAPI.GameOptions;
using MiraAPI.Modifiers;
using MiraAPI.PluginLoading;
@@ -20,7 +21,7 @@ public interface ICustomRole : IOptionable
string IdPart => GetType().Name;
/// <summary>
/// Gets the id part used to build the role's translation keys. It is recommended to call it ModGuid.Role.Faction
/// Gets the id part used to build the role's translation keys. It is recommended to call it ModGuid.Role.Faction.
/// </summary>
string IdPrefix => GetType().Namespace!;
@@ -297,9 +298,15 @@ public interface ICustomRole : IOptionable
/// <returns><see langword="true"/> if the role is able to spawn, otherwise <see langword="false"/>.</returns>
virtual bool CanSpawnOnCurrentMode()
{
return !GameManager.Instance.IsHideAndSeek();
return Configuration.AssociatedGameMode.IsInstanceOfType(CustomGameModeManager.ActiveMode);
}
/// <summary>
/// Gets whether the role is forcibly shown or disabled in the wiki screen.
/// </summary>
/// <returns><see langword="true"/> if the role is always displayed, otherwise <see langword="false"/> if it is never displayable, or <see langword="null"/> if it is dictated by amount and chance.</returns>
virtual bool? ForceShowRoleOnWiki => null;
/// <summary>
/// Gets the function that determines whether the role should be toggled on or off in the game settings.
/// </summary>
+119 -94
View File
@@ -5,6 +5,7 @@ using System.Linq;
using System.Reflection;
using System.Xml;
using MiraAPI.Utilities;
using MonoMod.Utils;
using Reactor.Localization.Utilities;
using UnityEngine;
@@ -14,7 +15,9 @@ public static class MiraLocaleManager
{
private const string LangDirectory = "mira_languages";
public static readonly Dictionary<string, Dictionary<MiraLanguage, Dictionary<string, string>>> Locale = [];
// Language, Xml Name, then Value
public static Dictionary<MiraLanguage, Dictionary<string, string>> Locale { get; } = [];
public static readonly List<string> RegisteredModIds = [];
public static readonly Dictionary<string, StringNames> RegisteredStringNames = [];
internal static readonly Dictionary<StringNames, string> StringNamesLookup = [];
@@ -88,22 +91,19 @@ public static class MiraLocaleManager
}
/// <summary>
/// Registers translations for a mod. Copies embedded XML to mira_languages/{modGuid}/ on first run,
/// then loads all XML files from that directory into memory.
/// Registers translations for a mod.
/// Call once per mod during plugin Load().
/// </summary>
/// <param name="modGuid">The mod GUID (e.g., "mira.example").</param>
public static void Register(string modGuid)
{
var callingAssembly = Assembly.GetCallingAssembly();
var dir = GetModLangDir(modGuid);
LoadInternalStrings(callingAssembly, callingAssembly.GetName().Name!, modGuid, dir);
LoadInternalStrings(callingAssembly, callingAssembly.GetName().Name!, modGuid);
}
/// <summary>
/// Registers translations for a mod. Copies embedded XML to mira_languages/{modGuid}/ on first run,
/// then loads all XML files from that directory into memory.
/// Registers translations for a mod.
/// Call once per mod during plugin Load().
/// </summary>
/// <param name="modGuid">The mod GUID (e.g., "mira.example").</param>
@@ -111,80 +111,95 @@ public static class MiraLocaleManager
public static void Register(string modGuid, string internalName)
{
var callingAssembly = Assembly.GetCallingAssembly();
var dir = GetModLangDir(modGuid);
LoadInternalStrings(callingAssembly, internalName, modGuid, dir);
LoadInternalStrings(callingAssembly, internalName, modGuid);
}
/// <summary>
/// Translates a key into the current language.
/// Searches all mods, with reverse lookup fallback.
/// </summary>
/// <param name="key">The string id to find.</param>
/// <param name="fallback">Fallback string to use if no translation is found.</param>
/// <returns>A <see cref="string"/> based on the key provided.</returns>
public static string Get(string key, string fallback = "")
{
var currentLang = CurrentLanguage;
return Get(CurrentLanguage, key, fallback != string.Empty ? fallback : key);
}
foreach (var (_, modLocale) in Locale)
/// <summary>
/// Translates a key into the current language.
/// </summary>
/// <param name="language">The specific language to prioritize.</param>
/// <param name="key">The string id to find.</param>
/// <param name="fallback">Fallback string to use if no translation is found.</param>
/// <returns>A <see cref="string"/> based on the key provided.</returns>
public static string Get(MiraLanguage language, string key, string fallback = "")
{
if (Locale.TryGetValue(language, out var translations) &&
translations.TryGetValue(key, out var translation))
{
if (modLocale.TryGetValue(currentLang, out var langDict) &&
langDict.TryGetValue(key, out var translated))
{
return translated;
}
return translation;
}
foreach (var (_, modLocale) in Locale)
if (Locale.TryGetValue(MiraLanguage.English, out var translationsEng) &&
translationsEng.TryGetValue(key, out var translationEng))
{
if (modLocale.TryGetValue(MiraLanguage.English, out var engDict) &&
engDict.TryGetValue(key, out var english))
{
return english;
}
return translationEng;
}
string? bestStructuredKey = null;
foreach (var (_, modLocale) in Locale)
{
if (modLocale.TryGetValue(MiraLanguage.English, out var engDict))
{
foreach (var (structuredKey, englishValue) in engDict)
{
if (englishValue == key)
{
bestStructuredKey = structuredKey;
break;
}
}
}
return fallback;
}
if (bestStructuredKey != null) break;
/// <summary>
/// Translates a key into the current language.
/// </summary>
/// <param name="key">The string id to find.</param>
/// <param name="parseList">List of keys to change into other text.</param>
/// <param name="fallback">Fallback string to use if no translation is found.</param>
/// <returns>A <see cref="string"/> based on the key provided.</returns>
public static string GetParsed(
string key,
Dictionary<string, string> parseList,
string fallback = "")
{
return GetParsed(CurrentLanguage, key, parseList, fallback != string.Empty ? fallback : key);
}
/// <summary>
/// Translates a key into the current language.
/// </summary>
/// <param name="language">The specific language to prioritize.</param>
/// <param name="key">The string id to find.</param>
/// <param name="parseList">List of keys to change into other text.</param>
/// <param name="fallback">Fallback string to use if no translation is found.</param>
/// <returns>A <see cref="string"/> based on the key provided.</returns>
public static string GetParsed(
MiraLanguage language,
string key,
Dictionary<string, string> parseList,
string fallback = "")
{
var text = fallback;
if (Locale.TryGetValue(MiraLanguage.English, out var translationsEng) &&
translationsEng.TryGetValue(key, out var translationEng))
{
text = translationEng;
}
if (bestStructuredKey != null)
if (language is not MiraLanguage.English &&
Locale.TryGetValue(language, out var translations) &&
translations.TryGetValue(key, out var translation))
{
foreach (var (_, modLocale) in Locale)
{
if (modLocale.TryGetValue(currentLang, out var langDict) &&
langDict.TryGetValue(bestStructuredKey, out var revTranslated))
{
return revTranslated;
}
}
foreach (var (_, modLocale) in Locale)
{
if (modLocale.TryGetValue(MiraLanguage.English, out var engDict) &&
engDict.TryGetValue(bestStructuredKey, out var revEnglish))
{
return revEnglish;
}
}
text = translation;
}
return fallback != string.Empty ? fallback : key;
foreach (var tmpText in parseList.Where(x => text.Contains(x.Key)))
{
text = text.Replace(tmpText.Key, tmpText.Value);
}
return text;
}
public static string BuildTranslationId(string modId, string idPart, string suffix)
@@ -215,17 +230,49 @@ public static class MiraLocaleManager
return newString;
}
private static void LoadInternalStrings(Assembly assembly, string internalName, string modGuid, string dir)
public static void LoadExternalLocale()
{
CheckExternalDirectory("mira.api", BepInEx.Paths.PluginPath);
CheckExternalDirectory("mira.api", BepInEx.Paths.BepInExRootPath);
CheckExternalDirectory("mira.api", BepInEx.Paths.GameRootPath);
foreach (var mod in RegisteredModIds)
{
CheckExternalDirectory(mod, GetModLangDir(mod));
}
}
public static void CheckExternalDirectory(string modGuid, string dir)
{
Directory.CreateDirectory(dir);
if (!Locale.ContainsKey(modGuid))
foreach (var filePath in Directory.GetFiles(dir, "*.xml"))
{
Locale[modGuid] = [];
}
var fileName = Path.GetFileNameWithoutExtension(filePath);
var lang = LangList.ContainsValue(fileName)
? LangList.First(x => x.Value == fileName).Key
: (MiraLanguage)(-1);
if ((int)lang == -1) continue;
try
{
Locale.TryAdd(lang, []);
var xmlContent = File.ReadAllText(filePath);
ParseXmlFile(xmlContent, lang, false);
}
catch (Exception e)
{
Error($"Failed to load external translation {filePath}: {e.Message}");
}
}
}
private static void LoadInternalStrings(Assembly assembly, string internalName, string modGuid)
{
var resourcePrefix = $"{internalName}.Resources.Locale.";
RegisteredModIds.Add(modGuid);
var atLeastOneLoaded = false;
foreach (var locale in LangList)
{
@@ -241,9 +288,8 @@ public static class MiraLocaleManager
string xmlContent = reader.ReadToEnd();
try
{
var dict = ParseXmlFile(xmlContent);
Locale[modGuid][locale.Key] = dict;
Info($"Loaded {locale.Key.ToDisplayString()} translation for mod {modGuid} ({dict.Count} keys)");
Locale.TryAdd(locale.Key, []);
ParseXmlFile(xmlContent, locale.Key, true);
atLeastOneLoaded = true;
}
catch (Exception e)
@@ -256,37 +302,11 @@ public static class MiraLocaleManager
{
Error($"No internal strings were found for {internalName}!");
}
if (!Locale.TryGetValue(modGuid, out _))
{
Locale[modGuid] = [];
}
foreach (var filePath in Directory.GetFiles(dir, "*"))
{
var fileName = Path.GetFileNameWithoutExtension(filePath);
var lang = LangList.ContainsValue(fileName)
? LangList.First(x => x.Value == fileName).Key
: (MiraLanguage)(-1);
if ((int)lang == -1) continue;
try
{
var dict = ParseXmlFile(filePath);
Locale[modGuid][lang] = dict;
Info($"Loaded external {lang.ToDisplayString()} translation for mod {modGuid} ({dict.Count} keys)");
}
catch (Exception e)
{
Error($"Failed to load external translation {filePath}: {e.Message}");
}
}
}
private static Dictionary<string, string> ParseXmlFile(string xmlContent)
private static void ParseXmlFile(string xmlContent, MiraLanguage language, bool loadingInternal)
{
var dict = new Dictionary<string, string>();
var dict = Locale[language];
XmlDocument xmlDoc = new();
try
{
@@ -295,6 +315,7 @@ public static class MiraLocaleManager
if (stringNodes != null)
{
var total = 0;
foreach (XmlNode node in stringNodes)
{
if (node.Attributes?["name"] != null)
@@ -316,16 +337,20 @@ public static class MiraLocaleManager
value = value.Replace("<nl>", "\n").Replace("<and>", "&");
if (loadingInternal && Locale[language].ContainsKey(name))
{
Error($"String for \"{name}\" in {language} was overwritten by duplicate!");
}
dict[name] = value;
total++;
}
}
Info($"Loaded {language.ToDisplayString()} translation with ({total} keys)");
}
}
catch (XmlException ex)
{
Error($"XML parsing error: {ex.Message}");
}
return dict;
}
}
@@ -11,6 +11,7 @@ public class MiraLocalizationProvider : LocalizationProvider
{
public override int Priority => ReactorPriority.Normal;
private static LocalizationProvider? _reactorProvider;
private static bool _loadedStrings;
public override bool TryGetText(StringNames stringName, out string? result)
{
@@ -39,6 +40,11 @@ public class MiraLocalizationProvider : LocalizationProvider
{
MiraApiPlugin.Culture = new(culture);
}
if (!_loadedStrings)
{
MiraLocaleManager.LoadExternalLocale();
_loadedStrings = true;
}
foreach (var tab in LocalSettingsTab.TabGroups)
{
+42
View File
@@ -529,6 +529,48 @@ public static class Helpers
return role is ICustomRole custom ? custom.RoleName : role.NiceName;
}
/// <summary>
/// Returns the string of a <see cref="RoleBehaviour"/>.
/// </summary>
/// <param name="role">The <see cref="RoleBehaviour"/> to find.</param>
/// <returns>The <see cref="RoleBehaviour"/>'s intro blurb.</returns>
public static string GetRoleIntroBlurb(this RoleBehaviour role)
{
if (role is ICustomRole custom)
{
return custom.RoleDescription;
}
return role.Blurb;
}
/// <summary>
/// Returns the string of a <see cref="RoleBehaviour"/>.
/// </summary>
/// <param name="role">The <see cref="RoleBehaviour"/> to find.</param>
/// <returns>The <see cref="RoleBehaviour"/>'s medium description.</returns>
public static string GetRoleMedDescription(this RoleBehaviour role)
{
if (role is ICustomRole custom)
{
return custom.RoleDescription;
}
return role.BlurbMed;
}
/// <summary>
/// Returns the string of a <see cref="RoleBehaviour"/>.
/// </summary>
/// <param name="role">The <see cref="RoleBehaviour"/> to find.</param>
/// <returns>The <see cref="RoleBehaviour"/>'s long description.</returns>
public static string GetRoleLongDescription(this RoleBehaviour role)
{
if (role is ICustomRole custom)
{
return custom.RoleLongDescription;
}
return role.BlurbLong;
}
/// <summary>
/// Returns whether the <see cref="RoleBehaviour"/> is blacklisted from appearing and spawning. (Only applicable to vanilla roles).
/// </summary>
+71
View File
@@ -0,0 +1,71 @@
using MiraAPI.Events;
using MiraAPI.Events.Mira;
using MiraAPI.Hud;
using MiraAPI.LocalSettings;
namespace MiraAPI.VanillaEvents;
public static class UiResetEvents
{
public static void Initialize()
{
MiraEventManager.RegisterEventHandler<UiButtonResetEvent>(@event => ResetButtonParents(@event));
MiraEventManager.RegisterEventHandler<UiButtonPostResetEvent>(@event => PlaceWikiButton(@event), -900);
MiraEventManager.RegisterEventHandler<UiButtonPostResetEvent>(@event => PlaceSubmergedButton(@event), -800);
MiraEventManager.RegisterEventHandler<UiButtonPostResetEvent>(@event => PlaceModifierUi(@event), -700);
}
public static void ResetButtonParents(UiButtonResetEvent @event)
{
var wikiButton = MiraHudHelper.VanillaMatchInfoButton;
var subButton = MiraHudHelper.SubmergedFloorButton;
var modDisplay = MiraHudHelper.ModifierDisplayOnRight ? MiraHudHelper.ModifierDisplayObject : null!;
if (wikiButton)
{
wikiButton.transform.SetParent(null);
}
if (subButton)
{
subButton.transform.SetParent(null);
}
if (modDisplay)
{
modDisplay.transform.SetParent(null);
}
}
public static void PlaceWikiButton(UiButtonPostResetEvent @event)
{
var wikiButton = MiraHudHelper.VanillaMatchInfoButton;
if (!wikiButton)
{
return;
}
var firstRow = @event.MainTopUiRow;
var secondRow = @event.SecondTopUiRow;
var opts = LocalSettingsTabSingleton<MiraApiSettings>.Instance;
wikiButton.transform.SetParent(opts.WikiOnBottomRow.Value ? secondRow.transform : firstRow.transform);
}
public static void PlaceSubmergedButton(UiButtonPostResetEvent @event)
{
var subButton = MiraHudHelper.SubmergedFloorButton;
if (!subButton)
{
return;
}
var secondRow = @event.SecondTopUiRow;
subButton.transform.SetParent(secondRow.transform);
}
public static void PlaceModifierUi(UiButtonPostResetEvent @event)
{
var modDisplay = MiraHudHelper.ModifierDisplayOnRight ? MiraHudHelper.ModifierDisplayObject : null!;
if (!modDisplay)
{
return;
}
var secondRow = @event.SecondTopUiRow;
modDisplay.transform.SetParent(secondRow.transform);
}
}
+2 -2
View File
@@ -122,7 +122,7 @@ public static class VotingUtils
CustomCastJudgeVote(srcPlayerId, suspectPlayerId, overruleNonce);
}
private static void CustomCastJudgeVote(PlayerId judgePlayerId, PlayerId targetPlayerId, ushort overruleNonce)
public static void CustomCastJudgeVote(PlayerId judgePlayerId, PlayerId targetPlayerId, ushort overruleNonce)
{
var plr = GameData.Instance.GetPlayerById(judgePlayerId.Value);
if (!plr) return;
@@ -187,7 +187,7 @@ public static class VotingUtils
CustomCastVote(srcPlayerId, suspectPlayerId);
}
private static void CustomCastVote(byte srcPlayerId, byte suspectPlayerId)
public static void CustomCastVote(byte srcPlayerId, byte suspectPlayerId)
{
var plr = GameData.Instance.GetPlayerById(srcPlayerId);
if (!plr) return;
+2
View File
@@ -1,5 +1,7 @@
[![](https://dcbadge.limes.pink/api/server/AEfHJGwggC)](https://discord.gg/AEfHJGwggC)
[English] | [简体中文](Translated/README_zh.md)
> This mod is not affiliated with Among Us or Innersloth LLC, and the content contained therein is not endorsed or otherwise sponsored by Innersloth LLC. Portions of the materials contained herein are property of Innersloth LLC. © Innersloth LLC.
# Mira API
+91
View File
@@ -0,0 +1,91 @@
[![](https://dcbadge.limes.pink/api/server/AEfHJGwggC)](https://discord.gg/AEfHJGwggC)
[English](../README.md) | [简体中文]
> 此模组与 Among Us 或 Innersloth LLC 无关,其中包含的内容未得到 Innersloth LLC 的认可或赞助。本文所含部分材料为 Innersloth LLC 的财产。© Innersloth LLC。
> 由[太空果](https://github.com/TAIKgroup)开启了Pr并提交了初版翻译,由[林梅](https://github.com/HayashiUme)维护并提供wiki翻译。
# Mira API
一个全面、易上手的 Among Us 模组 API 及实用工具库,内含:
- 角色
- 选项
- 附加职业
- 按钮
- 自定义颜色
- 事件
- 投票
- 资源
- 快捷键
- 本地设置
- 自定义游戏结束条件
- 强兼容性
- ~~游戏模式~~(敬请期待)
Mira API 力求全面而易于上手,同时尽可能复用原版游戏元素。
它是一个更简洁、覆盖常见用例的模组 API,减少了对游戏源码的修改。
**加入 [Discord](https://discord.gg/FYYqJU2bvp) 获取支持并了解最新版本**
# 使用方法
要开始使用 Mira API,你需要:
1. 通过 [DLL](https://github.com/All-Of-Us-Mods/MiraAPI/releases)、项目引用或 [NuGet 包](https://www.nuget.org/packages/AllOfUs.MiraAPI) 添加对 Mira API 的引用。
2. 在你的插件类上添加 BepInDependency,如下所示:`[BepInDependency(MiraApiPlugin.Id)]`
3. 在你的插件类上实现 `IMiraPlugin` 接口。
Mira API 还依赖 [Reactor](https://github.com/NuclearPowered/Reactor) 才能正常运行!
记得将其作为引用和 `BepInDependency` 包含进来!
完整示例请参见 [此文件](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/ExamplePlugin.cs)。
## 推荐的项目结构
使用 Mira API 时,强烈建议遵循以下项目结构,以保持代码整洁有序。
你也可以查看本仓库中的示例模组以获得一些指导。
```
MyMiraMod/
├── Buttons/
│ └── MyCoolButton.cs
├── Options/
│ ├── Roles/
│ │ └── CoolCustomRoleOptions.cs
│ └── MainOptionGroup.cs
├── Patches/
│ ├── Roles/
│ │ └── CoolCustomRole/
│ │ ├── PlayerControlPatches.cs
│ │ └── ExileControllerPatches.cs
│ └── General/
│ └── HudManagerPatches.cs
├── Resources/
│ ├── CoolButton.png
│ └── myAssets-win-x86.bundle
├── Roles/
│ └── CoolCustomRole.cs
├── MyMiraModPlugin.cs
└── MyModAssets.cs
```
# 文档
Mira API 每个功能的完整文档可在 **[wiki](https://github.com/All-Of-Us-Mods/MiraAPI/wiki)** 上找到:
- [入门 / IMiraPlugin](https://github.com/All-Of-Us-Mods/MiraAPI/wiki/Home)
- [自定义角色](https://github.com/All-Of-Us-Mods/MiraAPI/wiki/Custom-Roles)
- [选项](https://github.com/All-Of-Us-Mods/MiraAPI/wiki/Options)
- [附加职业](https://github.com/All-Of-Us-Mods/MiraAPI/wiki/Modifiers)
- [自定义按钮](https://github.com/All-Of-Us-Mods/MiraAPI/wiki/Custom-Buttons)
- [自定义颜色](https://github.com/All-Of-Us-Mods/MiraAPI/wiki/Colors)
- [事件](https://github.com/All-Of-Us-Mods/MiraAPI/wiki/Events)
- [会议与投票](https://github.com/All-Of-Us-Mods/MiraAPI/wiki/Meetings-and-Voting)
- [资源](https://github.com/All-Of-Us-Mods/MiraAPI/wiki/Assets)
- [快捷键](https://github.com/All-Of-Us-Mods/MiraAPI/wiki/Keybinds)
- [本地设置](https://github.com/All-Of-Us-Mods/MiraAPI/wiki/Local-Settings)
- [游戏模式](https://github.com/All-Of-Us-Mods/MiraAPI/wiki/Game-Modes)
- [自定义游戏结束](https://github.com/All-Of-Us-Mods/MiraAPI/wiki/Game-Over)
- [实用工具](https://github.com/All-Of-Us-Mods/MiraAPI/wiki/Utilities)