Skip to content

Commit

Permalink
Fix rituals
Browse files Browse the repository at this point in the history
- Add prepatches for handling 1.5 generic rituals
- Add ExternalAnnotations for Prepatcher
- Remove test patch left in by mistake
  • Loading branch information
Zetrith committed Jun 4, 2024
1 parent b514e05 commit 41f0131
Show file tree
Hide file tree
Showing 15 changed files with 215 additions and 88 deletions.
9 changes: 0 additions & 9 deletions Source/Client/Patches/Patches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -581,13 +581,4 @@ static void FixStorage(IStoreSettingsParent __instance, StorageSettings ___allow
___allowedNutritionSettings.owner ??= __instance;
}
}

[HarmonyPatch(typeof(PawnRoleSelectionWidgetBase<ILordJobRole>), nameof(PawnRoleSelectionWidgetBase<ILordJobRole>.TryAssign))]
static class Patc
{
static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> insts)
{
return insts;
}
}
}
4 changes: 2 additions & 2 deletions Source/Client/Syncing/Dict/SyncDictDlc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ public static class SyncDictDlc
WriteSync(data, dialog.ritual);
},
(ByteReader data) => {
var assignments = ReadSync<RitualRoleAssignments>(data) as MpRitualAssignments;
var assignments = (MpRitualAssignments)ReadSync<RitualRoleAssignments>(data);
if (assignments == null) return null;

var ritual = ReadSync<Precept_Ritual>(data); // todo handle ritual becoming null?
Expand All @@ -208,7 +208,7 @@ public static class SyncDictDlc
WriteSync(data, dialog.ritual);
},
(ByteReader data) => {
var assignment = ReadSync<RitualRoleAssignments>(data) as MpRitualAssignments;
var assignment = (MpRitualAssignments)ReadSync<RitualRoleAssignments>(data);
if (assignment == null) return null;

var ritual = ReadSync<Precept_Ritual>(data); // todo handle ritual becoming null?
Expand Down
36 changes: 10 additions & 26 deletions Source/Client/Syncing/Game/SyncDelegates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Reflection.Emit;
using HarmonyLib;
using Multiplayer.Client.Patches;
using MultiplayerLoader;
using Verse;

namespace Multiplayer.Client
Expand Down Expand Up @@ -266,44 +267,27 @@ private static void InitRituals()
SyncDelegate.Lambda(typeof(SocialCardUtility), nameof(SocialCardUtility.DrawPawnRoleSelection), 0); // Begin role change: remove role
SyncDelegate.Lambda(typeof(SocialCardUtility), nameof(SocialCardUtility.DrawPawnRoleSelection), 3); // Begin role change: assign role

// SyncDelegate.Lambda(typeof(Dialog_BeginRitual), nameof(Dialog_BeginRitual.DrawRoleSelection), 0); // Select role: none
// SyncDelegate.Lambda(typeof(Dialog_BeginRitual), nameof(Dialog_BeginRitual.DrawRoleSelection), 3); // Select role, set confirm text
// SyncDelegate.Lambda(typeof(Dialog_BeginRitual), nameof(Dialog_BeginRitual.DrawRoleSelection), 4); // Select role, no confirm text
SyncDelegate.Lambda(typeof(Dialog_BeginRitual), nameof(Dialog_BeginRitual.DrawRoleSelection), 0); // Select role: none
SyncDelegate.Lambda(typeof(Dialog_BeginRitual), nameof(Dialog_BeginRitual.DrawRoleSelection), 3); // Select role, set confirm text
SyncDelegate.Lambda(typeof(Dialog_BeginRitual), nameof(Dialog_BeginRitual.DrawRoleSelection), 4); // Select role, no confirm text

/*
Ritual dialog
PawnRoleSelectionWidgetBase
The UI's main interaction area is split into three types of groups of pawns.
Each has three action handlers: (drop), (leftclick), (rightclick)
The names in parenths below indicate what is synced for each handler.
<Pawn Group>: <Handlers>
(Zero or more) roles: (local TryAssignReplace, local TryAssign), (null), (delegate)
Spectators: (assgn.TryAssignSpectate), (local TryAssignAnyRole), (assgn.RemoveParticipant)
Not participating: (assgn.RemoveParticipant), (delegate), float menus: (assgn.TryAssignSpectate, local TryAssignReplace, local TryAssign)
(Zero or more) roles: (TryAssignReplace, TryAssign), (null), (delegate)
Spectators: (assgn.TryAssignSpectate), (TryAssignAnyRole), (assgn.RemoveParticipant)
Not participating: (assgn.RemoveParticipant), (delegate), float menus: (assgn.TryAssignSpectate, TryAssignReplace, TryAssign)
*/

var ritualRolesSerializer = Serializer.New(
(IEnumerable<ILordJobRole> roles, object target, object[] _) =>
{
var roleSelectionWidget = (PawnRitualRoleSelectionWidget)target;
return (roleSelectionWidget, roleSelectionWidget.assignments.RoleGroups().FirstOrDefault(g => g.SequenceEqual(roles))?.Key);
},
data => data.roleSelectionWidget.assignments.RoleGroups().FirstOrDefault(g => g.Key == data.Key)
);

// todo for 1.5
// SyncMethod.Register(typeof(PawnRoleSelectionWidgetBase<ILordJobRole>), nameof(PawnRoleSelectionWidgetBase<ILordJobRole>.TryAssignReplace))
// .TransformArgument(1, ritualRolesSerializer);
// SyncMethod.Register(typeof(PawnRoleSelectionWidgetBase<ILordJobRole>), nameof(PawnRoleSelectionWidgetBase<ILordJobRole>.TryAssignAnyRole));
// SyncMethod.Register(typeof(PawnRoleSelectionWidgetBase<ILordJobRole>), nameof(PawnRoleSelectionWidgetBase<ILordJobRole>.TryAssign))
// .TransformArgument(1, ritualRolesSerializer);
//
// SyncMethod.Lambda(typeof(PawnRoleSelectionWidgetBase<ILordJobRole>), nameof(PawnRoleSelectionWidgetBase<ILordJobRole>.DrawPawnListInternal), 7); // Roles right click delegate (try assign spectate)
// SyncMethod.Lambda(typeof(PawnRoleSelectionWidgetBase<ILordJobRole>), nameof(PawnRoleSelectionWidgetBase<ILordJobRole>.DrawPawnListInternal), 2); // Not participating left click delegate (try assign any role or spectate)

SyncMethod.Register(typeof(RitualRoleAssignments), nameof(RitualRoleAssignments.TryAssignSpectate));
SyncMethod.Register(typeof(RitualRoleAssignments), nameof(RitualRoleAssignments.RemoveParticipant));

SyncRituals.ApplyPrepatches(null);
}

