Skip to content

Commit

Permalink
#191 Fix NPE in creature building.
Browse files Browse the repository at this point in the history
NPE was caused by some events coming from non KMonoBehaviour - StateMachine.Instance.

Add reference mechanism for the type as well.
  • Loading branch information
zuev93 committed Jul 31, 2023
1 parent eb99e63 commit a457eb7
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@

namespace MultiplayerMod.Game.Mechanics.Objects;

[Serializable]
public record ObjectEventsArgs(
public record ComponentEventsArgs(
ComponentReference Target,
Type MethodType,
string MethodName,
Expand Down
52 changes: 35 additions & 17 deletions MultiplayerMod/Game/Mechanics/Objects/ObjectEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ namespace MultiplayerMod.Game.Mechanics.Objects;
[HarmonyPatch]
public static class ObjectEvents {

public static event Action<ObjectEventsArgs>? MethodCalled;
public static event Action<ComponentEventsArgs>? ComponentMethodCalled;
public static event Action<StateMachineEventsArgs>? StateMachineMethodCalled;

private static readonly Dictionary<Type, string[]> methodsForPatch = new() {
{
Expand Down Expand Up @@ -153,24 +154,41 @@ public static class ObjectEvents {

[HarmonyPostfix]
// ReSharper disable once UnusedMember.Local
private static void ObjectEventsPostfix(KMonoBehaviour __instance, MethodBase __originalMethod, object[] __args) =>
private static void ObjectEventsPostfix(object __instance, MethodBase __originalMethod, object[] __args) =>
PatchControl.RunIfEnabled(
() => {
MethodCalled?.Invoke(
new ObjectEventsArgs(
__instance.GetReference(),
__originalMethod.DeclaringType!,
__originalMethod.Name,
__args.Select(
obj =>
obj switch {
GameObject gameObject => gameObject.GetGridReference(),
KMonoBehaviour kMonoBehaviour => kMonoBehaviour.GetReference(),
_ => obj
}
).ToArray()
)
);
var args = __args.Select(
obj =>
obj switch {
GameObject gameObject => gameObject.GetGridReference(),
KMonoBehaviour kMonoBehaviour => kMonoBehaviour.GetReference(),
_ => obj
}
).ToArray();
switch (__instance) {
case KMonoBehaviour kMonoBehaviour:
ComponentMethodCalled?.Invoke(
new ComponentEventsArgs(
kMonoBehaviour.GetReference(),
__originalMethod.DeclaringType!,
__originalMethod.Name,
args
)
);
return;
case StateMachine.Instance stateMachine:
StateMachineMethodCalled?.Invoke(
new StateMachineEventsArgs(
stateMachine.GetReference(),
__originalMethod.DeclaringType!,
__originalMethod.Name,
args
)
);
return;
default:
throw new NotSupportedException($"{__instance} has un supported type");
}
}
);

Expand Down
11 changes: 11 additions & 0 deletions MultiplayerMod/Game/Mechanics/Objects/StateMachineEventsArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;
using MultiplayerMod.Multiplayer.Objects.Reference;

namespace MultiplayerMod.Game.Mechanics.Objects;

public record StateMachineEventsArgs(
StateMachineReference Target,
Type MethodType,
string MethodName,
object[] Args
);
10 changes: 10 additions & 0 deletions MultiplayerMod/Game/Mechanics/Objects/TargetExtractor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ public static IEnumerable<MethodBase> GetTargetMethods(Dictionary<Type, string[]
type => type.IsClass && (classTypes.Contains(type)
|| interfaceTypes.Any(interfaceType => interfaceType.IsAssignableFrom(type)))
)
.Where(
type => {
var isAssignableFrom = typeof(KMonoBehaviour).IsAssignableFrom(type) ||
typeof(StateMachine.Instance).IsAssignableFrom(type);
if (!isAssignableFrom) {
log.Error($"{type} can not be assigned to KMonoBehaviour.");
}
return isAssignableFrom;
}
)
.SelectMany(
type => {
if (classTypes.Contains(type))
Expand Down
32 changes: 23 additions & 9 deletions MultiplayerMod/Multiplayer/Commands/Gameplay/CallMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,44 @@ namespace MultiplayerMod.Multiplayer.Commands.Gameplay;

[Serializable]
public class CallMethod : IMultiplayerCommand {
private readonly ObjectEventsArgs eventArgs;
private readonly ComponentReference? componentTarget;
private readonly StateMachineReference? stateMachineTarget;
private readonly Type methodType;
private readonly string methodName;
private readonly object[] args;

public CallMethod(ObjectEventsArgs eventArgs) {
this.eventArgs = eventArgs;
public CallMethod(ComponentEventsArgs eventArgs) {
componentTarget = eventArgs.Target;
methodType = eventArgs.MethodType;
methodName = eventArgs.MethodName;
args = eventArgs.Args;
}

public CallMethod(StateMachineEventsArgs eventArgs) {
stateMachineTarget = eventArgs.Target;
methodType = eventArgs.MethodType;
methodName = eventArgs.MethodName;
args = eventArgs.Args;
}

public void Execute() {
var method = eventArgs.MethodType
var method = methodType
.GetMethod(
eventArgs.MethodName,
methodName,
BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance |
BindingFlags.DeclaredOnly
);
var args = eventArgs.Args.Select(
var args = this.args.Select(
arg =>
arg switch {
GameObjectReference gameObjectReference => gameObjectReference.GetGameObject(),
ComponentReference reference => reference.GetComponent(),
_ => arg
}
).ToArray();
var component = eventArgs.Target.GetComponent();
if (component != null) {
method?.Invoke(component, args);
object? obj = componentTarget != null ? componentTarget.GetComponent() : stateMachineTarget!.GetInstance();
if (obj != null) {
method?.Invoke(obj, args);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ private void BindOverlays() {
}

private void BindMechanics() {
ObjectEvents.MethodCalled += args => client.Send(new CallMethod(args));
ObjectEvents.ComponentMethodCalled += args => client.Send(new CallMethod(args));
ObjectEvents.StateMachineMethodCalled += args => client.Send(new CallMethod(args));
TelepadEvents.AcceptDelivery += args => client.Send(new AcceptDelivery(args));
TelepadEvents.Reject += reference => client.Send(new RejectDelivery(reference));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public ComponentReference(GameObjectReference gameObjectReference, Type type) {
ComponentType = type;
}

public Component GetComponent() => GameObjectReference.GetComponent(ComponentType);
public Component? GetComponent() => GameObjectReference.GetComponent(ComponentType);

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public abstract class GameObjectReference {

public T GetComponent<T>() where T : class => GetGameObject().GetComponent<T>();

public Component GetComponent(Type type) => GetGameObject().GetComponent(type);
public Component? GetComponent(Type type) => GetGameObject().GetComponent(type);

protected abstract GameObject? Resolve();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;

namespace MultiplayerMod.Multiplayer.Objects.Reference;

[Serializable]
public class StateMachineReference {

private ComponentReference<StateMachineController> ControllerReference { get; set; }
private Type StateMachineType { get; set; }

public StateMachineReference(
ComponentReference<StateMachineController> controllerReference,
Type stateMachineType
) {
ControllerReference = controllerReference;
StateMachineType = stateMachineType;
}

public StateMachine.Instance? GetInstance() => ControllerReference.GetComponent().GetSMI(StateMachineType);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Reflection;
using MultiplayerMod.Multiplayer.Objects.Reference;

namespace MultiplayerMod.Multiplayer.Objects;

public static class StateMachineReferenceExtensions {

public static StateMachineReference GetReference(this StateMachine.Instance instance) => new(
GetStateMachineController(instance).GetReference(),
instance.GetType()
);

// `controller` field is defined in StateMachine<,,,>.GenericInstance. However cast is impossible due to unknown
// generic argument types. So reflection is the most handy way to get its value :(
private static StateMachineController GetStateMachineController(StateMachine.Instance instance) =>
(StateMachineController) instance.GetType()
.GetField("controller", BindingFlags.Instance | BindingFlags.NonPublic)!
.GetValue(instance);
}

0 comments on commit a457eb7

Please sign in to comment.