diff --git a/source/E2E.Tests/Services/SiegeEvents/SiegeEventFieldTests.cs b/source/E2E.Tests/Services/SiegeEvents/SiegeEventFieldTests.cs new file mode 100644 index 000000000..fa21ef7a5 --- /dev/null +++ b/source/E2E.Tests/Services/SiegeEvents/SiegeEventFieldTests.cs @@ -0,0 +1,161 @@ +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.Siege; +using TaleWorlds.CampaignSystem.Settlements; + +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 your disabled methods + }; + + // Create SiegeEvent on the server + siegeEventId = TestEnvironment.CreateRegisteredObject(); + + // Create SiegeEvent on all clients + foreach (var client in Clients) + { + var clientSiegeEvent = ObjectHelper.SkipConstructor(); + Assert.True(client.ObjectManager.AddExisting(siegeEventId, clientSiegeEvent)); + } + } + + 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(BesiegerCamp), nameof(BesiegerCamp._leaderParty)); + 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.Same(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..a529b506f 100644 --- a/source/E2E.Tests/Services/SiegeEvents/SiegeEventLifetimeTests.cs +++ b/source/E2E.Tests/Services/SiegeEvents/SiegeEventLifetimeTests.cs @@ -1,31 +1,32 @@ -using E2E.Tests.Environment; +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.Siege; namespace E2E.Tests.Services.SiegeEvents; + public class SiegeEventLifetimeTests : IDisposable { - E2ETestEnvironment TestEnvironment { get; } + 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 List disabledMethods; + private readonly string siegeEventId; public SiegeEventLifetimeTests(ITestOutputHelper output) { TestEnvironment = new E2ETestEnvironment(output); - disabledMethods = new List - { - AccessTools.Method(typeof(MobileParty), nameof(MobileParty.OnPartyJoinedSiegeInternal)), + disabledMethods = new List { + //Add your 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..829e37969 --- /dev/null +++ b/source/E2E.Tests/Services/SiegeEvents/SiegeEventPropertyTests.cs @@ -0,0 +1,70 @@ +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.Siege; +using TaleWorlds.CampaignSystem; + +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 your disabled methods + }; + + // Create SiegeEvent on the server + siegeEventId = TestEnvironment.CreateRegisteredObject(); + + // Create SiegeEvent on all clients + foreach (var client in Clients) + { + var clientSiegeEvent = ObjectHelper.SkipConstructor(); + Assert.True(client.ObjectManager.AddExisting(siegeEventId, clientSiegeEvent)); + } + } + + 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/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) diff --git a/source/GameInterface/Services/Sieges/Handlers/SiegeEventLifetimeHandler.cs b/source/GameInterface/Services/Sieges/Handlers/SiegeEventLifetimeHandler.cs index c4eecef85..6b49a3b1e 100644 --- a/source/GameInterface/Services/Sieges/Handlers/SiegeEventLifetimeHandler.cs +++ b/source/GameInterface/Services/Sieges/Handlers/SiegeEventLifetimeHandler.cs @@ -1,11 +1,8 @@ -using Common; -using Common.Messaging; +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; diff --git a/source/GameInterface/Services/Sieges/SiegeEventSync.cs b/source/GameInterface/Services/Sieges/SiegeEventSync.cs new file mode 100644 index 000000000..3364059e6 --- /dev/null +++ b/source/GameInterface/Services/Sieges/SiegeEventSync.cs @@ -0,0 +1,21 @@ +using GameInterface.AutoSync; +using HarmonyLib; +using System; +using TaleWorlds.CampaignSystem.Siege; + +namespace GameInterface.Services.Sieges +{ + internal class SiegeEventSync : IAutoSync + { + public SiegeEventSync(IAutoSyncBuilder autoSyncBuilder) + { + // Fields + autoSyncBuilder.AddField(AccessTools.Field(typeof(SiegeEvent), nameof(SiegeEvent.BesiegedSettlement))); + autoSyncBuilder.AddField(AccessTools.Field(typeof(SiegeEvent), nameof(SiegeEvent.BesiegerCamp))); + autoSyncBuilder.AddField(AccessTools.Field(typeof(SiegeEvent), nameof(SiegeEvent._isBesiegerDefeated))); + + // Props + autoSyncBuilder.AddProperty(AccessTools.Property(typeof(SiegeEvent), nameof(SiegeEvent.SiegeStartTime))); + } + } +} 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(); } }