Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RPC add modifier with arguments #19

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
27 changes: 27 additions & 0 deletions MiraAPI.Example/Buttons/TestButton.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using MiraAPI.Example.Modifiers;
using MiraAPI.Hud;
using MiraAPI.Modifiers;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities.Extensions;
using UnityEngine;

namespace MiraAPI.Example.Buttons;

[RegisterButton]
public class TestButton : CustomActionButton
{
public override string Name => "Test Button";
public override float Cooldown => 0f;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;

protected override void OnClick()
{
var randomPlayer = PlayerControl.AllPlayerControls.ToArray().Random();
PlayerControl.LocalPlayer.RpcAddModifier<ModifierParams>("test", 1, randomPlayer);
}

public override bool Enabled(RoleBehaviour? role)
{
return true;
}
}
16 changes: 16 additions & 0 deletions MiraAPI.Example/Modifiers/ModifierParams.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using MiraAPI.Modifiers;
using Reactor.Utilities;

namespace MiraAPI.Example.Modifiers;

[RegisterModifier]
public class ModifierParams : BaseModifier
{
public override string ModifierName => "ModifierParams";

public ModifierParams(string param1, int param2, PlayerControl playerControl)
{
Logger<ExamplePlugin>.Error($"Param1: {param1}, Param2: {param2}");
Logger<ExamplePlugin>.Error($"Player: {playerControl.PlayerId}");
}
}
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
21 changes: 21 additions & 0 deletions MiraAPI/Modifiers/ModifierData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;

namespace MiraAPI.Modifiers;

/// <summary>
/// Modifier data for networking.
/// </summary>
/// <param name="type">The Type of the modifier.</param>
/// <param name="args">Parameters for constructor.</param>
public readonly struct ModifierData(Type type, object[] args)
{
/// <summary>
/// Gets the type of the modifier.
/// </summary>
public Type Type { get; } = type;

/// <summary>
/// Gets the parameters for the constructor.
/// </summary>
public object[] Args { get; } = args;
}
138 changes: 138 additions & 0 deletions MiraAPI/Modifiers/ModifierExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using MiraAPI.Networking;
using Reactor.Networking.Attributes;
using Reactor.Networking.Rpc;
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>
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
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;
}

Rpc<AddModifierRpc>.Instance.Send(target, new ModifierData(type, args));
}

/// <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;
}

player.RpcAddModifier(id.Value, args);
}
}
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);
}
}
61 changes: 61 additions & 0 deletions MiraAPI/Networking/AddModifierRpc.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Hazel;
using MiraAPI.Modifiers;
using Reactor.Networking.Attributes;
using Reactor.Networking.Rpc;
using Reactor.Networking.Serialization;

namespace MiraAPI.Networking;

/// <summary>
/// Remote procedure call for adding a modifier.
/// </summary>
/// <param name="plugin">Mira plugin.</param>
/// <param name="id">RPC ID.</param>
[RegisterCustomRpc((uint)MiraRpc.AddModifier)]
public class AddModifierRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, ModifierData>(plugin, id)
{
private static readonly Dictionary<Type, ParameterInfo[]> ParameterCache = [];

/// <inheritdoc />
public override RpcLocalHandling LocalHandling => RpcLocalHandling.Before;

/// <inheritdoc />
public override void Write(MessageWriter writer, ModifierData data)
{
var modId = ModifierManager.GetModifierId(data.Type);
writer.WritePacked((uint)modId!);
MessageSerializer.Serialize(writer, data.Args);
}

/// <inheritdoc />
public override ModifierData Read(MessageReader reader)
{
var modId = reader.ReadPackedUInt32();
var modifier = ModifierManager.GetModifierType(modId)!;

if (!ParameterCache.TryGetValue(modifier, out var paramTypes))
{
paramTypes = modifier.GetConstructors().OrderBy(x => x.GetParameters().Length).First().GetParameters();
ParameterCache[modifier] = paramTypes;
}

var objects = new object[paramTypes.Length];
foreach (var paramType in paramTypes)
{
objects[paramType.Position] = reader.Deserialize(paramType.ParameterType);
}

return new ModifierData(modifier, objects);
}

/// <inheritdoc />
public override void Handle(PlayerControl innerNetObject, ModifierData data)
{
var modifier = ModifierFactory.CreateInstance(data.Type, data.Args);
innerNetObject.GetModifierComponent()?.AddModifier(modifier);
}
}
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
Loading