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

946 sync basic character object fields #1067

Merged
merged 3 commits into from
Jan 9, 2025
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
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
Loading