private static void InitChoiceLetters()
Expand Down
5 changes: 4 additions & 1 deletion Source/Client/Syncing/Handler/SyncMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq;
using System.Reflection;
using Multiplayer.Client.Util;
using MultiplayerLoader;
using Verse;

namespace Multiplayer.Client
Expand All @@ -14,7 +15,7 @@ public record SyncTransformer(Type LiveType, Type NetworkType, Delegate Writer,
public delegate void SyncMethodWriter(object obj, SyncType type, string debugInfo);


public class SyncMethod : SyncHandler, ISyncMethod
public class SyncMethod : SyncHandler, ISyncMethod, ISyncMethodForPrepatcher
{
public readonly Type targetType;
public readonly MethodInfo method;
Expand All @@ -37,6 +38,8 @@ public class SyncMethod : SyncHandler, ISyncMethod
private Action<object, object[]> beforeCall;
private Action<object, object[]> afterCall;

public Type TargetType => targetType;

public SyncMethod(Type targetType, string instancePath, MethodInfo method, SyncType[] inTypes)
{
this.method = method;
Expand Down
6 changes: 1 addition & 5 deletions Source/Client/Syncing/ImplSerialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Multiplayer.Client;

internal static class ImplSerialization
{
public static void Init(RwSyncTypeHelper typeHelper)
public static void Init()
{
Multiplayer.serialization.RegisterForSyncWithImpl(typeof(IStoreSettingsParent));
Multiplayer.serialization.RegisterForSyncWithImpl(typeof(IStorageGroupMember));
Expand All @@ -19,9 +19,5 @@ public static void Init(RwSyncTypeHelper typeHelper)
Multiplayer.serialization.RegisterForSyncWithImpl(typeof(IThingHolder));
Multiplayer.serialization.RegisterForSyncWithImpl(typeof(IReloadableComp));
Multiplayer.serialization.RegisterForSyncWithImpl(typeof(Policy));

// todo for 1.5
Multiplayer.serialization.RegisterForSyncWithImpl(typeof(PawnRoleSelectionWidgetBase<ILordJobRole>));
typeHelper.AddImplManually(typeof(PawnRoleSelectionWidgetBase<ILordJobRole>), typeof(PawnRitualRoleSelectionWidget));
}
}
5 changes: 2 additions & 3 deletions Source/Client/Syncing/RwSerialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ public static class RwSerialization
{
public static void Init()
{
var typeHelper = new RwSyncTypeHelper();
Multiplayer.serialization = new Common.SyncSerialization(typeHelper);
Multiplayer.serialization = new Common.SyncSerialization(new RwSyncTypeHelper());

// CanHandle hooks
Multiplayer.serialization.AddCanHandleHook(syncType =>
Expand Down Expand Up @@ -113,7 +112,7 @@ public static void Init()
}
);

ImplSerialization.Init(typeHelper);
ImplSerialization.Init();
CompSerialization.Init();
ApiSerialization.Init();
DefSerialization.Init();
Expand Down
10 changes: 0 additions & 10 deletions Source/Client/Syncing/RwSyncTypeHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,8 @@ namespace Multiplayer.Client;

internal class RwSyncTypeHelper : SyncTypeHelper
{
private Dictionary<Type, List<Type>> manualImplTypes = new();

public void AddImplManually(Type baseType, Type implType)
{
manualImplTypes.GetOrAddNew(baseType).Add(implType);
}

public override List<Type> GetImplementations(Type baseType)
{
if (manualImplTypes.TryGetValue(baseType, out var manualImplType))
return manualImplType;

return baseType.IsInterface
? TypeCache.interfaceImplementationsOrdered[baseType]
: TypeCache.subClassesOrdered[baseType];
Expand Down
12 changes: 3 additions & 9 deletions Source/Client/Syncing/Sync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,10 @@ public static void PostInitHandlers()

public static SyncMethod Method(Type targetType, string methodName, SyncType[] argTypes = null)
{
return Method(targetType, null, methodName, argTypes);
}

public static SyncMethod Method(Type targetType, string instancePath, string methodName, SyncType[] argTypes = null)
{
var instanceType = instancePath == null ? targetType : MpReflection.PathType($"{targetType}/{instancePath}");
var method = AccessTools.Method(instanceType, methodName, argTypes?.Select(t => t.type).ToArray())
?? throw new Exception($"Couldn't find method {instanceType}::{methodName}");
var method = AccessTools.Method(targetType, methodName, argTypes?.Select(t => t.type).ToArray())
?? throw new Exception($"Couldn't find method {targetType}::{methodName}");

SyncMethod handler = new SyncMethod(targetType, instancePath, method, argTypes);
SyncMethod handler = new SyncMethod(targetType, null, method, argTypes);
handlers.Add(handler);
return handler;
}
Expand Down
9 changes: 4 additions & 5 deletions Source/Client/Syncing/SyncTemplates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<
{
int idx;
var label = gen.DefineLabel();
var parameter = original.GetParameters();
var parameters = original.GetParameters();

idx = 0;
foreach (var pInfo in parameter) {
foreach (var pInfo in parameters) {
var argIndex = idx++ + (original.IsStatic ? 0 : 1);
var pType = pInfo.ParameterType;
if (pInfo.IsOut) {
Expand All @@ -58,12 +58,12 @@ static IEnumerable<CodeInstruction> Transpiler(MethodBase original, IEnumerable<
yield return new CodeInstruction(OpCodes.Ldc_I4, Sync.methodBaseToInternalId[original]);
yield return new CodeInstruction(original.IsStatic ? OpCodes.Ldnull : OpCodes.Ldarg_0);

yield return new CodeInstruction(OpCodes.Ldc_I4, parameter.Length);
yield return new CodeInstruction(OpCodes.Ldc_I4, parameters.Length);
yield return new CodeInstruction(OpCodes.Newarr, typeof(object));

idx = 0;
var arrayIdx = 0;
foreach (var pInfo in parameter) {
foreach (var pInfo in parameters) {
var argIndex = idx++ + (original.IsStatic ? 0 : 1);
var pType = pInfo.ParameterType;
yield return new CodeInstruction(OpCodes.Dup);
Expand Down Expand Up @@ -116,7 +116,6 @@ static IEnumerable<CodeInstruction> CreateDefaultCodes(ILGenerator generator, Ty
yield return new CodeInstruction(OpCodes.Ldc_I8, (long) 0);
else
yield return new CodeInstruction(OpCodes.Ldc_I4, 0);
yield break;
}
}

Expand Down
14 changes: 0 additions & 14 deletions Source/Client/Util/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -253,20 +253,6 @@ public static string[] Names(this ParameterInfo[] pinfo)
return pinfo.Select(pi => pi.Name).ToArray();
}

public static string After(this string s, char c)
{
if (s.IndexOf(c) == -1)
throw new ArgumentException($"Char {c} not found in string {s}");
return s.Substring(s.IndexOf(c) + 1);
}

public static string Until(this string s, char c)
{
if (s.IndexOf(c) == -1)
throw new ArgumentException($"Char {c} not found in string {s}");
return s.Substring(0, s.IndexOf(c));
}

public static string CamelSpace(this string str)
{
var builder = new StringBuilder();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<assembly name="0PrepatcherAPI">
<member name="T:Prepatcher.FreePatchAttribute">
<attribute ctor="M:JetBrains.Annotations.MeansImplicitUseAttribute.#ctor(JetBrains.Annotations.ImplicitUseTargetFlags)">
<argument>3</argument>
</attribute>
</member>
</assembly>
9 changes: 9 additions & 0 deletions Source/MultiplayerLoader/ISyncMethodForPrepatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System;
using Multiplayer.API;

namespace MultiplayerLoader;

public interface ISyncMethodForPrepatcher : ISyncMethod
{
Type TargetType { get; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public static int GetMethodDebugId(MethodBase method)
}
catch (Exception e)
{
throw new Exception($"Extracting debug id for {method.DeclaringType}::{method.Name} failed at {cur} with: {e.Message}");
throw new Exception($"Extracting debug id for {method.DeclaringType}::{method.Name} failed at {cur} with: {e}");
}

throw new Exception($"Couldn't determine debug id for parent method {method.DeclaringType}::{method.Name}");
Expand Down Expand Up @@ -171,5 +171,19 @@ public static MethodBase GetMethod(Type type, string methodName, MethodType meth

return null;
}

public static string After(this string s, char c)
{
if (s.IndexOf(c) == -1)
throw new ArgumentException($"Char {c} not found in string {s}");
return s.Substring(s.IndexOf(c) + 1);
}

public static string Until(this string s, char c)
{
if (s.IndexOf(c) == -1)
throw new ArgumentException($"Char {c} not found in string {s}");
return s.Substring(0, s.IndexOf(c));
}
}
}
65 changes: 62 additions & 3 deletions Source/MultiplayerLoader/Prepatches.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
using Mono.Cecil;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Mono.Cecil;
using Mono.Cecil.Cil;
using MonoMod.Utils;
using Multiplayer.Common;
using Prepatcher;
using RimWorld;

namespace Multiplayer.Client;
namespace MultiplayerLoader;

public static class Prepatches
{
Expand All @@ -15,10 +19,65 @@ static void DontSetResolution(ModuleDefinition module)
if (MpVersion.IsDebug)
{
var resolutionUtilityType = module.ImportReference(typeof(ResolutionUtility)).Resolve();
resolutionUtilityType.FindMethod("SetResolutionRaw").Body.Instructions.Insert(
resolutionUtilityType.FindMethod(nameof(ResolutionUtility.SetResolutionRaw))!.Body.Instructions.Insert(
0,
Instruction.Create(OpCodes.Ret)
);
}
}

[FreePatch]
static void ApplySyncPrepatches(ModuleDefinition module)
{
SyncRituals.ApplyPrepatches(module);
}

internal static void AddPrepatcherPrefix(ModuleDefinition module, int id, MethodInfo target, MethodInfo prefix)
{
var targetMethod = module.ImportReference(target).Resolve();
var firstInst = targetMethod.Body.Instructions.First();

IEnumerable<Instruction> PrefixInstructions()
{
yield return Instruction.Create(OpCodes.Ldc_I4, id); // Load index
yield return Instruction.Create(OpCodes.Ldarg_0); // Load __instance

// Create __args array
yield return Instruction.Create(OpCodes.Ldc_I4, target.GetParameters().Length);
yield return Instruction.Create(OpCodes.Newarr, module.TypeSystem.Object);

int idx = 0;
foreach (var pInfo in target.GetParameters())
{
var pType = pInfo.ParameterType;

yield return Instruction.Create(OpCodes.Dup);
yield return Instruction.Create(OpCodes.Ldc_I4, idx);
yield return Instruction.Create(OpCodes.Ldarg, targetMethod.Parameters.ElementAt(idx));

if (pType.IsByRef)
throw new Exception("Byrefs aren't handled");
if (pType.IsValueType)
yield return Instruction.Create(OpCodes.Box, module.ImportReference(pType));

yield return Instruction.Create(OpCodes.Stelem_Ref);

idx++;
}

yield return Instruction.Create(OpCodes.Call, module.ImportReference(prefix));
yield return Instruction.Create(OpCodes.Brtrue, firstInst);

if (target.ReturnType == typeof(bool))
yield return Instruction.Create(OpCodes.Ldc_I4_0);
else if (target.ReturnType == typeof(void))
yield return Instruction.Create(OpCodes.Nop);
else
throw new Exception($"Can't handle return type {target.ReturnType}");

yield return Instruction.Create(OpCodes.Ret);
}

targetMethod.Body.Instructions.InsertRange(0, PrefixInstructions());
}
}
Loading

0 comments on commit 41f0131

Please sign in to comment.