From 9dedf06e9c351978ceb6153d3214da94b96bf4d1 Mon Sep 17 00:00:00 2001 From: Zetrith Date: Fri, 5 Apr 2024 09:07:03 +0200 Subject: [PATCH 1/7] First pass at RW 1.5 (#428) Ritual dialogs are still WIP Use nameof in SyncFields.cs Remove ReplaceUnityRngPollutionRetaliation and PollutionPumpRemoveCurrentMap as these are in vanilla now Remove use of SetVersion on sync handlers --- About/About.xml | 4 +- Source/Client/AsyncTime/AsyncTimePatches.cs | 2 +- Source/Client/Comp/World/FactionWorldData.cs | 8 +- Source/Client/Debug/DebugActions.cs | 1 + Source/Client/Debug/DebugPatches.cs | 2 +- Source/Client/Debug/DebugSync.cs | 1 + Source/Client/EarlyPatches/SettingsPatches.cs | 2 - .../Client/Factions/FactionContextSetters.cs | 5 +- .../Client/Factions/FactionCreationPatches.cs | 5 +- Source/Client/Factions/MultifactionPatches.cs | 4 +- Source/Client/Multiplayer.csproj | 2 +- Source/Client/MultiplayerGame.cs | 1 + Source/Client/MultiplayerStatic.cs | 7 +- Source/Client/Patches/ArbiterPatches.cs | 2 +- Source/Client/Patches/Determinism.cs | 60 +---- Source/Client/Patches/Feedback.cs | 4 +- Source/Client/Patches/LongEvents.cs | 4 +- Source/Client/Patches/Patches.cs | 10 +- Source/Client/Patches/Seeds.cs | 11 +- Source/Client/Patches/TickPatch.cs | 1 + Source/Client/Patches/UniqueIds.cs | 8 +- Source/Client/Persistent/Rituals.cs | 17 +- Source/Client/Saving/CacheForReloading.cs | 11 +- Source/Client/Saving/CrossRefs.cs | 2 +- Source/Client/Saving/SaveCompression.cs | 4 +- Source/Client/Syncing/Dict/SyncDictDlc.cs | 3 +- .../Client/Syncing/Dict/SyncDictRimWorld.cs | 16 +- Source/Client/Syncing/Game/SyncDelegates.cs | 30 ++- Source/Client/Syncing/Game/SyncFields.cs | 210 +++++++----------- Source/Client/Syncing/Game/SyncGame.cs | 2 - Source/Client/Syncing/Game/SyncMarkers.cs | 30 --- Source/Client/Syncing/Game/SyncMethods.cs | 58 +++-- .../Syncing/Game/ThingFilterContexts.cs | 22 +- .../Client/Syncing/Game/ThingFilterMarkers.cs | 16 +- Source/Client/Windows/TextAreaWindow.cs | 23 -- Source/Client/Windows/TwoTextAreasWindow.cs | 13 +- Source/Common/Version.cs | 4 +- 37 files changed, 227 insertions(+), 378 deletions(-) delete mode 100644 Source/Client/Windows/TextAreaWindow.cs diff --git a/About/About.xml b/About/About.xml index 0704b06d..f8070db9 100644 --- a/About/About.xml +++ b/About/About.xml @@ -3,12 +3,12 @@ rwmt.Multiplayer Multiplayer -
  • 1.4
  • +
  • 1.5
  • 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.4.3901</color>\n +Requires Rimworld >= v1.5.4034</color>\n Multiplayer mod for RimWorld. FAQ - https://hackmd.io/@rimworldmultiplayer/docs/ diff --git a/Source/Client/AsyncTime/AsyncTimePatches.cs b/Source/Client/AsyncTime/AsyncTimePatches.cs index b8c3b635..85b50ef4 100644 --- a/Source/Client/AsyncTime/AsyncTimePatches.cs +++ b/Source/Client/AsyncTime/AsyncTimePatches.cs @@ -169,7 +169,7 @@ static void Postfix() } } - [HarmonyPatch(typeof(LetterStack), nameof(LetterStack.ReceiveLetter), typeof(Letter), typeof(string))] + [HarmonyPatch(typeof(LetterStack), nameof(LetterStack.ReceiveLetter), typeof(Letter), typeof(string), typeof(int), typeof(bool))] static class ReceiveLetterPause { static IEnumerable Transpiler(IEnumerable insts) diff --git a/Source/Client/Comp/World/FactionWorldData.cs b/Source/Client/Comp/World/FactionWorldData.cs index d04fdd0f..2fe75f02 100644 --- a/Source/Client/Comp/World/FactionWorldData.cs +++ b/Source/Client/Comp/World/FactionWorldData.cs @@ -45,12 +45,12 @@ public void ExposeData() public void ReassignIds() { foreach (DrugPolicy p in drugPolicyDatabase.policies) - p.uniqueId = Find.UniqueIDsManager.GetNextThingID(); + p.id = Find.UniqueIDsManager.GetNextThingID(); - foreach (Outfit o in outfitDatabase.outfits) - o.uniqueId = Find.UniqueIDsManager.GetNextThingID(); + foreach (ApparelPolicy o in outfitDatabase.outfits) + o.id = Find.UniqueIDsManager.GetNextThingID(); - foreach (FoodRestriction o in foodRestrictionDatabase.foodRestrictions) + foreach (FoodPolicy o in foodRestrictionDatabase.foodRestrictions) o.id = Find.UniqueIDsManager.GetNextThingID(); } diff --git a/Source/Client/Debug/DebugActions.cs b/Source/Client/Debug/DebugActions.cs index 8459db96..d363131f 100644 --- a/Source/Client/Debug/DebugActions.cs +++ b/Source/Client/Debug/DebugActions.cs @@ -7,6 +7,7 @@ using System.Text; using HarmonyLib; +using LudeonTK; using RimWorld; using RimWorld.Planet; using UnityEngine; diff --git a/Source/Client/Debug/DebugPatches.cs b/Source/Client/Debug/DebugPatches.cs index 45a9c51f..04bcb062 100644 --- a/Source/Client/Debug/DebugPatches.cs +++ b/Source/Client/Debug/DebugPatches.cs @@ -35,7 +35,7 @@ static void Prefixfactionman() !trace.Contains("Client.FactionContext") && !trace.Contains("Thing.ExposeData") ) - Log.Message($"factionman call {trace}", true); + Log.Message($"factionman call {trace}"); } } } diff --git a/Source/Client/Debug/DebugSync.cs b/Source/Client/Debug/DebugSync.cs index a28b7502..0515d756 100644 --- a/Source/Client/Debug/DebugSync.cs +++ b/Source/Client/Debug/DebugSync.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using HarmonyLib; +using LudeonTK; using Multiplayer.Common; using RimWorld; diff --git a/Source/Client/EarlyPatches/SettingsPatches.cs b/Source/Client/EarlyPatches/SettingsPatches.cs index 12d9a6e4..f9aa984b 100644 --- a/Source/Client/EarlyPatches/SettingsPatches.cs +++ b/Source/Client/EarlyPatches/SettingsPatches.cs @@ -14,7 +14,6 @@ static class PrefGettersInMultiplayer { static IEnumerable TargetMethods() { - yield return AccessTools.PropertyGetter(typeof(Prefs), nameof(Prefs.PauseOnError)); yield return AccessTools.PropertyGetter(typeof(Prefs), nameof(Prefs.AutomaticPauseMode)); yield return AccessTools.PropertyGetter(typeof(Prefs), nameof(Prefs.PauseOnLoad)); yield return AccessTools.PropertyGetter(typeof(Prefs), nameof(Prefs.AdaptiveTrainingEnabled)); @@ -29,7 +28,6 @@ static class PrefSettersInMultiplayer { static IEnumerable TargetMethods() { - yield return AccessTools.PropertySetter(typeof(Prefs), nameof(Prefs.PauseOnError)); yield return AccessTools.PropertySetter(typeof(Prefs), nameof(Prefs.AutomaticPauseMode)); yield return AccessTools.PropertySetter(typeof(Prefs), nameof(Prefs.PauseOnLoad)); yield return AccessTools.PropertySetter(typeof(Prefs), nameof(Prefs.AdaptiveTrainingEnabled)); diff --git a/Source/Client/Factions/FactionContextSetters.cs b/Source/Client/Factions/FactionContextSetters.cs index 6d6cbe3c..55036296 100644 --- a/Source/Client/Factions/FactionContextSetters.cs +++ b/Source/Client/Factions/FactionContextSetters.cs @@ -103,10 +103,9 @@ static void Prefix(Bill_Production __instance, ref Map __state) { if (Multiplayer.Client == null) return; - var zoneManager = __instance.storeZone?.zoneManager ?? __instance.includeFromZone?.zoneManager; - if (__instance.Map != null && zoneManager != null) + if (__instance.Map != null && __instance.billStack?.billGiver is Thing { Faction: { } faction }) { - __instance.Map.PushFaction(zoneManager.map.MpComp().GetFactionId(zoneManager)); + __instance.Map.PushFaction(faction); __state = __instance.Map; } } diff --git a/Source/Client/Factions/FactionCreationPatches.cs b/Source/Client/Factions/FactionCreationPatches.cs index 2665a7ca..c3488029 100644 --- a/Source/Client/Factions/FactionCreationPatches.cs +++ b/Source/Client/Factions/FactionCreationPatches.cs @@ -19,11 +19,12 @@ static void Finalizer(ProgramState __state) } } -[HarmonyPatch(typeof(Page_ConfigureStartingPawns), nameof(Page_ConfigureStartingPawns.RandomizeCurPawn))] -static class ConfigureStartingPawns_RandomizeCurPawn_Patch +[HarmonyPatch(typeof(StartingPawnUtility), nameof(StartingPawnUtility.RandomizePawn))] +static class StartingPawnUtility_RandomizePawn_Patch { static void Prefix(ref ProgramState __state) { + // todo is this compatible with 1.5's "Create new wanderers?" __state = Current.ProgramState; Current.programStateInt = ProgramState.Entry; } diff --git a/Source/Client/Factions/MultifactionPatches.cs b/Source/Client/Factions/MultifactionPatches.cs index 75e62376..fa89ed29 100644 --- a/Source/Client/Factions/MultifactionPatches.cs +++ b/Source/Client/Factions/MultifactionPatches.cs @@ -322,7 +322,7 @@ static void Postfix(IAttackTarget target, ref bool __result) } } -[HarmonyPatch(typeof(LetterStack), nameof(LetterStack.ReceiveLetter), typeof(Letter), typeof(string))] +[HarmonyPatch(typeof(LetterStack), nameof(LetterStack.ReceiveLetter), typeof(Letter), typeof(string), typeof(int), typeof(bool))] static class LetterStackReceiveOnlyMyFaction { // todo the letter might get culled from the archive if it isn't in the stack and Sync depends on the archive @@ -333,7 +333,7 @@ static void Postfix(LetterStack __instance, Letter let) } } -[HarmonyPatch(typeof(LetterStack), nameof(LetterStack.ReceiveLetter), typeof(Letter), typeof(string))] +[HarmonyPatch(typeof(LetterStack), nameof(LetterStack.ReceiveLetter), typeof(Letter), typeof(string), typeof(int), typeof(bool))] static class LetterStackReceiveSoundOnlyMyFaction { private static MethodInfo PlayOneShotOnCamera = diff --git a/Source/Client/Multiplayer.csproj b/Source/Client/Multiplayer.csproj index a964b3a9..02410ac3 100644 --- a/Source/Client/Multiplayer.csproj +++ b/Source/Client/Multiplayer.csproj @@ -29,7 +29,7 @@ - + diff --git a/Source/Client/MultiplayerGame.cs b/Source/Client/MultiplayerGame.cs index 3e2ea1b0..ad9a1780 100644 --- a/Source/Client/MultiplayerGame.cs +++ b/Source/Client/MultiplayerGame.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using LudeonTK; using Multiplayer.API; using Multiplayer.Client.AsyncTime; using Multiplayer.Client.Comp; diff --git a/Source/Client/MultiplayerStatic.cs b/Source/Client/MultiplayerStatic.cs index 12c63686..40b2b69d 100644 --- a/Source/Client/MultiplayerStatic.cs +++ b/Source/Client/MultiplayerStatic.cs @@ -348,17 +348,18 @@ void TryPatch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod po var randomBoltMesh = typeof(LightningBoltMeshPool).GetProperty(nameof(LightningBoltMeshPool.RandomBoltMesh))!.GetGetMethod(); var drawTrackerCtor = typeof(Pawn_DrawTracker).GetConstructor(new[] { typeof(Pawn) }); var randomHair = typeof(PawnStyleItemChooser).GetMethod(nameof(PawnStyleItemChooser.RandomHairFor)); - var cannotAssignReason = typeof(Dialog_BeginRitual).GetMethod(nameof(Dialog_BeginRitual.CannotAssignReason), BindingFlags.NonPublic | BindingFlags.Instance); + // todo for 1.5 + // var cannotAssignReason = typeof(Dialog_BeginRitual).GetMethod(nameof(Dialog_BeginRitual.CannotAssignReason), BindingFlags.NonPublic | BindingFlags.Instance); var canEverSpectate = typeof(RitualRoleAssignments).GetMethod(nameof(RitualRoleAssignments.CanEverSpectate)); var effectMethods = new MethodBase[] { subSustainerStart, sampleCtor, subSoundPlay, effecterTick, effecterTrigger, effecterCleanup, randomBoltMesh, drawTrackerCtor, randomHair }; var moteMethods = typeof(MoteMaker).GetMethods(BindingFlags.Static | BindingFlags.Public) - .Where(m => m.Name != "MakeBombardmentMote"); // Special case, just calls MakeBombardmentMote_NewTmp, prevents Hugslib complains + .Where(m => m.Name != "MakeBombardmentMote"); // Special case, just calls MakeBombardmentMote_NewTmp, prevents Hugslib complaints var fleckMethods = typeof(FleckMaker).GetMethods(BindingFlags.Static | BindingFlags.Public) .Where(m => m.ReturnType == typeof(void)) .Concat(typeof(FleckManager).GetMethods() // FleckStatic uses Rand in Setup method, FleckThrown uses RandomInRange in TimeInterval. May as well catch all in case mods do the same. .Where(m => m.ReturnType == typeof(void))); - var ritualMethods = new[] { cannotAssignReason, canEverSpectate }; + var ritualMethods = new[] { canEverSpectate }; foreach (MethodBase m in effectMethods.Concat(moteMethods).Concat(fleckMethods).Concat(ritualMethods)) TryPatch(m, randPatchPrefix, randPatchPostfix); diff --git a/Source/Client/Patches/ArbiterPatches.cs b/Source/Client/Patches/ArbiterPatches.cs index 0787fea2..328ae75e 100644 --- a/Source/Client/Patches/ArbiterPatches.cs +++ b/Source/Client/Patches/ArbiterPatches.cs @@ -62,7 +62,7 @@ static IEnumerable TargetMethods() yield return AccessTools.Method(typeof(Prefs), nameof(Prefs.Save)); yield return AccessTools.Method(typeof(FloatMenuOption), nameof(FloatMenuOption.SetSizeMode)); yield return AccessTools.Method(typeof(Section), nameof(Section.RegenerateAllLayers)); - yield return AccessTools.Method(typeof(Section), nameof(Section.RegenerateLayers)); + yield return AccessTools.Method(typeof(Section), nameof(Section.RegenerateDirtyLayers)); yield return AccessTools.Method(typeof(SectionLayer), nameof(SectionLayer.DrawLayer)); yield return AccessTools.Method(typeof(Map), nameof(Map.MapUpdate)); yield return AccessTools.Method(typeof(GUIStyle), nameof(GUIStyle.CalcSize)); diff --git a/Source/Client/Patches/Determinism.cs b/Source/Client/Patches/Determinism.cs index b860fb85..849171dd 100644 --- a/Source/Client/Patches/Determinism.cs +++ b/Source/Client/Patches/Determinism.cs @@ -277,36 +277,6 @@ static bool Prefix() } } - [HarmonyPatch(typeof(QuestNode_Root_PollutionRetaliation), nameof(QuestNode_Root_PollutionRetaliation.RunInt))] - static class ReplaceUnityRngPollutionRetaliation - { - // Simplified transpiler from MP Compat. - // Source: https://github.com/rwmt/Multiplayer-Compatibility/blob/2e82e71aef64c5a5a4fc879db6f49d3c20da25cb/Source/PatchingUtilities.cs#L226 - static IEnumerable Transpiler(IEnumerable insts, MethodBase original) - { - var anythingPatched = false; - - var parameters = new[] { typeof(int), typeof(int) }; - var unityRandomRangeInt = AccessTools.DeclaredMethod(typeof(Random), nameof(Random.Range), parameters); - var verseRandomRangeInt = AccessTools.DeclaredMethod(typeof(Rand), nameof(Rand.Range), parameters); - - foreach (var inst in insts) - { - if ((inst.opcode == OpCodes.Call || inst.opcode == OpCodes.Callvirt) && inst.operand is MethodInfo method && method == unityRandomRangeInt) - { - inst.opcode = OpCodes.Call; - inst.operand = verseRandomRangeInt; - - anythingPatched = true; - } - - yield return inst; - } - - if (!anythingPatched) Log.Warning($"No Unity RNG was patched for method: {original?.FullDescription() ?? "(unknown method)"}"); - } - } - [HarmonyPatch(typeof(Pawn_RecordsTracker), nameof(Pawn_RecordsTracker.ExposeData))] static class RecordsTrackerExposePatch { @@ -376,7 +346,7 @@ private static int NewQueryTick(int ticks, SituationalThoughtHandler thoughtHand } } - [HarmonyPatch(typeof(SituationalThoughtHandler), nameof(SituationalThoughtHandler.CheckRecalculateMoodThoughts))] + [HarmonyPatch(typeof(SituationalThoughtHandler), nameof(SituationalThoughtHandler.UpdateAllMoodThoughts))] static class DontRecalculateMoodThoughtsInInterface { static bool Prefix(SituationalThoughtHandler __instance) @@ -384,7 +354,7 @@ static bool Prefix(SituationalThoughtHandler __instance) if (Multiplayer.Client != null && !Multiplayer.Ticking && !Multiplayer.ExecutingCmds) return false; // Notify_SituationalThoughtsDirty was called - if (__instance.lastMoodThoughtsRecalculationTick == -99999) + if (__instance.thoughtsDirty) __instance.cachedThoughts.Clear(); return true; @@ -521,30 +491,4 @@ private static int NewCacheStatus(int gameTick) } } - [HarmonyPatch(typeof(CompPollutionPump), nameof(CompPollutionPump.CompTick))] - static class PollutionPumpRemoveCurrentMap - { - private static MethodInfo currentMapGetter = AccessTools.PropertyGetter(typeof(Find), nameof(Find.CurrentMap)); - - static IEnumerable Transpiler(IEnumerable insts) - { - foreach (var inst in insts) - { - if (inst.operand == currentMapGetter) - { - yield return new CodeInstruction(OpCodes.Ldarg_0); - yield return new CodeInstruction(OpCodes.Call, MethodOf.Lambda(CompMap)); - continue; - } - - yield return inst; - } - } - - static Map CompMap(CompPollutionPump pump) - { - return pump.parent.Map; - } - } - } diff --git a/Source/Client/Patches/Feedback.cs b/Source/Client/Patches/Feedback.cs index b5dfdddc..ed0425fb 100644 --- a/Source/Client/Patches/Feedback.cs +++ b/Source/Client/Patches/Feedback.cs @@ -30,7 +30,7 @@ static IEnumerable TargetMethods() static bool Prefix() => !Cancel; } - [HarmonyPatch(typeof(Targeter), nameof(Targeter.BeginTargeting), typeof(TargetingParameters), typeof(Action), typeof(Pawn), typeof(Action), typeof(Texture2D))] + [HarmonyPatch(typeof(Targeter), nameof(Targeter.BeginTargeting), typeof(TargetingParameters), typeof(Action), typeof(Pawn), typeof(Action), typeof(Texture2D), typeof(bool))] static class CancelBeginTargeting { static bool Prefix() @@ -57,7 +57,7 @@ static class CancelMotesNotTargetedAtMe static IEnumerable TargetMethods() { yield return AccessTools.Method(typeof(MoteMaker), nameof(MoteMaker.MakeStaticMote), new[] { typeof(IntVec3), typeof(Map), typeof(ThingDef), typeof(float) }); - yield return AccessTools.Method(typeof(MoteMaker), nameof(MoteMaker.MakeStaticMote), new[] { typeof(Vector3), typeof(Map), typeof(ThingDef), typeof(float), typeof(bool) }); + yield return AccessTools.Method(typeof(MoteMaker), nameof(MoteMaker.MakeStaticMote), new[] { typeof(Vector3), typeof(Map), typeof(ThingDef), typeof(float), typeof(bool), typeof(float) }); } static bool Prefix(ThingDef moteDef) diff --git a/Source/Client/Patches/LongEvents.cs b/Source/Client/Patches/LongEvents.cs index 40e99255..b07b4e40 100644 --- a/Source/Client/Patches/LongEvents.cs +++ b/Source/Client/Patches/LongEvents.cs @@ -7,7 +7,7 @@ namespace Multiplayer.Client.Patches { - [HarmonyPatch(typeof(LongEventHandler), nameof(LongEventHandler.QueueLongEvent), typeof(Action), typeof(string), typeof(bool), typeof(Action), typeof(bool))] + [HarmonyPatch(typeof(LongEventHandler), nameof(LongEventHandler.QueueLongEvent), typeof(Action), typeof(string), typeof(bool), typeof(Action), typeof(bool), typeof(Action))] static class MarkLongEvents { private static MethodInfo MarkerMethod = AccessTools.Method(typeof(MarkLongEvents), nameof(Marker)); @@ -62,7 +62,7 @@ static void Postfix() } } - [HarmonyPatch(typeof(LongEventHandler), nameof(LongEventHandler.QueueLongEvent), new[] { typeof(Action), typeof(string), typeof(bool), typeof(Action), typeof(bool) })] + [HarmonyPatch(typeof(LongEventHandler), nameof(LongEventHandler.QueueLongEvent), new[] { typeof(Action), typeof(string), typeof(bool), typeof(Action), typeof(bool), typeof(Action) })] static class LongEventAlwaysSync { static void Prefix(ref bool doAsynchronously) diff --git a/Source/Client/Patches/Patches.cs b/Source/Client/Patches/Patches.cs index 7d8e76ab..5fbe2999 100644 --- a/Source/Client/Patches/Patches.cs +++ b/Source/Client/Patches/Patches.cs @@ -245,7 +245,7 @@ static class RootPlayStartMarker static void Finalizer() => starting = false; } - [HarmonyPatch(typeof(LongEventHandler), nameof(LongEventHandler.QueueLongEvent), new[] { typeof(Action), typeof(string), typeof(bool), typeof(Action), typeof(bool) })] + [HarmonyPatch(typeof(LongEventHandler), nameof(LongEventHandler.QueueLongEvent), new[] { typeof(Action), typeof(string), typeof(bool), typeof(Action), typeof(bool), typeof(Action) })] static class CancelRootPlayStartLongEvents { public static bool cancel; @@ -263,7 +263,7 @@ static class DisableScreenFade1 static bool Prefix() => LongEventHandler.eventQueue.All(e => e.eventTextKey == "MpLoading"); } - [HarmonyPatch(typeof(ScreenFader), nameof(ScreenFader.StartFade))] + [HarmonyPatch(typeof(ScreenFader), nameof(ScreenFader.StartFade), typeof(Color), typeof(float), typeof(float))] static class DisableScreenFade2 { static bool Prefix() => LongEventHandler.eventQueue.All(e => e.eventTextKey == "MpLoading"); @@ -381,11 +381,11 @@ static class WorkPrioritySameValue static bool Prefix(Pawn_WorkSettings __instance, WorkTypeDef w, int priority) => __instance.GetPriority(w) != priority; } - [HarmonyPatch(typeof(Pawn_PlayerSettings), nameof(Pawn_PlayerSettings.AreaRestriction), MethodType.Setter)] + [HarmonyPatch(typeof(Pawn_PlayerSettings), nameof(Pawn_PlayerSettings.AreaRestrictionInPawnCurrentMap), MethodType.Setter)] static class AreaRestrictionSameValue { [HarmonyPriority(MpPriority.MpFirst + 1)] - static bool Prefix(Pawn_PlayerSettings __instance, Area value) => __instance.AreaRestriction != value; + static bool Prefix(Pawn_PlayerSettings __instance, Area value) => __instance.AreaRestrictionInPawnCurrentMap != value; } [HarmonyPatch] @@ -426,7 +426,7 @@ internal static void Choose(QuestPart_Choice part, int index) } [HarmonyPatch(typeof(MoteMaker), nameof(MoteMaker.MakeStaticMote))] - [HarmonyPatch(new[] {typeof(Vector3), typeof(Map), typeof(ThingDef), typeof(float), typeof(bool)})] + [HarmonyPatch(new[] {typeof(Vector3), typeof(Map), typeof(ThingDef), typeof(float), typeof(bool), typeof(float)})] static class FixNullMotes { static Dictionary cache = new(); diff --git a/Source/Client/Patches/Seeds.cs b/Source/Client/Patches/Seeds.cs index ede7b2f5..188b6d70 100644 --- a/Source/Client/Patches/Seeds.cs +++ b/Source/Client/Patches/Seeds.cs @@ -91,7 +91,7 @@ static void Postfix(Map map, bool __state) } } - [HarmonyPatch(typeof(LongEventHandler), nameof(LongEventHandler.QueueLongEvent), new[] { typeof(Action), typeof(string), typeof(bool), typeof(Action), typeof(bool) })] + [HarmonyPatch(typeof(LongEventHandler), nameof(LongEventHandler.QueueLongEvent), typeof(Action), typeof(string), typeof(bool), typeof(Action), typeof(bool), typeof(Action))] static class SeedLongEvents { static void Prefix(ref Action action) @@ -106,7 +106,7 @@ static void Prefix(ref Action action) } // Seed the rotation random - [HarmonyPatch(typeof(GenSpawn), nameof(GenSpawn.Spawn), new[] { typeof(Thing), typeof(IntVec3), typeof(Map), typeof(Rot4), typeof(WipeMode), typeof(bool) })] + [HarmonyPatch(typeof(GenSpawn), nameof(GenSpawn.Spawn), new[] { typeof(Thing), typeof(IntVec3), typeof(Map), typeof(Rot4), typeof(WipeMode), typeof(bool), typeof(bool) })] static class GenSpawnRotatePatch { static MethodInfo Rot4GetRandom = AccessTools.Property(typeof(Rot4), nameof(Rot4.Random)).GetGetMethod(); @@ -154,7 +154,7 @@ static IEnumerable TargetMethods() { yield return AccessTools.Method(typeof(GrammarResolver), nameof(GrammarResolver.Resolve)); yield return AccessTools.Method(typeof(PawnBioAndNameGenerator), nameof(PawnBioAndNameGenerator.GeneratePawnName)); - yield return AccessTools.Method(typeof(NameGenerator), nameof(NameGenerator.GenerateName), new[] { typeof(RulePackDef), typeof(Predicate), typeof(bool), typeof(string), typeof(string) }); + yield return AccessTools.Method(typeof(NameGenerator), nameof(NameGenerator.GenerateName), new[] { typeof(RulePackDef), typeof(Predicate), typeof(bool), typeof(string), typeof(string), typeof(List) }); } [HarmonyPriority(MpPriority.MpFirst)] @@ -178,12 +178,11 @@ static class SeedPawnGraphics { static IEnumerable TargetMethods() { - yield return AccessTools.Method(typeof(PawnGraphicSet), nameof(PawnGraphicSet.ResolveAllGraphics)); - yield return AccessTools.Method(typeof(PawnGraphicSet), nameof(PawnGraphicSet.ResolveApparelGraphics)); + yield return AccessTools.Method(typeof(PawnRenderer), nameof(PawnRenderer.SetAllGraphicsDirty)); } [HarmonyPriority(MpPriority.MpFirst)] - static void Prefix(PawnGraphicSet __instance, ref bool __state) + static void Prefix(PawnRenderer __instance, ref bool __state) { Rand.PushState(__instance.pawn.thingIDNumber); __state = true; diff --git a/Source/Client/Patches/TickPatch.cs b/Source/Client/Patches/TickPatch.cs index cde190f1..f3740f8a 100644 --- a/Source/Client/Patches/TickPatch.cs +++ b/Source/Client/Patches/TickPatch.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using LudeonTK; using Multiplayer.Client.AsyncTime; using UnityEngine; using Verse; diff --git a/Source/Client/Patches/UniqueIds.cs b/Source/Client/Patches/UniqueIds.cs index a7c36cfb..21872c80 100644 --- a/Source/Client/Patches/UniqueIds.cs +++ b/Source/Client/Patches/UniqueIds.cs @@ -43,10 +43,10 @@ static IEnumerable TargetMethods() [HarmonyPatch(typeof(OutfitDatabase), nameof(OutfitDatabase.MakeNewOutfit))] static class OutfitUniqueIdPatch { - static void Postfix(Outfit __result) + static void Postfix(ApparelPolicy __result) { if (Multiplayer.Ticking || Multiplayer.ExecutingCmds) - __result.uniqueId = Find.UniqueIDsManager.GetNextThingID(); + __result.id = Find.UniqueIDsManager.GetNextThingID(); } } @@ -56,14 +56,14 @@ static class DrugPolicyUniqueIdPatch static void Postfix(DrugPolicy __result) { if (Multiplayer.Ticking || Multiplayer.ExecutingCmds) - __result.uniqueId = Find.UniqueIDsManager.GetNextThingID(); + __result.id = Find.UniqueIDsManager.GetNextThingID(); } } [HarmonyPatch(typeof(FoodRestrictionDatabase), nameof(FoodRestrictionDatabase.MakeNewFoodRestriction))] static class FoodRestrictionUniqueIdPatch { - static void Postfix(FoodRestriction __result) + static void Postfix(FoodPolicy __result) { if (Multiplayer.Ticking || Multiplayer.ExecutingCmds) __result.id = Find.UniqueIDsManager.GetNextThingID(); diff --git a/Source/Client/Persistent/Rituals.cs b/Source/Client/Persistent/Rituals.cs index 0a6b5a32..fc5936ba 100644 --- a/Source/Client/Persistent/Rituals.cs +++ b/Source/Client/Persistent/Rituals.cs @@ -46,7 +46,6 @@ public void Start() public void OpenWindow(bool sound = true) { var dialog = new BeginRitualProxy( - null, data.ritualLabel, data.ritual, data.target, @@ -58,7 +57,6 @@ public void OpenWindow(bool sound = true) data.confirmText, null, null, - null, data.outcome, data.extraInfos, null @@ -109,8 +107,8 @@ public class BeginRitualProxy : Dialog_BeginRitual, ISwitchToMap public RitualSession Session => map.MpComp().sessionManager.GetFirstOfType(); - public BeginRitualProxy(string header, string ritualLabel, Precept_Ritual ritual, TargetInfo target, Map map, ActionCallback action, Pawn organizer, RitualObligation obligation, Func filter = null, string confirmText = null, List requiredPawns = null, Dictionary forcedForRole = null, string ritualName = null, RitualOutcomeEffectDef outcome = null, List extraInfoText = null, Pawn selectedPawn = null) : - base(header, ritualLabel, ritual, target, map, action, organizer, obligation, filter, confirmText, requiredPawns, forcedForRole, ritualName, outcome, extraInfoText, selectedPawn) + 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) { soundClose = SoundDefOf.TabClose; @@ -177,18 +175,9 @@ static void Postfix(bool __state, ref DraggableResult __result) } } - [HarmonyPatch] + [HarmonyPatch(typeof(Dialog_BeginRitual), nameof(Dialog_BeginRitual.Start))] static class HandleStartRitual { - static MethodBase TargetMethod() - { - return MpMethodUtil.GetLocalFunc( - typeof(Dialog_BeginRitual), - nameof(Dialog_BeginRitual.DoWindowContents), - localFunc: "Start" - ); - } - static bool Prefix(Dialog_BeginRitual __instance) { if (__instance is BeginRitualProxy proxy) diff --git a/Source/Client/Saving/CacheForReloading.cs b/Source/Client/Saving/CacheForReloading.cs index 3bf5b2ac..483573d1 100644 --- a/Source/Client/Saving/CacheForReloading.cs +++ b/Source/Client/Saving/CacheForReloading.cs @@ -2,6 +2,7 @@ using RimWorld.Planet; using System; using System.Collections.Generic; +using System.Reflection; using Verse; namespace Multiplayer.Client @@ -12,17 +13,21 @@ public static class MapDrawerRegenPatch { public static Dictionary copyFrom = new(); + // These are readonly so they need to be set using reflection + private static FieldInfo mapDrawerMap = AccessTools.Field(typeof(MapDrawer), nameof(MapDrawer.map)); + private static FieldInfo sectionMap = AccessTools.Field(typeof(Section), nameof(Section.map)); + static bool Prefix(MapDrawer __instance) { Map map = __instance.map; if (!copyFrom.TryGetValue(map.uniqueID, out MapDrawer keepDrawer)) return true; map.mapDrawer = keepDrawer; - keepDrawer.map = map; + mapDrawerMap.SetValue(keepDrawer, map); foreach (Section section in keepDrawer.sections) { - section.map = map; + sectionMap.SetValue(section, map); for (int i = 0; i < section.layers.Count; i++) { @@ -30,8 +35,6 @@ static bool Prefix(MapDrawer __instance) if (!ShouldKeep(layer)) section.layers[i] = (SectionLayer)Activator.CreateInstance(layer.GetType(), section); - else if (layer is SectionLayer_LightingOverlay lighting) - lighting.glowGrid = map.glowGrid.glowGrid; else if (layer is SectionLayer_TerrainScatter scatter) scatter.scats.Do(s => s.map = map); } diff --git a/Source/Client/Saving/CrossRefs.cs b/Source/Client/Saving/CrossRefs.cs index 0e7b7906..98b7e19b 100644 --- a/Source/Client/Saving/CrossRefs.cs +++ b/Source/Client/Saving/CrossRefs.cs @@ -165,7 +165,7 @@ static void Postfix(Map map) } [HarmonyPatch(typeof(MapDeiniter))] - [HarmonyPatch(nameof(MapDeiniter.Deinit_NewTemp))] + [HarmonyPatch(nameof(MapDeiniter.Deinit))] public static class DeinitMapPatch { static void Prefix(Map map) diff --git a/Source/Client/Saving/SaveCompression.cs b/Source/Client/Saving/SaveCompression.cs index a134070e..9ea45d39 100644 --- a/Source/Client/Saving/SaveCompression.cs +++ b/Source/Client/Saving/SaveCompression.cs @@ -289,13 +289,13 @@ public static bool IsSaveRockRubble(Thing t) private static void SaveBinary(BinaryWriter writer, string label) { byte[] arr = (writer.BaseStream as MemoryStream).ToArray(); - DataExposeUtility.ByteArray(ref arr, label); + DataExposeUtility.LookByteArray(ref arr, label); } private static BinaryReader LoadBinary(string label) { byte[] arr = null; - DataExposeUtility.ByteArray(ref arr, label); + DataExposeUtility.LookByteArray(ref arr, label); if (arr == null) return null; return new BinaryReader(new MemoryStream(arr)); diff --git a/Source/Client/Syncing/Dict/SyncDictDlc.cs b/Source/Client/Syncing/Dict/SyncDictDlc.cs index b3eb6011..bc58e3f4 100644 --- a/Source/Client/Syncing/Dict/SyncDictDlc.cs +++ b/Source/Client/Syncing/Dict/SyncDictDlc.cs @@ -193,7 +193,8 @@ public static class SyncDictDlc dlog.target = assgn.session.data.target; // This is a cache set every frame at the top of Dialog_BeginRitual.DrawPawnList - dlog.rolesGroupedTmp = (from r in assgn.AllRolesForReading group r by r.mergeId ?? r.id).ToList(); + // todo for 1.5 + // dlog.rolesGroupedTmp = (from r in assgn.AllRolesForReading group r by r.mergeId ?? r.id).ToList(); return dlog; } diff --git a/Source/Client/Syncing/Dict/SyncDictRimWorld.cs b/Source/Client/Syncing/Dict/SyncDictRimWorld.cs index 75c234b7..a327e3ee 100644 --- a/Source/Client/Syncing/Dict/SyncDictRimWorld.cs +++ b/Source/Client/Syncing/Dict/SyncDictRimWorld.cs @@ -181,25 +181,25 @@ public static class SyncDictRimWorld #region Policies { - (ByteWriter data, Outfit policy) => { - data.WriteInt32(policy.uniqueId); + (ByteWriter data, ApparelPolicy policy) => { + data.WriteInt32(policy.id); }, (ByteReader data) => { int id = data.ReadInt32(); - return Current.Game.outfitDatabase.AllOutfits.Find(o => o.uniqueId == id); + return Current.Game.outfitDatabase.AllOutfits.Find(o => o.id == id); } }, { (ByteWriter data, DrugPolicy policy) => { - data.WriteInt32(policy.uniqueId); + data.WriteInt32(policy.id); }, (ByteReader data) => { int id = data.ReadInt32(); - return Current.Game.drugPolicyDatabase.AllPolicies.Find(o => o.uniqueId == id); + return Current.Game.drugPolicyDatabase.AllPolicies.Find(o => o.id == id); } }, { - (ByteWriter data, FoodRestriction policy) => { + (ByteWriter data, FoodPolicy policy) => { data.WriteInt32(policy.id); }, (ByteReader data) => { @@ -582,11 +582,13 @@ public static class SyncDictRimWorld { (ByteWriter data, Command_Ability command) => { WriteSync(data, command.ability); + WriteSync(data, command.Pawn); }, (ByteReader data) => { Ability ability = ReadSync(data); + Pawn pawn = ReadSync(data); - return new Command_Ability(ability); + return new Command_Ability(ability, pawn); } }, #endregion diff --git a/Source/Client/Syncing/Game/SyncDelegates.cs b/Source/Client/Syncing/Game/SyncDelegates.cs index f1e9008a..4ab13b3a 100644 --- a/Source/Client/Syncing/Game/SyncDelegates.cs +++ b/Source/Client/Syncing/Game/SyncDelegates.cs @@ -49,8 +49,6 @@ public static void Init() SyncMethod.Lambda(typeof(UnfinishedThing), nameof(UnfinishedThing.GetGizmos), 0); // Cancel unfinished thing SyncMethod.Lambda(typeof(CompTempControl), nameof(CompTempControl.CompGetGizmosExtra), 2); // Reset temperature - SyncDelegate.Lambda(typeof(CompTargetable), nameof(CompTargetable.SelectedUseOption), 0); // Use targetable - SyncDelegate.LambdaInGetter(typeof(Designator), nameof(Designator.RightClickFloatMenuOptions), 0) // Designate all .TransformField("things", Serializer.SimpleReader(() => Find.CurrentMap.listerThings.AllThings)).SetContext(SyncContext.CurrentMap); SyncDelegate.LambdaInGetter(typeof(Designator), nameof(Designator.RightClickFloatMenuOptions), 1).SetContext(SyncContext.CurrentMap); // Remove all designations @@ -98,8 +96,8 @@ public static void Init() SyncMethod.Lambda(typeof(CompBiosculpterPod), nameof(CompBiosculpterPod.CompGetGizmosExtra), 8).SetDebugOnly(); // Dev complete biotuner timer SyncMethod.Lambda(typeof(CompBiosculpterPod), nameof(CompBiosculpterPod.CompGetGizmosExtra), 9).SetDebugOnly(); // Dev fill nutrition and ingredients - SyncDelegate.Lambda(typeof(ITab_Pawn_Visitor), nameof(ITab_Pawn_Visitor.FillTab), 3).SetContext(SyncContext.MapSelected).CancelIfNoSelectedMapObjects(); // Select target prisoner ideology - SyncDelegate.Lambda(typeof(ITab_Pawn_Visitor), nameof(ITab_Pawn_Visitor.FillTab), 10).SetContext(SyncContext.MapSelected).CancelIfNoSelectedMapObjects(); // Cancel setting slave mode to execution + SyncDelegate.Lambda(typeof(ITab_Pawn_Visitor), nameof(ITab_Pawn_Visitor.DoPrisonerTab), 5).SetContext(SyncContext.MapSelected).CancelIfNoSelectedMapObjects(); // Select target prisoner ideology + SyncDelegate.Lambda(typeof(ITab_Pawn_Visitor), nameof(ITab_Pawn_Visitor.DoSlaveTab), 6).SetContext(SyncContext.MapSelected).CancelIfNoSelectedMapObjects(); // Cancel setting slave mode to execution SyncMethod.Lambda(typeof(ShipJob_Wait), nameof(ShipJob_Wait.GetJobGizmos), 0); // Dismiss (unload) shuttle SyncMethod.Lambda(typeof(ShipJob_Wait), nameof(ShipJob_Wait.GetJobGizmos), 1); // Send loaded shuttle @@ -115,7 +113,6 @@ public static void Init() SyncMethod.Lambda(typeof(CompSpawnerItems), nameof(CompSpawnerItems.CompGetGizmosExtra), 0).SetDebugOnly(); SyncMethod.Lambda(typeof(CompSpawnerPawn), nameof(CompSpawnerPawn.CompGetGizmosExtra), 0).SetDebugOnly(); - SyncMethod.Lambda(typeof(CompCanBeDormant), nameof(CompCanBeDormant.CompGetGizmosExtra), 0).SetDebugOnly(); SyncMethod.Lambda(typeof(CompSendSignalOnCountdown), nameof(CompSendSignalOnCountdown.CompGetGizmosExtra), 0).SetDebugOnly(); // CompRechargeable @@ -130,7 +127,7 @@ public static void Init() SyncDelegate.Lambda(typeof(Dialog_StyleSelection), nameof(Dialog_StyleSelection.DoWindowContents), 1); // Replace style SyncDelegate.Lambda(typeof(Dialog_StyleSelection), nameof(Dialog_StyleSelection.DoWindowContents), 2); // Add style - SyncDelegate.Lambda(typeof(CompActivable_RocketswarmLauncher), nameof(CompActivable_RocketswarmLauncher.TargetLocation), 0); + SyncDelegate.Lambda(typeof(CompInteractableRocketswarmLauncher), nameof(CompInteractableRocketswarmLauncher.TargetLocation), 0); SyncDelegate.Lambda(typeof(CompAtomizer), nameof(CompAtomizer.CompGetGizmosExtra), 3).SetDebugOnly(); // Set next atomization SyncDelegate.Lambda(typeof(CompBandNode), nameof(CompBandNode.CompGetGizmosExtra), 7); // Select pawn to tune to SyncDelegate.Lambda(typeof(CompDissolution), nameof(CompDissolution.CompGetGizmosExtra), 4).SetDebugOnly(); // Set next dissolve time @@ -223,8 +220,8 @@ The UI's main interaction area is split into three types of groups of pawns. Not participating: (assgn.RemoveParticipant), (delegate), float menus: (assgn.TryAssignSpectate, local TryAssignReplace, local TryAssign) */ - var RitualRolesSerializer = Serializer.New( - (IEnumerable roles, object target, object[] args) => + var ritualRolesSerializer = Serializer.New( + (IEnumerable roles, object target, object[] _) => { var dialog = target.GetPropertyOrField(SyncDelegate.DelegateThis) as Dialog_BeginRitual; var ids = from r in roles select r.id; @@ -233,14 +230,15 @@ The UI's main interaction area is split into three types of groups of pawns. (data) => data.ids.Select(id => data.def.roles.FirstOrDefault(r => r.id == id)) ); - SyncDelegate.LocalFunc(typeof(Dialog_BeginRitual), nameof(Dialog_BeginRitual.DrawPawnList), "TryAssignReplace") - .TransformArgument(1, RitualRolesSerializer); - SyncDelegate.LocalFunc(typeof(Dialog_BeginRitual), nameof(Dialog_BeginRitual.DrawPawnList), "TryAssignAnyRole"); - SyncDelegate.LocalFunc(typeof(Dialog_BeginRitual), nameof(Dialog_BeginRitual.DrawPawnList), "TryAssign") - .TransformArgument(1, RitualRolesSerializer); - - SyncDelegate.Lambda(typeof(Dialog_BeginRitual), nameof(Dialog_BeginRitual.DrawPawnList), 27); // Roles right click delegate (try assign spectate) - SyncDelegate.Lambda(typeof(Dialog_BeginRitual), nameof(Dialog_BeginRitual.DrawPawnList), 15); // Not participating left click delegate (try assign any role or spectate) + // todo for 1.5 + // SyncDelegate.LocalFunc(typeof(Dialog_BeginRitual), nameof(Dialog_BeginRitual.DrawPawnList), "TryAssignReplace") + // .TransformArgument(1, ritualRolesSerializer); + // SyncDelegate.LocalFunc(typeof(Dialog_BeginRitual), nameof(Dialog_BeginRitual.DrawPawnList), "TryAssignAnyRole"); + // SyncDelegate.LocalFunc(typeof(Dialog_BeginRitual), nameof(Dialog_BeginRitual.DrawPawnList), "TryAssign") + // .TransformArgument(1, ritualRolesSerializer); + // + // SyncDelegate.Lambda(typeof(Dialog_BeginRitual), nameof(Dialog_BeginRitual.DrawPawnList), 27); // Roles right click delegate (try assign spectate) + // SyncDelegate.Lambda(typeof(Dialog_BeginRitual), nameof(Dialog_BeginRitual.DrawPawnList), 15); // 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)); diff --git a/Source/Client/Syncing/Game/SyncFields.cs b/Source/Client/Syncing/Game/SyncFields.cs index 4d7d8ae8..d4ec8755 100644 --- a/Source/Client/Syncing/Game/SyncFields.cs +++ b/Source/Client/Syncing/Game/SyncFields.cs @@ -25,7 +25,6 @@ public static class SyncFields public static ISyncField SyncPsychicEntropyTargetFocus; public static ISyncField SyncGuiltAwaitingExecution; - public static ISyncField SyncGodMode; public static ISyncField SyncUseWorkPriorities; public static ISyncField SyncAutoHomeArea; public static ISyncField SyncAutoRebuild; @@ -38,11 +37,11 @@ public static class SyncFields public static ISyncField SyncThingFilterHitPoints; public static ISyncField SyncThingFilterQuality; + public static ISyncField SyncBillPaused; public static ISyncField SyncBillSuspended; public static ISyncField SyncIngredientSearchRadius; public static ISyncField SyncBillSkillRange; - public static ISyncField SyncBillIncludeZone; public static ISyncField SyncBillIncludeHpRange; public static ISyncField SyncBillIncludeQualityRange; public static ISyncField SyncBillPawnRestriction; @@ -51,8 +50,6 @@ public static class SyncFields public static ISyncField SyncBillMechsOnly; public static ISyncField SyncBillNonMechsOnly; - public static ISyncField SyncZoneLabel; - public static SyncField[] SyncBillProduction; public static SyncField[] SyncBillIncludeCriteria; @@ -61,14 +58,6 @@ public static class SyncFields public static ISyncField SyncTradeableCount; - // 1 - public static ISyncField SyncBillPaused; - - // 2 - public static ISyncField SyncOutfitLabel; - public static ISyncField SyncDrugPolicyLabel; - public static ISyncField SyncFoodRestrictionLabel; - public static ISyncField SyncStorytellerDef; public static ISyncField SyncStorytellerDifficultyDef; public static ISyncField SyncStorytellerDifficulty; @@ -79,7 +68,6 @@ public static class SyncFields public static ISyncField SyncDryadCaste; public static ISyncField SyncDesiredTreeConnectionStrength; - public static ISyncField SyncPlantCells; public static ISyncField SyncNeuralSuperchargerMode; @@ -97,33 +85,37 @@ public static class SyncFields public static void Init() { - SyncMedCare = Sync.Field(typeof(Pawn), "playerSettings", "medCare"); - SyncSelfTend = Sync.Field(typeof(Pawn), "playerSettings", "selfTend"); - SyncHostilityResponse = Sync.Field(typeof(Pawn), "playerSettings", "hostilityResponse"); - SyncFollowDrafted = Sync.Field(typeof(Pawn), "playerSettings", "followDrafted"); - SyncFollowFieldwork = Sync.Field(typeof(Pawn), "playerSettings", "followFieldwork"); - SyncInteractionMode = Sync.Field(typeof(Pawn), "guest", "interactionMode"); + SyncMedCare = Sync.Field(typeof(Pawn), nameof(Pawn.playerSettings), nameof(Pawn_PlayerSettings.medCare)); + SyncSelfTend = Sync.Field(typeof(Pawn), nameof(Pawn.playerSettings), nameof(Pawn_PlayerSettings.selfTend)); + SyncHostilityResponse = Sync.Field(typeof(Pawn), nameof(Pawn.playerSettings), nameof(Pawn_PlayerSettings.hostilityResponse)); + SyncFollowDrafted = Sync.Field(typeof(Pawn), nameof(Pawn.playerSettings), nameof(Pawn_PlayerSettings.followDrafted)); + SyncFollowFieldwork = Sync.Field(typeof(Pawn), nameof(Pawn.playerSettings), nameof(Pawn_PlayerSettings.followFieldwork)); + SyncInteractionMode = Sync.Field(typeof(Pawn), nameof(Pawn.guest), nameof(Pawn_GuestTracker.interactionMode)); SyncSlaveInteractionMode = Sync.Field(typeof(Pawn), nameof(Pawn.guest), nameof(Pawn_GuestTracker.slaveInteractionMode)); SyncIdeoForConversion = Sync.Field(typeof(Pawn), nameof(Pawn.guest), nameof(Pawn_GuestTracker.ideoForConversion)); - SyncBeCarried = Sync.Field(typeof(Pawn), "health", "beCarriedByCaravanIfSick"); - SyncPsychicEntropyLimit = Sync.Field(typeof(Pawn), "psychicEntropy", "limitEntropyAmount"); - SyncPsychicEntropyTargetFocus = Sync.Field(typeof(Pawn), "psychicEntropy", "targetPsyfocus").SetBufferChanges(); + SyncBeCarried = Sync.Field(typeof(Pawn), nameof(Pawn.health), nameof(Pawn_HealthTracker.beCarriedByCaravanIfSick)); + SyncPsychicEntropyLimit = Sync.Field(typeof(Pawn), nameof(Pawn.psychicEntropy), nameof(Pawn_PsychicEntropyTracker.limitEntropyAmount)); + SyncPsychicEntropyTargetFocus = Sync.Field(typeof(Pawn), nameof(Pawn.psychicEntropy), nameof(Pawn_PsychicEntropyTracker.targetPsyfocus)).SetBufferChanges(); SyncGuiltAwaitingExecution = Sync.Field(typeof(Pawn), nameof(Pawn.guilt), nameof(Pawn_GuiltTracker.awaitingExecution)); - SyncUseWorkPriorities = Sync.Field(null, "Verse.Current/Game/playSettings", "useWorkPriorities").PostApply(UseWorkPriorities_PostApply); - SyncAutoHomeArea = Sync.Field(null, "Verse.Current/Game/playSettings", "autoHomeArea"); - SyncAutoRebuild = Sync.Field(null, "Verse.Current/Game/playSettings", "autoRebuild"); + SyncUseWorkPriorities = Sync.Field(null, $"Verse.Current/Game/playSettings", nameof(PlaySettings.useWorkPriorities)).PostApply(UseWorkPriorities_PostApply); + SyncAutoHomeArea = Sync.Field(null, "Verse.Current/Game/playSettings", nameof(PlaySettings.autoHomeArea)); + SyncAutoRebuild = Sync.Field(null, "Verse.Current/Game/playSettings", nameof(PlaySettings.autoRebuild)); SyncDefaultCare = Sync.Fields( null, "Verse.Current/Game/playSettings", - nameof(PlaySettings.defaultCareForColonyHumanlike), - nameof(PlaySettings.defaultCareForColonyPrisoner), - nameof(PlaySettings.defaultCareForColonySlave), - nameof(PlaySettings.defaultCareForColonyAnimal), - nameof(PlaySettings.defaultCareForNeutralAnimal), + nameof(PlaySettings.defaultCareForColonist), + nameof(PlaySettings.defaultCareForEntities), + nameof(PlaySettings.defaultCareForGhouls), + nameof(PlaySettings.defaultCareForPrisoner), + nameof(PlaySettings.defaultCareForSlave), + nameof(PlaySettings.defaultCareForFriendlyFaction), nameof(PlaySettings.defaultCareForNeutralFaction), - nameof(PlaySettings.defaultCareForHostileFaction) + nameof(PlaySettings.defaultCareForHostileFaction), + nameof(PlaySettings.defaultCareForWildlife), + nameof(PlaySettings.defaultCareForNoFaction), + nameof(PlaySettings.defaultCareForTamedAnimal) ).SetBufferChanges(); SyncQuestDismissed = Sync.Field(typeof(Quest), nameof(Quest.dismissed)); @@ -133,82 +125,72 @@ public static void Init() SyncThingFilterHitPoints = Sync.Field(typeof(ThingFilterContext), "Filter/AllowedHitPointsPercents").SetBufferChanges(); SyncThingFilterQuality = Sync.Field(typeof(ThingFilterContext), "Filter/AllowedQualityLevels").SetBufferChanges(); - SyncBillSuspended = Sync.Field(typeof(Bill), "suspended"); - SyncIngredientSearchRadius = Sync.Field(typeof(Bill), "ingredientSearchRadius").SetBufferChanges(); - SyncBillSkillRange = Sync.Field(typeof(Bill), "allowedSkillRange").SetBufferChanges(); + SyncBillPaused = Sync.Field(typeof(Bill_Production), nameof(Bill_Production.paused)).SetBufferChanges(); + SyncBillSuspended = Sync.Field(typeof(Bill), nameof(Bill.suspended)); + SyncIngredientSearchRadius = Sync.Field(typeof(Bill), nameof(Bill.ingredientSearchRadius)).SetBufferChanges(); + SyncBillSkillRange = Sync.Field(typeof(Bill), nameof(Bill.allowedSkillRange)).SetBufferChanges(); - SyncBillIncludeZone = Sync.Field(typeof(Bill_Production), "includeFromZone"); - SyncBillIncludeHpRange = Sync.Field(typeof(Bill_Production), "hpRange").SetBufferChanges(); - SyncBillIncludeQualityRange = Sync.Field(typeof(Bill_Production), "qualityRange").SetBufferChanges(); - SyncBillPawnRestriction = Sync.Field(typeof(Bill), "pawnRestriction"); + SyncBillIncludeHpRange = Sync.Field(typeof(Bill_Production), nameof(Bill_Production.hpRange)).SetBufferChanges(); + SyncBillIncludeQualityRange = Sync.Field(typeof(Bill_Production), nameof(Bill_Production.qualityRange)).SetBufferChanges(); + SyncBillPawnRestriction = Sync.Field(typeof(Bill), nameof(Bill.pawnRestriction)); SyncBillSlavesOnly = Sync.Field(typeof(Bill), nameof(Bill.slavesOnly)); SyncBillMechsOnly = Sync.Field(typeof(Bill), nameof(Bill.mechsOnly)); SyncBillNonMechsOnly = Sync.Field(typeof(Bill), nameof(Bill.nonMechsOnly)); - SyncZoneLabel = Sync.Field(typeof(Zone), "label"); - SyncBillProduction = Sync.Fields( typeof(Bill_Production), null, - "repeatMode", - "repeatCount", - "targetCount", - "pauseWhenSatisfied", - "unpauseWhenYouHave" + nameof(Bill_Production.repeatMode), + nameof(Bill_Production.repeatCount), + nameof(Bill_Production.targetCount), + nameof(Bill_Production.pauseWhenSatisfied), + nameof(Bill_Production.unpauseWhenYouHave) ); SyncBillIncludeCriteria = Sync.Fields( typeof(Bill_Production), null, - "includeEquipped", - "includeTainted", - "limitToAllowedStuff" + nameof(Bill_Production.includeEquipped), + nameof(Bill_Production.includeTainted), + nameof(Bill_Production.limitToAllowedStuff) ); SyncDrugPolicyEntry = Sync.Fields( typeof(DrugPolicy), - "entriesInt/[]", - "allowedForAddiction", - "allowedForJoy", - "allowScheduled", - "takeToInventory" + $"{nameof(DrugPolicy.entriesInt)}/[]", + nameof(DrugPolicyEntry.allowedForAddiction), + nameof(DrugPolicyEntry.allowedForJoy), + nameof(DrugPolicyEntry.allowScheduled), + nameof(DrugPolicyEntry.takeToInventory) ); SyncDrugPolicyEntryBuffered = Sync.Fields( typeof(DrugPolicy), - "entriesInt/[]", - "daysFrequency", - "onlyIfMoodBelow", - "onlyIfJoyBelow" + $"{nameof(DrugPolicy.entriesInt)}/[]", + nameof(DrugPolicyEntry.daysFrequency), + nameof(DrugPolicyEntry.onlyIfMoodBelow), + nameof(DrugPolicyEntry.onlyIfJoyBelow) ).SetBufferChanges(); // This depends on the order of AutoSlaughterManager.configs being the same on all clients // The array is initialized using DefDatabase.AllDefs which shouldn't cause problems though SyncAutoSlaughter = Sync.Fields( typeof(AutoSlaughterManager), - "configs/[]", - "maxTotal", - "maxMales", - "maxMalesYoung", - "maxFemales", - "maxFemalesYoung", - "allowSlaughterPregnant" + $"{nameof(AutoSlaughterManager.configs)}/[]", + nameof(AutoSlaughterConfig.maxTotal), + nameof(AutoSlaughterConfig.maxMales), + nameof(AutoSlaughterConfig.maxMalesYoung), + nameof(AutoSlaughterConfig.maxFemales), + nameof(AutoSlaughterConfig.maxFemalesYoung), + nameof(AutoSlaughterConfig.allowSlaughterPregnant) ).PostApply(Autoslaughter_PostApply); - SyncTradeableCount = Sync.Field(typeof(MpTransferableReference), "CountToTransfer").SetBufferChanges().PostApply(TransferableCount_PostApply); + SyncTradeableCount = Sync.Field(typeof(MpTransferableReference), nameof(MpTransferableReference.CountToTransfer)).SetBufferChanges().PostApply(TransferableCount_PostApply); - // 1 - SyncBillPaused = Sync.Field(typeof(Bill_Production), nameof(Bill_Production.paused)).SetBufferChanges().SetVersion(1); - - // 2 - SyncOutfitLabel = Sync.Field(typeof(Outfit), "label").SetBufferChanges().SetVersion(2); - SyncDrugPolicyLabel = Sync.Field(typeof(DrugPolicy), "label").SetBufferChanges().SetVersion(2); - SyncFoodRestrictionLabel = Sync.Field(typeof(FoodRestriction), "label").SetBufferChanges().SetVersion(2); - - SyncStorytellerDef = Sync.Field(typeof(Storyteller), "def").SetHostOnly().PostApply(StorytellerDef_Post).SetVersion(2); - SyncStorytellerDifficultyDef = Sync.Field(typeof(Storyteller), "difficultyDef").SetHostOnly().PostApply(StorytellerDifficultyDef_Post).SetVersion(2); - SyncStorytellerDifficulty = Sync.Field(typeof(Storyteller), "difficulty").ExposeValue().SetHostOnly().PostApply(StorytellerDifficulty_Post).SetVersion(2); + SyncStorytellerDef = Sync.Field(typeof(Storyteller), nameof(Storyteller.def)).SetHostOnly().PostApply(StorytellerDef_Post); + SyncStorytellerDifficultyDef = Sync.Field(typeof(Storyteller), nameof(Storyteller.difficultyDef)).SetHostOnly().PostApply(StorytellerDifficultyDef_Post); + SyncStorytellerDifficulty = Sync.Field(typeof(Storyteller), nameof(Storyteller.difficulty)).ExposeValue().SetHostOnly().PostApply(StorytellerDifficulty_Post); SyncDryadCaste = Sync.Field(typeof(CompTreeConnection), nameof(CompTreeConnection.desiredMode)); SyncDesiredTreeConnectionStrength = Sync.Field(typeof(CompTreeConnection), nameof(CompTreeConnection.desiredConnectionStrength)); @@ -261,8 +243,8 @@ static void StorytellerDifficulty_Post(object target, object value) comp.storyteller.difficulty = Find.Storyteller.difficulty; } - [MpPrefix(typeof(HealthCardUtility), "DrawOverviewTab")] - static void HealthCardUtility(Pawn pawn) + [MpPrefix(typeof(HealthCardUtility), nameof(HealthCardUtility.DrawOverviewTab))] + static void HealthCardUtility_Patch(Pawn pawn) { if (pawn.playerSettings != null) { @@ -271,8 +253,8 @@ static void HealthCardUtility(Pawn pawn) } } - [MpPrefix(typeof(ITab_Pawn_Visitor), "FillTab")] - static void ITab_Pawn_Visitor(ITab __instance) + [MpPrefix(typeof(ITab_Pawn_Visitor), nameof(ITab_Pawn_Visitor.FillTab))] + static void ITab_Pawn_Visitor_Patch(ITab __instance) { Pawn pawn = __instance.SelPawn; SyncMedCare.Watch(pawn); @@ -321,12 +303,6 @@ static IEnumerable> MedicalCare_Postfix return WatchDropdowns(() => SyncMedCare.Watch(p), __result); } - [MpPostfix(typeof(Dialog_BillConfig), nameof(Dialog_BillConfig.GenerateStockpileInclusion))] - static IEnumerable> BillIncludeZone_Postfix(IEnumerable> __result, Bill ___bill) - { - return WatchDropdowns(() => SyncBillIncludeZone.Watch(___bill), __result); - } - [MpPrefix(typeof(TrainingCardUtility), nameof(TrainingCardUtility.DrawTrainingCard))] static void PawnSettingFollowWatch(Pawn pawn) { @@ -334,7 +310,7 @@ static void PawnSettingFollowWatch(Pawn pawn) SyncFollowFieldwork.Watch(pawn); } - [MpPrefix(typeof(Dialog_MedicalDefaults), "DoWindowContents")] + [MpPrefix(typeof(Dialog_MedicalDefaults), nameof(Dialog_MedicalDefaults.DoWindowContents))] static void MedicalDefaults() { SyncDefaultCare.Watch(); @@ -349,7 +325,7 @@ static void PsychicEntropyLimiterToggle(PsychicEntropyGizmo __instance) } } - [MpPrefix(typeof(Widgets), "CheckboxLabeled")] + [MpPrefix(typeof(Widgets), nameof(Widgets.CheckboxLabeled))] static void CheckboxLabeled() { // Watched here to get reset asap and not trigger any side effects @@ -357,26 +333,26 @@ static void CheckboxLabeled() SyncUseWorkPriorities.Watch(); } - [MpPrefix(typeof(PlaySettings), "DoPlaySettingsGlobalControls")] + [MpPrefix(typeof(PlaySettings), nameof(PlaySettings.DoPlaySettingsGlobalControls))] static void PlaySettingsControls() { SyncAutoHomeArea.Watch(); SyncAutoRebuild.Watch(); } - [MpPrefix(typeof(ThingFilterUI), "DrawHitPointsFilterConfig")] + [MpPrefix(typeof(ThingFilterUI), nameof(ThingFilterUI.DrawHitPointsFilterConfig))] static void ThingFilterHitPoints() { SyncThingFilterHitPoints.Watch(ThingFilterMarkers.DrawnThingFilter); } - [MpPrefix(typeof(ThingFilterUI), "DrawQualityFilterConfig")] + [MpPrefix(typeof(ThingFilterUI), nameof(ThingFilterUI.DrawQualityFilterConfig))] static void ThingFilterQuality() { SyncThingFilterQuality.Watch(ThingFilterMarkers.DrawnThingFilter); } - [MpPrefix(typeof(Bill), "DoInterface")] + [MpPrefix(typeof(Bill), nameof(Bill.DoInterface))] static void BillInterfaceCard(Bill __instance) { SyncBillSuspended.Watch(__instance); @@ -386,7 +362,7 @@ static void BillInterfaceCard(Bill __instance) SyncBillProduction.Watch(__instance); } - [MpPrefix(typeof(Dialog_BillConfig), "DoWindowContents")] + [MpPrefix(typeof(Dialog_BillConfig), nameof(Dialog_BillConfig.DoWindowContents))] static void DialogBillConfig(Dialog_BillConfig __instance) { Bill_Production bill = __instance.bill; @@ -413,7 +389,7 @@ static void BillRepeatMode(object __instance) SyncBillProduction.Watch(__instance.GetPropertyOrField("bill")); } - [MpPrefix(typeof(ITab_Bills), "TabUpdate")] + [MpPrefix(typeof(ITab_Bills), nameof(ITab_Bills.TabUpdate))] static void BillIngredientSearchRadius(ITab_Bills __instance) { // Apply the buffered value for smooth rendering @@ -422,13 +398,13 @@ static void BillIngredientSearchRadius(ITab_Bills __instance) SyncIngredientSearchRadius.Watch(mouseover); } - [MpPrefix(typeof(Dialog_BillConfig), "WindowUpdate")] + [MpPrefix(typeof(Dialog_BillConfig), nameof(Dialog_BillConfig.WindowUpdate))] static void BillIngredientSearchRadius(Dialog_BillConfig __instance) { SyncIngredientSearchRadius.Watch(__instance.bill); } - [MpPrefix(typeof(Dialog_ManageDrugPolicies), "DoPolicyConfigArea")] + [MpPrefix(typeof(Dialog_ManageDrugPolicies), nameof(Dialog_ManageDrugPolicies.DoPolicyConfigArea))] static void DialogManageDrugPolicies(Dialog_ManageDrugPolicies __instance) { DrugPolicy policy = __instance.SelectedPolicy; @@ -439,13 +415,8 @@ static void DialogManageDrugPolicies(Dialog_ManageDrugPolicies __instance) } } - [MpPrefix(typeof(Dialog_RenameZone), "SetName")] - static void RenameZone(Dialog_RenameZone __instance) - { - SyncZoneLabel.Watch(__instance.zone); - } - [MpPrefix(typeof(TransferableUIUtility), "DoCountAdjustInterface")] + [MpPrefix(typeof(TransferableUIUtility), nameof(TransferableUIUtility.DoCountAdjustInterface))] static void TransferableAdjustTo(Transferable trad) { var session = SyncSessionWithTransferablesMarker.DrawnSessionWithTransferables; @@ -472,9 +443,9 @@ static void DrawWindTurbineAutoCutOptions(CompAutoCutWindTurbine autoCut) } [MpPrefix(typeof(Dialog_AutoSlaughter), nameof(Dialog_AutoSlaughter.DoAnimalRow))] - static void Dialog_AutoSlaughter_Row(Map map, AutoSlaughterConfig config) + static void Dialog_AutoSlaughter_Row(Dialog_AutoSlaughter __instance, AutoSlaughterConfig config) { - SyncAutoSlaughter.Watch(map.autoSlaughterManager, map.autoSlaughterManager.configs.IndexOf(config)); + SyncAutoSlaughter.Watch(__instance.map.autoSlaughterManager, __instance.map.autoSlaughterManager.configs.IndexOf(config)); } [MpPrefix(typeof(Bill), nameof(Bill.DoInterface))] @@ -485,21 +456,6 @@ static void WatchBillPaused(Bill __instance) SyncBillPaused.Watch(__instance); } - [MpPrefix(typeof(Dialog_ManageOutfits), "DoNameInputRect")] - [MpPrefix(typeof(Dialog_ManageDrugPolicies), "DoNameInputRect")] - [MpPrefix(typeof(Dialog_ManageFoodRestrictions), "DoNameInputRect")] - static void WatchPolicyLabels() - { - if (SyncMarkers.dialogOutfit != null) - SyncOutfitLabel.Watch(SyncMarkers.dialogOutfit); - - if (SyncMarkers.drugPolicy != null) - SyncDrugPolicyLabel.Watch(SyncMarkers.drugPolicy); - - if (SyncMarkers.foodRestriction != null) - SyncFoodRestrictionLabel.Watch(SyncMarkers.foodRestriction); - } - static void UseWorkPriorities_PostApply(object target, object value) { // From MainTabWindow_Work.DoManualPrioritiesCheckbox @@ -557,13 +513,17 @@ static void WatchAwaitingExecution(Pawn pawn) SyncGuiltAwaitingExecution.Watch(pawn); } - [MpPrefix(typeof(GeneGizmo_Resource), nameof(GeneGizmo_Resource.GizmoOnGUI))] - static void SyncGeneResourceChange(GeneGizmo_Resource __instance) + [MpPrefix(typeof(Gizmo_Slider), nameof(Gizmo_Slider.GizmoOnGUI))] + static void SyncGeneResourceChange(Gizmo_Slider __instance) { - SyncGeneGizmoResource.Watch(__instance); - SyncGeneResource.Watch(__instance.gene); - if (__instance.gene is Gene_Hemogen) - SyncGeneHemogenAllowed.Watch(__instance.gene); + if (__instance is GeneGizmo_Resource geneGizmo) + { + SyncGeneGizmoResource.Watch(geneGizmo); + SyncGeneResource.Watch(geneGizmo.gene); + + if (geneGizmo.gene is Gene_Hemogen) + SyncGeneHemogenAllowed.Watch(geneGizmo.gene); + } } [MpPrefix(typeof(ITab_ContentsGenepackHolder), nameof(ITab_ContentsGenepackHolder.DoItemsLists))] diff --git a/Source/Client/Syncing/Game/SyncGame.cs b/Source/Client/Syncing/Game/SyncGame.cs index 15e38cfe..f9c1bc36 100644 --- a/Source/Client/Syncing/Game/SyncGame.cs +++ b/Source/Client/Syncing/Game/SyncGame.cs @@ -25,8 +25,6 @@ static void TryInit(string name, Action action) TryInit("SyncDelegates", SyncDelegates.Init); TryInit("SyncActions", SyncActions.Init); - //RuntimeHelpers.RunClassConstructor(typeof(SyncResearch).TypeHandle); - SyncFieldUtil.ApplyWatchFieldPatches(typeof(SyncFields)); } } diff --git a/Source/Client/Syncing/Game/SyncMarkers.cs b/Source/Client/Syncing/Game/SyncMarkers.cs index 7fde4106..4ce5e514 100644 --- a/Source/Client/Syncing/Game/SyncMarkers.cs +++ b/Source/Client/Syncing/Game/SyncMarkers.cs @@ -5,40 +5,10 @@ namespace Multiplayer.Client; public static class SyncMarkers { public static bool manualPriorities; - public static bool researchToil; - - public static DrugPolicy drugPolicy; - public static Outfit dialogOutfit; - public static FoodRestriction foodRestriction; [MpPrefix(typeof(MainTabWindow_Work), nameof(MainTabWindow_Work.DoManualPrioritiesCheckbox))] static void ManualPriorities_Prefix() => manualPriorities = true; [MpPostfix(typeof(MainTabWindow_Work), nameof(MainTabWindow_Work.DoManualPrioritiesCheckbox))] static void ManualPriorities_Postfix() => manualPriorities = false; - - [MpPrefix(typeof(JobDriver_Research), nameof(JobDriver_Research.MakeNewToils), lambdaOrdinal: 0)] - static void ResearchToil_Prefix() => researchToil = true; - - [MpPostfix(typeof(JobDriver_Research), nameof(JobDriver_Research.MakeNewToils), lambdaOrdinal: 0)] - static void ResearchToil_Postfix() => researchToil = false; - - [MpPrefix(typeof(Dialog_ManageOutfits), nameof(Dialog_ManageOutfits.DoWindowContents))] - static void ManageOutfit_Prefix(Dialog_ManageOutfits __instance) => dialogOutfit = __instance.SelectedOutfit; - - [MpPostfix(typeof(Dialog_ManageOutfits), nameof(Dialog_ManageOutfits.DoWindowContents))] - static void ManageOutfit_Postfix() => dialogOutfit = null; - - [MpPrefix(typeof(Dialog_ManageFoodRestrictions), nameof(Dialog_ManageFoodRestrictions.DoWindowContents))] - static void ManageFoodRestriction_Prefix(Dialog_ManageFoodRestrictions __instance) => - foodRestriction = __instance.SelectedFoodRestriction; - - [MpPostfix(typeof(Dialog_ManageFoodRestrictions), nameof(Dialog_ManageFoodRestrictions.DoWindowContents))] - static void ManageFoodRestriction_Postfix() => foodRestriction = null; - - [MpPrefix(typeof(Dialog_ManageDrugPolicies), nameof(Dialog_ManageDrugPolicies.DoWindowContents))] - static void ManageDrugPolicy_Prefix(Dialog_ManageDrugPolicies __instance) => drugPolicy = __instance.SelectedPolicy; - - [MpPostfix(typeof(Dialog_ManageDrugPolicies), nameof(Dialog_ManageDrugPolicies.DoWindowContents))] - static void ManageDrugPolicy_Postfix(Dialog_ManageDrugPolicies __instance) => drugPolicy = null; } diff --git a/Source/Client/Syncing/Game/SyncMethods.cs b/Source/Client/Syncing/Game/SyncMethods.cs index c92d3224..0c60557a 100644 --- a/Source/Client/Syncing/Game/SyncMethods.cs +++ b/Source/Client/Syncing/Game/SyncMethods.cs @@ -23,9 +23,9 @@ public static void Init() SyncMethod.Register(typeof(Pawn_DraftController), nameof(Pawn_DraftController.Drafted)); SyncMethod.Register(typeof(Pawn_DraftController), nameof(Pawn_DraftController.FireAtWill)); SyncMethod.Register(typeof(Pawn_DrugPolicyTracker), nameof(Pawn_DrugPolicyTracker.CurrentPolicy)).CancelIfAnyArgNull(); - SyncMethod.Register(typeof(Pawn_OutfitTracker), nameof(Pawn_OutfitTracker.CurrentOutfit)).CancelIfAnyArgNull(); - SyncMethod.Register(typeof(Pawn_FoodRestrictionTracker), nameof(Pawn_FoodRestrictionTracker.CurrentFoodRestriction)).CancelIfAnyArgNull(); - SyncMethod.Register(typeof(Pawn_PlayerSettings), nameof(Pawn_PlayerSettings.AreaRestriction)); + SyncMethod.Register(typeof(Pawn_OutfitTracker), nameof(Pawn_OutfitTracker.CurrentApparelPolicy)).CancelIfAnyArgNull(); + SyncMethod.Register(typeof(Pawn_FoodRestrictionTracker), nameof(Pawn_FoodRestrictionTracker.CurrentFoodPolicy)).CancelIfAnyArgNull(); + SyncMethod.Register(typeof(Pawn_PlayerSettings), nameof(Pawn_PlayerSettings.AreaRestrictionInPawnCurrentMap)); SyncMethod.Register(typeof(Pawn_PlayerSettings), nameof(Pawn_PlayerSettings.Master)); SyncMethod.Register(typeof(Pawn), nameof(Pawn.Name)).ExposeParameter(0) .SetPostInvoke((pawn, _) => ((Pawn)pawn).babyNamingDeadline = -1); // If a newborn was named then mark it as no longer needing to be named @@ -42,11 +42,12 @@ public static void Init() SyncMethod.Register(typeof(BillStack), nameof(BillStack.Delete)).CancelIfAnyArgNull(); SyncMethod.Register(typeof(BillStack), nameof(BillStack.Reorder)).CancelIfAnyArgNull(); SyncMethod.Register(typeof(Bill_Production), nameof(Bill_Production.SetStoreMode)); + SyncMethod.Register(typeof(Bill_Production), nameof(Bill_Production.SetIncludeGroup)); SyncMethod.Register(typeof(Building_TurretGun), nameof(Building_TurretGun.OrderAttack)); SyncMethod.Register(typeof(Building_TurretGun), nameof(Building_TurretGun.ExtractShell)); SyncMethod.Register(typeof(Area), nameof(Area.Invert)); SyncMethod.Register(typeof(Area), nameof(Area.Delete)); - SyncMethod.Register(typeof(Area_Allowed), nameof(Area_Allowed.SetLabel)); + SyncMethod.Register(typeof(Area_Allowed), nameof(Area_Allowed.RenamableLabel)); SyncMethod.Register(typeof(AreaManager), nameof(AreaManager.TryMakeNewAllowed)); SyncMethod.Register(typeof(MainTabWindow_Research), nameof(MainTabWindow_Research.DoBeginResearch)) .TransformTarget(Serializer.SimpleReader(() => new MainTabWindow_Research())); @@ -129,7 +130,7 @@ public static void Init() { var methods = typeof(ITargetingSource).AllImplementing() - .Except(typeof(CompActivable_RocketswarmLauncher)) // Skip it, as all it does is open another targeter + .Except(typeof(CompInteractableRocketswarmLauncher)) // Skip it, as all it does is open another targeter .Where(t => t.Assembly == typeof(Game).Assembly) .Select(t => t.GetMethod(nameof(ITargetingSource.OrderForceTarget), AccessTools.allDeclared)) .AllNotNull(); @@ -150,16 +151,11 @@ public static void Init() SyncMethod.Register(typeof(MonumentMarker), nameof(MonumentMarker.PlaceAllBlueprints)); SyncMethod.Register(typeof(MonumentMarker), nameof(MonumentMarker.PlaceBlueprintsSimilarTo)).ExposeParameter(0); - // 1 - SyncMethod.Register(typeof(TradeRequestComp), nameof(TradeRequestComp.Fulfill)).CancelIfAnyArgNull().SetVersion(1); - - // 2 - SyncMethod.Register(typeof(CompLaunchable), nameof(CompLaunchable.TryLaunch)).ExposeParameter(1).SetVersion(2); - SyncMethod.Register(typeof(OutfitForcedHandler), nameof(OutfitForcedHandler.Reset)).SetVersion(2); - SyncMethod.Register(typeof(Pawn_StoryTracker), nameof(Pawn_StoryTracker.Title)).SetVersion(2); - - // 3 - SyncMethod.Register(typeof(ShipUtility), nameof(ShipUtility.StartupHibernatingParts)).CancelIfAnyArgNull().SetVersion(3); + SyncMethod.Register(typeof(TradeRequestComp), nameof(TradeRequestComp.Fulfill)).CancelIfAnyArgNull(); + SyncMethod.Register(typeof(CompLaunchable), nameof(CompLaunchable.TryLaunch)).ExposeParameter(1); + SyncMethod.Register(typeof(OutfitForcedHandler), nameof(OutfitForcedHandler.Reset)); + SyncMethod.Register(typeof(Pawn_StoryTracker), nameof(Pawn_StoryTracker.Title)); + SyncMethod.Register(typeof(ShipUtility), nameof(ShipUtility.StartupHibernatingParts)).CancelIfAnyArgNull(); // Dialog_NodeTree Sync.RegisterSyncDialogNodeTree(typeof(IncidentWorker_CaravanMeeting), nameof(IncidentWorker_CaravanMeeting.TryExecuteWorker)); @@ -222,7 +218,8 @@ public static void Init() SyncMethod.Register(typeof(CompPollutionPump), nameof(CompPollutionPump.Pump)).SetDebugOnly(); SyncMethod.Lambda(typeof(CompProjectileInterceptor), nameof(CompProjectileInterceptor.CompGetGizmosExtra), 0).SetDebugOnly(); // Reset cooldown SyncMethod.Lambda(typeof(CompProjectileInterceptor), nameof(CompProjectileInterceptor.CompGetGizmosExtra), 2).SetDebugOnly(); // Toggle intercept non-hostile - SyncMethod.Lambda(typeof(CompReloadable), nameof(CompReloadable.CompGetWornGizmosExtra), 0).SetDebugOnly(); // Reload to full + SyncMethod.Lambda(typeof(CompApparelVerbOwner_Charged), nameof(CompApparelVerbOwner_Charged.CompGetWornGizmosExtra), 0).SetDebugOnly(); // Reload to full + SyncMethod.Lambda(typeof(CompEquippableAbilityReloadable), nameof(CompEquippableAbilityReloadable.CompGetEquippedGizmosExtra), 0).SetDebugOnly(); // Reload to full SyncMethod.Lambda(typeof(CompScanner), nameof(CompScanner.CompGetGizmosExtra), 0).SetDebugOnly(); // Find now SyncMethod.Lambda(typeof(CompTerrainPump), nameof(CompTerrainPump.CompGetGizmosExtra), 0).SetDebugOnly(); // Progress 1 day SyncMethod.Register(typeof(CompToxifier), nameof(CompToxifier.PolluteNextCell)).SetDebugOnly(); @@ -235,6 +232,7 @@ public static void Init() SyncMethod.Lambda(typeof(Pawn), nameof(Pawn.GetGizmos), 3).SetDebugOnly(); // Psychic entropy +20% SyncMethod.Lambda(typeof(Pawn), nameof(Pawn.GetGizmos), 6).SetDebugOnly(); // Reset faction permit cooldowns SyncMethod.Lambda(typeof(Pawn), nameof(Pawn.GetGizmos), 7).SetDebugOnly(); // Reset try romance cooldown + SyncMethod.Register(typeof(CompCanBeDormant), nameof(CompCanBeDormant.WakeUp)).SetDebugOnly(); SyncMethod.Register(typeof(Blueprint_Build), nameof(Blueprint_Build.ChangeStyleOfAllSelected)).SetContext(SyncContext.MapSelected); SyncMethod.Lambda(typeof(CompTurretGun), nameof(CompTurretGun.CompGetGizmosExtra), 1); // Toggle fire at will @@ -275,8 +273,6 @@ public static void Init() SyncMethod.Lambda(typeof(Building_MechCharger), nameof(Building_MechCharger.GetGizmos), 4).SetDebugOnly(); // Charge 100% // Gestator SyncMethod.Lambda(typeof(Building_MechGestator), nameof(Building_MechGestator.GetGizmos), 0).SetDebugOnly(); // Generate 5 waste - SyncMethod.Lambda(typeof(Building_MechGestator), nameof(Building_MechGestator.GetGizmos), 1).SetDebugOnly(); // Forming cycle +25% - SyncMethod.Lambda(typeof(Building_MechGestator), nameof(Building_MechGestator.GetGizmos), 2).SetDebugOnly(); // Complete cycle SyncMethod.Register(typeof(Bill_Mech), nameof(Bill_Mech.ForceCompleteAllCycles)).SetDebugOnly(); // Called from Building_MechGestator.GetGizmos // Carrier SyncMethod.Register(typeof(CompMechCarrier), nameof(CompMechCarrier.TrySpawnPawns)); @@ -362,19 +358,19 @@ static void MakeNewDrugPolicy_Postfix(DrugPolicy __result) } [MpPostfix(typeof(OutfitDatabase), nameof(OutfitDatabase.MakeNewOutfit))] - static void MakeNewOutfit_Postfix(Outfit __result) + static void MakeNewOutfit_Postfix(ApparelPolicy __result) { var dialog = GetDialogOutfits(); if (__result != null && dialog != null && TickPatch.currentExecutingCmdIssuedBySelf) - dialog.SelectedOutfit = __result; + dialog.SelectedPolicy = __result; } [MpPostfix(typeof(FoodRestrictionDatabase), nameof(FoodRestrictionDatabase.MakeNewFoodRestriction))] - static void MakeNewFood_Postfix(FoodRestriction __result) + static void MakeNewFood_Postfix(FoodPolicy __result) { var dialog = GetDialogFood(); if (__result != null && dialog != null && TickPatch.currentExecutingCmdIssuedBySelf) - dialog.SelectedFoodRestriction = __result; + dialog.SelectedPolicy = __result; } [MpPostfix(typeof(DrugPolicyDatabase), nameof(DrugPolicyDatabase.TryDelete))] @@ -386,24 +382,24 @@ static void TryDeleteDrugPolicy_Postfix(DrugPolicy policy, AcceptanceReport __re } [MpPostfix(typeof(OutfitDatabase), nameof(OutfitDatabase.TryDelete))] - static void TryDeleteOutfit_Postfix(Outfit outfit, AcceptanceReport __result) + static void TryDeleteOutfit_Postfix(ApparelPolicy apparelPolicy, AcceptanceReport __result) { var dialog = GetDialogOutfits(); - if (__result.Accepted && dialog != null && dialog.SelectedOutfit == outfit) - dialog.SelectedOutfit = null; + if (__result.Accepted && dialog != null && dialog.SelectedPolicy == apparelPolicy) + dialog.SelectedPolicy = null; } [MpPostfix(typeof(FoodRestrictionDatabase), nameof(FoodRestrictionDatabase.TryDelete))] - static void TryDeleteFood_Postfix(FoodRestriction foodRestriction, AcceptanceReport __result) + static void TryDeleteFood_Postfix(FoodPolicy foodPolicy, AcceptanceReport __result) { var dialog = GetDialogFood(); - if (__result.Accepted && dialog != null && dialog.SelectedFoodRestriction == foodRestriction) - dialog.SelectedFoodRestriction = null; + if (__result.Accepted && dialog != null && dialog.SelectedPolicy == foodPolicy) + dialog.SelectedPolicy = null; } static Dialog_ManageDrugPolicies GetDialogDrugPolicies() => Find.WindowStack?.WindowOfType(); - static Dialog_ManageOutfits GetDialogOutfits() => Find.WindowStack?.WindowOfType(); - static Dialog_ManageFoodRestrictions GetDialogFood() => Find.WindowStack?.WindowOfType(); + static Dialog_ManageApparelPolicies GetDialogOutfits() => Find.WindowStack?.WindowOfType(); + static Dialog_ManageFoodPolicies GetDialogFood() => Find.WindowStack?.WindowOfType(); [MpPostfix(typeof(WITab_Caravan_Gear), nameof(WITab_Caravan_Gear.TryEquipDraggedItem))] static void TryEquipDraggedItem_Postfix(WITab_Caravan_Gear __instance) @@ -546,7 +542,7 @@ static void SyncDesiredGenepackState(Genepack genepack, CompGenepackContainer co } } - [MpPrefix(typeof(Targeter), nameof(Targeter.BeginTargeting), new []{ typeof(ITargetingSource), typeof(ITargetingSource), typeof(bool), typeof(Func), typeof(Action) })] + [MpPrefix(typeof(Targeter), nameof(Targeter.BeginTargeting), new []{ typeof(ITargetingSource), typeof(ITargetingSource), typeof(bool), typeof(Func), typeof(Action), typeof(bool) })] static bool BeginTargeting(ITargetingSource source) { if (Multiplayer.Client == null || source.Targetable) diff --git a/Source/Client/Syncing/Game/ThingFilterContexts.cs b/Source/Client/Syncing/Game/ThingFilterContexts.cs index 6e903f0b..f40ab2d8 100644 --- a/Source/Client/Syncing/Game/ThingFilterContexts.cs +++ b/Source/Client/Syncing/Game/ThingFilterContexts.cs @@ -19,30 +19,30 @@ public record BillConfigWrapper(Bill Bill) : ThingFilterContext public override IEnumerable HiddenFilters => Bill.recipe.forceHiddenSpecialFilters; } -public record OutfitWrapper(Outfit Outfit) : ThingFilterContext +public record OutfitWrapper(ApparelPolicy Policy) : ThingFilterContext { - public override ThingFilter Filter => Outfit.filter; - public override ThingFilter ParentFilter => Dialog_ManageOutfits.apparelGlobalFilter; + public override ThingFilter Filter => Policy.filter; + public override ThingFilter ParentFilter => Dialog_ManageApparelPolicies.apparelGlobalFilter; public override IEnumerable HiddenFilters => SpecialThingFilterDefOf.AllowNonDeadmansApparel.ToEnumerable(); } -public record FoodRestrictionWrapper(FoodRestriction Food) : ThingFilterContext +public record FoodRestrictionWrapper(FoodPolicy Policy) : ThingFilterContext { - public override ThingFilter Filter => Food.filter; - public override ThingFilter ParentFilter => Dialog_ManageFoodRestrictions.foodGlobalFilter; + public override ThingFilter Filter => Policy.filter; + public override ThingFilter ParentFilter => Dialog_ManageFoodPolicies.foodGlobalFilter; public override IEnumerable HiddenFilters => SpecialThingFilterDefOf.AllowFresh.ToEnumerable(); } -public record PenAnimalsWrapper(CompAnimalPenMarker Pen) : ThingFilterContext +public record PenAnimalsWrapper(CompAnimalPenMarker PenMarker) : ThingFilterContext { - public override ThingFilter Filter => Pen.AnimalFilter; + public override ThingFilter Filter => PenMarker.AnimalFilter; public override ThingFilter ParentFilter => AnimalPenUtility.GetFixedAnimalFilter(); } -public record PenAutocutWrapper(CompAnimalPenMarker Pen) : ThingFilterContext +public record PenAutocutWrapper(CompAnimalPenMarker PenMarker) : ThingFilterContext { - public override ThingFilter Filter => Pen.AutoCutFilter; - public override ThingFilter ParentFilter => Pen.parent.Map.animalPenManager.GetFixedAutoCutFilter(); + public override ThingFilter Filter => PenMarker.AutoCutFilter; + public override ThingFilter ParentFilter => PenMarker.parent.Map.animalPenManager.GetFixedAutoCutFilter(); public override IEnumerable HiddenFilters => SpecialThingFilterDefOf.AllowFresh.ToEnumerable(); } diff --git a/Source/Client/Syncing/Game/ThingFilterMarkers.cs b/Source/Client/Syncing/Game/ThingFilterMarkers.cs index 86b12bc4..5afef117 100644 --- a/Source/Client/Syncing/Game/ThingFilterMarkers.cs +++ b/Source/Client/Syncing/Game/ThingFilterMarkers.cs @@ -48,18 +48,18 @@ static void BillConfig_Prefix(Dialog_BillConfig __instance) => [MpPostfix(typeof(Dialog_BillConfig), "DoWindowContents")] static void BillConfig_Postfix() => DrawnThingFilter = null; - [MpPrefix(typeof(Dialog_ManageOutfits), "DoWindowContents")] - static void ManageOutfit_Prefix(Dialog_ManageOutfits __instance) => - DrawnThingFilter = new OutfitWrapper(__instance.SelectedOutfit); + [MpPrefix(typeof(Dialog_ManageApparelPolicies), "DoContentsRect")] + static void ManageOutfit_Prefix(Dialog_ManageApparelPolicies __instance) => + DrawnThingFilter = new OutfitWrapper(__instance.SelectedPolicy); - [MpPostfix(typeof(Dialog_ManageOutfits), "DoWindowContents")] + [MpPostfix(typeof(Dialog_ManageApparelPolicies), "DoContentsRect")] static void ManageOutfit_Postfix() => DrawnThingFilter = null; - [MpPrefix(typeof(Dialog_ManageFoodRestrictions), "DoWindowContents")] - static void ManageFoodRestriction_Prefix(Dialog_ManageFoodRestrictions __instance) => - DrawnThingFilter = new FoodRestrictionWrapper(__instance.SelectedFoodRestriction); + [MpPrefix(typeof(Dialog_ManageFoodPolicies), "DoContentsRect")] + static void ManageFoodRestriction_Prefix(Dialog_ManageFoodPolicies __instance) => + DrawnThingFilter = new FoodRestrictionWrapper(__instance.SelectedPolicy); - [MpPostfix(typeof(Dialog_ManageFoodRestrictions), "DoWindowContents")] + [MpPostfix(typeof(Dialog_ManageFoodPolicies), "DoContentsRect")] static void ManageFoodRestriction_Postfix() => DrawnThingFilter = null; [MpPrefix(typeof(ITab_PenAutoCut), "FillTab")] diff --git a/Source/Client/Windows/TextAreaWindow.cs b/Source/Client/Windows/TextAreaWindow.cs deleted file mode 100644 index 79689d63..00000000 --- a/Source/Client/Windows/TextAreaWindow.cs +++ /dev/null @@ -1,23 +0,0 @@ -using UnityEngine; -using Verse; - -namespace Multiplayer.Client; - -public class TextAreaWindow : Window -{ - private string text; - private Vector2 scroll; - - public TextAreaWindow(string text) - { - this.text = text; - - absorbInputAroundWindow = true; - doCloseX = true; - } - - public override void DoWindowContents(Rect inRect) - { - Widgets.TextAreaScrollable(inRect, text, ref scroll); - } -} diff --git a/Source/Client/Windows/TwoTextAreasWindow.cs b/Source/Client/Windows/TwoTextAreasWindow.cs index e430e774..cc0bc3b1 100644 --- a/Source/Client/Windows/TwoTextAreasWindow.cs +++ b/Source/Client/Windows/TwoTextAreasWindow.cs @@ -24,8 +24,17 @@ public TwoTextAreasWindow(string left, string right) public override void DoWindowContents(Rect inRect) { - Widgets.TextAreaScrollable(inRect.LeftHalf(), left, ref scroll1); - Widgets.TextAreaScrollable(inRect.RightHalf(), right, ref scroll2); + TextAreaScrollable(inRect.LeftHalf(), left, ref scroll1); + TextAreaScrollable(inRect.RightHalf(), right, ref scroll2); + } + + private static string TextAreaScrollable(Rect rect, string text, ref Vector2 scrollbarPosition, bool readOnly = false) + { + Rect rect2 = new Rect(0f, 0f, rect.width - 16f, Mathf.Max(Text.CalcHeight(text, rect.width) + 10f, rect.height)); + Widgets.BeginScrollView(rect, ref scrollbarPosition, rect2); + string result = Widgets.TextArea(rect2, text, readOnly); + Widgets.EndScrollView(); + return result; } } diff --git a/Source/Common/Version.cs b/Source/Common/Version.cs index e82ec041..f0c8a9a5 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.9.8"; - public const int Protocol = 41; + public const string Version = "0.9.9"; + public const int Protocol = 42; public const string ApiAssemblyName = "0MultiplayerAPI"; From d08cdbc1a161e69ce91e5130b778d781d5f8c252 Mon Sep 17 00:00:00 2001 From: Zetrith Date: Fri, 5 Apr 2024 09:07:27 +0200 Subject: [PATCH 2/7] Sync serialization refactor (#431) - Move Sync serialization to Common - Move MpReflection to Common - Add simple serialization tests - Make Thing sync worker implicit - Update language version to 12 --- Source/Client/Debug/DebugActions.cs | 15 +- Source/Client/EarlyInit.cs | 21 +- Source/Client/Multiplayer.csproj | 2 +- Source/Client/MultiplayerData.cs | 20 +- Source/Client/Persistent/RitualData.cs | 4 +- Source/Client/Persistent/SessionManager.cs | 8 +- ...plSerialization.cs => ApiSerialization.cs} | 2 +- Source/Client/Syncing/CompSerialization.cs | 31 ++ Source/Client/Syncing/Dict/SyncDict.cs | 25 +- Source/Client/Syncing/Dict/SyncDictDlc.cs | 2 +- Source/Client/Syncing/Dict/SyncDictFast.cs | 87 ---- Source/Client/Syncing/Dict/SyncDictMisc.cs | 42 ++ .../Client/Syncing/Dict/SyncDictRimWorld.cs | 10 +- .../Syncing/{Game => }/RwImplSerialization.cs | 19 +- Source/Client/Syncing/RwSerialization.cs | 225 +++++++++ Source/Client/Syncing/RwTypeHelper.cs | 62 +++ Source/Client/Syncing/Sync.cs | 36 +- Source/Client/Syncing/Worker/SyncWorkers.cs | 473 ------------------ Source/Client/Util/CollectionExtensions.cs | 19 - Source/Common/Common.csproj | 3 +- .../Syncing/Logger/IHasLogger.cs | 0 .../Syncing/Logger/LogNode.cs | 4 +- .../Syncing/Logger/LoggingByteReader.cs | 11 +- .../Syncing/Logger/LoggingByteWriter.cs | 0 .../Syncing/Logger/SyncLogger.cs | 31 +- .../Syncing/SerializationException.cs | 0 Source/Common/Syncing/SyncDictPrimitives.cs | 23 + .../Syncing/SyncSerialization.cs | 311 ++++-------- .../Syncing/Worker/ReadingSyncWorker.cs | 10 +- .../Syncing/Worker/SyncWorkerDictionary.cs | 85 ++++ .../Worker/SyncWorkerDictionaryTree.cs | 138 +++++ .../Common/Syncing/Worker/SyncWorkerEntry.cs | 182 +++++++ .../Syncing/Worker/SyncWorkerTypeHelper.cs | 9 + .../Syncing/Worker/WritingSyncWorker.cs | 4 +- Source/Common/Util/CollectionExtensions.cs | 14 + Source/Common/Util/CompilerTypes.cs | 11 + .../{Client => Common}/Util/MpReflection.cs | 110 ++-- .../MultiplayerLoader.csproj | 2 +- Source/Tests/Helper/TestJoiningState.cs | 1 + Source/Tests/SerializationTest.cs | 94 ++++ Source/Tests/Tests.csproj | 2 +- 41 files changed, 1158 insertions(+), 990 deletions(-) rename Source/Client/Syncing/{ImplSerialization.cs => ApiSerialization.cs} (90%) create mode 100644 Source/Client/Syncing/CompSerialization.cs delete mode 100644 Source/Client/Syncing/Dict/SyncDictFast.cs rename Source/Client/Syncing/{Game => }/RwImplSerialization.cs (72%) create mode 100644 Source/Client/Syncing/RwSerialization.cs create mode 100644 Source/Client/Syncing/RwTypeHelper.cs delete mode 100644 Source/Client/Syncing/Worker/SyncWorkers.cs rename Source/{Client => Common}/Syncing/Logger/IHasLogger.cs (100%) rename Source/{Client => Common}/Syncing/Logger/LogNode.cs (76%) rename Source/{Client => Common}/Syncing/Logger/LoggingByteReader.cs (86%) rename Source/{Client => Common}/Syncing/Logger/LoggingByteWriter.cs (100%) rename Source/{Client => Common}/Syncing/Logger/SyncLogger.cs (60%) rename Source/{Client => Common}/Syncing/SerializationException.cs (100%) create mode 100644 Source/Common/Syncing/SyncDictPrimitives.cs rename Source/{Client => Common}/Syncing/SyncSerialization.cs (57%) rename Source/{Client => Common}/Syncing/Worker/ReadingSyncWorker.cs (90%) create mode 100644 Source/Common/Syncing/Worker/SyncWorkerDictionary.cs create mode 100644 Source/Common/Syncing/Worker/SyncWorkerDictionaryTree.cs create mode 100644 Source/Common/Syncing/Worker/SyncWorkerEntry.cs create mode 100644 Source/Common/Syncing/Worker/SyncWorkerTypeHelper.cs rename Source/{Client => Common}/Syncing/Worker/WritingSyncWorker.cs (94%) create mode 100644 Source/Common/Util/CollectionExtensions.cs rename Source/{Client => Common}/Util/MpReflection.cs (78%) create mode 100644 Source/Tests/SerializationTest.cs diff --git a/Source/Client/Debug/DebugActions.cs b/Source/Client/Debug/DebugActions.cs index d363131f..e4be8ae0 100644 --- a/Source/Client/Debug/DebugActions.cs +++ b/Source/Client/Debug/DebugActions.cs @@ -135,17 +135,18 @@ public static void SaveGame() public static void DumpSyncTypes() { var dict = new Dictionary() { - {"ThingComp", RwImplSerialization.thingCompTypes}, - {"AbilityComp", RwImplSerialization.abilityCompTypes}, {"Designator", RwImplSerialization.designatorTypes}, - {"WorldObjectComp", RwImplSerialization.worldObjectCompTypes}, - {"HediffComp", RwImplSerialization.hediffCompTypes}, {"IStoreSettingsParent", RwImplSerialization.storageParents}, {"IPlantToGrowSettable", RwImplSerialization.plantToGrowSettables}, - {"GameComponent", RwImplSerialization.gameCompTypes}, - {"WorldComponent", RwImplSerialization.worldCompTypes}, - {"MapComponent", RwImplSerialization.mapCompTypes}, + {"ThingComp", CompSerialization.thingCompTypes}, + {"AbilityComp", CompSerialization.abilityCompTypes}, + {"WorldObjectComp", CompSerialization.worldObjectCompTypes}, + {"HediffComp", CompSerialization.hediffCompTypes}, + + {"GameComponent", CompSerialization.gameCompTypes}, + {"WorldComponent", CompSerialization.worldCompTypes}, + {"MapComponent", CompSerialization.mapCompTypes}, }; foreach(var kv in dict) { diff --git a/Source/Client/EarlyInit.cs b/Source/Client/EarlyInit.cs index 4dddeb97..0c23bcd1 100644 --- a/Source/Client/EarlyInit.cs +++ b/Source/Client/EarlyInit.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Reflection; using HarmonyLib; using Multiplayer.Client.Patches; @@ -55,10 +56,12 @@ internal static void EarlyPatches(Harmony harmony) internal static void InitSync() { - using (DeepProfilerWrapper.Section("Multiplayer SyncSerialization.Init")) - SyncSerialization.Init(); + MpReflection.allAssembliesHook = RwAllAssemblies; - using (DeepProfilerWrapper.Section("Multiplayer SyncGame")) + using (DeepProfilerWrapper.Section("Multiplayer RwSerialization.Init")) + RwSerialization.Init(); + + using (DeepProfilerWrapper.Section("Multiplayer SyncGame.Init")) SyncGame.Init(); using (DeepProfilerWrapper.Section("Multiplayer Sync register attributes")) @@ -68,6 +71,18 @@ internal static void InitSync() Sync.ValidateAll(); } + private static IEnumerable RwAllAssemblies() + { + yield return Assembly.GetAssembly(typeof(Game)); + + foreach (ModContentPack mod in LoadedModManager.RunningMods) + foreach (Assembly assembly in mod.assemblies.loadedAssemblies) + yield return assembly; + + if (Assembly.GetEntryAssembly() != null) + yield return Assembly.GetEntryAssembly(); + } + internal static void LatePatches() { if (MpVersion.IsDebug) diff --git a/Source/Client/Multiplayer.csproj b/Source/Client/Multiplayer.csproj index 02410ac3..152cfba3 100644 --- a/Source/Client/Multiplayer.csproj +++ b/Source/Client/Multiplayer.csproj @@ -3,7 +3,7 @@ net472 true - 11 + 12 false bin false diff --git a/Source/Client/MultiplayerData.cs b/Source/Client/MultiplayerData.cs index 4ac9cd54..6c9add4a 100644 --- a/Source/Client/MultiplayerData.cs +++ b/Source/Client/MultiplayerData.cs @@ -120,20 +120,20 @@ internal static void CollectDefInfos() int TypeHash(Type type) => GenText.StableStringHash(type.FullName); - dict["ThingComp"] = GetDefInfo(RwImplSerialization.thingCompTypes, TypeHash); - dict["AbilityComp"] = GetDefInfo(RwImplSerialization.abilityCompTypes, TypeHash); - dict["Designator"] = GetDefInfo(RwImplSerialization.designatorTypes, TypeHash); - dict["WorldObjectComp"] = GetDefInfo(RwImplSerialization.worldObjectCompTypes, TypeHash); - dict["HediffComp"] = GetDefInfo(RwImplSerialization.hediffCompTypes, TypeHash); + dict["ThingComp"] = GetDefInfo(CompSerialization.thingCompTypes, TypeHash); + dict["AbilityComp"] = GetDefInfo(CompSerialization.abilityCompTypes, TypeHash); + dict["WorldObjectComp"] = GetDefInfo(CompSerialization.worldObjectCompTypes, TypeHash); + dict["HediffComp"] = GetDefInfo(CompSerialization.hediffCompTypes, TypeHash); dict["IStoreSettingsParent"] = GetDefInfo(RwImplSerialization.storageParents, TypeHash); dict["IPlantToGrowSettable"] = GetDefInfo(RwImplSerialization.plantToGrowSettables, TypeHash); + dict["Designator"] = GetDefInfo(RwImplSerialization.designatorTypes, TypeHash); dict["DefTypes"] = GetDefInfo(DefSerialization.DefTypes, TypeHash); - dict["GameComponent"] = GetDefInfo(RwImplSerialization.gameCompTypes, TypeHash); - dict["WorldComponent"] = GetDefInfo(RwImplSerialization.worldCompTypes, TypeHash); - dict["MapComponent"] = GetDefInfo(RwImplSerialization.mapCompTypes, TypeHash); - dict["ISyncSimple"] = GetDefInfo(ImplSerialization.syncSimples, TypeHash); - dict["ISession"] = GetDefInfo(ImplSerialization.sessions, TypeHash); + dict["GameComponent"] = GetDefInfo(CompSerialization.gameCompTypes, TypeHash); + dict["WorldComponent"] = GetDefInfo(CompSerialization.worldCompTypes, TypeHash); + dict["MapComponent"] = GetDefInfo(CompSerialization.mapCompTypes, TypeHash); + dict["ISyncSimple"] = GetDefInfo(ApiSerialization.syncSimples, TypeHash); + dict["ISession"] = GetDefInfo(ApiSerialization.sessions, TypeHash); dict["PawnBio"] = GetDefInfo(SolidBioDatabase.allBios, b => b.name.GetHashCode()); diff --git a/Source/Client/Persistent/RitualData.cs b/Source/Client/Persistent/RitualData.cs index a982b387..2827e942 100644 --- a/Source/Client/Persistent/RitualData.cs +++ b/Source/Client/Persistent/RitualData.cs @@ -28,9 +28,9 @@ public void Sync(SyncWorker sync) sync.Bind(ref extraInfos); if (sync is WritingSyncWorker writer1) - DelegateSerialization.WriteDelegate(writer1.writer, action); + DelegateSerialization.WriteDelegate(writer1.Writer, action); else if (sync is ReadingSyncWorker reader) - action = (ActionCallback)DelegateSerialization.ReadDelegate(reader.reader); + action = (ActionCallback)DelegateSerialization.ReadDelegate(reader.Reader); sync.Bind(ref ritualLabel); sync.Bind(ref confirmText); diff --git a/Source/Client/Persistent/SessionManager.cs b/Source/Client/Persistent/SessionManager.cs index bf0d1100..04485599 100644 --- a/Source/Client/Persistent/SessionManager.cs +++ b/Source/Client/Persistent/SessionManager.cs @@ -156,7 +156,7 @@ public void WriteSessionData(ByteWriter data) foreach (var session in semiPersistentSessions) { - data.WriteUShort((ushort)ImplSerialization.sessions.FindIndex(session.GetType())); + data.WriteUShort((ushort)ApiSerialization.sessions.FindIndex(session.GetType())); data.WriteInt32(session.SessionId); try @@ -181,13 +181,13 @@ public void ReadSessionData(ByteReader data) ushort typeIndex = data.ReadUShort(); int sessionId = data.ReadInt32(); - if (typeIndex >= ImplSerialization.sessions.Length) + if (typeIndex >= ApiSerialization.sessions.Length) { - Log.Error($"Received data for ISession type with index out of range: {typeIndex}, session types count: {ImplSerialization.sessions.Length}"); + Log.Error($"Received data for ISession type with index out of range: {typeIndex}, session types count: {ApiSerialization.sessions.Length}"); continue; } - var objType = ImplSerialization.sessions[typeIndex]; + var objType = ApiSerialization.sessions[typeIndex]; try { diff --git a/Source/Client/Syncing/ImplSerialization.cs b/Source/Client/Syncing/ApiSerialization.cs similarity index 90% rename from Source/Client/Syncing/ImplSerialization.cs rename to Source/Client/Syncing/ApiSerialization.cs index 2109eb54..5de092eb 100644 --- a/Source/Client/Syncing/ImplSerialization.cs +++ b/Source/Client/Syncing/ApiSerialization.cs @@ -4,7 +4,7 @@ namespace Multiplayer.Client; -public static class ImplSerialization +public static class ApiSerialization { public static Type[] syncSimples; public static Type[] sessions; diff --git a/Source/Client/Syncing/CompSerialization.cs b/Source/Client/Syncing/CompSerialization.cs new file mode 100644 index 00000000..8e6bac68 --- /dev/null +++ b/Source/Client/Syncing/CompSerialization.cs @@ -0,0 +1,31 @@ +using System; +using Multiplayer.Client.Util; +using RimWorld; +using RimWorld.Planet; +using Verse; + +namespace Multiplayer.Client; + +public static class CompSerialization +{ + public static Type[] gameCompTypes; + public static Type[] worldCompTypes; + public static Type[] mapCompTypes; + + public static Type[] thingCompTypes; + public static Type[] hediffCompTypes; + public static Type[] abilityCompTypes; + public static Type[] worldObjectCompTypes; + + public static void Init() + { + thingCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(ThingComp)); + hediffCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(HediffComp)); + abilityCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(AbilityComp)); + worldObjectCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(WorldObjectComp)); + + gameCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(GameComponent)); + worldCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(WorldComponent)); + mapCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(MapComponent)); + } +} diff --git a/Source/Client/Syncing/Dict/SyncDict.cs b/Source/Client/Syncing/Dict/SyncDict.cs index 1baebe07..0f235fba 100644 --- a/Source/Client/Syncing/Dict/SyncDict.cs +++ b/Source/Client/Syncing/Dict/SyncDict.cs @@ -1,17 +1,18 @@ -namespace Multiplayer.Client +namespace Multiplayer.Client; + +public static class SyncDict { - public static class SyncDict + internal static SyncWorkerDictionaryTree syncWorkers; + + public static void Init() { - internal static SyncWorkerDictionaryTree syncWorkers; + syncWorkers = SyncWorkerDictionaryTree.Merge( + SyncDictMisc.syncWorkers, + SyncDictRimWorld.syncWorkers, + SyncDictDlc.syncWorkers, + SyncDictMultiplayer.syncWorkers + ); - public static void Init() - { - syncWorkers = SyncWorkerDictionaryTree.Merge( - SyncDictMisc.syncWorkers, - SyncDictRimWorld.syncWorkers, - SyncDictDlc.syncWorkers, - SyncDictMultiplayer.syncWorkers - ); - } + SyncSerialization.syncTree = syncWorkers; } } diff --git a/Source/Client/Syncing/Dict/SyncDictDlc.cs b/Source/Client/Syncing/Dict/SyncDictDlc.cs index bc58e3f4..d16c8d91 100644 --- a/Source/Client/Syncing/Dict/SyncDictDlc.cs +++ b/Source/Client/Syncing/Dict/SyncDictDlc.cs @@ -13,7 +13,7 @@ namespace Multiplayer.Client { public static class SyncDictDlc { - internal static SyncWorkerDictionaryTree syncWorkers = new SyncWorkerDictionaryTree() + internal static SyncWorkerDictionaryTree syncWorkers = new() { #region Royalty { diff --git a/Source/Client/Syncing/Dict/SyncDictFast.cs b/Source/Client/Syncing/Dict/SyncDictFast.cs deleted file mode 100644 index 8c3b2abf..00000000 --- a/Source/Client/Syncing/Dict/SyncDictFast.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Multiplayer.API; -using Multiplayer.Common; -using UnityEngine; -using Verse; - -namespace Multiplayer.Client -{ - public static class SyncDictFast - { - internal static SyncWorkerDictionary syncWorkers = new SyncWorkerDictionary() - { - // missing decimal and char, good? - #region Built-in - - { (SyncWorker sync, ref bool b ) => sync.Bind(ref b) }, - { (SyncWorker sync, ref byte b ) => sync.Bind(ref b) }, - { (SyncWorker sync, ref sbyte b ) => sync.Bind(ref b) }, - { (SyncWorker sync, ref double d ) => sync.Bind(ref d) }, - { (SyncWorker sync, ref float f ) => sync.Bind(ref f) }, - { (SyncWorker sync, ref int i ) => sync.Bind(ref i) }, - { (SyncWorker sync, ref uint i ) => sync.Bind(ref i) }, - { (SyncWorker sync, ref long l ) => sync.Bind(ref l) }, - { (SyncWorker sync, ref ulong l ) => sync.Bind(ref l) }, - { (SyncWorker sync, ref short s ) => sync.Bind(ref s) }, - { (SyncWorker sync, ref ushort s ) => sync.Bind(ref s) }, - { (SyncWorker sync, ref string t ) => sync.Bind(ref t) }, - - #endregion - - #region Structs - { - (ByteWriter data, Rot4 rot) => data.WriteByte(rot.AsByte), - (ByteReader data) => new Rot4(data.ReadByte()) - }, - { - (ByteWriter data, IntVec3 vec) => { - if (vec.y < 0) { - data.WriteShort(-1); - } - else { - data.WriteShort((short)vec.y); - data.WriteShort((short)vec.x); - data.WriteShort((short)vec.z); - } - }, - (ByteReader data) => { - short y = data.ReadShort(); - if (y < 0) - return IntVec3.Invalid; - - short x = data.ReadShort(); - short z = data.ReadShort(); - - return new IntVec3(x, y, z); - } - }, - { - (SyncWorker sync, ref Vector2 vec) => { - sync.Bind(ref vec.x); - sync.Bind(ref vec.y); - } - }, - { - (SyncWorker sync, ref Vector3 vec) => { - sync.Bind(ref vec.x); - sync.Bind(ref vec.y); - sync.Bind(ref vec.z); - } - }, - #endregion - - #region Templates - /* - { (SyncWorker sync, ref object obj) => { } }, - { - (ByteWriter data, object obj) => { - - }, - (ByteReader data) => { - return null; - } - }, - */ - #endregion - }; - } -} diff --git a/Source/Client/Syncing/Dict/SyncDictMisc.cs b/Source/Client/Syncing/Dict/SyncDictMisc.cs index 956c45df..ef02dc04 100644 --- a/Source/Client/Syncing/Dict/SyncDictMisc.cs +++ b/Source/Client/Syncing/Dict/SyncDictMisc.cs @@ -114,6 +114,48 @@ public static class SyncDictMisc } }, #endregion + + #region Structs + { + (ByteWriter data, Rot4 rot) => data.WriteByte(rot.AsByte), + (ByteReader data) => new Rot4(data.ReadByte()) + }, + { + (ByteWriter data, IntVec3 vec) => { + if (vec.y < 0) { + data.WriteShort(-1); + } + else { + data.WriteShort((short)vec.y); + data.WriteShort((short)vec.x); + data.WriteShort((short)vec.z); + } + }, + (ByteReader data) => { + short y = data.ReadShort(); + if (y < 0) + return IntVec3.Invalid; + + short x = data.ReadShort(); + short z = data.ReadShort(); + + return new IntVec3(x, y, z); + } + }, + { + (SyncWorker sync, ref Vector2 vec) => { + sync.Bind(ref vec.x); + sync.Bind(ref vec.y); + } + }, + { + (SyncWorker sync, ref Vector3 vec) => { + sync.Bind(ref vec.x); + sync.Bind(ref vec.y); + sync.Bind(ref vec.z); + } + }, + #endregion }; } } diff --git a/Source/Client/Syncing/Dict/SyncDictRimWorld.cs b/Source/Client/Syncing/Dict/SyncDictRimWorld.cs index a327e3ee..ba0f1e91 100644 --- a/Source/Client/Syncing/Dict/SyncDictRimWorld.cs +++ b/Source/Client/Syncing/Dict/SyncDictRimWorld.cs @@ -11,6 +11,7 @@ using Verse.AI.Group; using static Multiplayer.Client.SyncSerialization; using static Multiplayer.Client.RwImplSerialization; +using static Multiplayer.Client.CompSerialization; // ReSharper disable RedundantLambdaParameterType namespace Multiplayer.Client @@ -703,16 +704,16 @@ public static class SyncDictRimWorld holder = thingComp; else if (ThingOwnerUtility.GetFirstSpawnedParentThing(thing) is { } parentThing) holder = parentThing; - else if (GetAnyParent(thing) is { } worldObj) + else if (RwSerialization.GetAnyParent(thing) is { } worldObj) holder = worldObj; - else if (GetAnyParent(thing) is { } worldObjComp) + else if (RwSerialization.GetAnyParent(thing) is { } worldObjComp) holder = worldObjComp; GetImpl(holder, supportedThingHolders, out Type implType, out int index); if (index == -1) { data.WriteByte(byte.MaxValue); - Log.Error($"Thing {ThingHolderString(thing)} is inaccessible"); + Log.Error($"Thing {RwSerialization.ThingHolderString(thing)} is inaccessible"); return; } @@ -755,7 +756,7 @@ public static class SyncDictRimWorld } return ThingsById.thingsById.GetValueSafe(thingId); - } + }, true }, { (SyncWorker data, ref ThingComp comp) => { @@ -1120,7 +1121,6 @@ public static class SyncDictRimWorld }, (ByteReader data) => (IStorageGroupMember)ReadSync(data) }, - #endregion #region Storage diff --git a/Source/Client/Syncing/Game/RwImplSerialization.cs b/Source/Client/Syncing/RwImplSerialization.cs similarity index 72% rename from Source/Client/Syncing/Game/RwImplSerialization.cs rename to Source/Client/Syncing/RwImplSerialization.cs index 7016ab11..64e4e40e 100644 --- a/Source/Client/Syncing/Game/RwImplSerialization.cs +++ b/Source/Client/Syncing/RwImplSerialization.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Multiplayer.API; using Multiplayer.Client.Util; using Multiplayer.Common; using RimWorld; @@ -12,16 +13,7 @@ public static class RwImplSerialization { public static Type[] storageParents; public static Type[] plantToGrowSettables; - - public static Type[] thingCompTypes; - public static Type[] hediffCompTypes; - public static Type[] abilityCompTypes; public static Type[] designatorTypes; - public static Type[] worldObjectCompTypes; - - public static Type[] gameCompTypes; - public static Type[] worldCompTypes; - public static Type[] mapCompTypes; internal static Type[] supportedThingHolders = { @@ -47,16 +39,7 @@ public static void Init() { storageParents = TypeUtil.AllImplementationsOrdered(typeof(IStoreSettingsParent)); plantToGrowSettables = TypeUtil.AllImplementationsOrdered(typeof(IPlantToGrowSettable)); - - thingCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(ThingComp)); - hediffCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(HediffComp)); - abilityCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(AbilityComp)); designatorTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(Designator)); - worldObjectCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(WorldObjectComp)); - - gameCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(GameComponent)); - worldCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(WorldComponent)); - mapCompTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(MapComponent)); } internal static T ReadWithImpl(ByteReader data, IList impls) where T : class diff --git a/Source/Client/Syncing/RwSerialization.cs b/Source/Client/Syncing/RwSerialization.cs new file mode 100644 index 00000000..c951ed95 --- /dev/null +++ b/Source/Client/Syncing/RwSerialization.cs @@ -0,0 +1,225 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using HarmonyLib; +using Multiplayer.API; +using Multiplayer.Common; +using Verse; + +namespace Multiplayer.Client; + +public static class RwSerialization +{ + public static void Init() + { + // CanHandle hooks + SyncSerialization.canHandleHooks.Add(syncType => + { + var type = syncType.type; + if (type.IsGenericType && type.GetGenericTypeDefinition() is { } gtd) + if (gtd == typeof(Pair<,>)) + return SyncSerialization.CanHandleGenericArgs(type); + + if (syncType.expose) + return typeof(IExposable).IsAssignableFrom(type); + if (type == typeof(ISyncSimple)) + return true; + if (typeof(ISyncSimple).IsAssignableFrom(type)) + return ApiSerialization.syncSimples. + Where(t => type.IsAssignableFrom(t)). + SelectMany(AccessTools.GetDeclaredFields). + All(f => SyncSerialization.CanHandle(f.FieldType)); + if (typeof(Def).IsAssignableFrom(type)) + return true; + if (typeof(Designator).IsAssignableFrom(type)) + return true; + + return SyncDict.syncWorkers.TryGetValue(type, out _); + }); + + // Verse.Pair<,> serialization + SyncSerialization.AddSerializationHook( + syncType => syncType.type.IsGenericType && syncType.type.GetGenericTypeDefinition() is { } gtd && gtd == typeof(Pair<,>), + (data, syncType) => + { + Type[] arguments = syncType.type.GetGenericArguments(); + object[] parameters = + { + SyncSerialization.ReadSyncObject(data, arguments[0]), + SyncSerialization.ReadSyncObject(data, arguments[1]), + }; + return syncType.type.GetConstructors().First().Invoke(parameters); + }, + (data, obj, syncType) => + { + var type = syncType.type; + Type[] arguments = type.GetGenericArguments(); + + SyncSerialization.WriteSyncObject(data, AccessTools.DeclaredField(type, "first").GetValue(obj), arguments[0]); + SyncSerialization.WriteSyncObject(data, AccessTools.DeclaredField(type, "second").GetValue(obj), arguments[1]); + } + ); + + // IExposable serialization + SyncSerialization.AddSerializationHook( + syncType => syncType.expose, + (data, syncType) => + { + if (!typeof(IExposable).IsAssignableFrom(syncType.type)) + throw new SerializationException($"Type {syncType.type} can't be exposed because it isn't IExposable"); + + byte[] exposableData = data.ReadPrefixedBytes(); + return ExposableSerialization.ReadExposable(syncType.type, exposableData); + }, + (data, obj, syncType) => + { + if (!typeof(IExposable).IsAssignableFrom(syncType.type)) + throw new SerializationException($"Type {syncType} can't be exposed because it isn't IExposable"); + + var log = (data as LoggingByteWriter)?.Log; + IExposable exposable = obj as IExposable; + byte[] xmlData = ScribeUtil.WriteExposable(exposable); + LogXML(log, xmlData); + data.WritePrefixedBytes(xmlData); + } + ); + + // ISyncSimple serialization + // todo null handling for ISyncSimple? + SyncSerialization.AddSerializationHook( + syncType => typeof(ISyncSimple).IsAssignableFrom(syncType.type), + (data, _) => + { + ushort typeIndex = data.ReadUShort(); + var objType = ApiSerialization.syncSimples[typeIndex]; + var obj = MpUtil.NewObjectNoCtor(objType); + foreach (var field in AccessTools.GetDeclaredFields(objType)) + field.SetValue(obj, SyncSerialization.ReadSyncObject(data, field.FieldType)); + return obj; + }, + (data, obj, _) => + { + data.WriteUShort((ushort)ApiSerialization.syncSimples.FindIndex(obj!.GetType())); + foreach (var field in AccessTools.GetDeclaredFields(obj.GetType())) + SyncSerialization.WriteSyncObject(data, field.GetValue(obj), field.FieldType); + } + ); + + // Def serialization + SyncSerialization.AddSerializationHook( + syncType => typeof(Def).IsAssignableFrom(syncType.type), + (data, _) => + { + ushort defTypeIndex = data.ReadUShort(); + if (defTypeIndex == ushort.MaxValue) + return null; + + ushort shortHash = data.ReadUShort(); + + var defType = DefSerialization.DefTypes[defTypeIndex]; + var def = DefSerialization.GetDef(defType, shortHash); + + if (def == null) + throw new SerializationException($"Couldn't find {defType} with short hash {shortHash}"); + + return def; + }, + (data, obj, _) => + { + if (obj is not Def def) + { + data.WriteUShort(ushort.MaxValue); + return; + } + + var defTypeIndex = Array.IndexOf(DefSerialization.DefTypes, def.GetType()); + if (defTypeIndex == -1) + throw new SerializationException($"Unknown def type {def.GetType()}"); + + data.WriteUShort((ushort)defTypeIndex); + data.WriteUShort(def.shortHash); + } + ); + + // Designator type changer + // todo handle null? + SyncSerialization.AddTypeChanger( + syncType => typeof(Designator).IsAssignableFrom(syncType.type), + (data, _) => + { + ushort desId = SyncSerialization.ReadSync(data); + return RwImplSerialization.designatorTypes[desId]; + }, + (data, obj, _) => + { + data.WriteUShort((ushort) Array.IndexOf(RwImplSerialization.designatorTypes, obj!.GetType())); + } + ); + + RwImplSerialization.Init(); + CompSerialization.Init(); + ApiSerialization.Init(); + DefSerialization.Init(); + + RwTypeHelper.Init(); + SyncWorkerTypeHelper.GetType = RwTypeHelper.GetType; + SyncWorkerTypeHelper.GetTypeIndex = RwTypeHelper.GetTypeIndex; + } + + internal static T GetAnyParent(Thing thing) where T : class + { + if (thing is T t) + return t; + + for (var parentHolder = thing.ParentHolder; parentHolder != null; parentHolder = parentHolder.ParentHolder) + if (parentHolder is T t2) + return t2; + + return null; + } + + internal static string ThingHolderString(Thing thing) + { + StringBuilder builder = new StringBuilder(thing.ToString()); + + for (var parentHolder = thing.ParentHolder; parentHolder != null; parentHolder = parentHolder.ParentHolder) + { + builder.Insert(0, "=>"); + builder.Insert(0, parentHolder.ToString()); + } + + return builder.ToString(); + } + + private static void LogXML(SyncLogger log, byte[] xmlData) + { + if (log == null) return; + + var reader = XmlReader.Create(new MemoryStream(xmlData)); + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + string name = reader.Name; + if (reader.GetAttribute("IsNull") == "True") + name += " (IsNull)"; + + if (reader.IsEmptyElement) + log.Node(name); + else + log.Enter(name); + } + else if (reader.NodeType == XmlNodeType.EndElement) + { + log.Exit(); + } + else if (reader.NodeType == XmlNodeType.Text) + { + log.AppendToCurrentName($": {reader.Value}"); + } + } + } +} diff --git a/Source/Client/Syncing/RwTypeHelper.cs b/Source/Client/Syncing/RwTypeHelper.cs new file mode 100644 index 00000000..990ebac4 --- /dev/null +++ b/Source/Client/Syncing/RwTypeHelper.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Multiplayer.Common; +using RimWorld; +using RimWorld.Planet; +using Verse; + +namespace Multiplayer.Client; + +internal static class RwTypeHelper +{ + private static Dictionary cache = new(); + + public static void Init() + { + cache[typeof(IStoreSettingsParent)] = RwImplSerialization.storageParents; + cache[typeof(IPlantToGrowSettable)] = RwImplSerialization.plantToGrowSettables; + cache[typeof(Designator)] = RwImplSerialization.designatorTypes; + + cache[typeof(ThingComp)] = CompSerialization.thingCompTypes; + cache[typeof(AbilityComp)] = CompSerialization.abilityCompTypes; + cache[typeof(WorldObjectComp)] = CompSerialization.worldObjectCompTypes; + cache[typeof(HediffComp)] = CompSerialization.hediffCompTypes; + + cache[typeof(GameComponent)] = CompSerialization.gameCompTypes; + cache[typeof(WorldComponent)] = CompSerialization.worldCompTypes; + cache[typeof(MapComponent)] = CompSerialization.mapCompTypes; + } + + internal static void FlushCache() + { + cache.Clear(); + } + + internal static Type[] GenTypeCache(Type type) + { + var types = GenTypes.AllTypes + .Where(t => t != type && type.IsAssignableFrom(t)) + .OrderBy(t => t.IsInterface) + .ToArray(); + + cache[type] = types; + return types; + } + + internal static Type GetType(ushort index, Type baseType) + { + if (!cache.TryGetValue(baseType, out Type[] types)) + types = GenTypeCache(baseType); + + return types[index]; + } + + internal static ushort GetTypeIndex(Type type, Type baseType) + { + if (!cache.TryGetValue(baseType, out Type[] types)) + types = GenTypeCache(baseType); + + return (ushort) types.FindIndex(type); + } +} diff --git a/Source/Client/Syncing/Sync.cs b/Source/Client/Syncing/Sync.cs index f6a1c631..e62706ed 100644 --- a/Source/Client/Syncing/Sync.cs +++ b/Source/Client/Syncing/Sync.cs @@ -141,9 +141,9 @@ internal static void RegisterAllAttributes(Assembly asm) RegisterSyncMethod(method, sma); else if (method.TryGetAttribute(out SyncWorkerAttribute swa)) RegisterSyncWorker(method, isImplicit: swa.isImplicit, shouldConstruct: swa.shouldConstruct); - else if (method.TryGetAttribute(out SyncDialogNodeTreeAttribute sdnta)) + else if (method.TryGetAttribute(out SyncDialogNodeTreeAttribute _)) RegisterSyncDialogNodeTree(method); - else if (method.TryGetAttribute(out PauseLockAttribute pea)) + else if (method.TryGetAttribute(out PauseLockAttribute _)) RegisterPauseLock(method); } catch (Exception e) @@ -176,7 +176,7 @@ private static void RegisterSyncMethod(MethodInfo method, SyncMethodAttribute at if (exposeParameters != null && exposeParameters.Any(p => p < 0 || p >= paramNum)) { - Log.Error($"Failed to register a method: One or more indexes of parameters to expose in SyncMethod attribute applied to {method.DeclaringType.FullName}::{method} is invalid."); + Log.Error($"Failed to register a method: One or more indexes of parameters to expose in SyncMethod attribute applied to {method.DeclaringType?.FullName}::{method} is invalid."); return; } @@ -206,7 +206,7 @@ private static void RegisterSyncMethod(MethodInfo method, SyncMethodAttribute at sm.ExposeParameter(exposeParameters[i]); } } catch (Exception exc) { - Log.Error($"An exception occurred while exposing parameter {i} ({method.GetParameters()[i]}) for method {method.DeclaringType.FullName}::{method}: {exc}"); + Log.Error($"An exception occurred while exposing parameter {i} ({method.GetParameters()[i]}) for method {method.DeclaringType?.FullName}::{method}: {exc}"); } } } @@ -275,27 +275,27 @@ public static void RegisterSyncWorker(MethodInfo method, Type targetType = null, Type[] parameters = method.GetParameters().Select(p => p.ParameterType).ToArray(); if (!method.IsStatic) { - Log.Error($"Error in {method.DeclaringType.FullName}::{method}: SyncWorker method has to be static."); + Log.Error($"Error in {method.DeclaringType?.FullName}::{method}: SyncWorker method has to be static."); return; } if (parameters.Length != 2) { - Log.Error($"Error in {method.DeclaringType.FullName}::{method}: SyncWorker method has an invalid number of parameters."); + Log.Error($"Error in {method.DeclaringType?.FullName}::{method}: SyncWorker method has an invalid number of parameters."); return; } if (parameters[0] != typeof(SyncWorker)) { - Log.Error($"Error in {method.DeclaringType.FullName}::{method}: SyncWorker method has an invalid first parameter (got {parameters[0]}, expected ISyncWorker)."); + Log.Error($"Error in {method.DeclaringType?.FullName}::{method}: SyncWorker method has an invalid first parameter (got {parameters[0]}, expected ISyncWorker)."); return; } if (targetType != null && parameters[1].IsAssignableFrom(targetType)) { - Log.Error($"Error in {method.DeclaringType.FullName}::{method}: SyncWorker method has an invalid second parameter (got {parameters[1]}, expected {targetType} or assignable)."); + Log.Error($"Error in {method.DeclaringType?.FullName}::{method}: SyncWorker method has an invalid second parameter (got {parameters[1]}, expected {targetType} or assignable)."); return; } if (!parameters[1].IsByRef) { - Log.Error($"Error in {method.DeclaringType.FullName}::{method}: SyncWorker method has an invalid second parameter, should be a ref."); + Log.Error($"Error in {method.DeclaringType?.FullName}::{method}: SyncWorker method has an invalid second parameter, should be a ref."); return; } @@ -303,50 +303,46 @@ public static void RegisterSyncWorker(MethodInfo method, Type targetType = null, if (isImplicit) { if (method.ReturnType != typeof(bool)) { - Log.Error($"Error in {method.DeclaringType.FullName}::{method}: SyncWorker set as implicit (or the argument type is an interface) requires bool type as a return value."); + Log.Error($"Error in {method.DeclaringType?.FullName}::{method}: SyncWorker set as implicit (or the argument type is an interface) requires bool type as a return value."); return; } } else if (method.ReturnType != typeof(void)) { - Log.Error($"Error in {method.DeclaringType.FullName}::{method}: SyncWorker set as explicit should have void as a return value."); + Log.Error($"Error in {method.DeclaringType?.FullName}::{method}: SyncWorker set as explicit should have void as a return value."); return; } SyncWorkerEntry entry = SyncDict.syncWorkers.GetOrAddEntry(type, isImplicit: isImplicit, shouldConstruct: shouldConstruct); - entry.Add(method); if (!(isImplicit || type.IsInterface) && entry.SyncWorkerCount > 1) { - Log.Warning($"Warning in {method.DeclaringType.FullName}::{method}: type {type} has already registered an explicit SyncWorker, the code in this method may be not used."); + Log.Warning($"Warning in {method.DeclaringType?.FullName}::{method}: type {type} has already registered an explicit SyncWorker, the code in this method may be not used."); } - Log.Message($"Registered a SyncWorker {method.DeclaringType.FullName}::{method} for type {type} in assembly {method.DeclaringType.Assembly.GetName().Name}"); + Log.Message($"Registered a SyncWorker {method.DeclaringType?.FullName}::{method} for type {type} in assembly {method.DeclaringType?.Assembly.GetName().Name}"); } public static void RegisterSyncWorker(SyncWorkerDelegate syncWorkerDelegate, Type targetType = null, bool isImplicit = false, bool shouldConstruct = false) { MethodInfo method = syncWorkerDelegate.Method; - Type[] parameters = method.GetParameters().Select(p => p.ParameterType).ToArray(); if (targetType != null && parameters[1].IsAssignableFrom(targetType)) { - Log.Error($"Error in {method.DeclaringType.FullName}::{method}: SyncWorker method has an invalid second parameter (got {parameters[1]}, expected {targetType} or assignable)."); + Log.Error($"Error in {method.DeclaringType?.FullName}::{method}: SyncWorker method has an invalid second parameter (got {parameters[1]}, expected {targetType} or assignable)."); return; } var type = targetType ?? typeof(T); - SyncWorkerEntry entry = SyncDict.syncWorkers.GetOrAddEntry(type, isImplicit: isImplicit, shouldConstruct: shouldConstruct); - entry.Add(syncWorkerDelegate); if (!(isImplicit || type.IsInterface) && entry.SyncWorkerCount > 1) { - Log.Warning($"Warning in {method.DeclaringType.FullName}::{method}: type {type} has already registered an explicit SyncWorker, the code in this method may be not used."); + Log.Warning($"Warning in {method.DeclaringType?.FullName}::{method}: type {type} has already registered an explicit SyncWorker, the code in this method may be not used."); } } public static void RegisterSyncDialogNodeTree(Type type, string methodOrPropertyName, SyncType[] argTypes = null) { - MethodInfo method = AccessTools.Method(type, methodOrPropertyName, argTypes != null ? argTypes.Select(t => t.type).ToArray() : null); + MethodInfo method = AccessTools.Method(type, methodOrPropertyName, argTypes?.Select(t => t.type).ToArray()); if (method == null) { diff --git a/Source/Client/Syncing/Worker/SyncWorkers.cs b/Source/Client/Syncing/Worker/SyncWorkers.cs deleted file mode 100644 index 64602c29..00000000 --- a/Source/Client/Syncing/Worker/SyncWorkers.cs +++ /dev/null @@ -1,473 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; - -using Multiplayer.API; -using Multiplayer.Common; - -using RimWorld; -using RimWorld.Planet; - -using Verse; - -namespace Multiplayer.Client -{ - public class SyncWorkerEntry - { - delegate bool SyncWorkerDelegate(SyncWorker sync, ref object obj); - - public Type type; - public bool shouldConstruct; - private List syncWorkers; - private List subclasses; - private SyncWorkerEntry parent; - - public int SyncWorkerCount => syncWorkers.Count(); - - public SyncWorkerEntry(Type type, bool shouldConstruct = false) - { - this.type = type; - this.syncWorkers = new List(); - this.shouldConstruct = shouldConstruct; - } - - public SyncWorkerEntry(SyncWorkerEntry other) - { - type = other.type; - syncWorkers = other.syncWorkers; - subclasses = other.subclasses; - shouldConstruct = other.shouldConstruct; - } - - public void Add(MethodInfo method) - { - // todo: Find a way to do this without DynDelegate - Add(DynDelegate.DynamicDelegate.Create(method), method.ReturnType == typeof(void)); - } - - public void Add(SyncWorkerDelegate func) - { - Add(new SyncWorkerDelegate((SyncWorker sync, ref object obj) => { - var obj2 = (T) obj; - func(sync, ref obj2); - obj = obj2; - return true; - }), true); - } - - private void Add(SyncWorkerDelegate sync, bool append = true) - { - if (append) - syncWorkers.Add(sync); - else - syncWorkers.Insert(0, sync); - } - - public bool Invoke(SyncWorker worker, ref object obj) - { - if (parent != null) { - parent.Invoke(worker, ref obj); - } - - for (int i = 0; i < syncWorkers.Count; i++) { - if (syncWorkers[i](worker, ref obj)) - return true; - - if (worker is ReadingSyncWorker reader) { - reader.Reset(); - } else if (worker is WritingSyncWorker writer) { - writer.Reset(); - } - } - - return false; - } - - public SyncWorkerEntry Add(SyncWorkerEntry other) - { - SyncWorkerEntry newEntry = Add(other.type, other, other.shouldConstruct); - - newEntry.subclasses = other.subclasses; - - return newEntry; - } - - public SyncWorkerEntry Add(Type type, bool shouldConstruct = false) - { - return Add(type, null, shouldConstruct); - } - - private SyncWorkerEntry Add(Type type, SyncWorkerEntry parent, bool shouldConstruct) - { - if (type == this.type) { - if (shouldConstruct) { - this.shouldConstruct = true; - } - - return this; - } - - if (type.IsAssignableFrom(this.type)) // Is parent - { - SyncWorkerEntry newEntry; - - if (parent != null) { - List ps = parent.subclasses; - newEntry = new SyncWorkerEntry(type, shouldConstruct); - - newEntry.subclasses.Add(this); - - ps[ps.IndexOf(this)] = newEntry; - return newEntry; - } else { - newEntry = new SyncWorkerEntry(this); - - this.type = type; - - this.shouldConstruct = shouldConstruct; - - syncWorkers = new List(); - subclasses = new List() { newEntry }; - return this; - } - - - } - - if (this.type.IsAssignableFrom(type)) // Is child - { - if (subclasses != null) { - for (int i = 0; i < subclasses.Count; i++) { - SyncWorkerEntry res = subclasses[i].Add(type, this, shouldConstruct); - if (res != null) - return res; - } - } else { - subclasses = new List(); - } - - var newEntry = new SyncWorkerEntry(type, shouldConstruct); - newEntry.parent = this; - subclasses.Add(newEntry); - - return newEntry; - } - - return null; - } - - public SyncWorkerEntry GetClosest(Type type) - { - if (this.type.IsAssignableFrom(type)) { - - if (subclasses == null) - return this; - - int len = subclasses.Count; - - if (len == 0) - return this; - - for (int i = 0; i < len; i++) { - SyncWorkerEntry res = subclasses[i].GetClosest(type); - - if (res != null) - return res; - } - - return this; - } - - return null; - } - - internal void PrintStructureInternal(int level, StringBuilder str) - { - str.Append(' ', 4 * level); - str.Append(type.ToString()); - - if (subclasses == null) { - str.AppendLine(); - return; - } - - str.AppendLine(" ┓ "); - - for (int i = 0; i < subclasses.Count; i++) - subclasses[i].PrintStructureInternal(level + 1, str); - } - } - - class SyncWorkerDictionary : IEnumerable - { - protected readonly Dictionary explicitEntries = new Dictionary(); - - public SyncWorkerEntry GetOrAddEntry(Type type, bool shouldConstruct = false) - { - if (explicitEntries.TryGetValue(type, out SyncWorkerEntry explicitEntry)) { - return explicitEntry; - } - - return AddExplicit(type, shouldConstruct); - } - - protected SyncWorkerEntry AddExplicit(Type type, bool shouldConstruct = false) - { - var explicitEntry = new SyncWorkerEntry(type, shouldConstruct); - - explicitEntries.Add(type, explicitEntry); - - return explicitEntry; - } - - internal void Add(SyncWorkerDelegate action) - { - var entry = GetOrAddEntry(typeof(T), shouldConstruct: false); - entry.Add(action); - } - - internal void Add(Action writer, Func reader) - { - var entry = GetOrAddEntry(typeof(T), shouldConstruct: false); - entry.Add(GetDelegate(writer, reader)); - } - - protected static SyncWorkerDelegate GetDelegate(Action writer, Func reader) - { - return (SyncWorker sync, ref T obj) => - { - if (sync.isWriting) - writer(((WritingSyncWorker)sync).writer, obj); - else - obj = reader(((ReadingSyncWorker)sync).reader); - }; - } - - public SyncWorkerEntry this[Type key] { - get { - TryGetValue(key, out SyncWorkerEntry entry); - return entry; - } - } - - public virtual IEnumerator GetEnumerator() - { - return explicitEntries.Values.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public virtual bool TryGetValue(Type type, out SyncWorkerEntry syncWorkerEntry) - { - explicitEntries.TryGetValue(type, out syncWorkerEntry); - - if (syncWorkerEntry != null) - return true; - - return false; - } - - public virtual string PrintStructure() - { - StringBuilder str = new StringBuilder(); - str.AppendLine("Explicit: "); - foreach (var e in explicitEntries.Values) { - e.PrintStructureInternal(0, str); - } - - return str.ToString(); - } - } - - class SyncWorkerDictionaryTree : SyncWorkerDictionary - { - protected readonly List implicitEntries = new List(); - protected readonly List interfaceEntries = new List(); - - public SyncWorkerEntry GetOrAddEntry(Type type, bool isImplicit = false, bool shouldConstruct = false) - { - if (explicitEntries.TryGetValue(type, out SyncWorkerEntry explicitEntry)) { - return explicitEntry; - } - - if (!isImplicit) { - return AddExplicit(type, shouldConstruct); - } - - if (type.IsInterface) { - - var interfaceEntry = interfaceEntries.FirstOrDefault(i => i.type == type); - - if (interfaceEntry == null) { - interfaceEntry = new SyncWorkerEntry(type, shouldConstruct); - - interfaceEntries.Add(interfaceEntry); - } - - return interfaceEntry; - } - - var entry = implicitEntries.FirstOrDefault(i => i.type == type); - - Stack toRemove = new Stack(); - - foreach (var e in implicitEntries) { - - if (type.IsAssignableFrom(e.type) || e.type.IsAssignableFrom(type)) { - if (entry != null) { - entry.Add(e); - toRemove.Push(e); - continue; - } - entry = e.Add(type, shouldConstruct); - } - } - - if (entry == null) { - entry = new SyncWorkerEntry(type, shouldConstruct); - implicitEntries.Add(entry); - return entry; - } - - foreach (var e in toRemove) { - implicitEntries.Remove(e); - } - - return entry; - } - - internal void Add(SyncWorkerDelegate action, bool isImplicit = false, bool shouldConstruct = false) - { - var entry = GetOrAddEntry(typeof(T), isImplicit: isImplicit, shouldConstruct: shouldConstruct); - - entry.Add(action); - } - - internal void Add(Action writer, Func reader, bool isImplicit = false, bool shouldConstruct = false) - { - var entry = GetOrAddEntry(typeof(T), isImplicit: isImplicit, shouldConstruct: shouldConstruct); - - entry.Add(GetDelegate(writer, reader)); - } - - public override bool TryGetValue(Type type, out SyncWorkerEntry syncWorkerEntry) - { - if (explicitEntries.TryGetValue(type, out syncWorkerEntry)) - return true; - - foreach (var e in implicitEntries) { - syncWorkerEntry = e.GetClosest(type); - - if (syncWorkerEntry != null) - return true; - } - - foreach (var e in interfaceEntries) { - syncWorkerEntry = e.GetClosest(type); - - if (syncWorkerEntry != null) - return true; - } - - return false; - } - - public override IEnumerator GetEnumerator() - { - return explicitEntries.Values.Union(implicitEntries).Union(interfaceEntries).GetEnumerator(); - } - - public override string PrintStructure() - { - StringBuilder str = new StringBuilder(); - str.AppendLine("Explicit: "); - foreach (var e in explicitEntries.Values) { - e.PrintStructureInternal(0, str); - } - str.AppendLine(); - str.AppendLine("Interface: "); - foreach (var e in interfaceEntries) { - e.PrintStructureInternal(0, str); - } - str.AppendLine(); - str.AppendLine("Implicit: "); - foreach (var e in implicitEntries) { - e.PrintStructureInternal(0, str); - } - - return str.ToString(); - } - - public static SyncWorkerDictionaryTree Merge(params SyncWorkerDictionaryTree[] trees) - { - var tree = new SyncWorkerDictionaryTree(); - - foreach (var t in trees) { - tree.explicitEntries.AddRange(t.explicitEntries); - tree.implicitEntries.AddRange(t.implicitEntries); - tree.interfaceEntries.AddRange(t.interfaceEntries); - } - - return tree; - } - } - - internal static class RwTypeHelper - { - private static Dictionary cache = new Dictionary(); - - static RwTypeHelper() - { - cache[typeof(IStoreSettingsParent)] = RwImplSerialization.storageParents; - cache[typeof(IPlantToGrowSettable)] = RwImplSerialization.plantToGrowSettables; - - cache[typeof(ThingComp)] = RwImplSerialization.thingCompTypes; - cache[typeof(AbilityComp)] = RwImplSerialization.abilityCompTypes; - cache[typeof(Designator)] = RwImplSerialization.designatorTypes; - cache[typeof(WorldObjectComp)] = RwImplSerialization.worldObjectCompTypes; - cache[typeof(HediffComp)] = RwImplSerialization.hediffCompTypes; - - cache[typeof(GameComponent)] = RwImplSerialization.gameCompTypes; - cache[typeof(WorldComponent)] = RwImplSerialization.worldCompTypes; - cache[typeof(MapComponent)] = RwImplSerialization.mapCompTypes; - } - - internal static void FlushCache() - { - cache.Clear(); - } - - internal static Type[] GenTypeCache(Type type) - { - var types = GenTypes.AllTypes - .Where(t => t != type && type.IsAssignableFrom(t)) - .OrderBy(t => t.IsInterface) - .ToArray(); - - cache[type] = types; - return types; - } - - internal static Type GetType(ushort index, Type baseType) - { - if (!cache.TryGetValue(baseType, out Type[] types)) - types = GenTypeCache(baseType); - - return types[index]; - } - - internal static ushort GetTypeIndex(Type type, Type baseType) - { - if (!cache.TryGetValue(baseType, out Type[] types)) - types = GenTypeCache(baseType); - - return (ushort) types.FindIndex(type); - } - } -} diff --git a/Source/Client/Util/CollectionExtensions.cs b/Source/Client/Util/CollectionExtensions.cs index 3c65e2e0..788db219 100644 --- a/Source/Client/Util/CollectionExtensions.cs +++ b/Source/Client/Util/CollectionExtensions.cs @@ -50,25 +50,6 @@ public static T RemoveFirst(this List list) return elem; } - static bool ArraysEqual(T[] a1, T[] a2) - { - if (ReferenceEquals(a1, a2)) - return true; - - if (a1 == null || a2 == null) - return false; - - if (a1.Length != a2.Length) - return false; - - EqualityComparer comparer = EqualityComparer.Default; - for (int i = 0; i < a1.Length; i++) - if (!comparer.Equals(a1[i], a2[i])) - return false; - - return true; - } - public static int RemoveAll(this Dictionary dictionary, Func predicate) { List list = null; diff --git a/Source/Common/Common.csproj b/Source/Common/Common.csproj index d58f09fc..5eb0de09 100644 --- a/Source/Common/Common.csproj +++ b/Source/Common/Common.csproj @@ -4,7 +4,7 @@ net472 true enable - 11 + 12 false false Multiplayer.Common @@ -16,6 +16,7 @@ + diff --git a/Source/Client/Syncing/Logger/IHasLogger.cs b/Source/Common/Syncing/Logger/IHasLogger.cs similarity index 100% rename from Source/Client/Syncing/Logger/IHasLogger.cs rename to Source/Common/Syncing/Logger/IHasLogger.cs diff --git a/Source/Client/Syncing/Logger/LogNode.cs b/Source/Common/Syncing/Logger/LogNode.cs similarity index 76% rename from Source/Client/Syncing/Logger/LogNode.cs rename to Source/Common/Syncing/Logger/LogNode.cs index 87670b2b..77630858 100644 --- a/Source/Client/Syncing/Logger/LogNode.cs +++ b/Source/Common/Syncing/Logger/LogNode.cs @@ -4,12 +4,12 @@ namespace Multiplayer.Client { public class LogNode { - public LogNode parent; + public LogNode? parent; public List children = new(); public string text; public bool expand; - public LogNode(string text, LogNode parent = null) + public LogNode(string text, LogNode? parent = null) { this.text = text; this.parent = parent; diff --git a/Source/Client/Syncing/Logger/LoggingByteReader.cs b/Source/Common/Syncing/Logger/LoggingByteReader.cs similarity index 86% rename from Source/Client/Syncing/Logger/LoggingByteReader.cs rename to Source/Common/Syncing/Logger/LoggingByteReader.cs index b0fd1c4b..5f580e49 100644 --- a/Source/Client/Syncing/Logger/LoggingByteReader.cs +++ b/Source/Common/Syncing/Logger/LoggingByteReader.cs @@ -4,11 +4,10 @@ namespace Multiplayer.Client { public class LoggingByteReader : ByteReader, IHasLogger { - public SyncLogger Log { get; } = new SyncLogger(); + public SyncLogger Log { get; } = new(); public LoggingByteReader(byte[] array) : base(array) { - } public override bool ReadBool() @@ -51,7 +50,7 @@ public override short ReadShort() return Log.NodePassthrough("short: ", base.ReadShort()); } - public override string ReadStringNullable(int maxLen = 32767) + public override string? ReadStringNullable(int maxLen = 32767) { return Log.NodePassthrough("string?: ", base.ReadStringNullable(maxLen)); } @@ -76,12 +75,12 @@ public override ushort ReadUShort() return Log.NodePassthrough("ushort: ", base.ReadUShort()); } - public override byte[] ReadPrefixedBytes(int maxLen = int.MaxValue) + public override byte[]? ReadPrefixedBytes(int maxLen = int.MaxValue) { Log.Pause(); - byte[] array = base.ReadPrefixedBytes(); + byte[]? array = base.ReadPrefixedBytes(maxLen); Log.Resume(); - Log.Node($"byte[{array.Length}]"); + Log.Node($"byte[{array?.Length}]"); return array; } } diff --git a/Source/Client/Syncing/Logger/LoggingByteWriter.cs b/Source/Common/Syncing/Logger/LoggingByteWriter.cs similarity index 100% rename from Source/Client/Syncing/Logger/LoggingByteWriter.cs rename to Source/Common/Syncing/Logger/LoggingByteWriter.cs diff --git a/Source/Client/Syncing/Logger/SyncLogger.cs b/Source/Common/Syncing/Logger/SyncLogger.cs similarity index 60% rename from Source/Client/Syncing/Logger/SyncLogger.cs rename to Source/Common/Syncing/Logger/SyncLogger.cs index 31e7a7f3..e2b4ca57 100644 --- a/Source/Client/Syncing/Logger/SyncLogger.cs +++ b/Source/Common/Syncing/Logger/SyncLogger.cs @@ -1,13 +1,10 @@ -using Verse; - namespace Multiplayer.Client { public class SyncLogger { public const string RootNodeName = "Root"; - public LogNode current = new LogNode(RootNodeName); - + public LogNode current = new(RootNodeName); private int stopped; public T NodePassthrough(string text, T val) @@ -19,28 +16,22 @@ public T NodePassthrough(string text, T val) public LogNode Node(string text) { if (stopped > 0) - { return current; - } LogNode logNode = new LogNode(text, current); current.children.Add(logNode); return logNode; } - public void Enter(string text) + public void Enter(string? text) { if (stopped <= 0) - { - current = Node(text); - } + current = Node(text ?? ""); } public void Exit() { if (stopped <= 0) - { - current = current.parent; - } + current = current.parent!; } public void Pause() @@ -57,20 +48,6 @@ public void AppendToCurrentName(string append) { current.text += append; } - - public void Print() - { - Print(current, 1); - } - - private void Print(LogNode node, int depth) - { - Log.Message(new string(' ', depth) + node.text); - foreach (LogNode child in node.children) - { - Print(child, depth + 1); - } - } } } diff --git a/Source/Client/Syncing/SerializationException.cs b/Source/Common/Syncing/SerializationException.cs similarity index 100% rename from Source/Client/Syncing/SerializationException.cs rename to Source/Common/Syncing/SerializationException.cs diff --git a/Source/Common/Syncing/SyncDictPrimitives.cs b/Source/Common/Syncing/SyncDictPrimitives.cs new file mode 100644 index 00000000..15756469 --- /dev/null +++ b/Source/Common/Syncing/SyncDictPrimitives.cs @@ -0,0 +1,23 @@ +using Multiplayer.API; + +namespace Multiplayer.Client; + +public static class SyncDictPrimitives +{ + internal static SyncWorkerDictionary syncWorkers = new() + { + // missing decimal and char, good? + { (SyncWorker sync, ref bool b ) => sync.Bind(ref b) }, + { (SyncWorker sync, ref byte b ) => sync.Bind(ref b) }, + { (SyncWorker sync, ref sbyte b ) => sync.Bind(ref b) }, + { (SyncWorker sync, ref double d ) => sync.Bind(ref d) }, + { (SyncWorker sync, ref float f ) => sync.Bind(ref f) }, + { (SyncWorker sync, ref int i ) => sync.Bind(ref i) }, + { (SyncWorker sync, ref uint i ) => sync.Bind(ref i) }, + { (SyncWorker sync, ref long l ) => sync.Bind(ref l) }, + { (SyncWorker sync, ref ulong l ) => sync.Bind(ref l) }, + { (SyncWorker sync, ref short s ) => sync.Bind(ref s) }, + { (SyncWorker sync, ref ushort s ) => sync.Bind(ref s) }, + { (SyncWorker sync, ref string t ) => sync.Bind(ref t) }, + }; +} diff --git a/Source/Client/Syncing/SyncSerialization.cs b/Source/Common/Syncing/SyncSerialization.cs similarity index 57% rename from Source/Client/Syncing/SyncSerialization.cs rename to Source/Common/Syncing/SyncSerialization.cs index 54ab699d..1ba6438a 100644 --- a/Source/Client/Syncing/SyncSerialization.cs +++ b/Source/Common/Syncing/SyncSerialization.cs @@ -4,24 +4,43 @@ using System; using System.Collections; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Runtime.CompilerServices; -using System.Text; -using System.Xml; -using Verse; namespace Multiplayer.Client { public static class SyncSerialization { - public static void Init() + public delegate bool CanHandleHook(SyncType syncType); + public delegate bool SyncTypeMatcher(SyncType syncType); + + public delegate object? SerializationReader(ByteReader data, SyncType syncType); + public delegate void SerializationWriter(ByteWriter data, object? obj, SyncType syncType); + + public delegate Type TypeChangerReader(ByteReader data, SyncType syncType); + public delegate void TypeChangerWriter(ByteWriter data, object? obj, SyncType syncType); + + public static List canHandleHooks = []; + private static List<(SyncTypeMatcher, SerializationReader, SerializationWriter)> serializationHooks = []; + private static List<(SyncTypeMatcher, TypeChangerReader, TypeChangerWriter)> typeChangerHooks = []; + + public static Action errorLogger = msg => Console.WriteLine($"Sync Error: {msg}"); + + public static void AddSerializationHook(SyncTypeMatcher matcher, SerializationReader reader, SerializationWriter writer) + { + serializationHooks.Add((matcher, reader, writer)); + } + + public static void AddTypeChanger(SyncTypeMatcher matcher, TypeChangerReader reader, TypeChangerWriter writer) { - RwImplSerialization.Init(); - ImplSerialization.Init(); - DefSerialization.Init(); + typeChangerHooks.Add((matcher, reader, writer)); } + public static SyncWorkerDictionaryTree? syncTree; + + private static Type[] supportedSystemGtds = + [typeof(List<>), typeof(IEnumerable<>), typeof(Nullable<>), typeof(Dictionary<,>), typeof(HashSet<>)]; + public static bool CanHandle(SyncType syncType) { var type = syncType.type; @@ -30,51 +49,37 @@ public static bool CanHandle(SyncType syncType) return true; if (type.IsByRef) return true; - if (SyncDictFast.syncWorkers.TryGetValue(type, out _)) + if (SyncDictPrimitives.syncWorkers.TryGetValue(type, out _)) return true; - if (syncType.expose) - return typeof(IExposable).IsAssignableFrom(type); if (typeof(ISynchronizable).IsAssignableFrom(type)) return true; if (type.IsEnum) return CanHandle(Enum.GetUnderlyingType(type)); if (type.IsArray) return type.GetArrayRank() == 1 && CanHandle(type.GetElementType()); - if (type.IsGenericType && type.GetGenericTypeDefinition() is { } gtd) - return - (false - || gtd == typeof(List<>) - || gtd == typeof(IEnumerable<>) - || gtd == typeof(Nullable<>) - || gtd == typeof(Dictionary<,>) - || gtd == typeof(Pair<,>) - || gtd == typeof(HashSet<>) - || typeof(ITuple).IsAssignableFrom(gtd)) - && CanHandleGenericArgs(type); - if (typeof(ISyncSimple).IsAssignableFrom(type)) - return ImplSerialization.syncSimples. - Where(t => type.IsAssignableFrom(t)). - SelectMany(AccessTools.GetDeclaredFields). - All(f => CanHandle(f.FieldType)); - if (typeof(Def).IsAssignableFrom(type)) - return true; - if (typeof(Designator).IsAssignableFrom(type)) + + if (type.IsGenericType && + type.GetGenericTypeDefinition() is { } gtd&& + (supportedSystemGtds.Contains(gtd) || typeof(ITuple).IsAssignableFrom(gtd))) + return CanHandleGenericArgs(type); + + if (Enumerable.Any(canHandleHooks, hook => hook(syncType))) return true; - return SyncDict.syncWorkers.TryGetValue(type, out _); + return syncTree != null && syncTree.TryGetValue(type, out _); } - private static bool CanHandleGenericArgs(Type genericType) + public static bool CanHandleGenericArgs(Type genericType) { return genericType.GetGenericArguments().All(arg => CanHandle(arg)); } - public static T ReadSync(ByteReader data) + public static T? ReadSync(ByteReader data) { - return (T)ReadSyncObject(data, typeof(T)); + return (T?)ReadSyncObject(data, typeof(T)); } - public static object ReadSyncObject(ByteReader data, SyncType syncType) + public static object? ReadSyncObject(ByteReader data, SyncType syncType) { var log = (data as LoggingByteReader)?.Log; Type type = syncType.type; @@ -83,7 +88,7 @@ public static object ReadSyncObject(ByteReader data, SyncType syncType) try { - object val = ReadSyncObjectInternal(data, syncType); + object? val = ReadSyncObjectInternal(data, syncType); log?.AppendToCurrentName($": {val}"); return val; } @@ -93,7 +98,7 @@ public static object ReadSyncObject(ByteReader data, SyncType syncType) } } - private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) + private static object? ReadSyncObjectInternal(ByteReader data, SyncType syncType) { Type type = syncType.type; @@ -109,8 +114,8 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) return null; } - if (SyncDictFast.syncWorkers.TryGetValue(type, out SyncWorkerEntry syncWorkerEntryEarly)) { - object res = null; + if (SyncDictPrimitives.syncWorkers.TryGetValue(type, out SyncWorkerEntry syncWorkerEntryEarly)) { + object? res = null; if (syncWorkerEntryEarly.shouldConstruct || type.IsValueType) res = Activator.CreateInstance(type); @@ -120,27 +125,10 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) return res; } - if (syncType.expose) - { - if (!typeof(IExposable).IsAssignableFrom(type)) - throw new SerializationException($"Type {type} can't be exposed because it isn't IExposable"); - - byte[] exposableData = data.ReadPrefixedBytes(); - return ExposableSerialization.ReadExposable(type, exposableData); - } - - if (typeof(ISynchronizable).IsAssignableFrom(type)) - { - var obj = Activator.CreateInstance(type); - - ((ISynchronizable) obj).Sync(new ReadingSyncWorker(data)); - return obj; - } - if (type.IsEnum) { Type underlyingType = Enum.GetUnderlyingType(type); - return Enum.ToObject(type, ReadSyncObject(data, underlyingType)); + return Enum.ToObject(type, ReadSyncObject(data, underlyingType)!); } if (type.IsArray) @@ -152,7 +140,7 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) if (length == ushort.MaxValue) return null; - Type elementType = type.GetElementType(); + Type elementType = type.GetElementType()!; Array arr = Array.CreateInstance(elementType, length); for (int i = 0; i < length; i++) @@ -198,11 +186,11 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) { Type[] arguments = type.GetGenericArguments(); - Array keys = (Array)ReadSyncObject(data, arguments[0].MakeArrayType()); + Array? keys = (Array?)ReadSyncObject(data, arguments[0].MakeArrayType()); if (keys == null) return null; - Array values = (Array)ReadSyncObject(data, arguments[1].MakeArrayType()); + Array values = (Array)ReadSyncObject(data, arguments[1].MakeArrayType())!; IDictionary dictionary = (IDictionary)Activator.CreateInstance(type); for (int i = 0; i < keys.Length; i++) @@ -215,14 +203,14 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) return dictionary; } - if (genericTypeDefinition == typeof(Pair<,>) || genericTypeDefinition == typeof(ValueTuple<,>)) + if (genericTypeDefinition == typeof(ValueTuple<,>)) // Binary ValueTuple { Type[] arguments = type.GetGenericArguments(); - object[] parameters = - { + object?[] parameters = + [ ReadSyncObject(data, arguments[0]), - ReadSyncObject(data, arguments[1]), - }; + ReadSyncObject(data, arguments[1]) + ]; return type.GetConstructors().First().Invoke(parameters); } @@ -230,16 +218,17 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) if (genericTypeDefinition == typeof(HashSet<>)) { Type element = type.GetGenericArguments()[0]; - object list = ReadSyncObject(data, typeof(List<>).MakeGenericType(element)); - return Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(element), list); + object? list = ReadSyncObject(data, typeof(List<>).MakeGenericType(element)); + return list == null ? null : Activator.CreateInstance(typeof(HashSet<>).MakeGenericType(element), list); } + // todo handle null non-value Tuples? if (typeof(ITuple).IsAssignableFrom(genericTypeDefinition)) // ValueTuple or Tuple { Type[] arguments = type.GetGenericArguments(); int size = data.ReadInt32(); - object[] values = new object[size]; + object?[] values = new object?[size]; for (int i = 0; i < size; i++) values[i] = ReadSyncObject(data, arguments[i]); @@ -248,45 +237,26 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) } } - if (typeof(ISyncSimple).IsAssignableFrom(type)) - { - ushort typeIndex = data.ReadUShort(); - var objType = ImplSerialization.syncSimples[typeIndex]; - var obj = MpUtil.NewObjectNoCtor(objType); - foreach (var field in AccessTools.GetDeclaredFields(objType)) - field.SetValue(obj, ReadSyncObject(data, field.FieldType)); - return obj; - } + foreach (var hook in serializationHooks) + if (hook.Item1(syncType)) + return hook.Item2(data, type); - // Def is a special case until the workers can read their own type - if (typeof(Def).IsAssignableFrom(type)) + if (typeof(ISynchronizable).IsAssignableFrom(type)) { - ushort defTypeIndex = data.ReadUShort(); - if (defTypeIndex == ushort.MaxValue) - return null; - - ushort shortHash = data.ReadUShort(); - - var defType = DefSerialization.DefTypes[defTypeIndex]; - var def = DefSerialization.GetDef(defType, shortHash); - - if (def == null) - throw new SerializationException($"Couldn't find {defType} with short hash {shortHash}"); + var obj = Activator.CreateInstance(type); - return def; + ((ISynchronizable) obj).Sync(new ReadingSyncWorker(data)); + return obj; } - // Designators can't be handled by SyncWorkers due to the type change - if (typeof(Designator).IsAssignableFrom(type)) - { - ushort desId = ReadSync(data); - type = RwImplSerialization.designatorTypes[desId]; // Replaces the type! - } + foreach (var hook in typeChangerHooks) + if (hook.Item1(syncType)) + syncType = type = hook.Item2(data, syncType); // Where the magic happens - if (SyncDict.syncWorkers.TryGetValue(type, out var syncWorkerEntry)) + if (syncTree != null && syncTree.TryGetValue(type, out var syncWorkerEntry)) { - object res = null; + object? res = null; if (syncWorkerEntry.shouldConstruct || type.IsValueType) res = Activator.CreateInstance(type); @@ -300,17 +270,17 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) } catch { - Log.Error($"Multiplayer: Error reading type: {type}"); + errorLogger($"Multiplayer: Error reading type: {type}"); throw; } } - public static void WriteSync(ByteWriter data, T obj) + public static void WriteSync(ByteWriter data, T? obj) { WriteSyncObject(data, obj, typeof(T)); } - public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncType) + public static void WriteSyncObject(ByteWriter data, object? obj, SyncType syncType) { Type type = syncType.type; var log = (data as LoggingByteWriter)?.Log; @@ -331,31 +301,12 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp return; } - if (SyncDictFast.syncWorkers.TryGetValue(type, out var syncWorkerEntryEarly)) { + if (SyncDictPrimitives.syncWorkers.TryGetValue(type, out var syncWorkerEntryEarly)) { syncWorkerEntryEarly.Invoke(new WritingSyncWorker(data), ref obj); return; } - if (syncType.expose) - { - if (!typeof(IExposable).IsAssignableFrom(type)) - throw new SerializationException($"Type {type} can't be exposed because it isn't IExposable"); - - IExposable exposable = obj as IExposable; - byte[] xmlData = ScribeUtil.WriteExposable(exposable); - LogXML(log, xmlData); - data.WritePrefixedBytes(xmlData); - - return; - } - - if (typeof(ISynchronizable).IsAssignableFrom(type)) - { - ((ISynchronizable) obj).Sync(new WritingSyncWorker(data)); - return; - } - if (type.IsEnum) { Type enumType = Enum.GetUnderlyingType(type); @@ -368,8 +319,8 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp if (type.GetArrayRank() != 1) throw new SerializationException("Multi dimensional arrays aren't supported."); - Type elementType = type.GetElementType(); - Array arr = (Array)obj; + Type elementType = type.GetElementType()!; + Array? arr = (Array?)obj; if (arr == null) { @@ -392,7 +343,7 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp if (genericTypeDefinition == typeof(List<>)) { - IList list = (IList)obj; + IList? list = (IList?)obj; Type listObjType = type.GetGenericArguments()[0]; if (list == null) @@ -413,7 +364,7 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp if (genericTypeDefinition == typeof(IEnumerable<>) || genericTypeDefinition == typeof(HashSet<>)) { - IEnumerable e = (IEnumerable)obj; + IEnumerable? e = (IEnumerable?)obj; Type elementType = type.GetGenericArguments()[0]; var listType = typeof(List<>).MakeGenericType(elementType); @@ -446,7 +397,7 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp if (genericTypeDefinition == typeof(Dictionary<,>)) { - IDictionary dictionary = (IDictionary)obj; + IDictionary? dictionary = (IDictionary?)obj; Type[] arguments = type.GetGenericArguments(); if (dictionary == null) @@ -467,16 +418,6 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp return; } - if (genericTypeDefinition == typeof(Pair<,>)) - { - Type[] arguments = type.GetGenericArguments(); - - WriteSyncObject(data, AccessTools.DeclaredField(type, "first").GetValue(obj), arguments[0]); - WriteSyncObject(data, AccessTools.DeclaredField(type, "second").GetValue(obj), arguments[1]); - - return; - } - if (genericTypeDefinition == typeof(ValueTuple<,>)) { Type[] arguments = type.GetGenericArguments(); @@ -490,7 +431,7 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp if (typeof(ITuple).IsAssignableFrom(genericTypeDefinition)) // ValueTuple or Tuple { Type[] arguments = type.GetGenericArguments(); - ITuple tuple = (ITuple)obj; + ITuple tuple = (ITuple)obj!; data.WriteInt32(tuple.Length); @@ -501,44 +442,29 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp } } - if (typeof(ISyncSimple).IsAssignableFrom(type)) - { - data.WriteUShort((ushort)ImplSerialization.syncSimples.FindIndex(obj.GetType())); - foreach (var field in AccessTools.GetDeclaredFields(obj.GetType())) - WriteSyncObject(data, field.GetValue(obj), field.FieldType); - return; - } - - // Special case - if (typeof(Def).IsAssignableFrom(type)) + foreach (var hook in serializationHooks) { - if (obj is not Def def) + if (hook.Item1(syncType)) { - data.WriteUShort(ushort.MaxValue); + hook.Item3(data, obj, syncType); return; } - - var defTypeIndex = Array.IndexOf(DefSerialization.DefTypes, def.GetType()); - if (defTypeIndex == -1) - throw new SerializationException($"Unknown def type {def.GetType()}"); - - data.WriteUShort((ushort)defTypeIndex); - data.WriteUShort(def.shortHash); - - return; } - // Special case for Designators to change the type - if (typeof(Designator).IsAssignableFrom(type)) + if (typeof(ISynchronizable).IsAssignableFrom(type)) { - data.WriteUShort((ushort) Array.IndexOf(RwImplSerialization.designatorTypes, obj.GetType())); + ((ISynchronizable) obj!).Sync(new WritingSyncWorker(data)); + return; } + foreach (var hook in typeChangerHooks) + if (hook.Item1(syncType)) + hook.Item3(data, obj, syncType); + // Where the magic happens - if (SyncDict.syncWorkers.TryGetValue(type, out var syncWorkerEntry)) + if (syncTree != null && syncTree.TryGetValue(type, out var syncWorkerEntry)) { syncWorkerEntry.Invoke(new WritingSyncWorker(data), ref obj); - return; } @@ -547,7 +473,7 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp } catch { - Log.Error($"Multiplayer: Error writing type: {type}, obj: {obj}, obj type: {obj?.GetType()}"); + errorLogger($"Multiplayer: Error writing type: {type}, obj: {obj}, obj type: {obj?.GetType()}"); throw; } finally @@ -555,60 +481,5 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp log?.Exit(); } } - - internal static T GetAnyParent(Thing thing) where T : class - { - if (thing is T t) - return t; - - for (var parentHolder = thing.ParentHolder; parentHolder != null; parentHolder = parentHolder.ParentHolder) - if (parentHolder is T t2) - return t2; - - return null; - } - - internal static string ThingHolderString(Thing thing) - { - StringBuilder builder = new StringBuilder(thing.ToString()); - - for (var parentHolder = thing.ParentHolder; parentHolder != null; parentHolder = parentHolder.ParentHolder) - { - builder.Insert(0, "=>"); - builder.Insert(0, parentHolder.ToString()); - } - - return builder.ToString(); - } - - private static void LogXML(SyncLogger log, byte[] xmlData) - { - if (log == null) return; - - var reader = XmlReader.Create(new MemoryStream(xmlData)); - - while (reader.Read()) - { - if (reader.NodeType == XmlNodeType.Element) - { - string name = reader.Name; - if (reader.GetAttribute("IsNull") == "True") - name += " (IsNull)"; - - if (reader.IsEmptyElement) - log.Node(name); - else - log.Enter(name); - } - else if (reader.NodeType == XmlNodeType.EndElement) - { - log.Exit(); - } - else if (reader.NodeType == XmlNodeType.Text) - { - log.AppendToCurrentName($": {reader.Value}"); - } - } - } } } diff --git a/Source/Client/Syncing/Worker/ReadingSyncWorker.cs b/Source/Common/Syncing/Worker/ReadingSyncWorker.cs similarity index 90% rename from Source/Client/Syncing/Worker/ReadingSyncWorker.cs rename to Source/Common/Syncing/Worker/ReadingSyncWorker.cs index 60bc3320..7d2ab8b2 100644 --- a/Source/Client/Syncing/Worker/ReadingSyncWorker.cs +++ b/Source/Common/Syncing/Worker/ReadingSyncWorker.cs @@ -10,7 +10,7 @@ public class ReadingSyncWorker : SyncWorker internal readonly ByteReader reader; readonly int initialPos; - internal ByteReader Reader => reader; + public ByteReader Reader => reader; public ReadingSyncWorker(ByteReader reader) : base(false) { @@ -25,7 +25,7 @@ public override void Bind(ref T obj, SyncType type) public override void Bind(ref T obj) { - obj = (T) SyncSerialization.ReadSyncObject(reader, typeof(T)); + obj = (T)SyncSerialization.ReadSyncObject(reader, typeof(T)); } public override void Bind(object obj, string name) @@ -33,9 +33,7 @@ public override void Bind(object obj, string name) object value = MpReflection.GetValue(obj, name); Type type = value.GetType(); - var res = SyncSerialization.ReadSyncObject(reader, type); - MpReflection.SetValue(obj, name, res); } @@ -94,14 +92,14 @@ public override void Bind(ref bool obj) obj = reader.ReadBool(); } - public override void Bind(ref string obj) + public override void Bind(ref string? obj) { obj = reader.ReadStringNullable(); } public override void BindType(ref Type type) { - type = RwTypeHelper.GetType(reader.ReadUShort(), typeof(T)); + type = SyncWorkerTypeHelper.GetType(reader.ReadUShort(), typeof(T)); } internal void Reset() diff --git a/Source/Common/Syncing/Worker/SyncWorkerDictionary.cs b/Source/Common/Syncing/Worker/SyncWorkerDictionary.cs new file mode 100644 index 00000000..c2d1caa0 --- /dev/null +++ b/Source/Common/Syncing/Worker/SyncWorkerDictionary.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using Multiplayer.API; +using Multiplayer.Common; + +namespace Multiplayer.Client; + +public class SyncWorkerDictionary : IEnumerable +{ + protected readonly Dictionary explicitEntries = new(); + + public SyncWorkerEntry GetOrAddEntry(Type type, bool shouldConstruct = false) + { + if (explicitEntries.TryGetValue(type, out SyncWorkerEntry explicitEntry)) + return explicitEntry; + + return AddExplicit(type, shouldConstruct); + } + + protected SyncWorkerEntry AddExplicit(Type type, bool shouldConstruct = false) + { + var explicitEntry = new SyncWorkerEntry(type, shouldConstruct); + explicitEntries.Add(type, explicitEntry); + return explicitEntry; + } + + public void Add(SyncWorkerDelegate action) + { + var entry = GetOrAddEntry(typeof(T), shouldConstruct: false); + entry.Add(action); + } + + public void Add(Action writer, Func reader) + { + var entry = GetOrAddEntry(typeof(T), shouldConstruct: false); + entry.Add(GetDelegate(writer, reader)); + } + + protected static SyncWorkerDelegate GetDelegate(Action writer, Func reader) + { + return (SyncWorker sync, ref T obj) => + { + if (sync.isWriting) + writer(((WritingSyncWorker)sync).writer, obj); + else + obj = reader(((ReadingSyncWorker)sync).reader); + }; + } + + public SyncWorkerEntry this[Type key] { + get { + TryGetValue(key, out SyncWorkerEntry entry); + return entry; + } + } + + public virtual IEnumerator GetEnumerator() + { + return explicitEntries.Values.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public virtual bool TryGetValue(Type type, out SyncWorkerEntry syncWorkerEntry) + { + explicitEntries.TryGetValue(type, out syncWorkerEntry); + return syncWorkerEntry != null; + } + + public virtual string PrintStructure() + { + StringBuilder str = new StringBuilder(); + str.AppendLine("Explicit: "); + foreach (var e in explicitEntries.Values) { + e.PrintStructureInternal(0, str); + } + + return str.ToString(); + } +} diff --git a/Source/Common/Syncing/Worker/SyncWorkerDictionaryTree.cs b/Source/Common/Syncing/Worker/SyncWorkerDictionaryTree.cs new file mode 100644 index 00000000..fbc84459 --- /dev/null +++ b/Source/Common/Syncing/Worker/SyncWorkerDictionaryTree.cs @@ -0,0 +1,138 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using Multiplayer.API; +using Multiplayer.Common; +using Multiplayer.Common.Util; + +namespace Multiplayer.Client; + +public class SyncWorkerDictionaryTree : SyncWorkerDictionary +{ + protected readonly List implicitEntries = []; + protected readonly List interfaceEntries = []; + + public SyncWorkerEntry GetOrAddEntry(Type type, bool isImplicit = false, bool shouldConstruct = false) + { + if (explicitEntries.TryGetValue(type, out SyncWorkerEntry explicitEntry)) { + return explicitEntry; + } + + if (!isImplicit) { + return AddExplicit(type, shouldConstruct); + } + + if (type.IsInterface) { + var interfaceEntry = interfaceEntries.FirstOrDefault(i => i.type == type); + + if (interfaceEntry == null) { + interfaceEntry = new SyncWorkerEntry(type, shouldConstruct); + interfaceEntries.Add(interfaceEntry); + } + + return interfaceEntry; + } + + var entry = implicitEntries.FirstOrDefault(i => i.type == type); + Stack toRemove = new Stack(); + + foreach (var e in implicitEntries) { + + if (type.IsAssignableFrom(e.type) || e.type.IsAssignableFrom(type)) { + if (entry != null) { + entry.Add(e); + toRemove.Push(e); + continue; + } + entry = e.Add(type, shouldConstruct); + } + } + + if (entry == null) { + entry = new SyncWorkerEntry(type, shouldConstruct); + implicitEntries.Add(entry); + return entry; + } + + foreach (var e in toRemove) { + implicitEntries.Remove(e); + } + + return entry; + } + + public void Add(SyncWorkerDelegate action, bool isImplicit = false, bool shouldConstruct = false) + { + var entry = GetOrAddEntry(typeof(T), isImplicit: isImplicit, shouldConstruct: shouldConstruct); + entry.Add(action); + } + + public void Add(Action writer, Func reader, bool isImplicit = false, bool shouldConstruct = false) + { + var entry = GetOrAddEntry(typeof(T), isImplicit: isImplicit, shouldConstruct: shouldConstruct); + entry.Add(GetDelegate(writer, reader)); + } + + public override bool TryGetValue(Type type, out SyncWorkerEntry syncWorkerEntry) + { + if (explicitEntries.TryGetValue(type, out syncWorkerEntry)) + return true; + + foreach (var e in implicitEntries) { + syncWorkerEntry = e.GetClosest(type); + + if (syncWorkerEntry != null) + return true; + } + + foreach (var e in interfaceEntries) { + syncWorkerEntry = e.GetClosest(type); + + if (syncWorkerEntry != null) + return true; + } + + return false; + } + + public override IEnumerator GetEnumerator() + { + return explicitEntries.Values.Union(implicitEntries).Union(interfaceEntries).GetEnumerator(); + } + + public override string PrintStructure() + { + StringBuilder str = new StringBuilder(); + str.AppendLine("Explicit: "); + foreach (var e in explicitEntries.Values) { + e.PrintStructureInternal(0, str); + } + str.AppendLine(); + str.AppendLine("Implicit: "); + foreach (var e in implicitEntries) { + e.PrintStructureInternal(0, str); + } + str.AppendLine(); + str.AppendLine("Interface: "); + foreach (var e in interfaceEntries) { + e.PrintStructureInternal(0, str); + } + + return str.ToString(); + } + + public static SyncWorkerDictionaryTree Merge(params SyncWorkerDictionaryTree[] trees) + { + var tree = new SyncWorkerDictionaryTree(); + + foreach (var t in trees) { + tree.explicitEntries.AddRange(t.explicitEntries); + tree.implicitEntries.AddRange(t.implicitEntries); + tree.interfaceEntries.AddRange(t.interfaceEntries); + } + + return tree; + } +} diff --git a/Source/Common/Syncing/Worker/SyncWorkerEntry.cs b/Source/Common/Syncing/Worker/SyncWorkerEntry.cs new file mode 100644 index 00000000..ada01cfc --- /dev/null +++ b/Source/Common/Syncing/Worker/SyncWorkerEntry.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using Multiplayer.API; + +namespace Multiplayer.Client; + +public class SyncWorkerEntry +{ + delegate bool SyncWorkerDelegate(SyncWorker sync, ref object? obj); + + public Type type; + public bool shouldConstruct; + private List syncWorkers; + private List? subclasses; + private SyncWorkerEntry? parent; + + public int SyncWorkerCount => syncWorkers.Count; + + public SyncWorkerEntry(Type type, bool shouldConstruct = false) + { + this.type = type; + this.syncWorkers = new List(); + this.shouldConstruct = shouldConstruct; + } + + public SyncWorkerEntry(SyncWorkerEntry other) + { + type = other.type; + syncWorkers = other.syncWorkers; + subclasses = other.subclasses; + shouldConstruct = other.shouldConstruct; + } + + public void Add(MethodInfo method) + { + // todo: Find a way to do this without DynDelegate + Add(DynDelegate.DynamicDelegate.Create(method), method.ReturnType == typeof(void)); + } + + public void Add(SyncWorkerDelegate func) + { + Add((SyncWorker sync, ref object? obj) => { + var obj2 = (T?) obj; + func(sync, ref obj2); + obj = obj2; + return true; + }, true); + } + + private void Add(SyncWorkerDelegate sync, bool append = true) + { + if (append) + syncWorkers.Add(sync); + else + syncWorkers.Insert(0, sync); + } + + public bool Invoke(SyncWorker worker, ref object? obj) + { + parent?.Invoke(worker, ref obj); + + for (int i = 0; i < syncWorkers.Count; i++) { + if (syncWorkers[i](worker, ref obj)) + return true; + + if (worker is ReadingSyncWorker reader) { + reader.Reset(); + } else if (worker is WritingSyncWorker writer) { + writer.Reset(); + } + } + + return false; + } + + public SyncWorkerEntry Add(SyncWorkerEntry other) + { + SyncWorkerEntry newEntry = Add(other.type, other, other.shouldConstruct); + newEntry.subclasses = other.subclasses; + return newEntry; + } + + public SyncWorkerEntry? Add(Type type, bool shouldConstruct = false) + { + return Add(type, null, shouldConstruct); + } + + private SyncWorkerEntry? Add(Type type, SyncWorkerEntry? parent, bool shouldConstruct) + { + if (type == this.type) { + if (shouldConstruct) { + this.shouldConstruct = true; + } + + return this; + } + + if (type.IsAssignableFrom(this.type)) // Is parent + { + SyncWorkerEntry newEntry; + + if (parent != null) { + List? ps = parent.subclasses; + newEntry = new SyncWorkerEntry(type, shouldConstruct); + + newEntry.subclasses.Add(this); + + ps[ps.IndexOf(this)] = newEntry; + return newEntry; + } else { + newEntry = new SyncWorkerEntry(this); + + this.type = type; + this.shouldConstruct = shouldConstruct; + syncWorkers = new List(); + subclasses = new List() { newEntry }; + + return this; + } + } + + if (this.type.IsAssignableFrom(type)) // Is child + { + if (subclasses != null) { + for (int i = 0; i < subclasses.Count; i++) { + SyncWorkerEntry? res = subclasses[i].Add(type, this, shouldConstruct); + if (res != null) + return res; + } + } else { + subclasses = new List(); + } + + var newEntry = new SyncWorkerEntry(type, shouldConstruct); + newEntry.parent = this; + subclasses.Add(newEntry); + + return newEntry; + } + + return null; + } + + public SyncWorkerEntry? GetClosest(Type queryType) + { + if (type.IsAssignableFrom(queryType)) { + if (subclasses == null) + return this; + + int len = subclasses.Count; + + for (int i = 0; i < len; i++) { + SyncWorkerEntry? res = subclasses[i].GetClosest(queryType); + + if (res != null) + return res; + } + + return this; + } + + return null; + } + + internal void PrintStructureInternal(int level, StringBuilder str) + { + str.Append(' ', 4 * level); + str.Append(type); + + if (subclasses == null) { + str.AppendLine(); + return; + } + + str.AppendLine(" ┓ "); + + for (int i = 0; i < subclasses.Count; i++) + subclasses[i].PrintStructureInternal(level + 1, str); + } +} diff --git a/Source/Common/Syncing/Worker/SyncWorkerTypeHelper.cs b/Source/Common/Syncing/Worker/SyncWorkerTypeHelper.cs new file mode 100644 index 00000000..023eee41 --- /dev/null +++ b/Source/Common/Syncing/Worker/SyncWorkerTypeHelper.cs @@ -0,0 +1,9 @@ +using System; + +namespace Multiplayer.Client; + +public static class SyncWorkerTypeHelper +{ + public new static Func GetType = (_, _) => throw new Exception("No implementation"); + public static Func GetTypeIndex = (_, _) => throw new Exception("No implementation"); +} diff --git a/Source/Client/Syncing/Worker/WritingSyncWorker.cs b/Source/Common/Syncing/Worker/WritingSyncWorker.cs similarity index 94% rename from Source/Client/Syncing/Worker/WritingSyncWorker.cs rename to Source/Common/Syncing/Worker/WritingSyncWorker.cs index e801a3f0..0b2d1220 100644 --- a/Source/Client/Syncing/Worker/WritingSyncWorker.cs +++ b/Source/Common/Syncing/Worker/WritingSyncWorker.cs @@ -10,7 +10,7 @@ public class WritingSyncWorker : SyncWorker internal readonly ByteWriter writer; readonly int initialPos; - internal ByteWriter Writer { get; } + public ByteWriter Writer => writer; public WritingSyncWorker(ByteWriter writer) : base(true) { @@ -98,7 +98,7 @@ public override void Bind(ref string obj) public override void BindType(ref Type type) { - writer.WriteUShort(RwTypeHelper.GetTypeIndex(type, typeof(T))); + writer.WriteUShort(SyncWorkerTypeHelper.GetTypeIndex(type, typeof(T))); } internal void Reset() diff --git a/Source/Common/Util/CollectionExtensions.cs b/Source/Common/Util/CollectionExtensions.cs new file mode 100644 index 00000000..e7e3d896 --- /dev/null +++ b/Source/Common/Util/CollectionExtensions.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; + +namespace Multiplayer.Common.Util; + +public static class CollectionExtensions +{ + public static void AddRange(this IDictionary dest, IDictionary source) + { + foreach (KeyValuePair item in source) + { + dest.Add(item.Key, item.Value); + } + } +} diff --git a/Source/Common/Util/CompilerTypes.cs b/Source/Common/Util/CompilerTypes.cs index 5ce8d9f1..2e5e626e 100644 --- a/Source/Common/Util/CompilerTypes.cs +++ b/Source/Common/Util/CompilerTypes.cs @@ -16,6 +16,17 @@ public AsyncMethodBuilderAttribute(Type builderType) BuilderType = builderType; } } + + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Struct, Inherited = false)] + public sealed class RequiredMemberAttribute : Attribute + { + } + + [AttributeUsage(System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + public sealed class CompilerFeatureRequiredAttribute : Attribute + { + public CompilerFeatureRequiredAttribute(string name) { } + } } namespace System.Diagnostics.CodeAnalysis diff --git a/Source/Client/Util/MpReflection.cs b/Source/Common/Util/MpReflection.cs similarity index 78% rename from Source/Client/Util/MpReflection.cs rename to Source/Common/Util/MpReflection.cs index 7e14e351..32385d31 100644 --- a/Source/Client/Util/MpReflection.cs +++ b/Source/Common/Util/MpReflection.cs @@ -4,40 +4,28 @@ using System.Linq; using System.Reflection; using System.Reflection.Emit; -using Verse; namespace Multiplayer.Client { public static class MpReflection { - public delegate object Getter(object instance, object index); - public delegate void Setter(object instance, object value, object index); + public static Func> allAssembliesHook; - public static IEnumerable AllAssemblies - { - get - { - yield return Assembly.GetAssembly(typeof(Game)); - - foreach (ModContentPack mod in LoadedModManager.RunningMods) - foreach (Assembly assembly in mod.assemblies.loadedAssemblies) - yield return assembly; + public delegate object Getter(object? instance, object? index); + public delegate void Setter(object? instance, object? value, object? index); - if (Assembly.GetEntryAssembly() != null) - yield return Assembly.GetEntryAssembly(); - } - } + public static IEnumerable AllAssemblies => allAssembliesHook(); private static Dictionary types = new(); private static Dictionary pathTypes = new(); private static Dictionary indexTypes = new(); private static Dictionary getters = new(); - private static Dictionary setters = new(); + private static Dictionary setters = new(); /// /// Get the value of a static property/field in type specified by memberPath /// - public static object GetValueStatic(Type type, string memberPath, object index = null) + public static object GetValueStatic(Type type, string memberPath, object? index = null) { return GetValue(null, type + "/" + memberPath, index); } @@ -45,7 +33,7 @@ public static object GetValueStatic(Type type, string memberPath, object index = /// /// Get the value of a static property/field specified by memberPath /// - public static object GetValueStatic(string memberPath, object index = null) + public static object GetValueStatic(string memberPath, object? index = null) { return GetValue(null, memberPath, index); } @@ -54,7 +42,7 @@ public static object GetValueStatic(string memberPath, object index = null) /// Get the value of a property/field specified by memberPath /// Type specification in path is not required if instance is provided /// - public static object GetValue(object instance, string memberPath, object index = null) + public static object GetValue(object? instance, string memberPath, object? index = null) { if (instance != null) memberPath = AppendType(memberPath, instance.GetType()); @@ -63,21 +51,23 @@ public static object GetValue(object instance, string memberPath, object index = return getters[memberPath](instance, index); } - public static void SetValueStatic(Type type, string memberPath, object value, object index = null) + public static void SetValueStatic(Type type, string memberPath, object? value, object? index = null) { SetValue(null, type + "/" + memberPath, value, index); } - public static void SetValue(object instance, string memberPath, object value, object index = null) + public static void SetValue(object? instance, string memberPath, object? value, object? index = null) { if (instance != null) memberPath = AppendType(memberPath, instance.GetType()); InitPropertyOrField(memberPath); - if (setters[memberPath] == null) + + var setter = setters[memberPath]; + if (setter == null) throw new Exception($"The value of {memberPath} can't be set"); - setters[memberPath](instance, value, index); + setter(instance, value, index); } public static Type PathType(string memberPath) @@ -86,10 +76,10 @@ public static Type PathType(string memberPath) return pathTypes[memberPath]; } - public static Type IndexType(string memberPath) + public static Type? IndexType(string memberPath) { InitPropertyOrField(memberPath); - return indexTypes.TryGetValue(memberPath, out Type indexType) ? indexType : null; + return indexTypes.GetValueOrDefault(memberPath); } /// @@ -122,7 +112,7 @@ private static void InitPropertyOrField(string memberPath) if (parts.Length < 2) throw new Exception($"Path requires at least the type and one member: {memberPath}"); - Type type = GetTypeByName(parts[0]); + Type? type = GetTypeByName(parts[0]); if (type == null) throw new Exception($"Type {parts[0]} not found for path: {memberPath}"); @@ -133,14 +123,14 @@ private static void InitPropertyOrField(string memberPath) for (int i = 1; i < parts.Length; i++) { string part = parts[i]; - MemberInfo memberFound = null; + MemberInfo? memberFound = null; if (part == "[]") { if (currentType.IsArray || currentType == typeof(Array)) { - currentType = currentType.GetElementType(); - memberFound = new ArrayAccess() { ElementType = currentType }; + currentType = currentType.GetElementType()!; + memberFound = new ArrayAccess { ElementType = currentType }; members.Add(memberFound); hasSetter = true; indexTypes[memberPath] = typeof(int); @@ -148,7 +138,8 @@ private static void InitPropertyOrField(string memberPath) continue; } - PropertyInfo indexer = currentType.GetProperties().FirstOrDefault(p => p.GetIndexParameters().Length == 1); + PropertyInfo? indexer = + currentType.GetProperties().FirstOrDefault(p => p.GetIndexParameters().Length == 1); if (indexer == null) continue; Type indexType = indexer.GetIndexParameters()[0].ParameterType; @@ -240,10 +231,7 @@ private static void InitPropertyOrField(string memberPath) if (lastMember is FieldInfo field) { - if (field.IsStatic) - setterGen.Emit(OpCodes.Stsfld, field); - else - setterGen.Emit(OpCodes.Stfld, field); + setterGen.Emit(field.IsStatic ? OpCodes.Stsfld : OpCodes.Stfld, field); } else if (lastMember is PropertyInfo prop) { @@ -325,57 +313,57 @@ private static void EmitAccess(Type type, List members, int count, I } } - public static Type GetTypeByName(string name) + public static Type? GetTypeByName(string name) { if (types.TryGetValue(name, out Type cached)) return cached; - Type type = - AllAssemblies.Select(a => a.GetType(name)).AllNotNull().FirstOrDefault() ?? + Type? type = + AllAssemblies.Select(a => a.GetType(name)).FirstOrDefault(t => t != null) ?? Type.GetType(name) ?? - AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetType(name)).AllNotNull().FirstOrDefault(); + AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetType(name)).FirstOrDefault(t => t != null); types[name] = type; return type; } - } - public class ArrayAccess : MemberInfo - { - public Type ElementType { get; set; } + private class ArrayAccess : MemberInfo + { + public required Type ElementType { get; init; } - public override MemberTypes MemberType => throw new NotImplementedException(); + public override MemberTypes MemberType => throw new NotImplementedException(); - public override string Name => throw new NotImplementedException(); + public override string Name => throw new NotImplementedException(); - public override Type DeclaringType => throw new NotImplementedException(); + public override Type DeclaringType => throw new NotImplementedException(); - public override Type ReflectedType => throw new NotImplementedException(); + public override Type ReflectedType => throw new NotImplementedException(); - public override object[] GetCustomAttributes(bool inherit) - { - throw new NotImplementedException(); - } + public override object[] GetCustomAttributes(bool inherit) + { + throw new NotImplementedException(); + } - public override object[] GetCustomAttributes(Type attributeType, bool inherit) - { - throw new NotImplementedException(); - } + public override object[] GetCustomAttributes(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } - public override bool IsDefined(Type attributeType, bool inherit) - { - throw new NotImplementedException(); + public override bool IsDefined(Type attributeType, bool inherit) + { + throw new NotImplementedException(); + } } } public static class ReflectionExtensions { - public static object GetPropertyOrField(this object obj, string memberPath, object index = null) + public static object GetPropertyOrField(this object? obj, string memberPath, object? index = null) { return MpReflection.GetValue(obj, memberPath, index); } - public static void SetPropertyOrField(this object obj, string memberPath, object value, object index = null) + public static void SetPropertyOrField(this object? obj, string memberPath, object? value, object? index = null) { MpReflection.SetValue(obj, memberPath, value, index); } @@ -385,7 +373,7 @@ public static void SetPropertyOrField(this object obj, string memberPath, object FieldInfo field => field.IsStatic, PropertyInfo prop => prop.GetGetMethod(true).IsStatic, MethodInfo method => method.IsStatic, - TypeInfo type => type.IsAbstract && type.IsSealed, + TypeInfo type => type is { IsAbstract: true, IsSealed: true }, _ => false, }; } diff --git a/Source/MultiplayerLoader/MultiplayerLoader.csproj b/Source/MultiplayerLoader/MultiplayerLoader.csproj index caf2227f..101493ba 100644 --- a/Source/MultiplayerLoader/MultiplayerLoader.csproj +++ b/Source/MultiplayerLoader/MultiplayerLoader.csproj @@ -3,7 +3,7 @@ net472 true - 10 + 12 false false MultiplayerLoader diff --git a/Source/Tests/Helper/TestJoiningState.cs b/Source/Tests/Helper/TestJoiningState.cs index 0563e224..a507e8f6 100644 --- a/Source/Tests/Helper/TestJoiningState.cs +++ b/Source/Tests/Helper/TestJoiningState.cs @@ -24,6 +24,7 @@ protected override async Task RunState() RwVersion, Array.Empty(), Array.Empty(), + RoundModeEnum.ToNearest, RoundModeEnum.ToNearest, Array.Empty() ); diff --git a/Source/Tests/SerializationTest.cs b/Source/Tests/SerializationTest.cs new file mode 100644 index 00000000..7bf1a1cc --- /dev/null +++ b/Source/Tests/SerializationTest.cs @@ -0,0 +1,94 @@ +using Multiplayer.API; +using Multiplayer.Client; +using Multiplayer.Common; + +namespace Tests; + +public class SerializationTest +{ + private static T? RoundtripSerialization(T? obj) + { + var writer = new ByteWriter(); + SyncSerialization.WriteSync(writer, obj); + return SyncSerialization.ReadSync(new ByteReader(writer.ToArray())); + } + + [Test] + public void TestString() + { + Assert.That(RoundtripSerialization("abc"), Is.EqualTo("abc")); + } + + [Test] + public void TestStringList() + { + List input = ["abc", "def"]; + Assert.That(RoundtripSerialization(input), Is.EqualTo(input)); + } + + [Test] + public void TestSyncWorkers() + { + SyncSerialization.syncTree = new SyncWorkerDictionaryTree + { + { + (ByteWriter writer, IA a) => + { + SyncSerialization.WriteSync(writer, a is A); + SyncSerialization.WriteSyncObject(writer, a, a.GetType()); + }, + (ByteReader reader) => + { + if (SyncSerialization.ReadSync(reader)) + return SyncSerialization.ReadSync(reader)!; + return SyncSerialization.ReadSync(reader)!; + } + }, + { + (SyncWorker worker, ref A a) => + { + worker.Bind(ref a.a); + }, true, true + }, + { + (SyncWorker worker, ref Z z) => + { + worker.Bind(ref z.z); + }, true, true + }, + }; + + { + var input = new A { a = 1 }; + Assert.That( + RoundtripSerialization(input), + Is.EqualTo(input) + ); + Assert.That( + RoundtripSerialization(input), + Is.EqualTo(input) + ); + } + + { + var input = new Z { z = 2 }; + Assert.That( + RoundtripSerialization(input), + Is.EqualTo(input) + ); + } + } + + public interface IA; + + // Using structs so that equality is by value which simplifies the test code + public struct A : IA + { + public int a; + } + + public struct Z : IA + { + public int z; + } +} diff --git a/Source/Tests/Tests.csproj b/Source/Tests/Tests.csproj index 64444ef5..5dd8fb59 100644 --- a/Source/Tests/Tests.csproj +++ b/Source/Tests/Tests.csproj @@ -4,7 +4,7 @@ net6.0 enable enable - 11 + 12 false From 58c67353ef4ee25df06e7d56fc75e9bebe276e45 Mon Sep 17 00:00:00 2001 From: Zetrith Date: Fri, 5 Apr 2024 09:11:09 +0200 Subject: [PATCH 3/7] Version 0.10, update to latest 1.5 version - Handle _NewTemps --- Source/Client/Factions/Blueprints.cs | 4 ++-- Source/Client/Multiplayer.csproj | 2 +- Source/Client/Patches/ThingMethodPatches.cs | 2 +- Source/Client/Syncing/Game/SyncMethods.cs | 2 +- Source/Common/Version.cs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Source/Client/Factions/Blueprints.cs b/Source/Client/Factions/Blueprints.cs index f65962f9..55da6117 100644 --- a/Source/Client/Factions/Blueprints.cs +++ b/Source/Client/Factions/Blueprints.cs @@ -15,7 +15,7 @@ namespace Multiplayer.Client // Don't draw other factions' blueprints // Don't link graphics of different factions' blueprints - [HarmonyPatch(typeof(GenConstruct), nameof(GenConstruct.CanPlaceBlueprintAt))] + [HarmonyPatch(typeof(GenConstruct), nameof(GenConstruct.CanPlaceBlueprintAt_NewTemp))] static class CanPlaceBlueprintAtPatch { static MethodInfo CanPlaceBlueprintOver = AccessTools.Method(typeof(GenConstruct), nameof(GenConstruct.CanPlaceBlueprintOver)); @@ -49,7 +49,7 @@ static IEnumerable Transpiler(IEnumerable e, M } - [HarmonyPatch(typeof(GenConstruct), nameof(GenConstruct.CanPlaceBlueprintAt))] + [HarmonyPatch(typeof(GenConstruct), nameof(GenConstruct.CanPlaceBlueprintAt_NewTemp))] static class CanPlaceBlueprintAtPatch2 { static IEnumerable Transpiler(IEnumerable e, MethodBase original) diff --git a/Source/Client/Multiplayer.csproj b/Source/Client/Multiplayer.csproj index 152cfba3..56b59b42 100644 --- a/Source/Client/Multiplayer.csproj +++ b/Source/Client/Multiplayer.csproj @@ -29,7 +29,7 @@ - + diff --git a/Source/Client/Patches/ThingMethodPatches.cs b/Source/Client/Patches/ThingMethodPatches.cs index 8ce42dbc..a3457f68 100644 --- a/Source/Client/Patches/ThingMethodPatches.cs +++ b/Source/Client/Patches/ThingMethodPatches.cs @@ -92,7 +92,7 @@ static void Finalizer(Container? __state) } } - [HarmonyPatch(typeof(Pawn_JobTracker), nameof(Pawn_JobTracker.CheckForJobOverride))] + [HarmonyPatch(typeof(Pawn_JobTracker), nameof(Pawn_JobTracker.CheckForJobOverride_NewTemp))] public static class JobTrackerOverride { static void Prefix(Pawn_JobTracker __instance, ref Container? __state) diff --git a/Source/Client/Syncing/Game/SyncMethods.cs b/Source/Client/Syncing/Game/SyncMethods.cs index 0c60557a..c049ad15 100644 --- a/Source/Client/Syncing/Game/SyncMethods.cs +++ b/Source/Client/Syncing/Game/SyncMethods.cs @@ -116,7 +116,7 @@ public static void Init() SyncMethod.Register(typeof(SettlementAbandonUtility), nameof(SettlementAbandonUtility.Abandon)).CancelIfAnyArgNull(); SyncMethod.Register(typeof(WorldSelector), nameof(WorldSelector.AutoOrderToTileNow)).CancelIfAnyArgNull(); SyncMethod.Register(typeof(CaravanMergeUtility), nameof(CaravanMergeUtility.TryMergeSelectedCaravans)).SetContext(SyncContext.WorldSelected); - SyncMethod.Register(typeof(PawnBanishUtility), nameof(PawnBanishUtility.Banish)).CancelIfAnyArgNull(); + SyncMethod.Register(typeof(PawnBanishUtility), nameof(PawnBanishUtility.Banish_NewTemp)).CancelIfAnyArgNull(); SyncMethod.Register(typeof(SettlementUtility), nameof(SettlementUtility.Attack)).CancelIfAnyArgNull(); SyncMethod.Register(typeof(WITab_Caravan_Gear), nameof(WITab_Caravan_Gear.TryEquipDraggedItem)).SetContext(SyncContext.WorldSelected).CancelIfNoSelectedWorldObjects().CancelIfAnyArgNull(); diff --git a/Source/Common/Version.cs b/Source/Common/Version.cs index f0c8a9a5..8c5885a6 100644 --- a/Source/Common/Version.cs +++ b/Source/Common/Version.cs @@ -2,7 +2,7 @@ namespace Multiplayer.Common { public static class MpVersion { - public const string Version = "0.9.9"; + public const string Version = "0.10"; public const int Protocol = 42; public const string ApiAssemblyName = "0MultiplayerAPI"; From d77273d8657c621966f36fc1a96878d271613122 Mon Sep 17 00:00:00 2001 From: SaberShip <6741766+SaberShip@users.noreply.github.com> Date: Fri, 5 Apr 2024 04:58:05 -0500 Subject: [PATCH 4/7] Animate red flash for blocking pause dialogs (#430) Co-authored-by: SaberShip --- Source/Client/AsyncTime/TimeControlUI.cs | 37 +++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/Source/Client/AsyncTime/TimeControlUI.cs b/Source/Client/AsyncTime/TimeControlUI.cs index 380a334e..c4edd005 100644 --- a/Source/Client/AsyncTime/TimeControlUI.cs +++ b/Source/Client/AsyncTime/TimeControlUI.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Reflection.Emit; using HarmonyLib; @@ -283,6 +283,10 @@ public static class ColonistBarTimeControl public static float btnWidth = TimeControls.TimeButSize.x; public static float btnHeight = TimeControls.TimeButSize.y; + private static Dictionary flashDict = new Dictionary(); + private static float flashInterval = 6f; + private static Color flashColor = Color.red; + static void Prefix(ref bool __state) { if (Event.current.type is EventType.MouseDown or EventType.MouseUp) @@ -316,6 +320,7 @@ static void DrawButtons() Rect groupBar = bar.drawer.GroupFrameRect(entry.group); float drawXPos = groupBar.x; Color bgColor = (entryTickable.ActualRateMultiplier(TimeSpeed.Normal) == 0f) ? pauseBgColor : normalBgColor; + Vector2 flashPos = new Vector2(drawXPos + btnWidth / 2, groupBar.yMax + btnHeight / 2); if (Multiplayer.GameComp.asyncTime) { @@ -337,6 +342,24 @@ static void DrawButtons() Rect button = new Rect(drawXPos, groupBar.yMax, btnWidth, btnHeight); MpTimeControls.TimeIndicateBlockingPause(button, bgColor); drawXPos += TimeControls.TimeButSize.x; + + float pauseTime = flashDict.GetValueOrDefault(flashPos); + if (flashDict.ContainsKey(flashPos)) + { + // Draw flash for ongoing blocking pause + DrawPauseFlash(flashPos); + } + else + { + // There is a new blocking pause + flashDict.Add(flashPos, Time.time); + } + } + + if (entryTickable.ActualRateMultiplier(TimeSpeed.Normal) != 0f && flashDict.ContainsKey(flashPos)) + { + // No longer any blocking pauses, stop flashing here + flashDict.Remove(flashPos); } List options = GetBlockingWindowOptions(entry, entryTickable); @@ -375,6 +398,18 @@ static void SwitchToMapOrWorld(Map map) Current.Game.CurrentMap = map; } } + + static void DrawPauseFlash(Vector2 pos) + { + float pauseTime = flashDict.GetValueOrDefault(pos); + float pauseDuration = Time.time - pauseTime; + + // Only flash at flashInterval from the time the blocking pause began + if (pauseDuration > 0f && pauseDuration % flashInterval < 1f) + { + GenUI.DrawFlash(pos.x, pos.y, UI.screenWidth * 0.6f, Pulser.PulseBrightness(1f, 1f, pauseDuration) * 0.4f, flashColor); + } + } } [HarmonyPatch(typeof(MainButtonWorker), nameof(MainButtonWorker.DoButton))] From e51220f076d060c1a57b5c2535a1fffa5c4e606d Mon Sep 17 00:00:00 2001 From: Zetrith Date: Fri, 5 Apr 2024 12:44:36 +0200 Subject: [PATCH 5/7] Sync slot groups and storage groups --- Source/Client/Debug/DebugActions.cs | 4 +- Source/Client/MultiplayerData.cs | 4 +- .../Client/Syncing/Dict/SyncDictRimWorld.cs | 77 ++++++++++++++----- Source/Client/Syncing/RwImplSerialization.cs | 14 ++-- Source/Client/Syncing/RwTypeHelper.cs | 4 +- 5 files changed, 77 insertions(+), 26 deletions(-) diff --git a/Source/Client/Debug/DebugActions.cs b/Source/Client/Debug/DebugActions.cs index e4be8ae0..4da0937e 100644 --- a/Source/Client/Debug/DebugActions.cs +++ b/Source/Client/Debug/DebugActions.cs @@ -136,8 +136,10 @@ public static void DumpSyncTypes() { var dict = new Dictionary() { {"Designator", RwImplSerialization.designatorTypes}, - {"IStoreSettingsParent", RwImplSerialization.storageParents}, + {"IStoreSettingsParent", RwImplSerialization.storageSettingsParent}, {"IPlantToGrowSettable", RwImplSerialization.plantToGrowSettables}, + {"ISlotGroup", RwImplSerialization.slotGroupTypes}, + {"ISlotGroupParent", RwImplSerialization.slotGroupParents}, {"ThingComp", CompSerialization.thingCompTypes}, {"AbilityComp", CompSerialization.abilityCompTypes}, diff --git a/Source/Client/MultiplayerData.cs b/Source/Client/MultiplayerData.cs index 6c9add4a..7233fd50 100644 --- a/Source/Client/MultiplayerData.cs +++ b/Source/Client/MultiplayerData.cs @@ -124,8 +124,10 @@ internal static void CollectDefInfos() dict["AbilityComp"] = GetDefInfo(CompSerialization.abilityCompTypes, TypeHash); dict["WorldObjectComp"] = GetDefInfo(CompSerialization.worldObjectCompTypes, TypeHash); dict["HediffComp"] = GetDefInfo(CompSerialization.hediffCompTypes, TypeHash); - dict["IStoreSettingsParent"] = GetDefInfo(RwImplSerialization.storageParents, TypeHash); + dict["IStoreSettingsParent"] = GetDefInfo(RwImplSerialization.storageSettingsParent, TypeHash); dict["IPlantToGrowSettable"] = GetDefInfo(RwImplSerialization.plantToGrowSettables, TypeHash); + dict["ISlotGroup"] = GetDefInfo(RwImplSerialization.slotGroupTypes, TypeHash); + dict["ISlotGroupParent"] = GetDefInfo(RwImplSerialization.slotGroupParents, TypeHash); dict["Designator"] = GetDefInfo(RwImplSerialization.designatorTypes, TypeHash); dict["DefTypes"] = GetDefInfo(DefSerialization.DefTypes, TypeHash); diff --git a/Source/Client/Syncing/Dict/SyncDictRimWorld.cs b/Source/Client/Syncing/Dict/SyncDictRimWorld.cs index ba0f1e91..00f8701e 100644 --- a/Source/Client/Syncing/Dict/SyncDictRimWorld.cs +++ b/Source/Client/Syncing/Dict/SyncDictRimWorld.cs @@ -1047,6 +1047,65 @@ public static class SyncDictRimWorld }, #endregion + #region Storage + { + (ByteWriter data, IStoreSettingsParent obj) => { + WriteWithImpl(data, obj, storageSettingsParent); + }, + (ByteReader data) => { + return ReadWithImpl(data, storageSettingsParent); + } + }, + { + (ByteWriter data, SlotGroup obj) => { + WriteSync(data, obj.parent); + }, + (ByteReader data) => + { + var parent = ReadSync(data); + return parent.GetSlotGroup(); + } + }, + { + (ByteWriter data, StorageGroup obj) => + { + data.MpContext().map = obj.Map; + WriteSync(data, obj.loadID); + }, + (ByteReader data) => + { + var loadId = data.ReadInt32(); + return data.MpContext().map.storageGroups.groups.Find(g => g.loadID == loadId); + } + }, + { + (ByteWriter data, ISlotGroup obj) => { + WriteWithImpl(data, obj, slotGroupTypes); + }, + (ByteReader data) => { + return ReadWithImpl(data, slotGroupTypes); + } + }, + { + (ByteWriter data, ISlotGroupParent obj) => { + WriteWithImpl(data, obj, slotGroupParents); + }, + (ByteReader data) => { + return ReadWithImpl(data, slotGroupParents); + } + }, + { + (ByteWriter data, IStorageGroupMember obj) => + { + if (obj is Thing thing) + WriteSync(data, thing); + else + throw new SerializationException($"Unknown IStorageGroupMember type: {obj.GetType()}"); + }, + (ByteReader data) => (IStorageGroupMember)ReadSync(data) + }, + #endregion + #region Interfaces { (ByteWriter data, ISelectable obj) => { @@ -1087,14 +1146,6 @@ public static class SyncDictRimWorld }; }, true }, - { - (ByteWriter data, IStoreSettingsParent obj) => { - WriteWithImpl(data, obj, storageParents); - }, - (ByteReader data) => { - return ReadWithImpl(data, storageParents); - } - }, { (ByteWriter data, IPlantToGrowSettable obj) => { WriteWithImpl(data, obj, plantToGrowSettables); @@ -1111,16 +1162,6 @@ public static class SyncDictRimWorld return ReadWithImpl(data, supportedThingHolders); } }, - { - (ByteWriter data, IStorageGroupMember obj) => - { - if (obj is Thing thing) - WriteSync(data, thing); - else - throw new SerializationException($"Unknown IStorageGroupMember type: {obj.GetType()}"); - }, - (ByteReader data) => (IStorageGroupMember)ReadSync(data) - }, #endregion #region Storage diff --git a/Source/Client/Syncing/RwImplSerialization.cs b/Source/Client/Syncing/RwImplSerialization.cs index 64e4e40e..d6b5452e 100644 --- a/Source/Client/Syncing/RwImplSerialization.cs +++ b/Source/Client/Syncing/RwImplSerialization.cs @@ -11,11 +11,13 @@ namespace Multiplayer.Client { public static class RwImplSerialization { - public static Type[] storageParents; - public static Type[] plantToGrowSettables; - public static Type[] designatorTypes; + public static Type[] storageSettingsParent; // IStoreSettingsParent + public static Type[] plantToGrowSettables; // IPlantToGrowSettable + public static Type[] slotGroupTypes; // ISlotGroup + public static Type[] slotGroupParents; // ISlotGroupParent + public static Type[] designatorTypes; // Designator - internal static Type[] supportedThingHolders = + internal static Type[] supportedThingHolders = // IThingHolder { typeof(Map), typeof(Thing), @@ -37,8 +39,10 @@ internal enum VerbOwnerType : byte public static void Init() { - storageParents = TypeUtil.AllImplementationsOrdered(typeof(IStoreSettingsParent)); + storageSettingsParent = TypeUtil.AllImplementationsOrdered(typeof(IStoreSettingsParent)); plantToGrowSettables = TypeUtil.AllImplementationsOrdered(typeof(IPlantToGrowSettable)); + slotGroupTypes = TypeUtil.AllImplementationsOrdered(typeof(ISlotGroup)); + slotGroupParents = TypeUtil.AllImplementationsOrdered(typeof(ISlotGroupParent)); designatorTypes = TypeUtil.AllSubclassesNonAbstractOrdered(typeof(Designator)); } diff --git a/Source/Client/Syncing/RwTypeHelper.cs b/Source/Client/Syncing/RwTypeHelper.cs index 990ebac4..f790cd0d 100644 --- a/Source/Client/Syncing/RwTypeHelper.cs +++ b/Source/Client/Syncing/RwTypeHelper.cs @@ -14,8 +14,10 @@ internal static class RwTypeHelper public static void Init() { - cache[typeof(IStoreSettingsParent)] = RwImplSerialization.storageParents; + cache[typeof(IStoreSettingsParent)] = RwImplSerialization.storageSettingsParent; cache[typeof(IPlantToGrowSettable)] = RwImplSerialization.plantToGrowSettables; + cache[typeof(ISlotGroup)] = RwImplSerialization.slotGroupTypes; + cache[typeof(ISlotGroupParent)] = RwImplSerialization.slotGroupParents; cache[typeof(Designator)] = RwImplSerialization.designatorTypes; cache[typeof(ThingComp)] = CompSerialization.thingCompTypes; From 1fe8dcc07abba379e937f232337e839bdd02de52 Mon Sep 17 00:00:00 2001 From: Zetrith Date: Fri, 5 Apr 2024 13:02:12 +0200 Subject: [PATCH 6/7] Update workshop_bundler.sh, no longer include 1.3 files --- workshop_bundler.sh | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/workshop_bundler.sh b/workshop_bundler.sh index 0a638d4e..3e150495 100755 --- a/workshop_bundler.sh +++ b/workshop_bundler.sh @@ -19,39 +19,31 @@ cp -r ../About ../Textures . cat < LoadFolders.xml + +
  • /
  • +
  • 1.5
  • +
  • /
  • 1.4
  • - -
  • /
  • -
  • 1.3.3311
  • -
    - -
  • /
  • -
  • 1.3
  • -
    EOF -sed -i "// a \ \ \ \
  • 1.3
  • " About/About.xml +sed -i "// a \ \ \ \
  • 1.4
  • " About/About.xml sed -i "/Multiplayer mod for RimWorld./aThis is version ${VERSION}." About/About.xml sed -i "s/.*<\/version>\$/${VERSION}<\/version>/" About/Manifest.xml # The current version -mkdir -p 1.4 -cp -r ../Assemblies ../AssembliesCustom ../Defs ../Languages 1.4/ -rm -f 1.4/Languages/.git 1.4/Languages/LICENSE 1.4/Languages/README.md +mkdir -p 1.5 +cp -r ../Assemblies ../AssembliesCustom ../Defs ../Languages 1.5/ +rm -f 1.5/Languages/.git 1.5/Languages/LICENSE 1.5/Languages/README.md # Past versions -mkdir -p 1.3.3311 -git --work-tree=1.3.3311 restore --recurse-submodules --source=origin/rw-1.3.3311 -- Assemblies Defs Languages -rm -f 1.3.3311/Languages/.git 1.3.3311/Languages/LICENSE 1.3.3311/Languages/README.md - -mkdir -p 1.3 -git --work-tree=1.3 restore --recurse-submodules --source=origin/rw-1.3 -- Assemblies Defs Languages -rm -f 1.3/Languages/.git 1.3/Languages/LICENSE 1.3/Languages/README.md +mkdir -p 1.4 +git --work-tree=1.4 restore --recurse-submodules --source=origin/rw-1.4 -- Assemblies AssembliesCustom Defs Languages +rm -f 1.4/Languages/.git 1.4/Languages/LICENSE 1.4/Languages/README.md cd .. From 5b7a8ea9da3a85bdc78cbe3471cb979f794c0131 Mon Sep 17 00:00:00 2001 From: Zetrith Date: Fri, 5 Apr 2024 13:05:42 +0200 Subject: [PATCH 7/7] Allow manual dispatch of "Build workshop" --- .github/workflows/build-workshop.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build-workshop.yml b/.github/workflows/build-workshop.yml index 5565ff02..90626c7d 100644 --- a/.github/workflows/build-workshop.yml +++ b/.github/workflows/build-workshop.yml @@ -1,6 +1,7 @@ name: Build workshop on: + workflow_dispatch: push: tags: - 'v*'