Skip to content

Commit

Permalink
Merge pull request #1067 from Bannerlord-Coop-Team/946-Sync-BasicChar…
Browse files Browse the repository at this point in the history
…acterObjectFields

946 sync basic character object fields
  • Loading branch information
EgardA authored Jan 9, 2025
2 parents c78130a + 92fa685 commit 164c27b
Show file tree
Hide file tree
Showing 12 changed files with 382 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using E2E.Tests.Environment;
using E2E.Tests.Environment.Instance;
using HarmonyLib;
using TaleWorlds.CampaignSystem.Settlements;
using TaleWorlds.Core;
using TaleWorlds.Localization;
using Xunit.Abstractions;

namespace E2E.Tests.Services.BasicCharacterObjects
Expand All @@ -24,16 +27,37 @@ public void ServerBasicCharacterObject_SyncAll()
// Arrange
var server = TestEnvironment.Server;

var basicHeroField = AccessTools.Field(typeof(BasicCharacterObject), nameof(BasicCharacterObject._isBasicHero));
var mountedField = AccessTools.Field(typeof(BasicCharacterObject), nameof(BasicCharacterObject._isMounted));
var rangedField = AccessTools.Field(typeof(BasicCharacterObject), nameof(BasicCharacterObject._isRanged));
var rosterField = AccessTools.Field(typeof(BasicCharacterObject), nameof(BasicCharacterObject._equipmentRoster));
var skillField = AccessTools.Field(typeof(BasicCharacterObject), nameof(BasicCharacterObject.DefaultCharacterSkills));
var nameField = AccessTools.Field(typeof(BasicCharacterObject), nameof(BasicCharacterObject._basicName));
// Get field intercept to use on the server to simulate the field changing
var heroIntercept = TestEnvironment.GetIntercept(basicHeroField);
var mountedIntercept = TestEnvironment.GetIntercept(mountedField);
var rangedIntercept = TestEnvironment.GetIntercept(rangedField);
var rosterIntercept = TestEnvironment.GetIntercept(rosterField);
var skillIntercept = TestEnvironment.GetIntercept(skillField);
var nameIntercept = TestEnvironment.GetIntercept(nameField);

// Act
string? characterId = null;
string? cultureId = null;
string? skillId = null;
string? equipmentRosterId = null;
TextObject name = new TextObject("test");
server.Call(() =>
{
BasicCharacterObject characterObject = new BasicCharacterObject();
BasicCultureObject culture = new BasicCultureObject();
MBCharacterSkills skills = new MBCharacterSkills();
MBEquipmentRoster equipmentRoster = new MBEquipmentRoster();

Assert.True(server.ObjectManager.TryGetId(characterObject, out characterId));
Assert.True(server.ObjectManager.TryGetId(culture, out cultureId));
Assert.True(server.ObjectManager.TryGetId(skills, out skillId));
Assert.True(server.ObjectManager.TryGetId(equipmentRoster, out equipmentRosterId));

characterObject.Age = 5;
characterObject.BeardTags = "test";
Expand All @@ -54,8 +78,19 @@ public void ServerBasicCharacterObject_SyncAll()
characterObject.Race = 4;
characterObject.TattooTags = "test";

// Simulate the field changing
heroIntercept.Invoke(null, new object[] { characterObject, true });
mountedIntercept.Invoke(null, new object[] { characterObject, true });
rangedIntercept.Invoke(null, new object[] { characterObject, true });
rosterIntercept.Invoke(null, new object[] { characterObject, equipmentRoster });
skillIntercept.Invoke(null, new object[] { characterObject, skills });
nameIntercept.Invoke(null, new object[] { characterObject, name });
});

Assert.True(server.ObjectManager.TryGetObject(skillId, out MBCharacterSkills serverSkills));
Assert.True(server.ObjectManager.TryGetObject(equipmentRosterId, out MBEquipmentRoster serverEquipmentRoster));
Assert.True(server.ObjectManager.TryGetObject(cultureId, out BasicCultureObject serverCulture));

foreach (var client in TestEnvironment.Clients)
{
Assert.True(client.ObjectManager.TryGetObject(characterId, out BasicCharacterObject clientCharacter));
Expand All @@ -66,7 +101,7 @@ public void ServerBasicCharacterObject_SyncAll()
Assert.Equal(FormationClass.Cavalry, clientCharacter.DefaultFormationClass);
Assert.Equal(69, clientCharacter.DefaultFormationGroup);
Assert.Equal(420, clientCharacter.DismountResistance);
Assert.Equal(clientCulture, clientCharacter.Culture); //Basic Culture Object lifetime
Assert.Equal(serverCulture.StringId, clientCharacter.Culture.StringId); //Basic Culture Object lifetime
Assert.Equal(42, clientCharacter.FaceDirtAmount);
Assert.True(clientCharacter.FaceMeshCache);
Assert.Equal(FormationPositionPreference.Middle, clientCharacter.FormationPositionPreference);
Expand All @@ -79,6 +114,13 @@ public void ServerBasicCharacterObject_SyncAll()
Assert.Equal(66, clientCharacter.Level);
Assert.Equal(4, clientCharacter.Race);
Assert.Equal("test", clientCharacter.TattooTags);

Assert.True(clientCharacter._isBasicHero);
Assert.True(clientCharacter._isMounted);
Assert.True(clientCharacter._isRanged);
Assert.Equal(serverEquipmentRoster.StringId, clientCharacter._equipmentRoster.StringId);
Assert.Equal(serverSkills.StringId, clientCharacter.DefaultCharacterSkills.StringId);
Assert.True(name.Equals(clientCharacter._basicName));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ public BasicCharacterObjectSync(IAutoSyncBuilder autoSyncBuilder)
autoSyncBuilder.AddProperty(AccessTools.Property(typeof(BasicCharacterObject), nameof(BasicCharacterObject.Level)));
autoSyncBuilder.AddProperty(AccessTools.Property(typeof(BasicCharacterObject), nameof(BasicCharacterObject.Race)));
autoSyncBuilder.AddProperty(AccessTools.Property(typeof(BasicCharacterObject), nameof(BasicCharacterObject.TattooTags)));

autoSyncBuilder.AddField(AccessTools.Field(typeof(BasicCharacterObject), nameof(BasicCharacterObject._isBasicHero)));
autoSyncBuilder.AddField(AccessTools.Field(typeof(BasicCharacterObject), nameof(BasicCharacterObject._isMounted)));
autoSyncBuilder.AddField(AccessTools.Field(typeof(BasicCharacterObject), nameof(BasicCharacterObject._isRanged)));
autoSyncBuilder.AddField(AccessTools.Field(typeof(BasicCharacterObject), nameof(BasicCharacterObject._equipmentRoster)));
autoSyncBuilder.AddField(AccessTools.Field(typeof(BasicCharacterObject), nameof(BasicCharacterObject.DefaultCharacterSkills)));
autoSyncBuilder.AddField(AccessTools.Field(typeof(BasicCharacterObject), nameof(BasicCharacterObject._basicName)));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using GameInterface.Services.Registry;
using System.Threading;
using TaleWorlds.Core;
using TaleWorlds.ObjectSystem;

namespace GameInterface.Services.CharacterSkills
{
internal class CharacterSkillsRegistry : RegistryBase<MBCharacterSkills>
{
private const string IdPrefix = "CoopCharacterSkills";
private static int InstanceCounter = 0;

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

public override void RegisterAll()
{
var objectManager = MBObjectManager.Instance;

if (objectManager == null)
{
Logger.Error("Unable to register objects when CampaignObjectManager is null");
return;
}

foreach (var skill in objectManager.GetObjectTypeList<MBCharacterSkills>())
{
RegisterExistingObject(skill.StringId, skill);
}
}

protected override string GetNewId(MBCharacterSkills obj)
{
return $"{IdPrefix}_{Interlocked.Increment(ref InstanceCounter)}";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using Common.Logging;
using Common.Messaging;
using Common.Network;
using Common.Util;
using GameInterface.Services.CharacterSkills.Messages;
using GameInterface.Services.ObjectManager;
using Serilog;
using TaleWorlds.Core;

namespace GameInterface.Services.CharacterSkills.Handlers
{
internal class CharacterSkillsLifetimeHandler : IHandler
{
private static readonly ILogger Logger = LogManager.GetLogger<CharacterSkillsLifetimeHandler>();
private readonly IMessageBroker messageBroker;
private readonly IObjectManager objectManager;
private readonly INetwork network;

public CharacterSkillsLifetimeHandler(IMessageBroker messageBroker, IObjectManager objectManager, INetwork network)
{
this.messageBroker = messageBroker;
this.objectManager = objectManager;
this.network = network;
messageBroker.Subscribe<CharacterSkillsCreated>(Handle);
messageBroker.Subscribe<NetworkCreateCharacterSkills>(Handle);
}

public void Dispose()
{
messageBroker.Unsubscribe<CharacterSkillsCreated>(Handle);
messageBroker.Unsubscribe<NetworkCreateCharacterSkills>(Handle);
}

private void Handle(MessagePayload<CharacterSkillsCreated> obj)
{
var payload = obj.What;

if (objectManager.AddNewObject(payload.CharacterSkills, out string CharacterSkillsId) == false) return;

var message = new NetworkCreateCharacterSkills(CharacterSkillsId);
network.SendAll(message);
}

private void Handle(MessagePayload<NetworkCreateCharacterSkills> obj)
{
var payload = obj.What;

var CharacterSkills = ObjectHelper.SkipConstructor<MBCharacterSkills>();
if (objectManager.AddExisting(payload.CharacterSkillsId, CharacterSkills) == false)
{
Logger.Error("Failed to add existing CharacterSkill, {id}", payload.CharacterSkillsId);
return;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Common.Messaging;
using TaleWorlds.Core;

namespace GameInterface.Services.CharacterSkills.Messages
{
internal class CharacterSkillsCreated : IEvent
{
public MBCharacterSkills CharacterSkills { get; }

public CharacterSkillsCreated(MBCharacterSkills characterSkills)
{
CharacterSkills = characterSkills;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Common.Messaging;
using ProtoBuf;

namespace GameInterface.Services.CharacterSkills.Messages
{
[ProtoContract(SkipConstructor = true)]
internal class NetworkCreateCharacterSkills : ICommand
{
[ProtoMember(1)]
public string CharacterSkillsId;
public NetworkCreateCharacterSkills(string characterSkillsId)
{
CharacterSkillsId = characterSkillsId;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Common.Logging;
using Common.Messaging;
using GameInterface.Policies;
using GameInterface.Services.CharacterSkills.Messages;
using HarmonyLib;
using Serilog;
using System;
using TaleWorlds.Core;

namespace GameInterface.Services.CharacterSkills.Patches
{
/// <summary>
/// Lifetime Patches for CharacterSkills
/// </summary>
[HarmonyPatch]
internal class CharacterSkillsLifetimePatches
{
private static ILogger Logger = LogManager.GetLogger<CharacterSkillsLifetimePatches>();

[HarmonyPatch(typeof(MBCharacterSkills), MethodType.Constructor)]
[HarmonyPrefix]
private static bool CreateCharacterSkillsPrefix(ref MBCharacterSkills __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(MBCharacterSkills), Environment.StackTrace);
return false;
}

var message = new CharacterSkillsCreated(__instance);

MessageBroker.Instance.Publish(__instance, message);

return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using GameInterface.Services.Registry;
using System.Threading;
using TaleWorlds.Core;
using TaleWorlds.ObjectSystem;

namespace GameInterface.Services.EquipmentRoster
{
internal class EquipmentRosterRegistry : RegistryBase<MBEquipmentRoster>
{
private const string IdPrefix = "CoopEquipmentRoster";
private static int InstanceCounter = 0;

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

public override void RegisterAll()
{
var objectManager = MBObjectManager.Instance;

if (objectManager == null)
{
Logger.Error("Unable to register objects when CampaignObjectManager is null");
return;
}

foreach (var equipRoster in objectManager.GetObjectTypeList<MBEquipmentRoster>())
{
RegisterExistingObject(equipRoster.StringId, equipRoster);
}
}

protected override string GetNewId(MBEquipmentRoster obj)
{
return $"{IdPrefix}_{Interlocked.Increment(ref InstanceCounter)}";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using Common.Logging;
using Common.Messaging;
using Common.Network;
using Common.Util;
using GameInterface.Services.EquipmentRoster.Messages;
using GameInterface.Services.ObjectManager;
using Serilog;
using TaleWorlds.Core;

namespace GameInterface.Services.EquipmentRoster.Handlers
{
internal class EquipmentRosterLifetimeHandler : IHandler
{
private static readonly ILogger Logger = LogManager.GetLogger<EquipmentRosterLifetimeHandler>();
private readonly IMessageBroker messageBroker;
private readonly IObjectManager objectManager;
private readonly INetwork network;

public EquipmentRosterLifetimeHandler(IMessageBroker messageBroker, IObjectManager objectManager, INetwork network)
{
this.messageBroker = messageBroker;
this.objectManager = objectManager;
this.network = network;
messageBroker.Subscribe<EquipmentRosterCreated>(Handle);
messageBroker.Subscribe<NetworkCreateEquipmentRoster>(Handle);
}

public void Dispose()
{
messageBroker.Unsubscribe<EquipmentRosterCreated>(Handle);
messageBroker.Unsubscribe<NetworkCreateEquipmentRoster>(Handle);
}

private void Handle(MessagePayload<EquipmentRosterCreated> obj)
{
var payload = obj.What;

if (objectManager.AddNewObject(payload.EquipmentRoster, out string EquipmentRosterId) == false) return;

var message = new NetworkCreateEquipmentRoster(EquipmentRosterId);
network.SendAll(message);
}

private void Handle(MessagePayload<NetworkCreateEquipmentRoster> obj)
{
var payload = obj.What;

var EquipmentRoster = ObjectHelper.SkipConstructor<MBEquipmentRoster>();
if (objectManager.AddExisting(payload.EquipmentRosterId, EquipmentRoster) == false)
{
Logger.Error("Failed to add existing EquipmentRoster, {id}", payload.EquipmentRosterId);
return;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using Common.Messaging;
using TaleWorlds.Core;

namespace GameInterface.Services.EquipmentRoster.Messages
{
internal class EquipmentRosterCreated : IEvent
{
public MBEquipmentRoster EquipmentRoster { get; }

public EquipmentRosterCreated(MBEquipmentRoster equipmentRoster)
{
EquipmentRoster = equipmentRoster;
}
}
}
Loading

0 comments on commit 164c27b

Please sign in to comment.