diff --git a/About/About.xml b/About/About.xml
index f8070db9..e7c40d3a 100644
--- a/About/About.xml
+++ b/About/About.xml
@@ -7,8 +7,8 @@
RimWorld Multiplayer Team
https://github.com/rwmt/Multiplayer
- <color=red><b>Important: </b> This mod should be placed right below Core and expansions in the mod list to work properly!
-Requires Rimworld >= v1.5.4034</color>\n
+ <b>Important: This mod should be placed right below Core and expansions in the mod list to work properly!
+Requires RimWorld >= 1.5.4104</b>\n
Multiplayer mod for RimWorld.
FAQ - https://hackmd.io/@rimworldmultiplayer/docs/
diff --git a/Source/Client/AsyncTime/SetMapTime.cs b/Source/Client/AsyncTime/SetMapTime.cs
index 28df1278..91c706b7 100644
--- a/Source/Client/AsyncTime/SetMapTime.cs
+++ b/Source/Client/AsyncTime/SetMapTime.cs
@@ -34,7 +34,7 @@ internal static void Prefix(ref TimeSnapshot? __state)
}
[HarmonyPriority(MpPriority.MpLast)]
- internal static void Postfix(TimeSnapshot? __state) => __state?.Set();
+ internal static void Finalizer(TimeSnapshot? __state) => __state?.Set();
}
[HarmonyPatch]
diff --git a/Source/Client/EarlyInit.cs b/Source/Client/EarlyInit.cs
index 10f96a76..7e565f57 100644
--- a/Source/Client/EarlyInit.cs
+++ b/Source/Client/EarlyInit.cs
@@ -35,7 +35,7 @@ internal static void EarlyPatches(Harmony harmony)
harmony.PatchMeasure(
AccessTools.Constructor(typeof(Def), Type.EmptyTypes),
new HarmonyMethod(typeof(RandPatches), nameof(RandPatches.Prefix)),
- new HarmonyMethod(typeof(RandPatches), nameof(RandPatches.Postfix))
+ finalizer: new HarmonyMethod(typeof(RandPatches), nameof(RandPatches.Finalizer))
);
Assembly.GetCallingAssembly().GetTypes().Do(type =>
diff --git a/Source/Client/Factions/MultifactionPatches.cs b/Source/Client/Factions/MultifactionPatches.cs
index 0f5af4ee..67cfd392 100644
--- a/Source/Client/Factions/MultifactionPatches.cs
+++ b/Source/Client/Factions/MultifactionPatches.cs
@@ -359,43 +359,6 @@ static void PlaySoundReplacement(SoundDef sound, Map map)
}
}
-[HarmonyPatch]
-static class DontClearDialogBeginRitualCache
-{
- private static MethodInfo listClear = AccessTools.Method(typeof(List), "Clear");
-
- static IEnumerable TargetMethods()
- {
- yield return typeof(Dialog_BeginRitual).GetConstructors(BindingFlags.Public | BindingFlags.Instance).First();
- }
-
- static IEnumerable Transpiler(IEnumerable insts, ILGenerator gen)
- {
- var list = insts.ToList();
- var brLabel = gen.DefineLabel();
-
- foreach (var inst in list)
- {
- if (inst.operand == listClear)
- {
- yield return new CodeInstruction(OpCodes.Call,
- AccessTools.Method(typeof(DontClearDialogBeginRitualCache), nameof(ShouldCancelCacheClear)));
- yield return new CodeInstruction(OpCodes.Brfalse, brLabel);
- yield return new CodeInstruction(OpCodes.Pop);
- yield return new CodeInstruction(OpCodes.Ret);
- yield return new CodeInstruction(OpCodes.Nop) { labels = { brLabel } };
- }
-
- yield return inst;
- }
- }
-
- static bool ShouldCancelCacheClear()
- {
- return Multiplayer.Ticking || Multiplayer.ExecutingCmds;
- }
-}
-
[HarmonyPatch(typeof(Apparel), nameof(Apparel.WornGraphicPath), MethodType.Getter)]
static class ApparelWornGraphicPathGetterPatch
{
diff --git a/Source/Client/Multiplayer.csproj b/Source/Client/Multiplayer.csproj
index 1662ae95..a38b0088 100644
--- a/Source/Client/Multiplayer.csproj
+++ b/Source/Client/Multiplayer.csproj
@@ -32,7 +32,7 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
diff --git a/Source/Client/MultiplayerGame.cs b/Source/Client/MultiplayerGame.cs
index 076f251e..0eec307d 100644
--- a/Source/Client/MultiplayerGame.cs
+++ b/Source/Client/MultiplayerGame.cs
@@ -56,6 +56,7 @@ public MultiplayerGame()
District.nextDistrictID = 1;
Region.nextId = 1;
ListerHaulables.groupCycleIndex = 0;
+ ListerHaulables.cellCycleIndices.Clear();
ZoneColorUtility.nextGrowingZoneColorIndex = 0;
ZoneColorUtility.nextStorageZoneColorIndex = 0;
diff --git a/Source/Client/MultiplayerStatic.cs b/Source/Client/MultiplayerStatic.cs
index 40b2b69d..96a5f662 100644
--- a/Source/Client/MultiplayerStatic.cs
+++ b/Source/Client/MultiplayerStatic.cs
@@ -337,7 +337,7 @@ void TryPatch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod po
// Remove side effects from methods which are non-deterministic during ticking (e.g. camera dependent motes and sound effects)
{
var randPatchPrefix = new HarmonyMethod(typeof(RandPatches), nameof(RandPatches.Prefix));
- var randPatchPostfix = new HarmonyMethod(typeof(RandPatches), nameof(RandPatches.Postfix));
+ var randPatchFinalizer = new HarmonyMethod(typeof(RandPatches), nameof(RandPatches.Finalizer));
var subSustainerStart = MpMethodUtil.GetLambda(typeof(SubSustainer), parentMethodType: MethodType.Constructor, parentArgs: new[] { typeof(Sustainer), typeof(SubSoundDef) });
var sampleCtor = typeof(Sample).GetConstructor(new[] { typeof(SubSoundDef) });
@@ -362,7 +362,7 @@ void TryPatch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod po
var ritualMethods = new[] { canEverSpectate };
foreach (MethodBase m in effectMethods.Concat(moteMethods).Concat(fleckMethods).Concat(ritualMethods))
- TryPatch(m, randPatchPrefix, randPatchPostfix);
+ TryPatch(m, randPatchPrefix, finalizer: randPatchFinalizer);
}
SetCategory("Non-deterministic patches 2");
@@ -435,17 +435,17 @@ void TryPatch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod po
// Set the map time for GUI methods depending on it
{
var setMapTimePrefix = new HarmonyMethod(AccessTools.Method(typeof(SetMapTimeForUI), nameof(SetMapTimeForUI.Prefix)));
- var setMapTimePostfix = new HarmonyMethod(AccessTools.Method(typeof(SetMapTimeForUI), nameof(SetMapTimeForUI.Postfix)));
+ var setMapTimeFinalizer = new HarmonyMethod(AccessTools.Method(typeof(SetMapTimeForUI), nameof(SetMapTimeForUI.Finalizer)));
var windowMethods = new[] { "DoWindowContents", "WindowUpdate" };
foreach (string m in windowMethods)
- TryPatch(typeof(MainTabWindow_Inspect).GetMethod(m), setMapTimePrefix, setMapTimePostfix);
+ TryPatch(typeof(MainTabWindow_Inspect).GetMethod(m), setMapTimePrefix, finalizer: setMapTimeFinalizer);
foreach (var t in typeof(InspectTabBase).AllSubtypesAndSelf())
{
var method = t.GetMethod("FillTab", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.DeclaredOnly, null, Type.EmptyTypes, null);
if (method != null && !method.IsAbstract)
- TryPatch(method, setMapTimePrefix, setMapTimePostfix);
+ TryPatch(method, setMapTimePrefix, finalizer: setMapTimeFinalizer);
}
}
}
diff --git a/Source/Client/Patches/Patches.cs b/Source/Client/Patches/Patches.cs
index 5fbe2999..28ef9014 100644
--- a/Source/Client/Patches/Patches.cs
+++ b/Source/Client/Patches/Patches.cs
@@ -355,7 +355,7 @@ static void Postfix(WorldRoutePlanner __instance, ref bool __result)
{
if (Multiplayer.Client == null) return;
- // Ignore pause
+ // Ignore unpausing
if (__result && __instance.active && WorldRendererUtility.WorldRenderedNow)
__result = false;
}
@@ -586,4 +586,13 @@ static void FixStorage(IStoreSettingsParent __instance, StorageSettings ___allow
___allowedNutritionSettings.owner ??= __instance;
}
}
+
+ [HarmonyPatch(typeof(PawnRoleSelectionWidgetBase), nameof(PawnRoleSelectionWidgetBase.TryAssign))]
+ static class Patc
+ {
+ static IEnumerable Transpiler(IEnumerable insts)
+ {
+ return insts;
+ }
+ }
}
diff --git a/Source/Client/Patches/Seeds.cs b/Source/Client/Patches/Seeds.cs
index 188b6d70..d4301500 100644
--- a/Source/Client/Patches/Seeds.cs
+++ b/Source/Client/Patches/Seeds.cs
@@ -140,7 +140,7 @@ public static void Prefix(ref bool __state)
}
[HarmonyPriority(MpPriority.MpLast)]
- public static void Postfix(bool __state)
+ public static void Finalizer(bool __state)
{
if (__state)
Rand.PopState();
diff --git a/Source/Client/Persistent/CaravanFormingPatches.cs b/Source/Client/Persistent/CaravanFormingPatches.cs
index 510dfab2..18922d96 100644
--- a/Source/Client/Persistent/CaravanFormingPatches.cs
+++ b/Source/Client/Persistent/CaravanFormingPatches.cs
@@ -27,7 +27,7 @@ static void Postfix(bool __state, ref bool __result)
GUI.color = Color.white;
if (__result)
{
- CaravanFormingProxy.drawing.Session?.Remove();
+ CaravanFormingProxy.drawing.Session?.Cancel();
__result = false;
}
}
@@ -163,6 +163,7 @@ static void Prefix(Dialog_FormCaravan __instance, Map map, bool reform, Action o
if (__instance.GetType() != typeof(Dialog_FormCaravan))
return;
+ // Handles showing the dialog from TimedForcedExit.CompTick -> TimedForcedExit.ForceReform
if (Multiplayer.ExecutingCmds || Multiplayer.Ticking)
{
var comp = map.MpComp();
diff --git a/Source/Client/Persistent/CaravanFormingSession.cs b/Source/Client/Persistent/CaravanFormingSession.cs
index b211d9c9..7f2639af 100644
--- a/Source/Client/Persistent/CaravanFormingSession.cs
+++ b/Source/Client/Persistent/CaravanFormingSession.cs
@@ -134,7 +134,12 @@ public void Reset()
}
[SyncMethod]
- public void Remove()
+ public void Cancel()
+ {
+ Remove();
+ }
+
+ private void Remove()
{
map.MpComp().sessionManager.RemoveSession(this);
Find.WorldRoutePlanner.Stop();
diff --git a/Source/Client/Persistent/Rituals.cs b/Source/Client/Persistent/Rituals.cs
index fc5936ba..c5ebcc91 100644
--- a/Source/Client/Persistent/Rituals.cs
+++ b/Source/Client/Persistent/Rituals.cs
@@ -1,11 +1,10 @@
using HarmonyLib;
using Multiplayer.API;
using RimWorld;
-using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
-using Multiplayer.Client.Util;
+using System.Reflection.Emit;
using UnityEngine;
using Verse;
using static Verse.Widgets;
@@ -46,6 +45,7 @@ public void Start()
public void OpenWindow(bool sound = true)
{
var dialog = new BeginRitualProxy(
+ data.assignments,
data.ritualLabel,
data.ritual,
data.target,
@@ -60,10 +60,7 @@ public void OpenWindow(bool sound = true)
data.outcome,
data.extraInfos,
null
- )
- {
- assignments = data.assignments
- };
+ );
if (!sound)
dialog.soundAppear = null;
@@ -107,12 +104,43 @@ public class BeginRitualProxy : Dialog_BeginRitual, ISwitchToMap
public RitualSession Session => map.MpComp().sessionManager.GetFirstOfType();
- public BeginRitualProxy(string ritualLabel, Precept_Ritual ritual, TargetInfo target, Map map, ActionCallback action, Pawn organizer, RitualObligation obligation, PawnFilter filter = null, string okButtonText = null, List requiredPawns = null, Dictionary forcedForRole = null, RitualOutcomeEffectDef outcome = null, List extraInfoText = null, Pawn selectedPawn = null) :
- base(ritualLabel, ritual, target, map, action, organizer, obligation, filter, okButtonText, requiredPawns, forcedForRole, outcome, extraInfoText, selectedPawn)
+ // In the base type, the unused fields are used to create RitualRoleAssignments which already exist in an MP session
+ // They are here to help keep the base constructor in sync with this one
+ public BeginRitualProxy(
+ RitualRoleAssignments assignments,
+ string ritualLabel,
+ Precept_Ritual ritual,
+ TargetInfo target,
+ Map map,
+ ActionCallback action,
+ Pawn organizer,
+ RitualObligation obligation,
+ PawnFilter filter = null,
+ string okButtonText = null,
+ // ReSharper disable once UnusedParameter.Local
+ List requiredPawns = null,
+ // ReSharper disable once UnusedParameter.Local
+ Dictionary forcedForRole = null,
+ RitualOutcomeEffectDef outcome = null,
+ List extraInfoText = null,
+ // ReSharper disable once UnusedParameter.Local
+ Pawn selectedPawn = null) :
+ base(assignments, ritual, target, ritual?.outcomeEffect?.def ?? outcome)
{
+ this.obligation = obligation;
+ this.filter = filter;
+ this.organizer = organizer;
+ this.map = map;
+ this.action = action;
+ ritualExplanation = ritual?.ritualExplanation;
+ this.ritualLabel = ritualLabel;
+ this.okButtonText = okButtonText ?? "OK".Translate();
+ extraInfos = extraInfoText;
+
soundClose = SoundDefOf.TabClose;
// This gets cancelled in the base constructor if called from ticking/cmd in DontClearDialogBeginRitualCache
+ // Note that: cachedRoles is a static field, cachedRoles is only used for UI drawing
cachedRoles.Clear();
if (ritual is { ideo: not null })
{
@@ -199,15 +227,15 @@ static bool Prefix(Window window)
&& window.GetType() == typeof(Dialog_BeginRitual) // Doesn't let BeginRitualProxy through
&& (Multiplayer.ExecutingCmds || Multiplayer.Ticking))
{
- var dialog = (Dialog_BeginRitual)window;
- dialog.PostOpen(); // Completes initialization
+ var tempDialog = (Dialog_BeginRitual)window;
+ tempDialog.PostOpen(); // Completes initialization
- var comp = dialog.map.MpComp();
+ var comp = tempDialog.map.MpComp();
var session = comp.sessionManager.GetFirstOfType();
if (session != null &&
- (session.data.ritual != dialog.ritual ||
- session.data.outcome != dialog.outcome))
+ (session.data.ritual != tempDialog.ritual ||
+ session.data.outcome != tempDialog.outcome))
{
Messages.Message("MpAnotherRitualInProgress".Translate(), MessageTypeDefOf.RejectInput, false);
return false;
@@ -217,16 +245,16 @@ static bool Prefix(Window window)
{
var data = new RitualData
{
- ritual = dialog.ritual,
- target = dialog.target,
- obligation = dialog.obligation,
- outcome = dialog.outcome,
- extraInfos = dialog.extraInfos,
- action = dialog.action,
- ritualLabel = dialog.ritualLabel,
- confirmText = dialog.confirmText,
- organizer = dialog.organizer,
- assignments = MpUtil.ShallowCopy(dialog.assignments, new MpRitualAssignments())
+ ritual = tempDialog.ritual,
+ target = tempDialog.target,
+ obligation = tempDialog.obligation,
+ outcome = tempDialog.outcome,
+ extraInfos = tempDialog.extraInfos,
+ action = tempDialog.action,
+ ritualLabel = tempDialog.ritualLabel,
+ confirmText = tempDialog.confirmText,
+ organizer = tempDialog.organizer,
+ assignments = MpUtil.ShallowCopy(tempDialog.assignments, new MpRitualAssignments())
};
session = comp.CreateRitualSession(data);
@@ -242,6 +270,44 @@ static bool Prefix(Window window)
}
}
+ // Note that cachedRoles is a static field and is only used for UI drawing
+ [HarmonyPatch]
+ static class DontClearDialogBeginRitualCache
+ {
+ private static MethodInfo listClear = AccessTools.Method(typeof(List), "Clear");
+
+ static IEnumerable TargetMethods()
+ {
+ yield return typeof(Dialog_BeginRitual).GetConstructors(BindingFlags.Public | BindingFlags.Instance).First();
+ }
+
+ static IEnumerable Transpiler(IEnumerable insts, ILGenerator gen)
+ {
+ var list = insts.ToList();
+ var brLabel = gen.DefineLabel();
+
+ foreach (var inst in list)
+ {
+ if (inst.operand == listClear)
+ {
+ yield return new CodeInstruction(OpCodes.Call,
+ AccessTools.Method(typeof(DontClearDialogBeginRitualCache), nameof(ShouldCancelCacheClear)));
+ yield return new CodeInstruction(OpCodes.Brfalse, brLabel);
+ yield return new CodeInstruction(OpCodes.Pop);
+ yield return new CodeInstruction(OpCodes.Ret);
+ yield return new CodeInstruction(OpCodes.Nop) { labels = { brLabel } };
+ }
+
+ yield return inst;
+ }
+ }
+
+ static bool ShouldCancelCacheClear()
+ {
+ return Multiplayer.Ticking || Multiplayer.ExecutingCmds;
+ }
+ }
+
// We do not sync Ability.QueueCastingJob - instead syncing the delegate method queueing the job.
// We do this to avoid confirmation dialogs from some of the abilities, like neuroquake and a few ones added in Biotech.
// This causes an issue with abilities like throne speech or leader speech - the ritual dialog itself is treated as the confirmation dialog.
diff --git a/Source/Client/Syncing/ApiSerialization.cs b/Source/Client/Syncing/ApiSerialization.cs
index 630cba16..e5037a2d 100644
--- a/Source/Client/Syncing/ApiSerialization.cs
+++ b/Source/Client/Syncing/ApiSerialization.cs
@@ -11,7 +11,7 @@ public static class ApiSerialization
public static void Init()
{
- syncSimples = TypeCache.AllImplementationsOrdered(typeof(ISyncSimple));
- sessions = TypeCache.AllImplementationsOrdered(typeof(Session));
+ syncSimples = TypeCache.AllInterfaceImplementationsOrdered(typeof(ISyncSimple));
+ sessions = TypeCache.AllSubclassesNonAbstractOrdered(typeof(Session));
}
}
diff --git a/Source/Client/Syncing/Dict/SyncDictDlc.cs b/Source/Client/Syncing/Dict/SyncDictDlc.cs
index 53fbb638..2f0e2eae 100644
--- a/Source/Client/Syncing/Dict/SyncDictDlc.cs
+++ b/Source/Client/Syncing/Dict/SyncDictDlc.cs
@@ -188,7 +188,7 @@ public static class SyncDictDlc
}
},
{
- // Currently only used for PawnRitualRoleSelectionWidget syncing
+ // Currently only used for Dialog_BeginRitual delegate syncing
(ByteWriter data, PawnRitualRoleSelectionWidget dialog) => {
WriteSync(data, dialog.ritualAssignments);
WriteSync(data, dialog.ritual);
@@ -201,6 +201,20 @@ public static class SyncDictDlc
return new PawnRitualRoleSelectionWidget(assignments, ritual, assignments.session.data.target, assignments.session.data.outcome);
}
},
+ {
+ // Currently only used for Dialog_BeginRitual delegate syncing
+ (ByteWriter data, Dialog_BeginRitual dialog) => {
+ WriteSync(data, dialog.assignments);
+ WriteSync(data, dialog.ritual);
+ },
+ (ByteReader data) => {
+ var assignment = ReadSync(data) as MpRitualAssignments;
+ if (assignment == null) return null;
+
+ var ritual = ReadSync(data); // todo handle ritual becoming null?
+ return new Dialog_BeginRitual(assignment, ritual, assignment.ritualTarget, assignment.session.data.outcome);
+ }
+ },
{
// This dialog has nothing of interest to us besides the methods which we need for syncing
(ByteWriter _, Dialog_StyleSelection _) => { },
diff --git a/Source/Client/Syncing/Game/SyncDelegates.cs b/Source/Client/Syncing/Game/SyncDelegates.cs
index 5161ee6e..b9d5e4ba 100644
--- a/Source/Client/Syncing/Game/SyncDelegates.cs
+++ b/Source/Client/Syncing/Game/SyncDelegates.cs
@@ -63,7 +63,7 @@ public static void Init()
SyncDelegate.Lambda(typeof(CaravanVisitUtility), nameof(CaravanVisitUtility.TradeCommand), 0).CancelIfAnyFieldNull(); // Caravan trade with settlement
SyncDelegate.Lambda(typeof(FactionGiftUtility), nameof(FactionGiftUtility.OfferGiftsCommand), 0).CancelIfAnyFieldNull(); // Caravan offer gifts
- SyncDelegate.Lambda(typeof(Building_Bed), nameof(Building_Bed.GetFloatMenuOptions), 0).CancelIfAnyFieldNull(); // Use medical bed
+ SyncDelegate.Lambda(typeof(Building_Bed), nameof(Building_Bed.GetBedRestFloatMenuOption), 0).CancelIfAnyFieldNull(); // Use medical bed
SyncMethod.Lambda(typeof(CompRefuelable), nameof(CompRefuelable.CompGetGizmosExtra), 1); // Toggle Auto-refuel
SyncMethod.Lambda(typeof(CompRefuelable), nameof(CompRefuelable.CompGetGizmosExtra), 2).SetDebugOnly(); // Set fuel to 0
@@ -284,7 +284,7 @@ The UI's main interaction area is split into three types of groups of pawns.
var ritualRolesSerializer = Serializer.New(
(IEnumerable roles, object target, object[] _) =>
{
- var roleSelectionWidget = (PawnRoleSelectionWidgetBase)target;
+ 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)
diff --git a/Source/Client/Syncing/ImplSerialization.cs b/Source/Client/Syncing/ImplSerialization.cs
index 65cdd98c..5a7d3181 100644
--- a/Source/Client/Syncing/ImplSerialization.cs
+++ b/Source/Client/Syncing/ImplSerialization.cs
@@ -4,9 +4,9 @@
namespace Multiplayer.Client;
-public static class ImplSerialization
+internal static class ImplSerialization
{
- public static void Init()
+ public static void Init(RwSyncTypeHelper typeHelper)
{
Multiplayer.serialization.RegisterForSyncWithImpl(typeof(IStoreSettingsParent));
Multiplayer.serialization.RegisterForSyncWithImpl(typeof(IStorageGroupMember));
@@ -21,6 +21,7 @@ public static void Init()
Multiplayer.serialization.RegisterForSyncWithImpl(typeof(Policy));
// todo for 1.5
- // Multiplayer.serialization.RegisterForSyncWithImpl(typeof(PawnRoleSelectionWidgetBase));
+ Multiplayer.serialization.RegisterForSyncWithImpl(typeof(PawnRoleSelectionWidgetBase));
+ typeHelper.AddImplManually(typeof(PawnRoleSelectionWidgetBase), typeof(PawnRitualRoleSelectionWidget));
}
}
diff --git a/Source/Client/Syncing/RwSerialization.cs b/Source/Client/Syncing/RwSerialization.cs
index f9dfcae7..56f54899 100644
--- a/Source/Client/Syncing/RwSerialization.cs
+++ b/Source/Client/Syncing/RwSerialization.cs
@@ -16,18 +16,24 @@ public static class RwSerialization
{
public static void Init()
{
- Multiplayer.serialization = new Common.SyncSerialization(new RwSyncTypeHelper());
+ var typeHelper = new RwSyncTypeHelper();
+ Multiplayer.serialization = new Common.SyncSerialization(typeHelper);
// CanHandle hooks
Multiplayer.serialization.AddCanHandleHook(syncType =>
{
var type = syncType.type;
+
+ // Verse.Pair<,>
if (type.IsGenericType && type.GetGenericTypeDefinition() is { } gtd)
if (gtd == typeof(Pair<,>))
return Multiplayer.serialization.CanHandleGenericArgs(type);
+ // Verse.IExposable
if (syncType.expose)
return typeof(IExposable).IsAssignableFrom(type);
+
+ // Multiplayer.API.ISyncSimple
if (type == typeof(ISyncSimple))
return true;
if (typeof(ISyncSimple).IsAssignableFrom(type))
@@ -35,12 +41,8 @@ public static void Init()
Where(t => type.IsAssignableFrom(t)).
SelectMany(AccessTools.GetDeclaredFields).
All(f => Multiplayer.serialization.CanHandle(f.FieldType));
- if (typeof(Def).IsAssignableFrom(type))
- return true;
- if (typeof(Designator).IsAssignableFrom(type))
- return true;
- return SyncDict.syncWorkers.TryGetValue(type, out _);
+ return false;
});
// Verse.Pair<,> serialization
@@ -66,7 +68,7 @@ public static void Init()
}
);
- // IExposable serialization
+ // Verse.IExposable serialization
Multiplayer.serialization.AddSerializationHook(
syncType => syncType.expose,
(data, syncType) =>
@@ -90,7 +92,7 @@ public static void Init()
}
);
- // ISyncSimple serialization
+ // Multiplayer.API.ISyncSimple serialization
// todo null handling for ISyncSimple?
Multiplayer.serialization.AddSerializationHook(
syncType => typeof(ISyncSimple).IsAssignableFrom(syncType.type),
@@ -111,7 +113,7 @@ public static void Init()
}
);
- ImplSerialization.Init();
+ ImplSerialization.Init(typeHelper);
CompSerialization.Init();
ApiSerialization.Init();
DefSerialization.Init();
diff --git a/Source/Client/Syncing/RwSyncTypeHelper.cs b/Source/Client/Syncing/RwSyncTypeHelper.cs
index a245fd58..dd1c2b0e 100644
--- a/Source/Client/Syncing/RwSyncTypeHelper.cs
+++ b/Source/Client/Syncing/RwSyncTypeHelper.cs
@@ -7,8 +7,18 @@ namespace Multiplayer.Client;
internal class RwSyncTypeHelper : SyncTypeHelper
{
+ private Dictionary> manualImplTypes = new();
+
+ public void AddImplManually(Type baseType, Type implType)
+ {
+ manualImplTypes.GetOrAddNew(baseType).Add(implType);
+ }
+
public override List GetImplementations(Type baseType)
{
+ if (manualImplTypes.TryGetValue(baseType, out var manualImplType))
+ return manualImplType;
+
return baseType.IsInterface
? TypeCache.interfaceImplementationsOrdered[baseType]
: TypeCache.subClassesOrdered[baseType];
diff --git a/Source/Client/Util/TypeCache.cs b/Source/Client/Util/TypeCache.cs
index bdc868e5..d0393044 100644
--- a/Source/Client/Util/TypeCache.cs
+++ b/Source/Client/Util/TypeCache.cs
@@ -63,7 +63,7 @@ public static IEnumerable AllImplementing(this Type type)
return interfaceImplementations.GetValueSafe(type) ?? Enumerable.Empty();
}
- public static Type[] AllImplementationsOrdered(Type type)
+ public static Type[] AllInterfaceImplementationsOrdered(Type type)
{
return type.AllImplementing()
.OrderBy(t => t.IsInterface)
diff --git a/Source/Client/Windows/ServerBrowser.cs b/Source/Client/Windows/ServerBrowser.cs
index 0d6055ef..a17b9188 100644
--- a/Source/Client/Windows/ServerBrowser.cs
+++ b/Source/Client/Windows/ServerBrowser.cs
@@ -150,7 +150,7 @@ Anomaly compatibility is also a work-in-progress. Compatibility with other mods
We recommend downgrading RimWorld to 1.4 if you want to continue playing a stable version.
""";
- TooltipHandler.TipRegion(new Rect(x, 0, 400, 300), v15Notice);
+ TooltipHandler.TipRegion(new Rect(x, 0, 400, 25), v15Notice);
if (false) // todo
Button(
diff --git a/Source/Common/Common.csproj b/Source/Common/Common.csproj
index f31f47b8..039415ad 100644
--- a/Source/Common/Common.csproj
+++ b/Source/Common/Common.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/Source/Common/Syncing/SyncSerialization.cs b/Source/Common/Syncing/SyncSerialization.cs
index e6aaa34c..6dae3d3e 100644
--- a/Source/Common/Syncing/SyncSerialization.cs
+++ b/Source/Common/Syncing/SyncSerialization.cs
@@ -98,7 +98,6 @@ public bool CanHandleGenericArgs(Type genericType)
// For each serializationHooks:
// if matcher: serialize; break
// Implementations of Multiplayer.API.ISynchronizable
- // Run all typeChangerHooks
// syncTree.TryGetValue(type)
public object? ReadSyncObject(ByteReader data, SyncType syncType)
@@ -311,9 +310,6 @@ public void WriteSyncObject(ByteWriter data, object? obj, SyncType syncType)
var log = (data as LoggingByteWriter)?.Log;
log?.Enter($"{type.FullName}: {obj ?? "null"}");
- if (obj != null && !type.IsInstanceOfType(obj))
- throw new SerializationException($"Serializing with type {type} but got object of type {obj.GetType()}");
-
try
{
if (typeof(object) == type)
diff --git a/Source/Common/Version.cs b/Source/Common/Version.cs
index 231d9ccf..db91a7f4 100644
--- a/Source/Common/Version.cs
+++ b/Source/Common/Version.cs
@@ -2,8 +2,8 @@ namespace Multiplayer.Common
{
public static class MpVersion
{
- public const string Version = "0.10.2";
- public const int Protocol = 44;
+ public const string Version = "0.10.3";
+ public const int Protocol = 45;
public const string ApiAssemblyName = "0MultiplayerAPI";
diff --git a/Source/MultiplayerLoader/MultiplayerLoader.csproj b/Source/MultiplayerLoader/MultiplayerLoader.csproj
index fea717ee..195aae26 100644
--- a/Source/MultiplayerLoader/MultiplayerLoader.csproj
+++ b/Source/MultiplayerLoader/MultiplayerLoader.csproj
@@ -10,7 +10,7 @@
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive