Skip to content

Commit

Permalink
RPC add modifier with arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
XtraCube committed Feb 2, 2025
1 parent 51fcc2c commit 9485099
Show file tree
Hide file tree
Showing 9 changed files with 227 additions and 130 deletions.
1 change: 1 addition & 0 deletions MiraAPI.Example/Buttons/Freezer/FreezeButton.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.Hud;
using MiraAPI.Modifiers;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
Expand Down
2 changes: 1 addition & 1 deletion MiraAPI.Example/Buttons/MeetingButton.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using MiraAPI.Example.Modifiers;
using MiraAPI.Hud;
using MiraAPI.Utilities;
using MiraAPI.Modifiers;
using MiraAPI.Utilities.Assets;
using UnityEngine;

Expand Down
1 change: 0 additions & 1 deletion MiraAPI/GameOptions/OptionTypes/ModdedEnumOption.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Globalization;
using System.Linq;
using System.Text;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
Expand Down
139 changes: 139 additions & 0 deletions MiraAPI/Modifiers/ModifierExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using MiraAPI.Networking;
using Reactor.Networking.Attributes;
using Reactor.Utilities;

namespace MiraAPI.Modifiers;

/// <summary>
/// Extension methods for modifiers.
/// </summary>
public static class ModifierExtensions
{
internal static Dictionary<PlayerControl, ModifierComponent> ModifierComponents { get; } = [];

/// <summary>
/// Gets the ModifierComponent for a player.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <returns>A ModifierComponent if there is one, null otherwise.</returns>
public static ModifierComponent? GetModifierComponent(this PlayerControl player)
{
if (ModifierComponents.TryGetValue(player, out var component))
{
return component;
}

component = player.GetComponent<ModifierComponent>();
if (component == null)
{
return null;
}

ModifierComponents[player] = component;
return component;
}

/// <summary>
/// Gets a modifier by its type, or null if the player doesn't have it.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
/// <returns>The Modifier if it is found, null otherwise.</returns>
public static T? GetModifier<T>(this PlayerControl? player) where T : BaseModifier
{
return player?.GetModifierComponent()?.ActiveModifiers.Find(x => x is T) as T;
}

/// <summary>
/// Checks if a player has a modifier.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
/// <returns>True if the Modifier is present, false otherwise.</returns>
public static bool HasModifier<T>(this PlayerControl? player) where T : BaseModifier
{
return player?.GetModifierComponent() != null &&
player.GetModifierComponent()!.HasModifier<T>();
}

/// <summary>
/// Checks if a player has a modifier by its ID.
/// </summary>
/// <param name="player">The PlayerControl object.</param>
/// <param name="id">The Modifier ID.</param>
/// <returns>True if the Modifier is present, false otherwise.</returns>
public static bool HasModifier(this PlayerControl? player, uint id)
{
return player?.GetModifierComponent() != null &&
player.GetModifierComponent()!.HasModifier(id);
}

/// <summary>
/// Remote Procedure Call to remove a modifier from a player.
/// </summary>
/// <param name="target">The player to remove the modifier from.</param>
/// <param name="modifierId">The ID of the modifier.</param>
[MethodRpc((uint)MiraRpc.RemoveModifier)]
public static void RpcRemoveModifier(this PlayerControl target, uint modifierId)
{
target.GetModifierComponent()?.RemoveModifier(modifierId);
}

/// <summary>
/// Remote Procedure Call to remove a modifier from a player.
/// </summary>
/// <param name="player">The player to remove the modifier from.</param>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
public static void RpcRemoveModifier<T>(this PlayerControl player) where T : BaseModifier
{
var id = ModifierManager.GetModifierId(typeof(T));

if (id == null)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
return;
}

player.RpcRemoveModifier(id.Value);
}

/// <summary>
/// Remote Procedure Call to add a modifier to a player.
/// </summary>
/// <param name="target">The player to add the modifier to.</param>
/// <param name="modifierId">The modifier ID.</param>
[MethodRpc((uint)MiraRpc.AddModifier)]
public static void RpcAddModifier(this PlayerControl target, uint modifierId, params object[] args)
{
var type = ModifierManager.GetModifierType(modifierId);
if (type == null)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier with id {modifierId} because it is not registered.");
return;
}

var modifier = ModifierFactory.CreateInstance(type, args);
target.GetModifierComponent()?.AddModifier(modifier);
}

