Skip to content

Commit

Permalink
bug: fix heroes having equipment of last battle on campaign map
Browse files Browse the repository at this point in the history
For instance, naked lords in a siege attack were then naked on the campaign map.
  • Loading branch information
JoeFwd committed Nov 1, 2024
1 parent 27514ab commit 31b10d3
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 137 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
using System.Collections.Generic;
using Bannerlord.ExpandedTemplate.Domain.EquipmentPool;
using Bannerlord.ExpandedTemplate.Integration.SetSpawnEquipment.MissionLogic.EquipmentSetters;
using SandBox.Missions.MissionLogics;
using TaleWorlds.CampaignSystem;
using TaleWorlds.Core;
using TaleWorlds.MountAndBlade;

namespace Bannerlord.ExpandedTemplate.Integration.SetSpawnEquipment.MissionLogic;

public class EquipmentSetterMissionLogic : TaleWorlds.MountAndBlade.MissionLogic
{
private readonly HeroEquipmentSetter _heroEquipmentSetter;
private readonly TroopEquipmentPoolSetter _troopEquipmentPoolSetter;
private readonly IGetEquipmentPool _getEquipmentPool;
private readonly CharacterEquipmentRosterReference _characterEquipmentRosterReference;

private readonly Dictionary<string, MBEquipmentRoster> _nativeTroopEquipmentRosters = new();
private readonly Dictionary<string, Equipment> _nativeHeroEquipment = new();

public EquipmentSetterMissionLogic(HeroEquipmentSetter heroEquipmentSetter,
TroopEquipmentPoolSetter troopEquipmentPoolSetter, IGetEquipmentPool getEquipmentPool,
CharacterEquipmentRosterReference characterEquipmentRosterReference)
{
_heroEquipmentSetter = heroEquipmentSetter;
_troopEquipmentPoolSetter = troopEquipmentPoolSetter;
_getEquipmentPool = getEquipmentPool;
_characterEquipmentRosterReference = characterEquipmentRosterReference;
}

public override void OnBehaviorInitialize()
{
base.OnBehaviorInitialize();

_nativeTroopEquipmentRosters.Clear();
_nativeHeroEquipment.Clear();
}

public override void OnAgentCreated(Agent agent)
{
MBEquipmentRoster? agentEquipmentRoster =
_characterEquipmentRosterReference.GetEquipmentRoster(agent.Character);

if (agentEquipmentRoster is null) return;
if (!CanOverrideEquipment(agent)) return;

base.OnAgentCreated(agent);

string id = agent.Character.StringId;
if (agent.Character is CharacterObject characterObject)
id = characterObject.OriginalCharacter?.StringId ?? id;

var equipmentPool = _getEquipmentPool.GetTroopEquipmentPool(id);
if (equipmentPool.IsEmpty())
equipmentPool = _getEquipmentPool.GetTroopEquipmentPool(agentEquipmentRoster.StringId);

if (agent.IsHero)
{
_nativeHeroEquipment[agent.Character.StringId] = agent.Character.Equipment.Clone();
_heroEquipmentSetter.SetEquipmentFromEquipmentPool(agent, equipmentPool);
}
else
{
_nativeTroopEquipmentRosters[agent.Character.StringId] = agentEquipmentRoster;
_troopEquipmentPoolSetter.SetEquipmentPool(agent, equipmentPool);
}
}

public override void OnAgentBuild(Agent agent, Banner banner)
{
if (!CanOverrideEquipment(agent)) return;

base.OnAgentBuild(agent, banner);

if (agent.IsHero)
_heroEquipmentSetter.SetEquipment(agent, _nativeHeroEquipment[agent.Character.StringId]);
else
_troopEquipmentPoolSetter.SetEquipmentPool(agent,
_nativeTroopEquipmentRosters[agent.Character.StringId]);
}

private static bool CanOverrideEquipment(IAgent agent)
{
return (Mission.Current?.GetMissionBehavior<MissionAgentHandler>() is not null ||
Mission.Current?.GetMissionBehavior<IMissionAgentSpawnLogic>() is not null) &&
agent?.Character is not null &&
!(Clan.PlayerClan?.Heroes?.Exists(
hero => hero?.StringId is not null && agent?.Character?.StringId == hero.StringId) ?? true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using System.Reflection;
using Bannerlord.ExpandedTemplate.Domain.Logging.Port;
using TaleWorlds.Core;

namespace Bannerlord.ExpandedTemplate.Integration.SetSpawnEquipment.MissionLogic.EquipmentSetters;

public class CharacterEquipmentRosterReference
{
private static readonly FieldInfo? EquipmentRosterField =
typeof(BasicCharacterObject).GetField("_equipmentRoster", BindingFlags.NonPublic | BindingFlags.Instance)!;

public CharacterEquipmentRosterReference(ILoggerFactory loggerFactory)
{
ILogger logger = loggerFactory.CreateLogger<CharacterEquipmentRosterReference>();

if (EquipmentRosterField is null || EquipmentRosterField.FieldType != typeof(MBEquipmentRoster))
logger.Error(
"BasicCharacterObject's _mbEquipmentRoster field could not be found preventing equipment pool override");
}

/// <summary>
/// Returns the equipment roster reference of a characterObject
/// </summary>
/// <param name="characterObject"></param>
/// <returns>
/// the internal MBEquipmentRoster object from the characterObject object or
/// null if the characterObject is null or if the under the hood reflection implementation fails due to
/// a game update)
/// </returns>
public MBEquipmentRoster? GetEquipmentRoster(BasicCharacterObject characterObject)
{
if (characterObject is null) return null;
return (MBEquipmentRoster?)EquipmentRosterField?.GetValue(characterObject);
}

/// <summary>
/// Sets the equipment roster reference of a characterObject
/// </summary>
/// <param name="characterObject"></param>
/// <param name="mbEquipmentRoster"></param>
public void SetEquipmentRoster(BasicCharacterObject characterObject, MBEquipmentRoster mbEquipmentRoster)
{
if (characterObject is null) return;
EquipmentRosterField?.SetValue(characterObject, mbEquipmentRoster);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using Bannerlord.ExpandedTemplate.Domain.EquipmentPool;
using Bannerlord.ExpandedTemplate.Domain.EquipmentPool.Model;
using Bannerlord.ExpandedTemplate.Domain.Logging.Port;
using Bannerlord.ExpandedTemplate.Integration.SetSpawnEquipment.EquipmentPools.Mappers;
using TaleWorlds.Core;
using Equipment = TaleWorlds.Core.Equipment;

namespace Bannerlord.ExpandedTemplate.Integration.SetSpawnEquipment.MissionLogic.EquipmentSetters;

public class HeroEquipmentSetter
{
private readonly IGetEquipment _getEquipment;
private readonly EquipmentMapper _equipmentMapper;
private readonly CharacterEquipmentRosterReference _characterEquipmentRosterReference;
private readonly ILogger _logger;

public HeroEquipmentSetter(IGetEquipment getEquipment, EquipmentMapper equipmentMapper,
CharacterEquipmentRosterReference characterEquipmentRosterReference, ILoggerFactory loggerFactory)
{
_getEquipment = getEquipment;
_equipmentMapper = equipmentMapper;
_characterEquipmentRosterReference = characterEquipmentRosterReference;
_logger = loggerFactory.CreateLogger<HeroEquipmentSetter>();
}

public void SetEquipmentFromEquipmentPool(IAgent agent, EquipmentPool equipmentPool)
{
MBEquipmentRoster? agentEquipmentRoster =
_characterEquipmentRosterReference.GetEquipmentRoster(agent.Character);

if (agentEquipmentRoster is null) return;

var equipment = _getEquipment.GetEquipmentFromEquipmentPool(equipmentPool);
if (equipment is null)
SetEquipment(agent, new Equipment());
else
SetEquipment(agent,
_equipmentMapper.Map(equipment, agentEquipmentRoster));
}

public void SetEquipment(IAgent agent, Equipment? equipment)
{
if (agent?.Character?.Equipment is null)
{
_logger.Error(
"Expected a hero Agent to have a non-nullable Character field with a non-nullable Equipment field");
return;
}

if (equipment is null)
{
_logger.Error(
$"Could find any equipment for ${agent.Character.StringId}");
return;
}

agent.Character.Equipment.FillFrom(equipment);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Bannerlord.ExpandedTemplate.Domain.EquipmentPool.Model;
using Bannerlord.ExpandedTemplate.Integration.SetSpawnEquipment.EquipmentPools.Mappers;
using TaleWorlds.Core;

namespace Bannerlord.ExpandedTemplate.Integration.SetSpawnEquipment.MissionLogic.EquipmentSetters;

public class TroopEquipmentPoolSetter
{
private readonly EquipmentPoolsMapper _equipmentPoolsMapper;
private readonly CharacterEquipmentRosterReference _characterEquipmentRosterReference;

public TroopEquipmentPoolSetter(EquipmentPoolsMapper equipmentPoolsMapper,
CharacterEquipmentRosterReference characterEquipmentRosterReference)

{
_equipmentPoolsMapper = equipmentPoolsMapper;
_characterEquipmentRosterReference = characterEquipmentRosterReference;
}

public void SetEquipmentPool(IAgent agent, EquipmentPool equipmentPool)
{
if (agent?.Character is null) return;

MBEquipmentRoster? agentEquipmentRoster =
_characterEquipmentRosterReference.GetEquipmentRoster(agent.Character);

if (agentEquipmentRoster is null) return;

SetEquipmentPool(agent, _equipmentPoolsMapper.MapEquipmentPool(equipmentPool, agentEquipmentRoster));
}

public void SetEquipmentPool(IAgent agent, MBEquipmentRoster equipmentRoster)
{
if (agent?.Character is null) return;
_characterEquipmentRosterReference.SetEquipmentRoster(agent.Character, equipmentRoster);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Bannerlord.ExpandedTemplate.Integration.SetSpawnEquipment.MissionLogic
{
public class ForceCivilianEquipmentSetter : TaleWorlds.MountAndBlade.MissionLogic
public class ForceCivilianEquipmentMissionLogic : TaleWorlds.MountAndBlade.MissionLogic
{
public override void OnAgentCreated(Agent agent)
{
Expand Down

This file was deleted.

Loading

0 comments on commit 31b10d3

Please sign in to comment.