forked from rwmt/Multiplayer
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathSyncActions.cs
More file actions
137 lines (112 loc) · 6.16 KB
/
SyncActions.cs
File metadata and controls
137 lines (112 loc) · 6.16 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
using RimWorld;
using RimWorld.Planet;
using System;
using System.Collections.Generic;
using System.Reflection;
using HarmonyLib;
using Verse;
namespace Multiplayer.Client
{
static class SyncActions
{
static SyncAction<FloatMenuOption, WorldObject, Caravan, object> SyncWorldObjCaravanMenus;
static SyncAction<FloatMenuOption, WorldObject, IEnumerable<IThingHolder>, CompLaunchable> SyncTransportPodMenus;
static Type CaravanActionConfirmationType;
static Type TransportPodActionConfirmationType;
public static void Init()
{
void Error(string error)
{
Multiplayer.loadingErrors = true;
Log.Error(error);
}
// TODO: Use MpMethodUtil instead if we decide to make it work with generic types/methods (already in MP Compat, so use it). Or remove this TODO if we decide not to.
CaravanActionConfirmationType = AccessTools.Inner(typeof(CaravanArrivalActionUtility), "<>c__DisplayClass0_1`1");
TransportPodActionConfirmationType = AccessTools.Inner(typeof(TransportPodsArrivalActionUtility), "<>c__DisplayClass0_0`1");
if (CaravanActionConfirmationType == null) Error($"Could not find type: {nameof(CaravanArrivalActionUtility)}.<>c__DisplayClass0_1<T>");
if (TransportPodActionConfirmationType == null) Error($"Could not find type: {nameof(TransportPodsArrivalActionUtility)}.<>c__DisplayClass0_0<T>");
SyncWorldObjCaravanMenus = RegisterActions((WorldObject obj, Caravan c) => obj.GetFloatMenuOptions(c), ActionGetter, WorldObjectCaravanMenuWrapper);
SyncWorldObjCaravanMenus.PatchAll(nameof(WorldObject.GetFloatMenuOptions));
SyncTransportPodMenus = RegisterActions((WorldObject obj, IEnumerable<IThingHolder> p, CompLaunchable r) => obj.GetTransportPodsFloatMenuOptions(p, r), o => ref o.action, TransportPodMenuWrapper);
SyncTransportPodMenus.PatchAll(nameof(WorldObject.GetTransportPodsFloatMenuOptions));
}
private static ref Action ActionGetter(FloatMenuOption o) => ref o.action;
private static Action WorldObjectCaravanMenuWrapper(FloatMenuOption instance, WorldObject a, Caravan b, object c, Action original, Action sync)
{
if (instance.action.Method.DeclaringType is not { IsGenericType: true })
return null;
if (instance.action.Method.DeclaringType.GetGenericTypeDefinition() != CaravanActionConfirmationType)
return null;
return () =>
{
var field = AccessTools.DeclaredField(original.Target.GetType(), "action");
if (!Multiplayer.ExecutingCmds)
{
// If not in a synced call then replace the method that will be
// called by confirmation dialog with our synced method call.
field.SetValue(original.Target, sync);
original();
return;
}
// If we're in a synced call, just call the action itself (after confirmation dialog)
((Action)field.GetValue(original.Target))();
};
}
public static Action TransportPodMenuWrapper(FloatMenuOption instance, WorldObject worldObject, IEnumerable<IThingHolder> thingHolders, CompLaunchable compLaunchable, Action original, Action sync)
{
if (instance.action.Method.DeclaringType is not { IsGenericType: true })
return null;
if (instance.action.Method.DeclaringType.GetGenericTypeDefinition() != TransportPodActionConfirmationType)
return null;
return () =>
{
var field = AccessTools.DeclaredField(original.Target.GetType(), "uiConfirmationCallback");
if (Multiplayer.ExecutingCmds)
{
// Remove UI confirmation during synced commands so the method is just called directly
field.SetValue(original.Target, null);
original();
return;
}
var confirmation = (Action<Action>)field.GetValue(original.Target);
// If no confirmation dialog, just sync
if (confirmation == null)
sync();
// If there's a confirmation dialog, call it with sync as its synced method
else
confirmation(sync);
};
}
static SyncAction<T, A, B, object> RegisterActions<T, A, B>(Func<A, B, IEnumerable<T>> func, ActionGetter<T> actionGetter, ActionWrapper<T, A, B, object> actionWrapper = null)
{
return RegisterActions<T, A, B, object>((a, b, c) => func(a, b), actionGetter, actionWrapper);
}
static SyncAction<T, A, B, C> RegisterActions<T, A, B, C>(Func<A, B, C, IEnumerable<T>> func, ActionGetter<T> actionGetter, ActionWrapper<T, A, B, C> actionWrapper = null)
{
var sync = new SyncAction<T, A, B, C>(func, actionGetter, actionWrapper);
Sync.handlers.Add(sync);
return sync;
}
public static Dictionary<MethodBase, ISyncAction> syncActions = new();
public static bool wantOriginal;
private static bool syncingActions; // Prevents from running on base methods
public static void SyncAction_Prefix(ref bool __state)
{
__state = syncingActions;
syncingActions = true;
}
public static void SyncAction1_Postfix(object __instance, object __0, ref object __result, MethodBase __originalMethod, bool __state)
{
SyncAction2_Postfix(__instance, __0, null, ref __result, __originalMethod, __state);
}
public static void SyncAction2_Postfix(object __instance, object __0, object __1, ref object __result, MethodBase __originalMethod, bool __state)
{
if (!__state)
{
syncingActions = false;
if (Multiplayer.ShouldSync && !wantOriginal && !syncingActions)
__result = syncActions[__originalMethod].DoSync(__instance, __0, __1);
}
}
}
}