/// <summary>
/// Remote Procedure Call to add a modifier to a player.
/// </summary>
/// <param name="player">The player to add the modifier to.</param>
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
/// <typeparam name="T">The modifier Type.</typeparam>
public static void RpcAddModifier<T>(this PlayerControl player, params object[] args) where T : BaseModifier
{
var id = ModifierManager.GetModifierId(typeof(T));
if (id == null)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
return;
}

var modifier = ModifierFactory<T>.CreateInstance(args);
player.GetModifierComponent()!.AddModifier(modifier);
}
}
83 changes: 83 additions & 0 deletions MiraAPI/Modifiers/ModifierFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using System;
using System.Linq;
using System.Linq.Expressions;

namespace MiraAPI.Modifiers;

public static class ModifierFactory
{
private static Func<object[], BaseModifier>? _constructor;

private static Func<object[], BaseModifier> CreateConstructor(Type type)
{
var constructorInfo = type.GetConstructors()
.OrderByDescending(c => c.GetParameters().Length)
.FirstOrDefault() ?? throw new InvalidOperationException($"Type {type} does not have a public constructor.");
var parameters = constructorInfo.GetParameters();

var argsParam = Expression.Parameter(typeof(object[]), "args");
var constructorParams = new Expression[parameters.Length];

for (var i = 0; i < parameters.Length; i++)
{
var paramType = parameters[i].ParameterType;
var paramAccess = Expression.ArrayIndex(argsParam, Expression.Constant(i));
var convertedParam = Expression.Convert(paramAccess, paramType);
constructorParams[i] = convertedParam;
}

var newExpr = Expression.New(constructorInfo, constructorParams);
var lambda = Expression.Lambda<Func<object[], BaseModifier>>(newExpr, argsParam);

return lambda.Compile();
}

public static BaseModifier CreateInstance(Type type, params object[] args)
{
_constructor ??= CreateConstructor(type);
return _constructor(args);
}
}

/// <summary>
/// Factory for creating instances of a modifier. More efficient than using reflection.
/// </summary>
/// <typeparam name="T">The modifier type.</typeparam>
public static class ModifierFactory<T> where T : BaseModifier
{
private static readonly Func<object[], T> Constructor = CreateConstructor();

private static Func<object[], T> CreateConstructor()
{
var constructorInfo = typeof(T).GetConstructors()
.OrderByDescending(c => c.GetParameters().Length)
.FirstOrDefault() ?? throw new InvalidOperationException($"Type {typeof(T)} does not have a public constructor.");
var parameters = constructorInfo.GetParameters();

var argsParam = Expression.Parameter(typeof(object[]), "args");
var constructorParams = new Expression[parameters.Length];

for (var i = 0; i < parameters.Length; i++)
{
var paramType = parameters[i].ParameterType;
var paramAccess = Expression.ArrayIndex(argsParam, Expression.Constant(i));
var convertedParam = Expression.Convert(paramAccess, paramType);
constructorParams[i] = convertedParam;
}

var newExpr = Expression.New(constructorInfo, constructorParams);
var lambda = Expression.Lambda<Func<object[], T>>(newExpr, argsParam);

return lambda.Compile();
}

/// <summary>
/// Creates an instance of the modifier.
/// </summary>
/// <param name="args">Parameters for the constructor.</param>
/// <returns>An instance of the modifier.</returns>
public static T CreateInstance(params object[] args)
{
return Constructor(args);
}
}
2 changes: 1 addition & 1 deletion MiraAPI/Patches/Modifiers/EndGameDidWinPatch.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Linq;
using HarmonyLib;
using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
using MiraAPI.Utilities;

namespace MiraAPI.Patches.Modifiers;

Expand Down
2 changes: 1 addition & 1 deletion MiraAPI/Patches/PlayerControlPatches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,6 @@ public static void PlayerControlFixedUpdatePostfix(PlayerControl __instance)
[HarmonyPatch(nameof(PlayerControl.OnDestroy))]
public static void PlayerControlOnDestroyPrefix(PlayerControl __instance)
{
Utilities.Extensions.ModifierComponents.Remove(__instance);
ModifierExtensions.ModifierComponents.Remove(__instance);
}
}
2 changes: 1 addition & 1 deletion MiraAPI/Patches/VentPatches.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using HarmonyLib;
using MiraAPI.Modifiers;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using UnityEngine;

namespace MiraAPI.Patches;
Expand Down
Loading

0 comments on commit 9485099

Please sign in to comment.