Merge branch 'refs/heads/master' into collapsible-options

This commit is contained in:
XtraCube committed 2024-08-27 13:58:25 -04:00
commit 1cc41052e3
15 files changed
+474 -54

No files matched your search

+1
View File
@@ -19,6 +19,7 @@ public interface IModdedOption
public ConfigDefinition ConfigDefinition { get; }
public void ValueChanged(OptionBehaviour optionBehaviour);
public OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container);
public abstract float GetFloatData();
public NetData GetNetData();
public void HandleNetData(byte[] data);
}
@@ -39,7 +39,12 @@ public class ModdedEnumOption : ModdedOption<int>
return stringOption;
}
public override float GetFloatData()
{
return Value;
}
public override NetData GetNetData()
{
return new NetData(Id, BitConverter.GetBytes(Value));
@@ -65,6 +65,11 @@ public class ModdedNumberOption : ModdedOption<float>
return numberOption;
}
public override float GetFloatData()
{
return Value;
}
public override NetData GetNetData()
{
return new NetData(Id, BitConverter.GetBytes(Value));
@@ -77,6 +77,8 @@ public abstract class ModdedOption<T> : IModdedOption
OnValueChanged(newValue);
}
public abstract float GetFloatData();
public abstract NetData GetNetData();
public abstract void HandleNetData(byte[] data);
@@ -40,6 +40,11 @@ public class ModdedStringOption : ModdedOption<int>
return stringOption;
}
public override float GetFloatData()
{
return Value;
}
public override NetData GetNetData()
{
return new NetData(Id, BitConverter.GetBytes(Value));
@@ -32,6 +32,11 @@ public class ModdedToggleOption : ModdedOption<bool>
return toggleOption;
}
public override float GetFloatData()
{
return Value ? 1 : 0;
}
public override NetData GetNetData()
{
return new NetData(Id, BitConverter.GetBytes(Value));
+338 -5
View File
@@ -1,14 +1,28 @@
using HarmonyLib;
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using Object = UnityEngine.Object;
namespace MiraAPI.Patches.Options;
[HarmonyPatch(typeof(LobbyViewSettingsPane))]
public static class LobbyViewPanePatches
{
public static int CurrentSelectedMod { get; private set; }
public static MiraPluginInfo? SelectedMod => CurrentSelectedMod == 0 ? null : MiraPluginManager.Instance.RegisteredPlugins.ElementAt(CurrentSelectedMod-1).Value;
[HarmonyPostfix]
[HarmonyPatch(nameof(LobbyViewSettingsPane.Awake))]
public static void AwakePatch(LobbyViewSettingsPane __instance)
@@ -16,7 +30,7 @@ public static class LobbyViewPanePatches
__instance.gameModeText.transform.localPosition = new Vector3(-2.3f, 2.4f, -2);
// Create the next button
GameObject nextButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject;
var nextButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject;
nextButton.GetComponent<BoxCollider2D>().size = new Vector2(0.2f, 0.3f);
nextButton.transform.localPosition = new Vector3(-5.4f, 2.4f, -2f);
nextButton.transform.localScale = new Vector3(3, 3, 2);
@@ -30,24 +44,343 @@ public static class LobbyViewPanePatches
hover.transform.localPosition = new Vector3(0, 0f, 0.3f);
hover.sprite = MiraAssets.NextButtonActive.LoadAsset();
PassiveButton passiveButton = nextButton.gameObject.GetComponent<PassiveButton>();
var passiveButton = nextButton.gameObject.GetComponent<PassiveButton>();
passiveButton.OnClick = new Button.ButtonClickedEvent();
passiveButton.OnClick.AddListener((UnityAction)(() =>
{
CurrentSelectedMod += 1;
if (CurrentSelectedMod > MiraPluginManager.Instance.RegisteredPlugins.Count)
{
CurrentSelectedMod = 0;
}
__instance.RefreshTab();
__instance.scrollBar.ScrollToTop();
}));
// Create the back button
GameObject backButton = Object.Instantiate(nextButton, __instance.BackButton.transform.parent).gameObject;
var backButton = Object.Instantiate(nextButton, __instance.BackButton.transform.parent).gameObject;
backButton.transform.localPosition = new Vector3(-6.3f, 2.4f, -2f);
backButton.name = "LeftArrowButton";
backButton.transform.FindChild("Normal").gameObject.GetComponentInChildren<SpriteRenderer>().flipX
= backButton.transform.FindChild("Hover").gameObject.GetComponentInChildren<SpriteRenderer>().flipX
= true;
PassiveButton passiveButton2 = backButton.gameObject.GetComponent<PassiveButton>();
var passiveButton2 = backButton.gameObject.GetComponent<PassiveButton>();
passiveButton2.OnClick = new Button.ButtonClickedEvent();
passiveButton2.OnClick.AddListener((UnityAction)(() =>
{
CurrentSelectedMod -= 1;
if (CurrentSelectedMod < 0)
{
CurrentSelectedMod = MiraPluginManager.Instance.RegisteredPlugins.Count;
}
__instance.RefreshTab();
__instance.scrollBar.ScrollToTop();
}));
}
[HarmonyPostfix]
[HarmonyPatch(nameof(LobbyViewSettingsPane.Update))]
public static void UpdatePatch(LobbyViewSettingsPane __instance)
{
__instance.gameModeText.text = SelectedMod?.PluginInfo.Metadata.Name ?? "Default";
}
[HarmonyPrefix]
[HarmonyPatch(nameof(LobbyViewSettingsPane.DrawNormalTab))]
public static bool DrawNormalTabPatch(LobbyViewSettingsPane __instance)
{
if (CurrentSelectedMod == 0)
{
return true;
}
DrawOptionsTab(__instance);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(LobbyViewSettingsPane.DrawRolesTab))]
public static bool DrawRolesTabPatch(LobbyViewSettingsPane __instance)
{
if (CurrentSelectedMod == 0)
{
return true;
}
DrawRolesTab(__instance);
return false;
}
private static void DrawOptionsTab(LobbyViewSettingsPane instance)
{
if (SelectedMod == null)
{
return;
}
var num = 1.44f;
var filteredGroups = SelectedMod.OptionGroups.Where(x => x.GroupVisible.Invoke() && x.AdvancedRole is null);
foreach (var group in filteredGroups)
{
if (group.Options.Count == 0)
{
continue;
}
var categoryHeaderMasked = Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true);
categoryHeaderMasked.SetHeader(StringNames.Name, 61);
categoryHeaderMasked.Title.text = group.GroupName;
categoryHeaderMasked.transform.localScale = Vector3.one;
categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, num, -2f);
instance.settingsInfo.Add(categoryHeaderMasked.gameObject);
num -= 0.85f;
var i = 0;
foreach (var option in group.Options)
{
if (!option.Visible.Invoke())
{
continue;
}
var viewSettingsInfoPanel = Object.Instantiate(instance.infoPanelOrigin, instance.settingsContainer, true);
viewSettingsInfoPanel.transform.localScale = Vector3.one;
float num2;
if (i % 2 == 0)
{
num2 = -8.95f;
if (i > 0)
{
num -= 0.59f;
}
}
else
{
num2 = -3f;
}
viewSettingsInfoPanel.transform.localPosition = new Vector3(num2, num, -2f);
var data = option.Data;
if (data.Type == OptionTypes.Checkbox)
{
viewSettingsInfoPanel.SetInfoCheckbox(data.Title, 61, Mathf.Approximately(option.GetFloatData(), 1));
}
else
{
viewSettingsInfoPanel.SetInfo(data.Title, data.GetValueString(option.GetFloatData()), 61);
}
instance.settingsInfo.Add(viewSettingsInfoPanel.gameObject);
i++;
}
num -= 0.59f;
}
instance.scrollBar.CalculateAndSetYBounds(instance.settingsInfo.Count + 10, 2f, 6f, 0.59f);
}
private static void DrawRolesTab(LobbyViewSettingsPane instance)
{
if (SelectedMod == null)
{
return;
}
var num = 0.95f;
var num2 = -6.53f;
var categoryHeaderMasked =
Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true);
categoryHeaderMasked.SetHeader(StringNames.RoleQuotaLabel, 61);
categoryHeaderMasked.transform.localScale = Vector3.one;
categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, 1.26f, -2f);
instance.settingsInfo.Add(categoryHeaderMasked.gameObject);
var list = new List<Type>();
foreach (var team in Enum.GetValues<ModdedRoleTeams>())
{
var filteredRoles = SelectedMod.CustomRoles.Values
.OfType<ICustomRole>()
.Where(x => !x.HideSettings && x.Team == team).ToList();
if (filteredRoles.Count == 0)
{
continue;
}
var categoryHeaderRoleVariant =
Object.Instantiate(instance.categoryHeaderRoleOrigin, instance.settingsContainer, true);
switch (team)
{
case ModdedRoleTeams.Crewmate:
categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61);
break;
case ModdedRoleTeams.Impostor:
categoryHeaderRoleVariant.SetHeader(StringNames.ImpostorRolesHeader, 61);
break;
case ModdedRoleTeams.Neutral:
default:
categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61);
categoryHeaderRoleVariant.Title.text = team + " Roles";
categoryHeaderRoleVariant.Background.color = Color.gray;
categoryHeaderRoleVariant.Title.color = Color.white;
break;
}
categoryHeaderRoleVariant.transform.localScale = Vector3.one;
categoryHeaderRoleVariant.transform.localPosition = new Vector3(0.09f, num, -2f);
instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject);
num -= 0.696f;
foreach (var customRole in filteredRoles)
{
var roleBehaviour = customRole as RoleBehaviour;
if (roleBehaviour == null)
{
continue;
}
var chancePerGame =
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(roleBehaviour.Role);
var numPerGame =
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(roleBehaviour.Role);
var flag = numPerGame == 0;
var viewSettingsInfoPanelRoleVariant =
Object.Instantiate(
instance.infoPanelRoleOrigin, instance.settingsContainer, true);
viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one;
viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f);
var advancedRoleOptions = SelectedMod.Options
.Where(x => x.AdvancedRole == customRole.GetType())
.ToList();
if (!flag && advancedRoleOptions.Count > 0)
{
list.Add(customRole.GetType());
}
viewSettingsInfoPanelRoleVariant.SetInfo(roleBehaviour.NiceName, numPerGame, chancePerGame,
61, customRole.RoleColor, customRole.Icon.LoadAsset(), true);
viewSettingsInfoPanelRoleVariant.titleText.color =
viewSettingsInfoPanelRoleVariant.chanceTitle.color =
viewSettingsInfoPanelRoleVariant.chanceBackground.color =
viewSettingsInfoPanelRoleVariant.background.color =
customRole.RoleColor.GetAlternateColor();
instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject);
num -= 0.664f;
}
}
if (list.Count > 0)
{
var categoryHeaderMasked2 =
Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true);
categoryHeaderMasked2.SetHeader(StringNames.RoleSettingsLabel, 61);
categoryHeaderMasked2.transform.localScale = Vector3.one;
categoryHeaderMasked2.transform.localPosition = new Vector3(-9.77f, num, -2f);
instance.settingsInfo.Add(categoryHeaderMasked2.gameObject);
num -= 1.7f;
var num3 = 0f;
for (var k = 0; k < list.Count; k++)
{
float num4;
if (k % 2 == 0)
{
num4 = -5.8f;
if (k > 0)
{
num -= num3 + 0.59f;
num3 = 0f;
}
}
else
{
num4 = 0.14999962f;
}
var advancedRoleViewPanel =
Object.Instantiate(instance.advancedRolePanelOrigin, instance.settingsContainer, true);
advancedRoleViewPanel.transform.localScale = Vector3.one;
advancedRoleViewPanel.transform.localPosition = new Vector3(num4, num, -2f);
var num5 = SetUpAdvancedRoleViewPanel(advancedRoleViewPanel, list[k], 0.59f, 61);
if (num5 > num3)
{
num3 = num5;
}
instance.settingsInfo.Add(advancedRoleViewPanel.gameObject);
}
}
instance.scrollBar.SetYBoundsMax(-num);
}
private static float SetUpAdvancedRoleViewPanel(AdvancedRoleViewPanel viewPanel, Type roleType, float spacingY, int maskLayer)
{
if (SelectedMod == null)
{
return 0;
}
var role = SelectedMod.CustomRoles.Values.FirstOrDefault(x => x.GetType() == roleType);
if (role == null)
{
return 0;
}
if (role is not ICustomRole customRole)
{
return 0;
}
viewPanel.header.SetHeader(role.StringName, maskLayer, role.TeamType == RoleTeamTypes.Crewmate, customRole.Icon.LoadAsset());
viewPanel.divider.material.SetInt(PlayerMaterial.MaskLayer, maskLayer);
var num = viewPanel.yPosStart;
var num2 = 1.08f;
var filteredOptions = SelectedMod.Options
.Where(x => x.AdvancedRole == roleType)
.ToList();
for (var i = 0; i < filteredOptions.Count; i++)
{
var option = filteredOptions[i];
BaseGameSetting baseGameSetting = option.Data;
var viewSettingsInfoPanel = Object.Instantiate(viewPanel.infoPanelOrigin, viewPanel.transform, true);
viewSettingsInfoPanel.transform.localScale = Vector3.one;
viewSettingsInfoPanel.transform.localPosition = new Vector3(viewPanel.xPosStart, num, -2f);
var value = option.GetFloatData();
if (baseGameSetting.Type == OptionTypes.Checkbox)
{
viewSettingsInfoPanel.SetInfoCheckbox(baseGameSetting.Title, maskLayer, value > 0f);
}
else
{
viewSettingsInfoPanel.SetInfo(baseGameSetting.Title, baseGameSetting.GetValueString(value), maskLayer);
}
num -= spacingY;
if (i > 0)
{
num2 += 0.8f;
}
}
return num2;
}
}
@@ -107,6 +107,8 @@ public static class RoleSettingMenuPatches
Vector3.zero, Quaternion.identity, __instance.RoleChancesSettings.transform);
categoryHeaderEditRole3.SetHeader(StringNames.None, 20);
categoryHeaderEditRole3.Title.text = "Neutral Roles";
categoryHeaderEditRole3.Background.color = Color.gray;
categoryHeaderEditRole3.Title.color = Color.white;
categoryHeaderEditRole3.transform.localPosition = new Vector3(4.986f, num, -2f);
num -= 0.522f;
+5 -5
View File
@@ -6,12 +6,12 @@ using System.Collections.Generic;
namespace MiraAPI.PluginLoading;
public class MiraPluginInfo(string id, IMiraPlugin miraPlugin, PluginInfo info)
public class MiraPluginInfo(IMiraPlugin miraPlugin, PluginInfo info)
{
public string PluginId { get; } = id;
public IMiraPlugin MiraPlugin { get; } = miraPlugin;
public PluginInfo PluginInfo { get; } = info;
public ConfigFile PluginConfig { get; } = miraPlugin.GetConfigFile();
public string PluginId { get; set; } = info.Metadata.GUID;
public IMiraPlugin MiraPlugin { get; set; } = miraPlugin;
public PluginInfo PluginInfo { get; set; } = info;
public ConfigFile PluginConfig { get; set; } = miraPlugin.GetConfigFile();
public readonly List<AbstractOptionGroup> OptionGroups = [];
public readonly List<IModdedOption> Options = [];
+16 -11
View File
@@ -1,5 +1,4 @@
using BepInEx;
using BepInEx.Unity.IL2CPP;
using BepInEx.Unity.IL2CPP;
using Il2CppInterop.Runtime.Injection;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
@@ -12,6 +11,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MiraAPI.Utilities;
using Reactor.Networking;
namespace MiraAPI.PluginLoading;
@@ -30,18 +30,17 @@ internal class MiraPluginManager
internal void Initialize()
{
Instance = this;
IL2CPPChainloader.Instance.PluginLoad += (_, assembly, plugin) =>
IL2CPPChainloader.Instance.PluginLoad += (pluginInfo, assembly, plugin) =>
{
if (!plugin.GetType().GetInterfaces().Contains(typeof(IMiraPlugin)))
{
return;
}
var id = MetadataHelper.GetMetadata(plugin.GetType()).GUID;
var info = new MiraPluginInfo(id, plugin as IMiraPlugin, IL2CPPChainloader.Instance.Plugins[id]);
var info = new MiraPluginInfo(plugin as IMiraPlugin, pluginInfo);
RegisterAllOptions(assembly, info);
RegisterRoleAttribute(assembly, info);
RegisterButtonAttribute(assembly);
@@ -49,7 +48,7 @@ internal class MiraPluginManager
RegisteredPlugins.Add(assembly, info);
Logger<MiraApiPlugin>.Info($"Registering mod {id} with Mira API.");
Logger<MiraApiPlugin>.Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API.");
};
}
@@ -68,7 +67,7 @@ internal class MiraPluginManager
{
continue;
}
foreach (var property in type.GetProperties())
{
if (property.PropertyType.IsAssignableTo(typeof(IModdedOption)))
@@ -76,7 +75,7 @@ internal class MiraPluginManager
ModdedOptionsManager.RegisterPropertyOption(type, property, pluginInfo);
continue;
}
var attribute = property.GetCustomAttribute<ModdedOptionAttribute>();
if (attribute == null)
{
@@ -97,10 +96,16 @@ internal class MiraPluginManager
{
continue;
}
if (!ModList.GetById(pluginInfo.PluginId).IsRequiredOnAllClients)
{
Logger<MiraApiPlugin>.Error("Custom roles are only supported on all clients.");
return;
}
ClassInjector.RegisterTypeInIl2Cpp(type);
var role = CustomRoleManager.RegisterRole(type, attribute.RoleId, pluginInfo);
var role = CustomRoleManager.RegisterRole(type, pluginInfo);
try
{
@@ -114,7 +119,7 @@ internal class MiraPluginManager
{
Logger<MiraApiPlugin>.Error($"{k}: {v}");
}
Logger<MiraApiPlugin>.Error(e);
}
}
+8 -12
View File
@@ -20,23 +20,19 @@ public static class CustomRoleManager
{
public static readonly Dictionary<ushort, RoleBehaviour> CustomRoles = new();
private static int GetNextRoleId(ushort requestedId)
private static ushort _roleId = 100;
private static ushort GetNextRoleId()
{
while (CustomRoles.ContainsKey(requestedId) || requestedId <= Enum.GetNames<RoleTypes>().Length)
{
Logger<MiraApiPlugin>.Error(requestedId + ", " + (requestedId+1));
requestedId++;
}
return requestedId;
return _roleId++;
}
public static void RegisterInRoleManager()
internal static void RegisterInRoleManager()
{
RoleManager.Instance.AllRoles = RoleManager.Instance.AllRoles.Concat(CustomRoles.Values).ToArray();
}
internal static RoleBehaviour RegisterRole(Type roleType, ushort roleId, MiraPluginInfo parentMod)
internal static RoleBehaviour RegisterRole(Type roleType, MiraPluginInfo parentMod)
{
if (!(typeof(RoleBehaviour).IsAssignableFrom(roleType) && typeof(ICustomRole).IsAssignableFrom(roleType)))
{
@@ -52,7 +48,7 @@ public static class CustomRoleManager
return null;
}
roleBehaviour.Role = (RoleTypes)GetNextRoleId(roleId);
roleBehaviour.Role = (RoleTypes)GetNextRoleId();
roleBehaviour.TeamType = customRole.Team == ModdedRoleTeams.Neutral ? RoleTeamTypes.Crewmate : (RoleTeamTypes)customRole.Team;
roleBehaviour.NameColor = customRole.RoleColor;
roleBehaviour.StringName = CustomStringName.CreateAndRegister(customRole.RoleName);
@@ -72,7 +68,7 @@ public static class CustomRoleManager
RoleManager.GhostRoles.Add(roleBehaviour.Role);
}
CustomRoles.Add(roleId, roleBehaviour);
CustomRoles.Add(_roleId, roleBehaviour);
if (customRole.HideSettings)
{
+1 -13
View File
@@ -3,16 +3,4 @@
namespace MiraAPI.Roles;
[AttributeUsage(AttributeTargets.Class)]
public class RegisterCustomRoleAttribute : Attribute
{
public ushort RoleId { get; }
public RegisterCustomRoleAttribute()
{
RoleId = (ushort)(20 + CustomRoleManager.CustomRoles.Count);
}
public RegisterCustomRoleAttribute(ushort roleId)
{
RoleId = roleId;
}
}
public class RegisterCustomRoleAttribute : Attribute;
+17
View File
@@ -0,0 +1,17 @@
using System;
using System.Linq;
namespace MiraAPI.Roles;
public class RoleId
{
public static ushort Get<T>() where T : RoleBehaviour
{
if (!CustomRoleManager.CustomRoles.Values.OfType<T>().Any())
{
throw new InvalidOperationException($"Role {typeof(T)} is not registered");
}
return (ushort)CustomRoleManager.CustomRoles.Values.OfType<T>().Single().Role;
}
}
+2 -2
View File
@@ -72,7 +72,7 @@ public static class Extensions
public static Color DarkenColor(this Color color)
{
return new Color(color.r - 0.3f, color.g - 0.3f, color.b - 0.3f);
return new Color(color.r - 0.45f, color.g - 0.45f, color.b - 0.45f);
}
public static Color GetAlternateColor(this Color color)
{
@@ -81,7 +81,7 @@ public static class Extensions
public static Color LightenColor(this Color color)
{
return new Color(color.r + 0.3f, color.g + 0.3f, color.b + 0.3f);
return new Color(color.r + 0.45f, color.g + 0.45f, color.b + 0.45f);
}
public static bool IsColorDark(this Color color)
+61 -5
View File
@@ -5,14 +5,16 @@
A thorough, but simple, Among Us modding API and utility library that covers:
- Roles
- Options
- Modifiers
- Game Modes
- Buttons
- Assets
- HUD Elements
- Compatibility
- ~~Modifiers~~ (coming soon)
- ~~Game Modes~~ (coming soon)
Mira API strives to be simple and easy to use, while also using as many base game elements as possible. The result is a less intrusive, better modding API that covers general use cases.
**Join the [Discord](https://discord.gg/all-of-us-launchpad-794950428756410429) for support and to stay updated on the latest releases**
# Usage
To start using Mira API, you need to:
@@ -22,16 +24,46 @@ To start using Mira API, you need to:
For a full example, see [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/ExamplePlugin.cs).
## Recommended Project Structure
It is highly recommended to follow this project structure when using Mira API in order to keep your code clean and organized. You can also view the Example Mod in this repository for some guidance.
```
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
```
## Roles
Roles are very simple in Mira API. There are 3 things you need to do to create a custom role:
1. Create a class that inherits from a base game role (like `RoleBehaviour`, `CrewmateRole`, `ImpostorRole`, etc).
1. Create a class that inherits from a base game role (like `CrewmateRole`, `ImpostorRole`, etc)
2. Implement the `ICustomRole` interface from Mira API.
3. Add the `[RegisterCustomRole]` attribute to the class.
Note: For step 1, if you are making neutral roles, choose either `CrewmateRole` or `ImpostorRole` as the base depending on if it can kill or not!
Mira API handles everything else, from adding the proper options to the settings menu, to managing the role assignment at the start of the game. There are no extra steps on the developer's part.
See [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Roles/CustomRole.cs) for a code example.
## Options
Options are also very simple in Mira API. Options are split up into Groups and Options. Every Option needs to be in a Group.
Options are also very simple in Mira API. Mira API handles all the hard work behind the scenes, so developers only have to follow a few steps to create their custom options. The Options API is split up into Groups and Options. Every Option needs to be in a Group.
To create a group, you need to create a class that inherits from the `AbstractOptionGroup` abstract class. Groups contain 4 properties, `GroupName`, `GroupColor`, `GroupVisible`, and `AdvancedRole`. Only the `GroupName` is required.
@@ -57,6 +89,8 @@ Once you have an options group, there are two ways to make the actual options:
- Use an Option Attribute with a property.
- Create a ModdedOption property.
### Option Attributes
This is an example of using an Option Attribute on a property:
```csharp
// The first parameter is always the name of the option. The rest are dependent on the type of option.
@@ -64,6 +98,26 @@ This is an example of using an Option Attribute on a property:
public float SussyLevel { get; set; } = 4f; // You can set a default value here.
```
Here are the available Option Attributes and their signatures:
```csharp
ModdedEnumOption(string name, Type enumType, Type roleType = null)
ModdedNumberOption(
string name,
float min,
float max,
float increment=1
NumberSuffixes suffixType = NumberSuffixes.None,
bool zeroInfinity = false,
Type roleType = null)
ModdedStringOption(string title, string[] values, Type roleType = null)
ModdedToggleOption(string name, Type roleType = null)
```
### ModdedOption Properties
And this is an example of a ModdedOption property:
```csharp
public ModdedToggleOption YeezusAbility { get; } = new ModdedToggleOption("Yeezus Ability", false);
@@ -114,6 +168,8 @@ Mira API provides a simple interface for adding ability buttons to the game. The
1. Create a class that inherits from the `CustomActionButton` class and implement the properties and methods.
2. Add the `[RegisterCustomButton]` attribute to the class.
All other tasks and logic required to add the button to the game are handled by Mira API.
The button API is simple, but provides a lot of flexibility. There are various methods you can override to customize the behaviour of your button. See [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI/Hud/CustomActionButton.cs) for a full list of methods you can override.
An example button can be found [here](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Buttons/ExampleButton.cs).