diff --git a/Languages b/Languages index 44102a0e..a755da3b 160000 --- a/Languages +++ b/Languages @@ -1 +1 @@ -Subproject commit 44102a0e9ad26e0ecc801273ca981be4d8125a53 +Subproject commit a755da3bfe442fa72ad8bfa843a549be6652ae16 diff --git a/Source/Client/Debug/DebugActions.cs b/Source/Client/Debug/DebugActions.cs index cc2557de..ab472133 100644 --- a/Source/Client/Debug/DebugActions.cs +++ b/Source/Client/Debug/DebugActions.cs @@ -65,16 +65,16 @@ public static void SaveGameLocal() public static void DumpSyncTypes() { var dict = new Dictionary() { - {"ThingComp", SyncSerialization.thingCompTypes}, - {"AbilityComp", SyncSerialization.abilityCompTypes}, - {"Designator", SyncSerialization.designatorTypes}, - {"WorldObjectComp", SyncSerialization.worldObjectCompTypes}, - {"IStoreSettingsParent", SyncSerialization.storageParents}, - {"IPlantToGrowSettable", SyncSerialization.plantToGrowSettables}, - - {"GameComponent", SyncSerialization.gameCompTypes}, - {"WorldComponent", SyncSerialization.worldCompTypes}, - {"MapComponent", SyncSerialization.mapCompTypes}, + {"ThingComp", ImplSerialization.thingCompTypes}, + {"AbilityComp", ImplSerialization.abilityCompTypes}, + {"Designator", ImplSerialization.designatorTypes}, + {"WorldObjectComp", ImplSerialization.worldObjectCompTypes}, + {"IStoreSettingsParent", ImplSerialization.storageParents}, + {"IPlantToGrowSettable", ImplSerialization.plantToGrowSettables}, + + {"GameComponent", ImplSerialization.gameCompTypes}, + {"WorldComponent", ImplSerialization.worldCompTypes}, + {"MapComponent", ImplSerialization.mapCompTypes}, }; foreach(var kv in dict) { diff --git a/Source/Client/Multiplayer.cs b/Source/Client/Multiplayer.cs index e9f7ae4a..f0fd2d57 100644 --- a/Source/Client/Multiplayer.cs +++ b/Source/Client/Multiplayer.cs @@ -209,7 +209,7 @@ private static void EarlyPatches() private static void InitSync() { using (DeepProfilerWrapper.Section("Multiplayer CollectTypes")) - SyncSerialization.CollectTypes(); + SyncSerialization.Init(); using (DeepProfilerWrapper.Section("Multiplayer SyncGame")) SyncGame.Init(); diff --git a/Source/Client/MultiplayerData.cs b/Source/Client/MultiplayerData.cs index e4392c97..6b8819f1 100644 --- a/Source/Client/MultiplayerData.cs +++ b/Source/Client/MultiplayerData.cs @@ -112,17 +112,17 @@ internal static void CollectDefInfos() int TypeHash(Type type) => GenText.StableStringHash(type.FullName); - dict["ThingComp"] = GetDefInfo(SyncSerialization.thingCompTypes, TypeHash); - dict["AbilityComp"] = GetDefInfo(SyncSerialization.abilityCompTypes, TypeHash); - dict["Designator"] = GetDefInfo(SyncSerialization.designatorTypes, TypeHash); - dict["WorldObjectComp"] = GetDefInfo(SyncSerialization.worldObjectCompTypes, TypeHash); - dict["IStoreSettingsParent"] = GetDefInfo(SyncSerialization.storageParents, TypeHash); - dict["IPlantToGrowSettable"] = GetDefInfo(SyncSerialization.plantToGrowSettables, TypeHash); - dict["DefTypes"] = GetDefInfo(SyncSerialization.defTypes, TypeHash); - - dict["GameComponent"] = GetDefInfo(SyncSerialization.gameCompTypes, TypeHash); - dict["WorldComponent"] = GetDefInfo(SyncSerialization.worldCompTypes, TypeHash); - dict["MapComponent"] = GetDefInfo(SyncSerialization.mapCompTypes, TypeHash); + dict["ThingComp"] = GetDefInfo(ImplSerialization.thingCompTypes, TypeHash); + dict["AbilityComp"] = GetDefInfo(ImplSerialization.abilityCompTypes, TypeHash); + dict["Designator"] = GetDefInfo(ImplSerialization.designatorTypes, TypeHash); + dict["WorldObjectComp"] = GetDefInfo(ImplSerialization.worldObjectCompTypes, TypeHash); + dict["IStoreSettingsParent"] = GetDefInfo(ImplSerialization.storageParents, TypeHash); + dict["IPlantToGrowSettable"] = GetDefInfo(ImplSerialization.plantToGrowSettables, TypeHash); + dict["DefTypes"] = GetDefInfo(DefSerialization.DefTypes, TypeHash); + + dict["GameComponent"] = GetDefInfo(ImplSerialization.gameCompTypes, TypeHash); + dict["WorldComponent"] = GetDefInfo(ImplSerialization.worldCompTypes, TypeHash); + dict["MapComponent"] = GetDefInfo(ImplSerialization.mapCompTypes, TypeHash); dict["PawnBio"] = GetDefInfo(SolidBioDatabase.allBios, b => b.name.GetHashCode()); dict["Backstory"] = GetDefInfo(BackstoryDatabase.allBackstories.Keys, b => b.GetHashCode()); diff --git a/Source/Client/MultiplayerGame.cs b/Source/Client/MultiplayerGame.cs index 01eccc09..85759246 100644 --- a/Source/Client/MultiplayerGame.cs +++ b/Source/Client/MultiplayerGame.cs @@ -70,8 +70,6 @@ public MultiplayerGame() SetThingMakerSeed(1); - Prefs.PauseOnLoad = false; // causes immediate desyncs on load if misaligned between host and clients - foreach (var field in typeof(DebugSettings).GetFields(BindingFlags.Public | BindingFlags.Static)) if (!field.IsLiteral && field.FieldType == typeof(bool)) field.SetValue(null, default(bool)); diff --git a/Source/Client/MultiplayerSession.cs b/Source/Client/MultiplayerSession.cs index cca536bd..9d8cb77b 100644 --- a/Source/Client/MultiplayerSession.cs +++ b/Source/Client/MultiplayerSession.cs @@ -132,7 +132,13 @@ public void ProcessDisconnectPacket(MpDisconnectReason reason, byte[] data) string strVersion = reader.ReadString(); int proto = reader.ReadInt32(); - disconnectInfo.titleTranslated = "MpWrongMultiplayerVersionInfo".Translate(strVersion, proto); + disconnectInfo.wideWindow = true; + disconnectInfo.descTranslated = "MpWrongMultiplayerVersionInfo".Translate(strVersion, proto, MpVersion.Version); + + if (proto < MpVersion.Protocol) + disconnectInfo.descTranslated += "\n" + "MpWrongVersionUpdateInfoHost".Translate(); + else + disconnectInfo.descTranslated += "\n" + "MpWrongVersionUpdateInfo".Translate(); } if (reason == MpDisconnectReason.ConnectingFailed) @@ -214,7 +220,7 @@ public void ProcessTimeControl() public void ScheduleCommand(ScheduledCommand cmd) { - MpLog.Debug($"Cmd: {cmd.type}, faction: {cmd.factionId}, map: {cmd.mapId}, ticks: {cmd.ticks}"); + MpLog.Debug(cmd.ToString()); dataSnapshot.mapCmds.GetOrAddNew(cmd.mapId).Add(cmd); if (Current.ProgramState != ProgramState.Playing) return; @@ -303,6 +309,7 @@ public struct SessionDisconnectInfo public string descTranslated; public string specialButtonTranslated; public Action specialButtonAction; + public bool wideWindow; } public class GameDataSnapshot diff --git a/Source/Client/MultiplayerStatic.cs b/Source/Client/MultiplayerStatic.cs index 00956a36..0297f19a 100644 --- a/Source/Client/MultiplayerStatic.cs +++ b/Source/Client/MultiplayerStatic.cs @@ -109,7 +109,8 @@ static MultiplayerStatic() using (DeepProfilerWrapper.Section("MultiplayerData PrecacheMods")) MultiplayerData.PrecacheMods(); - SimpleProfiler.Print("mp_prof_out.txt"); + if (GenCommandLine.CommandLineArgPassed("profiler")) + SimpleProfiler.Print("mp_prof_out.txt"); } private static void DoubleLongEvent(Action action, string textKey) diff --git a/Source/Client/Patches/TickPatch.cs b/Source/Client/Patches/TickPatch.cs index d2758802..fa020046 100644 --- a/Source/Client/Patches/TickPatch.cs +++ b/Source/Client/Patches/TickPatch.cs @@ -153,6 +153,8 @@ public static void Tick(out bool worked) { ScheduledCommand cmd = tickable.Cmds.Dequeue(); tickable.ExecuteCmd(cmd); + + if (LongEventHandler.eventQueue.Count > 0) return; // Yield to e.g. join-point creation } } diff --git a/Source/Client/Syncing/DefSerialization.cs b/Source/Client/Syncing/DefSerialization.cs new file mode 100644 index 00000000..89586f42 --- /dev/null +++ b/Source/Client/Syncing/DefSerialization.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using HarmonyLib; +using Multiplayer.Common; +using Verse; + +namespace Multiplayer.Client +{ + public static class DefSerialization + { + public static Type[] DefTypes { get; private set; } + private static Dictionary hashableType = new(); + + public static void Init() + { + DefTypes = ImplSerialization.AllSubclassesNonAbstractOrdered(typeof(Def)); + + foreach (var defType in DefTypes) + { + var hashable = defType; + for (var t = defType; t != typeof(Def); t = t.BaseType) + if (!t.IsAbstract) + hashable = t; + + hashableType[defType] = hashable; + } + } + + private static Dictionary methodCache = new(); + + public static Def GetDef(Type defType, ushort hash) + { + return (Def)methodCache.AddOrGet( + hashableType[defType], + static t => typeof(DefDatabase<>).MakeGenericType(t).GetMethod("GetByShortHash") + ).Invoke(null, new[] { (object)hash }); + } + } +} diff --git a/Source/Client/Syncing/Dict/SyncDictRimWorld.cs b/Source/Client/Syncing/Dict/SyncDictRimWorld.cs index 4269fd18..e9013f32 100644 --- a/Source/Client/Syncing/Dict/SyncDictRimWorld.cs +++ b/Source/Client/Syncing/Dict/SyncDictRimWorld.cs @@ -12,6 +12,7 @@ using Verse.AI; using Verse.AI.Group; using static Multiplayer.Client.SyncSerialization; +using static Multiplayer.Client.ImplSerialization; // ReSharper disable RedundantLambdaParameterType namespace Multiplayer.Client diff --git a/Source/Client/Syncing/ExposableSerialization.cs b/Source/Client/Syncing/ExposableSerialization.cs new file mode 100644 index 00000000..dd3726e9 --- /dev/null +++ b/Source/Client/Syncing/ExposableSerialization.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using HarmonyLib; +using Multiplayer.Common; +using Verse; + +namespace Multiplayer.Client +{ + public static class ExposableSerialization + { + private static readonly MethodInfo ReadExposableDefinition = + AccessTools.Method(typeof(ScribeUtil), nameof(ScribeUtil.ReadExposable)); + + private static Dictionary readExposableCache = new(); + + public static IExposable ReadExposable(Type type, byte[] data) + { + return (IExposable)readExposableCache.AddOrGet(type, newType => ReadExposableDefinition.MakeGenericMethod(newType)).Invoke(null, new[] { (object)data, null }); + } + } +} diff --git a/Source/Client/Syncing/Handler/SyncMethod.cs b/Source/Client/Syncing/Handler/SyncMethod.cs index adaabfa8..aaf02ad8 100644 --- a/Source/Client/Syncing/Handler/SyncMethod.cs +++ b/Source/Client/Syncing/Handler/SyncMethod.cs @@ -36,6 +36,7 @@ public record SyncTransformer(Type LiveType, Type NetworkType, Delegate Writer, public delegate void SyncMethodWriter(object obj, SyncType type, string debugInfo); + [HotSwappable] public class SyncMethod : SyncHandler, ISyncMethod { public readonly Type targetType; @@ -163,7 +164,11 @@ public override void Handle(ByteReader data) else target = ReadTarget(data); - if (targetType != null && target == null) return; + if (targetType != null && target == null) + { + MpLog.Debug($"SyncMethod {this} read target null"); + return; + } if (!instancePath.NullOrEmpty()) target = target.GetPropertyOrField(instancePath); diff --git a/Source/Client/Syncing/ImplSerialization.cs b/Source/Client/Syncing/ImplSerialization.cs new file mode 100644 index 00000000..f8974978 --- /dev/null +++ b/Source/Client/Syncing/ImplSerialization.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Multiplayer.Common; +using RimWorld; +using RimWorld.Planet; +using Verse; + +namespace Multiplayer.Client +{ + public static class ImplSerialization + { + public static Type[] storageParents; + public static Type[] plantToGrowSettables; + + public static Type[] thingCompTypes; + 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 = + { + typeof(Map), + typeof(Thing), + typeof(ThingComp), + typeof(WorldObject), + typeof(WorldObjectComp) + }; + + internal enum ISelectableImpl : byte + { + None, Thing, Zone, WorldObject + } + + internal enum VerbOwnerType : byte + { + None, Pawn, Ability, CompEquippable, CompReloadable + } + + public static void Init() + { + storageParents = AllImplementationsOrdered(typeof(IStoreSettingsParent)); + plantToGrowSettables = AllImplementationsOrdered(typeof(IPlantToGrowSettable)); + + thingCompTypes = AllSubclassesNonAbstractOrdered(typeof(ThingComp)); + abilityCompTypes = AllSubclassesNonAbstractOrdered(typeof(AbilityComp)); + designatorTypes = AllSubclassesNonAbstractOrdered(typeof(Designator)); + worldObjectCompTypes = AllSubclassesNonAbstractOrdered(typeof(WorldObjectComp)); + + gameCompTypes = AllSubclassesNonAbstractOrdered(typeof(GameComponent)); + worldCompTypes = AllSubclassesNonAbstractOrdered(typeof(WorldComponent)); + mapCompTypes = AllSubclassesNonAbstractOrdered(typeof(MapComponent)); + } + + internal static T ReadWithImpl(ByteReader data, IList impls) where T : class + { + ushort impl = data.ReadUShort(); + if (impl == ushort.MaxValue) return null; + return (T)SyncSerialization.ReadSyncObject(data, impls[impl]); + } + + internal static void WriteWithImpl(ByteWriter data, object obj, IList impls) where T : class + { + if (obj == null) + { + data.WriteUShort(ushort.MaxValue); + return; + } + + GetImpl(obj, impls, out Type implType, out int impl); + + if (implType == null) + throw new SerializationException($"Unknown {typeof(T)} implementation type {obj.GetType()}"); + + data.WriteUShort((ushort)impl); + SyncSerialization.WriteSyncObject(data, obj, implType); + } + + internal static void GetImpl(object obj, IList impls, out Type type, out int index) + { + type = null; + index = -1; + + if (obj == null) return; + + for (int i = 0; i < impls.Count; i++) + { + if (impls[i].IsAssignableFrom(obj.GetType())) + { + type = impls[i]; + index = i; + break; + } + } + } + + public static Type[] AllImplementationsOrdered(Type type) + { + return type.AllImplementing() + .OrderBy(t => t.IsInterface) + .ThenBy(t => t.Name) + .ToArray(); + } + + public static Type[] AllSubclassesNonAbstractOrdered(Type type) { + return type + .AllSubclassesNonAbstract() + .OrderBy(t => t.Name) + .ToArray(); + } + } +} diff --git a/Source/Client/Syncing/SyncFieldUtil.cs b/Source/Client/Syncing/SyncFieldUtil.cs index 2b8184b6..51e77de5 100644 --- a/Source/Client/Syncing/SyncFieldUtil.cs +++ b/Source/Client/Syncing/SyncFieldUtil.cs @@ -122,8 +122,7 @@ public static bool CheckShouldRemove(SyncField field, BufferTarget target, Buffe public static object SnapshotValueIfNeeded(SyncField field, object value) { if (field.fieldType.expose) - return SyncSerialization.ReadExposable(field.fieldType.type) - .Invoke(null, new[] { ScribeUtil.WriteExposable((IExposable)value), null }); + return ExposableSerialization.ReadExposable(field.fieldType.type, ScribeUtil.WriteExposable((IExposable)value)); return value; } diff --git a/Source/Client/Syncing/SyncSerialization.cs b/Source/Client/Syncing/SyncSerialization.cs index d767c54a..debd248f 100644 --- a/Source/Client/Syncing/SyncSerialization.cs +++ b/Source/Client/Syncing/SyncSerialization.cs @@ -26,86 +26,12 @@ public interface ISyncSimple { } public static class SyncSerialization { - private static Type[] AllImplementationsOrdered(Type type) + public static void Init() { - return - type.AllImplementing() - .OrderBy(t => t.IsInterface) - .ThenBy(t => t.Name) - .ToArray(); + ImplSerialization.Init(); + DefSerialization.Init(); } - private static Type[] AllSubclassesNonAbstractOrdered(Type type) { - return type - .AllSubclassesNonAbstract() - .OrderBy(t => t.Name) - .ToArray(); - } - - public static Type[] storageParents; - public static Type[] plantToGrowSettables; - - public static Type[] thingCompTypes; - public static Type[] abilityCompTypes; - public static Type[] designatorTypes; - public static Type[] worldObjectCompTypes; - - public static Type[] gameCompTypes; - public static Type[] worldCompTypes; - public static Type[] mapCompTypes; - - public static Type[] defTypes; - - public static void CollectTypes() - { - storageParents = AllImplementationsOrdered(typeof(IStoreSettingsParent)); - plantToGrowSettables = AllImplementationsOrdered(typeof(IPlantToGrowSettable)); - - thingCompTypes = AllSubclassesNonAbstractOrdered(typeof(ThingComp)); - abilityCompTypes = AllSubclassesNonAbstractOrdered(typeof(AbilityComp)); - designatorTypes = AllSubclassesNonAbstractOrdered(typeof(Designator)); - worldObjectCompTypes = AllSubclassesNonAbstractOrdered(typeof(WorldObjectComp)); - - gameCompTypes = AllSubclassesNonAbstractOrdered(typeof(GameComponent)); - worldCompTypes = AllSubclassesNonAbstractOrdered(typeof(WorldComponent)); - mapCompTypes = AllSubclassesNonAbstractOrdered(typeof(MapComponent)); - - defTypes = AllSubclassesNonAbstractOrdered(typeof(Def)); - } - - internal static Type[] supportedThingHolders = - { - typeof(Map), - typeof(Thing), - typeof(ThingComp), - typeof(WorldObject), - typeof(WorldObjectComp) - }; - - private static MethodInfo ReadExposableDefinition = - AccessTools.Method(typeof(ScribeUtil), nameof(ScribeUtil.ReadExposable)); - - private static Dictionary ReadExposableInsts = new(); - - public static MethodInfo ReadExposable(Type type) - { - return ReadExposableInsts.AddOrGet(type, newType => ReadExposableDefinition.MakeGenericMethod(newType)); - } - - internal enum ISelectableImpl : byte - { - None, Thing, Zone, WorldObject - } - - internal enum VerbOwnerType : byte - { - None, Pawn, Ability, CompEquippable, CompReloadable - } - - private static MethodInfo GetDefByIdMethod = AccessTools.Method(typeof(SyncSerialization), nameof(GetDefById)); - - public static T GetDefById(ushort id) where T : Def => DefDatabase.GetByShortHash(id); - public static bool CanHandle(SyncType syncType) { var type = syncType.type; @@ -206,7 +132,7 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) throw new SerializationException($"Type {type} can't be exposed because it isn't IExposable"); byte[] exposableData = data.ReadPrefixedBytes(); - return ReadExposable(type).Invoke(null, new[] { exposableData, null }); + return ExposableSerialization.ReadExposable(type, exposableData); } if (typeof(ISynchronizable).IsAssignableFrom(type)) @@ -337,9 +263,10 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) return null; ushort shortHash = data.ReadUShort(); - var defType = defTypes[defTypeIndex]; - var def = (Def)GetDefByIdMethod.MakeGenericMethod(defType).Invoke(null, new object[] { shortHash }); + 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}"); @@ -350,7 +277,7 @@ private static object ReadSyncObjectInternal(ByteReader data, SyncType syncType) if (typeof(Designator).IsAssignableFrom(type)) { ushort desId = ReadSync(data); - type = designatorTypes[desId]; // Replaces the type! + type = ImplSerialization.designatorTypes[desId]; // Replaces the type! } // Where the magic happens @@ -579,7 +506,7 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp return; } - var defTypeIndex = Array.IndexOf(defTypes, def.GetType()); + var defTypeIndex = Array.IndexOf(DefSerialization.DefTypes, def.GetType()); if (defTypeIndex == -1) throw new SerializationException($"Unknown def type {def.GetType()}"); @@ -592,7 +519,7 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp // Special case for Designators to change the type if (typeof(Designator).IsAssignableFrom(type)) { - data.WriteUShort((ushort) Array.IndexOf(designatorTypes, obj.GetType())); + data.WriteUShort((ushort) Array.IndexOf(ImplSerialization.designatorTypes, obj.GetType())); } // Where the magic happens @@ -617,59 +544,16 @@ public static void WriteSyncObject(ByteWriter data, object obj, SyncType syncTyp } } - internal static T ReadWithImpl(ByteReader data, IList impls) where T : class - { - ushort impl = data.ReadUShort(); - if (impl == ushort.MaxValue) return null; - return (T)ReadSyncObject(data, impls[impl]); - } - - internal static void WriteWithImpl(ByteWriter data, object obj, IList impls) where T : class - { - if (obj == null) - { - data.WriteUShort(ushort.MaxValue); - return; - } - - GetImpl(obj, impls, out Type implType, out int impl); - - if (implType == null) - throw new SerializationException($"Unknown {typeof(T)} implementation type {obj.GetType()}"); - - data.WriteUShort((ushort)impl); - WriteSyncObject(data, obj, implType); - } - - internal static void GetImpl(object obj, IList impls, out Type type, out int index) - { - type = null; - index = -1; - - if (obj == null) return; - - for (int i = 0; i < impls.Count; i++) - { - if (impls[i].IsAssignableFrom(obj.GetType())) - { - type = impls[i]; - index = i; - break; - } - } - } - internal static T GetAnyParent(Thing thing) where T : class { - T t = thing as T; - if (t != null) + 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 (T)((object)null); + return null; } internal static string ThingHolderString(Thing thing) diff --git a/Source/Client/Syncing/Worker/SyncWorkers.cs b/Source/Client/Syncing/Worker/SyncWorkers.cs index 51d7294b..06b5bd47 100644 --- a/Source/Client/Syncing/Worker/SyncWorkers.cs +++ b/Source/Client/Syncing/Worker/SyncWorkers.cs @@ -269,7 +269,7 @@ public virtual bool TryGetValue(Type type, out SyncWorkerEntry syncWorkerEntry) if (syncWorkerEntry != null) return true; - + return false; } @@ -424,17 +424,17 @@ internal static class TypeRWHelper static TypeRWHelper() { - cache[typeof(IStoreSettingsParent)] = SyncSerialization.storageParents; - cache[typeof(IPlantToGrowSettable)] = SyncSerialization.plantToGrowSettables; + cache[typeof(IStoreSettingsParent)] = ImplSerialization.storageParents; + cache[typeof(IPlantToGrowSettable)] = ImplSerialization.plantToGrowSettables; - cache[typeof(ThingComp)] = SyncSerialization.thingCompTypes; - cache[typeof(AbilityComp)] = SyncSerialization.abilityCompTypes; - cache[typeof(Designator)] = SyncSerialization.designatorTypes; - cache[typeof(WorldObjectComp)] = SyncSerialization.worldObjectCompTypes; + cache[typeof(ThingComp)] = ImplSerialization.thingCompTypes; + cache[typeof(AbilityComp)] = ImplSerialization.abilityCompTypes; + cache[typeof(Designator)] = ImplSerialization.designatorTypes; + cache[typeof(WorldObjectComp)] = ImplSerialization.worldObjectCompTypes; - cache[typeof(GameComponent)] = SyncSerialization.gameCompTypes; - cache[typeof(WorldComponent)] = SyncSerialization.worldCompTypes; - cache[typeof(MapComponent)] = SyncSerialization.mapCompTypes; + cache[typeof(GameComponent)] = ImplSerialization.gameCompTypes; + cache[typeof(WorldComponent)] = ImplSerialization.worldCompTypes; + cache[typeof(MapComponent)] = ImplSerialization.mapCompTypes; } internal static void FlushCache() diff --git a/Source/Client/UI/MainMenuPatches.cs b/Source/Client/UI/MainMenuPatches.cs index a27257c1..58068ffa 100644 --- a/Source/Client/UI/MainMenuPatches.cs +++ b/Source/Client/UI/MainMenuPatches.cs @@ -117,6 +117,7 @@ static void ShowModDebugInfo() for (int i = 0; i < 200; i++) info.remoteMods.Add(info.remoteMods.Last()); info.remoteFiles.Add("rwmt.multiplayer", new ModFile() { relPath = "/Test/Test.xml" }); + //info.remoteFiles.Add("ludeon.rimworld", new ModFile() { relPath = "/Test/Test.xml" }); Find.WindowStack.Add(new JoinDataWindow(info)); } diff --git a/Source/Client/Util/SimpleProfiler.cs b/Source/Client/Util/SimpleProfiler.cs index ee35ef2a..b1745d12 100644 --- a/Source/Client/Util/SimpleProfiler.cs +++ b/Source/Client/Util/SimpleProfiler.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using UnityEngine; @@ -35,17 +36,20 @@ public static class SimpleProfiler public static bool available; public static bool running; + [MethodImpl(MethodImplOptions.NoInlining)] public static void CheckAvailable() { available = GetModuleHandle("simple_profiler.dll").ToInt32() != 0; } + [MethodImpl(MethodImplOptions.NoInlining)] public static void Init(string id) { if (!available) return; init_profiler(id); } + [MethodImpl(MethodImplOptions.NoInlining)] public static void Start() { if (!available) return; @@ -53,6 +57,7 @@ public static void Start() running = true; } + [MethodImpl(MethodImplOptions.NoInlining)] public static void Pause() { if (!available) return; @@ -60,6 +65,7 @@ public static void Pause() running = false; } + [MethodImpl(MethodImplOptions.NoInlining)] public static void Print(string file) { if (!available) return; @@ -239,4 +245,4 @@ public static bool IsOfGenericType(this Type typeToCheck, Type genericType, out } } } -} \ No newline at end of file +} diff --git a/Source/Client/Windows/DisconnectedWindow.cs b/Source/Client/Windows/DisconnectedWindow.cs index b3ee1c30..db94ef2f 100644 --- a/Source/Client/Windows/DisconnectedWindow.cs +++ b/Source/Client/Windows/DisconnectedWindow.cs @@ -11,10 +11,12 @@ namespace Multiplayer.Client [HotSwappable] public class DisconnectedWindow : Window { - public override Vector2 InitialSize => new(320f, info.specialButtonTranslated != null ? 210f : 160f); + public override Vector2 InitialSize => new(info.wideWindow ? 430f : 320f, height); - protected SessionDisconnectInfo info; + public override float Margin => 26f; + private float height; + protected SessionDisconnectInfo info; public bool returnToServerBrowser; public DisconnectedWindow(SessionDisconnectInfo info) @@ -37,19 +39,21 @@ public DisconnectedWindow(SessionDisconnectInfo info) public override void DoWindowContents(Rect inRect) { Text.Font = GameFont.Small; - - Text.Anchor = TextAnchor.MiddleCenter; - Rect labelRect = inRect; - - labelRect.yMax -= ButtonHeight + ButtonSpacing; - if (info.specialButtonTranslated != null) - labelRect.yMax -= ButtonHeight + ButtonSpacing; + Text.Anchor = TextAnchor.UpperCenter; var text = info.descTranslated.NullOrEmpty() ? info.titleTranslated : $"{info.titleTranslated}\n{info.descTranslated}"; - Widgets.Label(labelRect, text); + var buttonHeight = ButtonHeight + ButtonSpacing; + if (info.specialButtonTranslated != null) + buttonHeight += ButtonHeight + ButtonSpacing; + var textHeight = Text.CalcHeight(text, inRect.width); + height = textHeight + buttonHeight + Margin * 2; + + SetInitialSizeAndPosition(); + + Widgets.Label(inRect, text); Text.Anchor = TextAnchor.UpperLeft; DrawButtons(inRect); @@ -60,7 +64,7 @@ private void DrawButtons(Rect inRect) var isPlaying = Current.ProgramState != ProgramState.Entry; var buttonWidth = isPlaying ? 140f : 120f; - var buttonRect = new Rect((inRect.width - buttonWidth) / 2f, inRect.height - ButtonHeight - ButtonSpacing, buttonWidth, ButtonHeight); + var buttonRect = new Rect((inRect.width - buttonWidth) / 2f, inRect.height - ButtonHeight, buttonWidth, ButtonHeight); var buttonText = isPlaying ? "QuitToMainMenu" : "CloseButton"; if (Widgets.ButtonText(buttonRect, buttonText.Translate())) diff --git a/Source/Client/Windows/JoinDataWindow.cs b/Source/Client/Windows/JoinDataWindow.cs index 979a19da..cb7bf907 100644 --- a/Source/Client/Windows/JoinDataWindow.cs +++ b/Source/Client/Windows/JoinDataWindow.cs @@ -20,7 +20,7 @@ namespace Multiplayer.Client [HotSwappable] public class JoinDataWindow : Window { - public override Vector2 InitialSize => new(640f, 580f); + public override Vector2 InitialSize => new(660f, 610f); private enum Tab { @@ -216,7 +216,12 @@ void RefreshFiles() filesRoot, RefreshFiles, true, - MpUtil.TranslateWithDoubleNewLines("MpMismatchFilesInfo", 2), + "MpMismatchFilesInfo".Translate() + + "\n\n" + + "MpMismatchFilesInfoMods".Translate() + + (string)(HasCoreMismatches() ? "\n\n" + "MpMismatchFilesInfoCore".Translate() : "") + + "\n\n" + + "MpMismatchFilesInfoHost".Translate(), filesRoot.children.Any() ? null : "MpFilesMatch" ); else if (tab == Tab.Configs) @@ -263,6 +268,8 @@ void RefreshFiles() } } + private bool HasCoreMismatches() => filesRoot.children.Any(c => c.id.Contains("ludeon")); + private string DiffString() { var str = ""; @@ -353,8 +360,10 @@ private void DrawGeneralTab(Rect inRect) } } - Vector2 treeScroll; - int nodeCount; + private Vector2 treeScroll; + private int nodeCount; + private Vector2 descScroll; + private Dictionary strHeight = new(); static readonly Color Red = new(1f, 0.25f, 0.25f); static readonly Color Orange = new(1f, 0.5f, 0.25f); @@ -486,10 +495,17 @@ private void DrawTreeTab(Rect inRect, string labelKey, Node root, Action refresh GUI.EndGroup(); if (desc != null) - MpUI.Label( - new Rect(0, combined.yMax + 15f, combined.width, 0f).MaxY(inRect.yMax).CenteredOnXIn(inRect), - desc - ); + { + var scrollOut = new Rect(0, combined.yMax + 15f, combined.width, 0f).MaxY(inRect.yMax).CenteredOnXIn(inRect); + var height = Text.CalcHeight(desc, scrollOut.width - 16f); + var descRect = scrollOut.Width(scrollOut.width - 16f).Height(height); + + Widgets.BeginScrollView(scrollOut, ref descScroll, descRect); + { + MpUI.Label(descRect, desc); + } + Widgets.EndScrollView(); + } } Vector2 modScrollLeft; diff --git a/Source/Common/ScheduledCommand.cs b/Source/Common/ScheduledCommand.cs index 05a10135..ab769333 100644 --- a/Source/Common/ScheduledCommand.cs +++ b/Source/Common/ScheduledCommand.cs @@ -52,5 +52,10 @@ public static ScheduledCommand Deserialize(ByteReader data) return new ScheduledCommand(cmd, ticks, factionId, mapId, playerId, extraBytes); } + + public override string ToString() + { + return $"Cmd: {type}, faction: {factionId}, map: {mapId}, ticks: {ticks}, player: {playerId}"; + } } } diff --git a/Source/Common/Version.cs b/Source/Common/Version.cs index 011e4d0c..7b014919 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.6.0.3"; - public const int Protocol = 24; + public const string Version = "0.6.0.4"; + public const int Protocol = 25; public const string ApiAssemblyName = "0MultiplayerAPI"; diff --git a/Source/Multiplayer.sln.DotSettings b/Source/Multiplayer.sln.DotSettings index 3eb1506b..a5e3625a 100644 --- a/Source/Multiplayer.sln.DotSettings +++ b/Source/Multiplayer.sln.DotSettings @@ -45,6 +45,7 @@ True True True + True True True True