From cd108cb3968e89d78d4c90b9c87869a5035fe2ff Mon Sep 17 00:00:00 2001 From: brodrigz Date: Thu, 7 Nov 2024 23:22:55 -0300 Subject: [PATCH 1/6] Run CLI scaffolding command --- .../SiegeEvents/SiegeEventFieldTests.cs | 157 ++++++++++++++++++ .../SiegeEvents/SiegeEventLifetimeTests.cs | 75 ++++----- .../SiegeEvents/SiegeEventPropertyTests.cs | 65 ++++++++ .../Handlers/SiegeEventLifetimeHandler.cs | 91 ++++++++++ .../Lifetime/NetworkCreateSiegeEvent.cs | 20 +++ .../Lifetime/NetworkDestroySiegeEvent.cs | 20 +++ .../Messages/Lifetime/SiegeEventCreated.cs | 16 ++ .../Messages/Lifetime/SiegeEventDestroyed.cs | 16 ++ .../Patches/SiegeEventLifetimePatches.cs | 62 +++++++ .../SiegeEvents/SiegeEventRegistry.cs | 30 ++++ .../Services/SiegeEvents/SiegeEventSync.cs | 26 +++ 11 files changed, 531 insertions(+), 47 deletions(-) create mode 100644 source/E2E.Tests/Services/SiegeEvents/SiegeEventFieldTests.cs create mode 100644 source/E2E.Tests/Services/SiegeEvents/SiegeEventPropertyTests.cs create mode 100644 source/GameInterface/Services/SiegeEvents/Handlers/SiegeEventLifetimeHandler.cs create mode 100644 source/GameInterface/Services/SiegeEvents/Messages/Lifetime/NetworkCreateSiegeEvent.cs create mode 100644 source/GameInterface/Services/SiegeEvents/Messages/Lifetime/NetworkDestroySiegeEvent.cs create mode 100644 source/GameInterface/Services/SiegeEvents/Messages/Lifetime/SiegeEventCreated.cs create mode 100644 source/GameInterface/Services/SiegeEvents/Messages/Lifetime/SiegeEventDestroyed.cs create mode 100644 source/GameInterface/Services/SiegeEvents/Patches/SiegeEventLifetimePatches.cs create mode 100644 source/GameInterface/Services/SiegeEvents/SiegeEventRegistry.cs create mode 100644 source/GameInterface/Services/SiegeEvents/SiegeEventSync.cs diff --git a/source/E2E.Tests/Services/SiegeEvents/SiegeEventFieldTests.cs b/source/E2E.Tests/Services/SiegeEvents/SiegeEventFieldTests.cs new file mode 100644 index 000000000..6d22fd903 --- /dev/null +++ b/source/E2E.Tests/Services/SiegeEvents/SiegeEventFieldTests.cs @@ -0,0 +1,157 @@ +using TaleWorlds.CampaignSystem.Siege; +using TaleWorlds.CampaignSystem.Settlements; +using System; +using E2E.Tests.Environment; +using E2E.Tests.Environment.Instance; +using E2E.Tests.Util; +using HarmonyLib; +using System.Reflection; +using Xunit.Abstractions; +using static Common.Extensions.ReflectionExtensions; +using Common.Util; +using TaleWorlds.CampaignSystem.Party; + +namespace E2E.Tests.Services.SiegeEvents; + +public class SiegeEventFieldTests : IDisposable +{ + private readonly List disabledMethods; + private E2ETestEnvironment TestEnvironment { get; } + private EnvironmentInstance Server => TestEnvironment.Server; + private IEnumerable Clients => TestEnvironment.Clients; + private IEnumerable AllEnvironmentInstances => Clients.Append(Server); + + private readonly string siegeEventId; + + public SiegeEventFieldTests(ITestOutputHelper output) + { + TestEnvironment = new E2ETestEnvironment(output); + + disabledMethods = new List + { + // Add disabled methods + }; + + // Create SiegeEvent on the server + siegeEventId = TestEnvironment.CreateRegisteredObject(disabledMethods); + } + + public void Dispose() + { + TestEnvironment.Dispose(); + } + + + [Fact] + public void ServerChangeSiegeEventBesiegedSettlement_SyncAllClients() + { + // Arrange + var field = AccessTools.Field(typeof(SiegeEvent), nameof(SiegeEvent.BesiegedSettlement)); + var intercept = TestEnvironment.GetIntercept(field); + + /// Create instances on server + Assert.True(Server.ObjectManager.AddNewObject(ObjectHelper.SkipConstructor(), out var besiegedSettlementId)); + + /// Create instances on all clients + foreach (var client in Clients) + { + var clientBesiegedSettlement = ObjectHelper.SkipConstructor(); + Assert.True(client.ObjectManager.AddExisting(besiegedSettlementId, clientBesiegedSettlement)); + } + + // Act + Server.Call(() => + { + Assert.True(Server.ObjectManager.TryGetObject(siegeEventId, out var SiegeEvent)); + Assert.True(Server.ObjectManager.TryGetObject(besiegedSettlementId, out var serverBesiegedSettlement)); + + Assert.Null(SiegeEvent.BesiegedSettlement); + + /// Simulate the field changing + intercept.Invoke(null, new object[] { SiegeEvent, serverBesiegedSettlement}); + + Assert.Same(serverBesiegedSettlement, SiegeEvent.BesiegedSettlement); + }); + + // Assert + foreach (var client in Clients) + { + Assert.True(client.ObjectManager.TryGetObject(siegeEventId, out var SiegeEvent)); + + Assert.True(client.ObjectManager.TryGetObject(besiegedSettlementId, out var clientBesiegedSettlement)); + + Assert.True(clientBesiegedSettlement == SiegeEvent.BesiegedSettlement); + } + } + + [Fact] + public void ServerChangeSiegeEventBesiegerCamp_SyncAllClients() + { + // Arrange + var field = AccessTools.Field(typeof(SiegeEvent), nameof(SiegeEvent.BesiegerCamp)); + var intercept = TestEnvironment.GetIntercept(field); + + /// Create instances on server + Assert.True(Server.ObjectManager.AddNewObject(ObjectHelper.SkipConstructor(), out var besiegerCampId)); + + /// Create instances on all clients + foreach (var client in Clients) + { + var clientBesiegerCamp = ObjectHelper.SkipConstructor(); + Assert.True(client.ObjectManager.AddExisting(besiegerCampId, clientBesiegerCamp)); + } + + // Act + Server.Call(() => + { + Assert.True(Server.ObjectManager.TryGetObject(siegeEventId, out var SiegeEvent)); + Assert.True(Server.ObjectManager.TryGetObject(besiegerCampId, out var serverBesiegerCamp)); + + Assert.Null(SiegeEvent.BesiegerCamp); + + /// Simulate the field changing + intercept.Invoke(null, new object[] { SiegeEvent, serverBesiegerCamp}); + + Assert.Same(serverBesiegerCamp, SiegeEvent.BesiegerCamp); + }); + + // Assert + foreach (var client in Clients) + { + Assert.True(client.ObjectManager.TryGetObject(siegeEventId, out var SiegeEvent)); + + Assert.True(client.ObjectManager.TryGetObject(besiegerCampId, out var clientBesiegerCamp)); + + Assert.True(clientBesiegerCamp == SiegeEvent.BesiegerCamp); + } + } + + + [Fact] + public void ServerChangeSiegeEventIsBesiegerDefeated_SyncAllClients() + { + // Arrange + var field = AccessTools.Field(typeof(SiegeEvent), nameof(SiegeEvent._isBesiegerDefeated)); + var intercept = TestEnvironment.GetIntercept(field); + Assert.True(Server.ObjectManager.TryGetObject(siegeEventId, out var serverSiegeEvent)); + var newValue=Random(); + + // Act + Server.Call(() => + { + /// Simulate the field changing + intercept.Invoke(null, new object[] { serverSiegeEvent, newValue }); + + Assert.Equal(newValue, serverSiegeEvent._isBesiegerDefeated); + }); + + // Assert + foreach (var client in TestEnvironment.Clients) + { + Assert.True(client.ObjectManager.TryGetObject(siegeEventId, out var clientSiegeEvent)); + Assert.Equal(serverSiegeEvent._isBesiegerDefeated, clientSiegeEvent._isBesiegerDefeated); + } + } + } + + \ No newline at end of file diff --git a/source/E2E.Tests/Services/SiegeEvents/SiegeEventLifetimeTests.cs b/source/E2E.Tests/Services/SiegeEvents/SiegeEventLifetimeTests.cs index c20be3c65..f351829a0 100644 --- a/source/E2E.Tests/Services/SiegeEvents/SiegeEventLifetimeTests.cs +++ b/source/E2E.Tests/Services/SiegeEvents/SiegeEventLifetimeTests.cs @@ -1,19 +1,23 @@ -using E2E.Tests.Environment; +using TaleWorlds.CampaignSystem.Siege; +using E2E.Tests.Environment; +using E2E.Tests.Environment.Instance; using E2E.Tests.Util; using HarmonyLib; +using Common.Util; using System.Reflection; -using TaleWorlds.CampaignSystem.MapEvents; -using TaleWorlds.CampaignSystem.Party; -using TaleWorlds.CampaignSystem.Settlements; -using TaleWorlds.CampaignSystem.Siege; using Xunit.Abstractions; +using static Common.Extensions.ReflectionExtensions; +using TaleWorlds.CampaignSystem.Party; namespace E2E.Tests.Services.SiegeEvents; + public class SiegeEventLifetimeTests : IDisposable { - E2ETestEnvironment TestEnvironment { get; } - - private List disabledMethods; + private readonly List disabledMethods; + private E2ETestEnvironment TestEnvironment { get; } + private EnvironmentInstance Server => TestEnvironment.Server; + private IEnumerable Clients => TestEnvironment.Clients; + private IEnumerable AllEnvironmentInstances => Clients.Append(Server); public SiegeEventLifetimeTests(ITestOutputHelper output) { @@ -21,11 +25,8 @@ public SiegeEventLifetimeTests(ITestOutputHelper output) disabledMethods = new List { - AccessTools.Method(typeof(MobileParty), nameof(MobileParty.OnPartyJoinedSiegeInternal)), + // Add disabled methods }; - - disabledMethods.AddRange(AccessTools.GetDeclaredConstructors(typeof(SiegeEvent))); - } public void Dispose() @@ -33,21 +34,19 @@ public void Dispose() TestEnvironment.Dispose(); } - [Fact] - public void ServerCreate_SiegeEvent_SyncAllClients() + [Fact] + public void ServerCreateSiegeEvent_SyncAllClients() { // Arrange - var server = TestEnvironment.Server; + string? siegeEventId = null; // Act - string? siegeEventId = null; - server.Call((Action)(() => + Server.Call(() => { var siegeEvent = GameObjectCreator.CreateInitializedObject(); - - Assert.True(server.ObjectManager.TryGetId(siegeEvent, out siegeEventId)); - }), - disabledMethods: disabledMethods); + Assert.True(Server.ObjectManager.TryGetId(siegeEvent, out siegeEventId)); + }, disabledMethods + ); // Assert Assert.NotNull(siegeEventId); @@ -59,41 +58,23 @@ public void ServerCreate_SiegeEvent_SyncAllClients() } [Fact] - public void ClientCreate_SiegeEvent_DoesNothing() + public void ClientCreateSiegeEvent_DoesNothing() { // Arrange - var server = TestEnvironment.Server; - - string? settlementId = null; - string? mobilePartyId = null; - server.Call(() => - { - var settlement = GameObjectCreator.CreateInitializedObject(); - var mobileParty = GameObjectCreator.CreateInitializedObject(); - - Assert.True(server.ObjectManager.TryGetId(settlement, out settlementId)); - Assert.True(server.ObjectManager.TryGetId(mobileParty, out mobilePartyId)); - }); - - Assert.NotNull(settlementId); - Assert.NotNull(mobilePartyId); + string? clientSiegeEventId = null; // Act - string? clientBeseigerCampId = null; - var firstClient = TestEnvironment.Clients.First(); firstClient.Call(() => { - Assert.True(firstClient.ObjectManager.TryGetObject(settlementId, out var settlement)); - Assert.True(firstClient.ObjectManager.TryGetObject(mobilePartyId, out var mobileParty)); - - var SiegeEvent = new SiegeEvent(settlement, mobileParty); + var SiegeEvent = ObjectHelper.SkipConstructor(); - Assert.False(firstClient.ObjectManager.TryGetId(SiegeEvent, out clientBeseigerCampId)); - }, - disabledMethods: disabledMethods); + Assert.False(firstClient.ObjectManager.TryGetId(SiegeEvent, out clientSiegeEventId)); + }); // Assert - Assert.Null(clientBeseigerCampId); + Assert.Null(clientSiegeEventId); } } + + \ No newline at end of file diff --git a/source/E2E.Tests/Services/SiegeEvents/SiegeEventPropertyTests.cs b/source/E2E.Tests/Services/SiegeEvents/SiegeEventPropertyTests.cs new file mode 100644 index 000000000..4f6e5c346 --- /dev/null +++ b/source/E2E.Tests/Services/SiegeEvents/SiegeEventPropertyTests.cs @@ -0,0 +1,65 @@ +using TaleWorlds.CampaignSystem.Siege; +using TaleWorlds.CampaignSystem; +using E2E.Tests.Environment; +using E2E.Tests.Environment.Instance; +using E2E.Tests.Util; +using HarmonyLib; +using System.Reflection; +using Xunit.Abstractions; +using Common.Util; +using static Common.Extensions.ReflectionExtensions; +using TaleWorlds.CampaignSystem.Party; + +namespace E2E.Tests.Services.SiegeEvents; + +public class SiegeEventPropertyTests : IDisposable +{ + private readonly List disabledMethods; + private E2ETestEnvironment TestEnvironment { get; } + private EnvironmentInstance Server => TestEnvironment.Server; + private IEnumerable Clients => TestEnvironment.Clients; + private IEnumerable AllEnvironmentInstances => Clients.Append(Server); + + private readonly string siegeEventId; + + public SiegeEventPropertyTests(ITestOutputHelper output) + { + TestEnvironment = new E2ETestEnvironment(output); + + disabledMethods = new List + { + // Add disabled methods + }; + + // Create SiegeEvent on the server + siegeEventId = TestEnvironment.CreateRegisteredObject(disabledMethods); + } + + public void Dispose() + { + TestEnvironment.Dispose(); + } + + + [Fact] + public void ServerChangeSiegeEventSiegeStartTime_SyncAllClients() + { + // Arrange + Assert.True(Server.ObjectManager.TryGetObject(siegeEventId, out var serverSiegeEvent)); + var newValue=Random(); + + // Act + Server.Call(() => + { + serverSiegeEvent.SiegeStartTime = newValue; + }); + + // Assert + foreach (var client in TestEnvironment.Clients) + { + Assert.True(client.ObjectManager.TryGetObject(siegeEventId, out var clientSiegeEvent)); + Assert.Equal(serverSiegeEvent.SiegeStartTime, clientSiegeEvent.SiegeStartTime); + } + } + +} diff --git a/source/GameInterface/Services/SiegeEvents/Handlers/SiegeEventLifetimeHandler.cs b/source/GameInterface/Services/SiegeEvents/Handlers/SiegeEventLifetimeHandler.cs new file mode 100644 index 000000000..6faa76253 --- /dev/null +++ b/source/GameInterface/Services/SiegeEvents/Handlers/SiegeEventLifetimeHandler.cs @@ -0,0 +1,91 @@ +using Common.Logging; +using Common.Messaging; +using Common.Network; +using Common.Util; +using GameInterface.Services.SiegeEvents.Messages; +using GameInterface.Services.ObjectManager; +using HarmonyLib; +using Serilog; +using TaleWorlds.Library; +using TaleWorlds.CampaignSystem.Siege; +namespace GameInterface.Services.SiegeEvents.Handlers; + + +/// +/// Lifetime handler for objects. +/// +internal class SiegeEventLifetimeHandler: IHandler +{ + private static readonly ILogger Logger = LogManager.GetLogger(); + private readonly IMessageBroker messageBroker; + private readonly INetwork network; + private readonly IObjectManager objectManager; + public SiegeEventLifetimeHandler(IMessageBroker messageBroker, INetwork network, IObjectManager objectManager) + { + this.messageBroker = messageBroker; + this.network = network; + this.objectManager = objectManager; + messageBroker.Subscribe(HandleCreatedEvent); + messageBroker.Subscribe(HandleCreateCommand); + messageBroker.Subscribe(HandleDestroyedEvent); + messageBroker.Subscribe(HandleDestroyCommand); + } + + public void Dispose() + { + messageBroker.Unsubscribe(HandleCreatedEvent); + messageBroker.Unsubscribe(HandleCreateCommand); + } + + private void HandleCreatedEvent(MessagePayload payload) + { + if (!objectManager.AddNewObject(payload.What.Instance, out var id)) + { + Logger.Error("Failed to AddNewObject on {EventHandler}", nameof(SiegeEventCreated)); + return; + } + + network.SendAll(new NetworkCreateSiegeEvent(id)); + } + + private void HandleCreateCommand(MessagePayload payload) + { + var newSiegeEvent = ObjectHelper.SkipConstructor(); + + // TODO:Initialize null lists! + + if (!objectManager.AddExisting(payload.What.SiegeEventId, newSiegeEvent)) + { + Logger.Error("Failed to create {ObjectName} on {EventHandler}", nameof(SiegeEvent), nameof(NetworkCreateSiegeEvent)); + return; + } + } + + private void HandleDestroyedEvent(MessagePayload payload) + { + var obj = payload.What.Instance; + + if (!objectManager.TryGetId(obj, out var id)) return; + + if (!objectManager.Remove(obj)) + { + Logger.Error("Unable to remove {ObjectName} with id: {Id} on {EventHandler}", nameof(SiegeEvent), id, nameof(SiegeEventDestroyed)); + return; + } + + network.SendAll(new NetworkDestroySiegeEvent(id)); + } + + private void HandleDestroyCommand(MessagePayload payload) + { + var id = payload.What.SiegeEventId; + + if (!objectManager.TryGetObject(id, out var obj)) return; + + if (!objectManager.Remove(obj)) + { + Logger.Error("Failed to remove {ObjectName} with Id: {Id} on {EventHandler}", nameof(SiegeEvent), id, nameof(NetworkDestroySiegeEvent)); + return; + } + } +} diff --git a/source/GameInterface/Services/SiegeEvents/Messages/Lifetime/NetworkCreateSiegeEvent.cs b/source/GameInterface/Services/SiegeEvents/Messages/Lifetime/NetworkCreateSiegeEvent.cs new file mode 100644 index 000000000..07b02ef65 --- /dev/null +++ b/source/GameInterface/Services/SiegeEvents/Messages/Lifetime/NetworkCreateSiegeEvent.cs @@ -0,0 +1,20 @@ +using Common.Messaging; +using ProtoBuf; +using TaleWorlds.CampaignSystem.Siege; +namespace GameInterface.Services.SiegeEvents.Messages; + + +/// +/// An event published to clients, commanding them to create a SiegeEvent. +/// +[ProtoContract(SkipConstructor = true)] +internal class NetworkCreateSiegeEvent : ICommand +{ + [ProtoMember(1)] + public string SiegeEventId { get; } + + public NetworkCreateSiegeEvent(string siegeEventId) + { + SiegeEventId = siegeEventId; + } +} diff --git a/source/GameInterface/Services/SiegeEvents/Messages/Lifetime/NetworkDestroySiegeEvent.cs b/source/GameInterface/Services/SiegeEvents/Messages/Lifetime/NetworkDestroySiegeEvent.cs new file mode 100644 index 000000000..9c0f8a737 --- /dev/null +++ b/source/GameInterface/Services/SiegeEvents/Messages/Lifetime/NetworkDestroySiegeEvent.cs @@ -0,0 +1,20 @@ +using Common.Messaging; +using ProtoBuf; +using TaleWorlds.CampaignSystem.Siege; +namespace GameInterface.Services.SiegeEvents.Messages; + + +/// +/// An event published to clients, commanding them to destroy a SiegeEvent. +/// +[ProtoContract(SkipConstructor = true)] +internal class NetworkDestroySiegeEvent : ICommand +{ + [ProtoMember(1)] + public string SiegeEventId { get; } + + public NetworkDestroySiegeEvent(string siegeEventId) + { + SiegeEventId = siegeEventId; + } +} diff --git a/source/GameInterface/Services/SiegeEvents/Messages/Lifetime/SiegeEventCreated.cs b/source/GameInterface/Services/SiegeEvents/Messages/Lifetime/SiegeEventCreated.cs new file mode 100644 index 000000000..bed35b0c7 --- /dev/null +++ b/source/GameInterface/Services/SiegeEvents/Messages/Lifetime/SiegeEventCreated.cs @@ -0,0 +1,16 @@ +using Common.Messaging; +using TaleWorlds.CampaignSystem.Siege; +namespace GameInterface.Services.SiegeEvents.Messages; + + +/// +/// An event that is published internally when a SiegeEvent is created. +/// +internal class SiegeEventCreated : IEvent +{ + public SiegeEventCreated(SiegeEvent instance) + { + Instance = instance; + } + public SiegeEvent Instance { get; } +} \ No newline at end of file diff --git a/source/GameInterface/Services/SiegeEvents/Messages/Lifetime/SiegeEventDestroyed.cs b/source/GameInterface/Services/SiegeEvents/Messages/Lifetime/SiegeEventDestroyed.cs new file mode 100644 index 000000000..e15a9c4d7 --- /dev/null +++ b/source/GameInterface/Services/SiegeEvents/Messages/Lifetime/SiegeEventDestroyed.cs @@ -0,0 +1,16 @@ +using Common.Messaging; +using TaleWorlds.CampaignSystem.Siege; +namespace GameInterface.Services.SiegeEvents.Messages; + + +/// +/// An event that is published internally when a SiegeEvent is destroyed. +/// +internal class SiegeEventDestroyed : IEvent +{ + public SiegeEventDestroyed(SiegeEvent instance) + { + Instance = instance; + } + public SiegeEvent Instance { get; } +} \ No newline at end of file diff --git a/source/GameInterface/Services/SiegeEvents/Patches/SiegeEventLifetimePatches.cs b/source/GameInterface/Services/SiegeEvents/Patches/SiegeEventLifetimePatches.cs new file mode 100644 index 000000000..298567808 --- /dev/null +++ b/source/GameInterface/Services/SiegeEvents/Patches/SiegeEventLifetimePatches.cs @@ -0,0 +1,62 @@ +using Common.Logging; +using Common.Messaging; +using GameInterface.Policies; +using GameInterface.Services.SiegeEvents.Messages; +using HarmonyLib; +using Serilog; +using System; +using System.Collections.Generic; +using System.Reflection; +using TaleWorlds.CampaignSystem.Siege; +namespace GameInterface.Services.SiegeEvents.Patches; + + +/// +/// Patches for managing lifetime of objects. +/// +[HarmonyPatch] +internal class SiegeEventLifetimePatches +{ + static readonly ILogger Logger = LogManager.GetLogger(); + + static IEnumerable TargetMethods() => AccessTools.GetDeclaredConstructors(typeof(SiegeEvent)); + + static bool Prefix(ref SiegeEvent __instance) + { + // Call original if we call this function + if (CallOriginalPolicy.IsOriginalAllowed()) return true; + + if (ModInformation.IsClient) + { + Logger.Error("Client created unmanaged {name}\n" + + "Callstack: {callstack}", typeof(SiegeEvent), Environment.StackTrace); + return true; + } + + var message = new SiegeEventCreated(__instance); + + MessageBroker.Instance.Publish(__instance, message); + + return true; + } + + + //[HarmonyPatch(typeof(SiegeEvent), "Remove method name here!")] + //[HarmonyPrefix()] + private static bool RemovePrefix(ref SiegeEvent __instance) + { + // Call original if we call this function + if (CallOriginalPolicy.IsOriginalAllowed()) return true; + + if (ModInformation.IsClient) + { + Logger.Error("Client destroyed unmanaged {name}\n" + + "Callstack: {callstack}", typeof(SiegeEvent), Environment.StackTrace); + return false; + } + + MessageBroker.Instance.Publish(__instance, new SiegeEventDestroyed(__instance)); + + return true; + } +} diff --git a/source/GameInterface/Services/SiegeEvents/SiegeEventRegistry.cs b/source/GameInterface/Services/SiegeEvents/SiegeEventRegistry.cs new file mode 100644 index 000000000..315744dbc --- /dev/null +++ b/source/GameInterface/Services/SiegeEvents/SiegeEventRegistry.cs @@ -0,0 +1,30 @@ +using GameInterface.Services.Registry; +using System.Linq; +using System.Threading; +using TaleWorlds.CampaignSystem.Siege; + +namespace GameInterface.Services.SiegeEvents; + + +/// +/// Registry manager for SiegeEvent +/// +internal class SiegeEventRegistry : RegistryBase +{ + private const string SiegeEventIdPrefix = "CoopSiegeEvent"; + private static int InstanceCounter = 0; + + public SiegeEventRegistry (IRegistryCollection collection) : base(collection) + { + } + + public override void RegisterAll() + { + // Implement RegisterAll if needed + } + + protected override string GetNewId(SiegeEvent obj) + { + return $"{SiegeEventIdPrefix}_{Interlocked.Increment(ref InstanceCounter)}"; + } +} \ No newline at end of file diff --git a/source/GameInterface/Services/SiegeEvents/SiegeEventSync.cs b/source/GameInterface/Services/SiegeEvents/SiegeEventSync.cs new file mode 100644 index 000000000..67a16a672 --- /dev/null +++ b/source/GameInterface/Services/SiegeEvents/SiegeEventSync.cs @@ -0,0 +1,26 @@ +using TaleWorlds.CampaignSystem.Siege; +using TaleWorlds.CampaignSystem.Settlements; +using System; +using TaleWorlds.CampaignSystem; +using GameInterface.AutoSync; +using HarmonyLib; + +namespace GameInterface.Services.SiegeEvents; + + +/// +/// Configures AutoSync for SiegeEvent +/// +internal class SiegeEventSync : IAutoSync +{ + public SiegeEventSync(IAutoSyncBuilder autoSyncBuilder) + { + // Fields + autoSyncBuilder.AddField(AccessTools.Field(typeof(SiegeEvent), nameof(SiegeEvent.BesiegedSettlement)));// WARNING: BesiegedSettlement is a public field, for AutoSync to work you must also add any methods outside declaring class that change its value + autoSyncBuilder.AddField(AccessTools.Field(typeof(SiegeEvent), nameof(SiegeEvent.BesiegerCamp)));// WARNING: BesiegerCamp is a public field, for AutoSync to work you must also add any methods outside declaring class that change its value + autoSyncBuilder.AddField(AccessTools.Field(typeof(SiegeEvent), nameof(SiegeEvent._isBesiegerDefeated))); + + // Properties + autoSyncBuilder.AddProperty(AccessTools.Property(typeof(SiegeEvent), nameof(SiegeEvent.SiegeStartTime))); + } +} From 3fd178c878a64f726a172ca2f49c99ce8cfe7a50 Mon Sep 17 00:00:00 2001 From: brodrigz Date: Thu, 7 Nov 2024 23:23:57 -0300 Subject: [PATCH 2/6] Restore SiegeEvent RegisterAll --- .../SiegeEvents/SiegeEventRegistry.cs | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/source/GameInterface/Services/SiegeEvents/SiegeEventRegistry.cs b/source/GameInterface/Services/SiegeEvents/SiegeEventRegistry.cs index 315744dbc..fdc59cf91 100644 --- a/source/GameInterface/Services/SiegeEvents/SiegeEventRegistry.cs +++ b/source/GameInterface/Services/SiegeEvents/SiegeEventRegistry.cs @@ -1,6 +1,7 @@ using GameInterface.Services.Registry; using System.Linq; using System.Threading; +using TaleWorlds.CampaignSystem; using TaleWorlds.CampaignSystem.Siege; namespace GameInterface.Services.SiegeEvents; @@ -11,20 +12,23 @@ namespace GameInterface.Services.SiegeEvents; /// internal class SiegeEventRegistry : RegistryBase { - private const string SiegeEventIdPrefix = "CoopSiegeEvent"; - private static int InstanceCounter = 0; + private const string SiegeEventIdPrefix = "CoopSiegeEvent"; + private static int InstanceCounter = 0; - public SiegeEventRegistry (IRegistryCollection collection) : base(collection) - { - } + public SiegeEventRegistry(IRegistryCollection collection) : base(collection) + { + } - public override void RegisterAll() - { - // Implement RegisterAll if needed - } + public override void RegisterAll() + { + foreach (var siegeEvent in Campaign.Current.SiegeEventManager.SiegeEvents) + { + RegisterNewObject(siegeEvent, out _); + } + } - protected override string GetNewId(SiegeEvent obj) - { - return $"{SiegeEventIdPrefix}_{Interlocked.Increment(ref InstanceCounter)}"; - } + protected override string GetNewId(SiegeEvent obj) + { + return $"{SiegeEventIdPrefix}_{Interlocked.Increment(ref InstanceCounter)}"; + } } \ No newline at end of file From 879093d513807554edf8a591a6fb77bb8120b2e6 Mon Sep 17 00:00:00 2001 From: brodrigz Date: Thu, 7 Nov 2024 23:24:38 -0300 Subject: [PATCH 3/6] Restore disabled methods --- source/E2E.Tests/Services/SiegeEvents/SiegeEventFieldTests.cs | 4 +++- .../E2E.Tests/Services/SiegeEvents/SiegeEventLifetimeTests.cs | 4 +++- .../E2E.Tests/Services/SiegeEvents/SiegeEventPropertyTests.cs | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/source/E2E.Tests/Services/SiegeEvents/SiegeEventFieldTests.cs b/source/E2E.Tests/Services/SiegeEvents/SiegeEventFieldTests.cs index 6d22fd903..503a2ce69 100644 --- a/source/E2E.Tests/Services/SiegeEvents/SiegeEventFieldTests.cs +++ b/source/E2E.Tests/Services/SiegeEvents/SiegeEventFieldTests.cs @@ -29,9 +29,11 @@ public SiegeEventFieldTests(ITestOutputHelper output) disabledMethods = new List { - // Add disabled methods + AccessTools.Method(typeof(MobileParty), nameof(MobileParty.OnPartyJoinedSiegeInternal)), }; + disabledMethods.AddRange(AccessTools.GetDeclaredConstructors(typeof(SiegeEvent))); + // Create SiegeEvent on the server siegeEventId = TestEnvironment.CreateRegisteredObject(disabledMethods); } diff --git a/source/E2E.Tests/Services/SiegeEvents/SiegeEventLifetimeTests.cs b/source/E2E.Tests/Services/SiegeEvents/SiegeEventLifetimeTests.cs index f351829a0..43028048b 100644 --- a/source/E2E.Tests/Services/SiegeEvents/SiegeEventLifetimeTests.cs +++ b/source/E2E.Tests/Services/SiegeEvents/SiegeEventLifetimeTests.cs @@ -25,8 +25,10 @@ public SiegeEventLifetimeTests(ITestOutputHelper output) disabledMethods = new List { - // Add disabled methods + AccessTools.Method(typeof(MobileParty), nameof(MobileParty.OnPartyJoinedSiegeInternal)), }; + + disabledMethods.AddRange(AccessTools.GetDeclaredConstructors(typeof(SiegeEvent))); } public void Dispose() diff --git a/source/E2E.Tests/Services/SiegeEvents/SiegeEventPropertyTests.cs b/source/E2E.Tests/Services/SiegeEvents/SiegeEventPropertyTests.cs index 4f6e5c346..4cbc1609f 100644 --- a/source/E2E.Tests/Services/SiegeEvents/SiegeEventPropertyTests.cs +++ b/source/E2E.Tests/Services/SiegeEvents/SiegeEventPropertyTests.cs @@ -28,9 +28,11 @@ public SiegeEventPropertyTests(ITestOutputHelper output) disabledMethods = new List { - // Add disabled methods + AccessTools.Method(typeof(MobileParty), nameof(MobileParty.OnPartyJoinedSiegeInternal)), }; + disabledMethods.AddRange(AccessTools.GetDeclaredConstructors(typeof(SiegeEvent))); + // Create SiegeEvent on the server siegeEventId = TestEnvironment.CreateRegisteredObject(disabledMethods); } From 1d89d0cf690dee9ad2b25348d50617a6865c6dc1 Mon Sep 17 00:00:00 2001 From: brodrigz Date: Thu, 7 Nov 2024 23:25:19 -0300 Subject: [PATCH 4/6] Remove old files --- .../Handlers/SettlementLifetimeHandler.cs | 1 - .../DisableSiegeAftermathCampaignBehavior.cs | 2 +- .../DisableSiegeAmbushCampaignBehavior.cs | 2 +- .../DisableSiegeEventCampaignBehavior.cs | 2 +- .../Handlers/SiegeEventLifetimeHandler.cs | 48 ------------------- .../Messages/NetworkCreateSiegeEvent.cs | 16 ------- .../Sieges/Messages/SiegeEventCreated.cs | 15 ------ .../Patches/SeigeEventLifetimePatches.cs | 41 ---------------- .../Services/Sieges/SiegeEventRegistry.cs | 30 ------------ 9 files changed, 3 insertions(+), 154 deletions(-) rename source/GameInterface/Services/{Sieges => SiegeEvents}/Patches/Disable/DisableSiegeAftermathCampaignBehavior.cs (82%) rename source/GameInterface/Services/{Sieges => SiegeEvents}/Patches/Disable/DisableSiegeAmbushCampaignBehavior.cs (82%) rename source/GameInterface/Services/{Sieges => SiegeEvents}/Patches/Disable/DisableSiegeEventCampaignBehavior.cs (82%) delete mode 100644 source/GameInterface/Services/Sieges/Handlers/SiegeEventLifetimeHandler.cs delete mode 100644 source/GameInterface/Services/Sieges/Messages/NetworkCreateSiegeEvent.cs delete mode 100644 source/GameInterface/Services/Sieges/Messages/SiegeEventCreated.cs delete mode 100644 source/GameInterface/Services/Sieges/Patches/SeigeEventLifetimePatches.cs delete mode 100644 source/GameInterface/Services/Sieges/SiegeEventRegistry.cs diff --git a/source/GameInterface/Services/Settlements/Handlers/SettlementLifetimeHandler.cs b/source/GameInterface/Services/Settlements/Handlers/SettlementLifetimeHandler.cs index 362c09a21..24a9d6ea7 100644 --- a/source/GameInterface/Services/Settlements/Handlers/SettlementLifetimeHandler.cs +++ b/source/GameInterface/Services/Settlements/Handlers/SettlementLifetimeHandler.cs @@ -4,7 +4,6 @@ using Common.Util; using GameInterface.Services.ObjectManager; using GameInterface.Services.Settlements.Messages; -using GameInterface.Services.Sieges.Messages; using TaleWorlds.CampaignSystem.Settlements; using TaleWorlds.CampaignSystem.Siege; diff --git a/source/GameInterface/Services/Sieges/Patches/Disable/DisableSiegeAftermathCampaignBehavior.cs b/source/GameInterface/Services/SiegeEvents/Patches/Disable/DisableSiegeAftermathCampaignBehavior.cs similarity index 82% rename from source/GameInterface/Services/Sieges/Patches/Disable/DisableSiegeAftermathCampaignBehavior.cs rename to source/GameInterface/Services/SiegeEvents/Patches/Disable/DisableSiegeAftermathCampaignBehavior.cs index ce7f691f7..045f789db 100644 --- a/source/GameInterface/Services/Sieges/Patches/Disable/DisableSiegeAftermathCampaignBehavior.cs +++ b/source/GameInterface/Services/SiegeEvents/Patches/Disable/DisableSiegeAftermathCampaignBehavior.cs @@ -1,7 +1,7 @@ using HarmonyLib; using TaleWorlds.CampaignSystem.CampaignBehaviors; -namespace GameInterface.Services.Sieges.Patches.Disable; +namespace GameInterface.Services.SiegeEvents.Patches.Disable; [HarmonyPatch(typeof(SiegeAftermathCampaignBehavior))] internal class DisableSiegeAftermathCampaignBehavior diff --git a/source/GameInterface/Services/Sieges/Patches/Disable/DisableSiegeAmbushCampaignBehavior.cs b/source/GameInterface/Services/SiegeEvents/Patches/Disable/DisableSiegeAmbushCampaignBehavior.cs similarity index 82% rename from source/GameInterface/Services/Sieges/Patches/Disable/DisableSiegeAmbushCampaignBehavior.cs rename to source/GameInterface/Services/SiegeEvents/Patches/Disable/DisableSiegeAmbushCampaignBehavior.cs index 76d64a26c..364590155 100644 --- a/source/GameInterface/Services/Sieges/Patches/Disable/DisableSiegeAmbushCampaignBehavior.cs +++ b/source/GameInterface/Services/SiegeEvents/Patches/Disable/DisableSiegeAmbushCampaignBehavior.cs @@ -1,7 +1,7 @@ using HarmonyLib; using TaleWorlds.CampaignSystem.CampaignBehaviors; -namespace GameInterface.Services.Sieges.Patches.Disable; +namespace GameInterface.Services.SiegeEvents.Patches.Disable; [HarmonyPatch(typeof(SiegeAmbushCampaignBehavior))] internal class DisableSiegeAmbushCampaignBehavior diff --git a/source/GameInterface/Services/Sieges/Patches/Disable/DisableSiegeEventCampaignBehavior.cs b/source/GameInterface/Services/SiegeEvents/Patches/Disable/DisableSiegeEventCampaignBehavior.cs similarity index 82% rename from source/GameInterface/Services/Sieges/Patches/Disable/DisableSiegeEventCampaignBehavior.cs rename to source/GameInterface/Services/SiegeEvents/Patches/Disable/DisableSiegeEventCampaignBehavior.cs index 806a5557a..103074a13 100644 --- a/source/GameInterface/Services/Sieges/Patches/Disable/DisableSiegeEventCampaignBehavior.cs +++ b/source/GameInterface/Services/SiegeEvents/Patches/Disable/DisableSiegeEventCampaignBehavior.cs @@ -1,7 +1,7 @@ using HarmonyLib; using TaleWorlds.CampaignSystem.CampaignBehaviors; -namespace GameInterface.Services.Sieges.Patches.Disable; +namespace GameInterface.Services.SiegeEvents.Patches.Disable; [HarmonyPatch(typeof(SiegeEventCampaignBehavior))] internal class DisableSiegeEventCampaignBehavior diff --git a/source/GameInterface/Services/Sieges/Handlers/SiegeEventLifetimeHandler.cs b/source/GameInterface/Services/Sieges/Handlers/SiegeEventLifetimeHandler.cs deleted file mode 100644 index c4eecef85..000000000 --- a/source/GameInterface/Services/Sieges/Handlers/SiegeEventLifetimeHandler.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Common; -using Common.Messaging; -using Common.Network; -using Common.Util; -using GameInterface.Services.ObjectManager; -using GameInterface.Services.Sieges.Messages; -using TaleWorlds.CampaignSystem.Party; -using TaleWorlds.CampaignSystem.Settlements; -using TaleWorlds.CampaignSystem.Siege; - -namespace GameInterface.Services.Sieges.Handlers; -internal class SiegeEventLifetimeHandler : IHandler -{ - private readonly IMessageBroker messageBroker; - private readonly INetwork network; - private readonly IObjectManager objectManager; - - - public SiegeEventLifetimeHandler(IMessageBroker messageBroker, INetwork network, IObjectManager objectManager) - { - this.messageBroker = messageBroker; - this.network = network; - this.objectManager = objectManager; - messageBroker.Subscribe(Handle); - messageBroker.Subscribe(Handle); - } - - public void Dispose() - { - messageBroker.Unsubscribe(Handle); - messageBroker.Unsubscribe(Handle); - } - - - private void Handle(MessagePayload payload) - { - if (objectManager.AddNewObject(payload.What.Instance, out var siegeEventId) == false) return; - - network.SendAll(new NetworkCreateSiegeEvent(siegeEventId)); - } - - private void Handle(MessagePayload payload) - { - var newSiegeEvent = ObjectHelper.SkipConstructor(); - - objectManager.AddExisting(payload.What.SiegeId, newSiegeEvent); - } -} diff --git a/source/GameInterface/Services/Sieges/Messages/NetworkCreateSiegeEvent.cs b/source/GameInterface/Services/Sieges/Messages/NetworkCreateSiegeEvent.cs deleted file mode 100644 index 7340f5c18..000000000 --- a/source/GameInterface/Services/Sieges/Messages/NetworkCreateSiegeEvent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Common.Messaging; -using ProtoBuf; - -namespace GameInterface.Services.Sieges.Messages; - -[ProtoContract(SkipConstructor = true)] -internal class NetworkCreateSiegeEvent : ICommand -{ - [ProtoMember(1)] - public string SiegeId { get; } - - public NetworkCreateSiegeEvent(string siegeId) - { - SiegeId = siegeId; - } -} diff --git a/source/GameInterface/Services/Sieges/Messages/SiegeEventCreated.cs b/source/GameInterface/Services/Sieges/Messages/SiegeEventCreated.cs deleted file mode 100644 index da2967a79..000000000 --- a/source/GameInterface/Services/Sieges/Messages/SiegeEventCreated.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Common.Messaging; -using TaleWorlds.CampaignSystem.Party; -using TaleWorlds.CampaignSystem.Settlements; -using TaleWorlds.CampaignSystem.Siege; - -namespace GameInterface.Services.Sieges.Messages; -internal class SiegeEventCreated : IEvent -{ - public SiegeEvent Instance { get; } - - public SiegeEventCreated(SiegeEvent instance) - { - Instance = instance; - } -} diff --git a/source/GameInterface/Services/Sieges/Patches/SeigeEventLifetimePatches.cs b/source/GameInterface/Services/Sieges/Patches/SeigeEventLifetimePatches.cs deleted file mode 100644 index 46f29638e..000000000 --- a/source/GameInterface/Services/Sieges/Patches/SeigeEventLifetimePatches.cs +++ /dev/null @@ -1,41 +0,0 @@ -using Common.Logging; -using Common.Messaging; -using GameInterface.Policies; -using GameInterface.Services.Sieges.Messages; -using HarmonyLib; -using Serilog; -using System; -using System.Collections.Generic; -using System.Reflection; -using TaleWorlds.CampaignSystem.Party; -using TaleWorlds.CampaignSystem.Settlements; -using TaleWorlds.CampaignSystem.Siege; - -namespace GameInterface.Services.Sieges.Patches; - -[HarmonyPatch] -internal class SiegeEventLifetimePatches -{ - static readonly ILogger Logger = LogManager.GetLogger(); - - static IEnumerable TargetMethods() => AccessTools.GetDeclaredConstructors(typeof(SiegeEvent)); - - static bool Prefix(ref SiegeEvent __instance) - { - // Call original if we call this function - if (CallOriginalPolicy.IsOriginalAllowed()) return true; - - if (ModInformation.IsClient) - { - Logger.Error("Client created unmanaged {name}\n" - + "Callstack: {callstack}", typeof(SiegeEvent), Environment.StackTrace); - return true; - } - - var message = new SiegeEventCreated(__instance); - - MessageBroker.Instance.Publish(__instance, message); - - return true; - } -} diff --git a/source/GameInterface/Services/Sieges/SiegeEventRegistry.cs b/source/GameInterface/Services/Sieges/SiegeEventRegistry.cs deleted file mode 100644 index d9c7afd9b..000000000 --- a/source/GameInterface/Services/Sieges/SiegeEventRegistry.cs +++ /dev/null @@ -1,30 +0,0 @@ -using GameInterface.Services.Registry; -using System.Threading; -using TaleWorlds.CampaignSystem; -using TaleWorlds.CampaignSystem.Siege; - -namespace GameInterface.Services.Sieges; - -/// -/// Registry for objects -/// -internal class SiegeEventRegistry : RegistryBase -{ - private const string SeigeEventPrefix = $"Coop{nameof(SiegeEvent)}"; - private static int InstanceCounter = 0; - - public SiegeEventRegistry(IRegistryCollection collection) : base(collection) { } - - public override void RegisterAll() - { - foreach(var siegeEvent in Campaign.Current.SiegeEventManager.SiegeEvents) - { - RegisterNewObject(siegeEvent, out _); - } - } - - protected override string GetNewId(SiegeEvent party) - { - return $"{SeigeEventPrefix}_{ Interlocked.Increment(ref InstanceCounter)}"; - } -} \ No newline at end of file From e699bf621c72a46672971eebff22d011951d53a1 Mon Sep 17 00:00:00 2001 From: brodrigz Date: Thu, 7 Nov 2024 23:25:28 -0300 Subject: [PATCH 5/6] Refactor settlement registry --- .../GameInterface/Services/Settlements/SettlementRegistry.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/source/GameInterface/Services/Settlements/SettlementRegistry.cs b/source/GameInterface/Services/Settlements/SettlementRegistry.cs index c0834dd24..fa51ca83f 100644 --- a/source/GameInterface/Services/Settlements/SettlementRegistry.cs +++ b/source/GameInterface/Services/Settlements/SettlementRegistry.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Threading; using TaleWorlds.CampaignSystem; using TaleWorlds.CampaignSystem.Settlements; using TaleWorlds.ObjectSystem; @@ -10,6 +11,7 @@ namespace GameInterface.Services.Settlements; internal class SettlementRegistry : RegistryBase { public static readonly string SettlementStringIdPrefix = "CoopSettlement"; + private static int SettlementCounter = 0; public SettlementRegistry(IRegistryCollection collection) : base(collection) { } @@ -40,8 +42,7 @@ public override bool RegisterExistingObject(string id, object obj) protected override string GetNewId(Settlement settlement) { - settlement.StringId = Campaign.Current.CampaignObjectManager.FindNextUniqueStringId(SettlementStringIdPrefix); - return settlement.StringId; + return $"{SettlementStringIdPrefix}_{Interlocked.Increment(ref SettlementCounter)}"; } private void AddToCampaignObjectManager(object obj) From cf2e2a98d36ddb8fec0b862756f9d81dd837913f Mon Sep 17 00:00:00 2001 From: brodrigz Date: Thu, 7 Nov 2024 23:25:36 -0300 Subject: [PATCH 6/6] Add CampaignTime surrogate --- .../Surrogates/CampaignTimeSurrogate.cs | 27 +++++++++++++++++++ .../Surrogates/SurrogateCollection.cs | 4 +++ 2 files changed, 31 insertions(+) create mode 100644 source/GameInterface/Surrogates/CampaignTimeSurrogate.cs diff --git a/source/GameInterface/Surrogates/CampaignTimeSurrogate.cs b/source/GameInterface/Surrogates/CampaignTimeSurrogate.cs new file mode 100644 index 000000000..7f10cede6 --- /dev/null +++ b/source/GameInterface/Surrogates/CampaignTimeSurrogate.cs @@ -0,0 +1,27 @@ +using ProtoBuf; +using TaleWorlds.CampaignSystem; +using TaleWorlds.Localization; + +namespace GameInterface.Surrogates; + +[ProtoContract] +internal struct CampaignTimeSurrogate +{ + [ProtoMember(1)] + public long NumberOfTicks { get; set; } + + public CampaignTimeSurrogate(CampaignTime campaignTime) + { + NumberOfTicks = campaignTime.NumTicks; + } + + public static implicit operator CampaignTimeSurrogate(CampaignTime campaignTime) + { + return new CampaignTimeSurrogate(campaignTime); + } + + public static implicit operator CampaignTime(CampaignTimeSurrogate surrogate) + { + return new CampaignTime(surrogate.NumberOfTicks); + } +} \ No newline at end of file diff --git a/source/GameInterface/Surrogates/SurrogateCollection.cs b/source/GameInterface/Surrogates/SurrogateCollection.cs index 38cfd5046..e3d005604 100644 --- a/source/GameInterface/Surrogates/SurrogateCollection.cs +++ b/source/GameInterface/Surrogates/SurrogateCollection.cs @@ -1,4 +1,5 @@ using ProtoBuf.Meta; +using TaleWorlds.CampaignSystem; using TaleWorlds.Library; using TaleWorlds.Localization; @@ -15,5 +16,8 @@ public SurrogateCollection() if (RuntimeTypeModel.Default.CanSerialize(typeof(TextObject)) == false) RuntimeTypeModel.Default.SetSurrogate(); + + if (RuntimeTypeModel.Default.CanSerialize(typeof(CampaignTime)) == false) + RuntimeTypeModel.Default.SetSurrogate(); } }