Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Party Visual Lifetime #992

Merged
merged 5 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions source/E2E.Tests/E2E.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
<Reference Include="SandBox.GauntletUI">
<HintPath>..\..\mb2\Modules\SandBox\bin\Win64_Shipping_Client\SandBox.GauntletUI.dll</HintPath>
</Reference>
<Reference Include="SandBox.View">
<HintPath>..\..\mb2\Modules\SandBox\bin\Win64_Shipping_Client\SandBox.View.dll</HintPath>
</Reference>
<Reference Include="TaleWorlds.CampaignSystem">
<HintPath>..\..\mb2\bin\Win64_Shipping_Client\TaleWorlds.CampaignSystem.dll</HintPath>
</Reference>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using E2E.Tests.Environment;
using SandBox.View.Map;
using TaleWorlds.CampaignSystem.Party;
using Xunit.Abstractions;

namespace E2E.Tests.Services.PartyVisuals
{
public class PartyVisualsLifetimeTests : IDisposable
{
E2ETestEnvironment TestEnvironment { get; }
public PartyVisualsLifetimeTests(ITestOutputHelper output)
{
TestEnvironment = new E2ETestEnvironment(output);
}

public void Dispose()
{
TestEnvironment.Dispose();
}

[Fact]
public void ServerCreatePartyVisual_SyncAllClients()
{
// Arrange
var server = TestEnvironment.Server;

// Act
string? visualId = null;
server.Call(() =>
{
var MobileParty = new MobileParty();
var partyBase = new PartyBase(MobileParty);
var partyVisual = new PartyVisual(partyBase);

Assert.True(server.ObjectManager.TryGetId(partyVisual, out visualId));
});

// Assert
Assert.NotNull(visualId);

foreach (var client in TestEnvironment.Clients)
{
Assert.True(client.ObjectManager.TryGetObject<PartyVisual>(visualId, out var _));
}
}

[Fact]
public void ClientCreatePartyVisual_DoesNothing()
{
// Arrange
var client1 = TestEnvironment.Clients.First();
var server = TestEnvironment.Server;

// Act
string? PartyVisualId = null;
string? baseId = null;

server.Call(() =>
{
var MobileParty = new MobileParty();
var partyBase = new PartyBase(MobileParty);

Assert.True(server.ObjectManager.TryGetId(partyBase, out baseId));
});

client1.Call(() =>
{
Assert.True(server.ObjectManager.TryGetObject(baseId, out PartyBase baseParty));
var partyVisual = new PartyVisual(baseParty);

Assert.False(client1.ObjectManager.TryGetId(partyVisual, out PartyVisualId));
});

// Assert
Assert.Null(PartyVisualId);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Common;
using Common.Messaging;
using Common.Network;
using Common.Util;
using GameInterface.Services.ObjectManager;
using GameInterface.Services.PartyVisuals.Messages;
using SandBox.View.Map;
using TaleWorlds.CampaignSystem.Party;

namespace GameInterface.Services.PartyVisuals.Handlers
{
public class PartyVisualLifetimeHandler : IHandler
{
private readonly IMessageBroker messageBroker;
private readonly INetwork network;
private readonly IObjectManager objectManager;


public PartyVisualLifetimeHandler(IMessageBroker messageBroker, INetwork network, IObjectManager objectManager)
{
this.messageBroker = messageBroker;
this.network = network;
this.objectManager = objectManager;
messageBroker.Subscribe<PartyVisualCreated>(Handle);
messageBroker.Subscribe<NetworkCreatePartyVisual>(Handle);
messageBroker.Subscribe<PartyVisualDestroyed>(Handle);
messageBroker.Subscribe<NetworkDestroyPartyVisual>(Handle);
}

public void Dispose()
{
messageBroker.Unsubscribe<PartyVisualCreated>(Handle);
messageBroker.Unsubscribe<NetworkCreatePartyVisual>(Handle);
messageBroker.Unsubscribe<PartyVisualDestroyed>(Handle);
messageBroker.Unsubscribe<NetworkDestroyPartyVisual>(Handle);
}


private void Handle(MessagePayload<PartyVisualCreated> payload)
{
objectManager.AddNewObject(payload.What.PartyVisual, out var visualId);
objectManager.TryGetId(payload.What.PartyBase, out string partyBaseId);

network.SendAll(new NetworkCreatePartyVisual(visualId, partyBaseId));
}

private void Handle(MessagePayload<NetworkCreatePartyVisual> payload)
{
objectManager.TryGetObject<PartyBase>(payload.What.PartyBaseId, out var partyBase);

PartyVisual newVisual = new PartyVisual(partyBase);

objectManager.AddExisting(payload.What.PartyVisualId, newVisual);
}

private void Handle(MessagePayload<PartyVisualDestroyed> payload)
{
objectManager.TryGetId(payload.What.PartyVisual, out string visualId);
objectManager.Remove(payload.What.PartyVisual);

network.SendAll(new NetworkDestroyPartyVisual(visualId));
}

private void Handle(MessagePayload<NetworkDestroyPartyVisual> payload)
{
objectManager.TryGetObject(payload.What.PartyVisualId, out PartyVisual partyVisual);
objectManager.Remove(partyVisual);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Common.Messaging;
using ProtoBuf;

namespace GameInterface.Services.PartyVisuals.Messages
{
[ProtoContract(SkipConstructor = true)]
internal record NetworkCreatePartyVisual : ICommand
{
[ProtoMember(1)]
public string PartyVisualId { get; }

[ProtoMember(2)]
public string PartyBaseId { get; }

public NetworkCreatePartyVisual(string partyVisualId, string partyBaseId)
{
PartyVisualId = partyVisualId;
PartyBaseId = partyBaseId;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using Common.Messaging;
using ProtoBuf;

namespace GameInterface.Services.PartyVisuals.Messages
{
[ProtoContract(SkipConstructor = true)]
internal record NetworkDestroyPartyVisual : ICommand
{
[ProtoMember(1)]
public string PartyVisualId { get; }

public NetworkDestroyPartyVisual(string partyVisualId)
{
PartyVisualId = partyVisualId;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Common.Messaging;
using SandBox.View.Map;
using TaleWorlds.CampaignSystem.Party;

namespace GameInterface.Services.PartyVisuals.Messages
{
internal record PartyVisualCreated : IEvent
{
public PartyVisual PartyVisual { get; }
public PartyBase PartyBase { get; }

public PartyVisualCreated(PartyVisual partyVisual, PartyBase partyBase)
{
PartyVisual = partyVisual;
PartyBase = partyBase;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Common.Messaging;
using SandBox.View.Map;

namespace GameInterface.Services.PartyVisuals.Messages
{
internal record PartyVisualDestroyed : IEvent
{
public PartyVisual PartyVisual { get; }

public PartyVisualDestroyed(PartyVisual partyVisual)
{
PartyVisual = partyVisual;
}
}
}
38 changes: 38 additions & 0 deletions source/GameInterface/Services/PartyVisuals/PartyVisualRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using GameInterface.Services.Registry;
using SandBox.View.Map;
using System.Threading;
using TaleWorlds.CampaignSystem.Settlements;
using TaleWorlds.CampaignSystem.Settlements.Workshops;
using TaleWorlds.ObjectSystem;

namespace GameInterface.Services.PartyVisuals
{
internal class PartyVisualRegistry : RegistryBase<PartyVisual>
{
private const string PartyVisualIdPrefix = $"Coop{nameof(PartyVisual)}";
private static int InstanceCounter = 0;

public PartyVisualRegistry(IRegistryCollection collection) : base(collection) { }

public override void RegisterAll()
{
var visualManager = PartyVisualManager.Current;

if (visualManager == null)
{
Logger.Error("Unable to register party visuals when PartyVisualManager is null");
return;
}

foreach (PartyVisual visual in visualManager._visualsFlattened)
{
RegisterNewObject(visual, out var _);
}
}

protected override string GetNewId(PartyVisual visual)
{
return $"{PartyVisualIdPrefix}_{Interlocked.Increment(ref InstanceCounter)}";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using Common.Logging;
using Common.Messaging;
using GameInterface.Policies;
using GameInterface.Services.PartyVisuals.Messages;
using HarmonyLib;
using SandBox.View.Map;
using Serilog;
using System;
using TaleWorlds.CampaignSystem.Party;

namespace GameInterface.Services.PartyVisuals.Patches
{
[HarmonyPatch]
public class PartyVisualLifetimePatches
{
private static ILogger Logger = LogManager.GetLogger<PartyVisualLifetimePatches>();

[HarmonyPatch(typeof(PartyVisual), MethodType.Constructor, typeof(PartyBase))]
[HarmonyPrefix]
private static bool CreatePartyVisualPrefix(ref PartyVisual __instance, PartyBase partyBase)
{
// 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(PartyVisual), Environment.StackTrace);
return true;
}

var message = new PartyVisualCreated(__instance, partyBase);

MessageBroker.Instance.Publish(__instance, message);

return true;
}

[HarmonyPatch(typeof(PartyVisual), nameof(PartyVisual.OnPartyRemoved))]
[HarmonyPostfix]
private static void OnMobilePartyDestroyedPostfix(ref PartyVisual __instance)
{
if (CallOriginalPolicy.IsOriginalAllowed()) return;

if (ModInformation.IsClient)
{
Logger.Error("Client destroyed unmanaged {name}\n"
+ "Callstack: {callstack}", typeof(PartyVisual), Environment.StackTrace);
return;
}

var message = new PartyVisualDestroyed(__instance);

MessageBroker.Instance.Publish(__instance, message);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
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;

namespace GameInterface.Services.Settlements.Handlers;
internal class SettlementLifetimeHandler : IHandler
Expand All @@ -15,7 +13,6 @@ internal class SettlementLifetimeHandler : IHandler
private readonly INetwork network;
private readonly IObjectManager objectManager;


public SettlementLifetimeHandler(IMessageBroker messageBroker, INetwork network, IObjectManager objectManager)
{
this.messageBroker = messageBroker;
Expand Down