Skip to content

Commit

Permalink
feat: pick random equipment from equipment pool for heroes
Browse files Browse the repository at this point in the history
* refactor equipment mapping from equipment pool mapper  into a proper mapper for re-use
* add usecase for getting a random equipment from a domain equipment pool
  • Loading branch information
JoeFwd committed Oct 20, 2024
1 parent 2b49f24 commit 7670e22
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 107 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System.Xml.Linq;
using Bannerlord.ExpandedTemplate.Domain.EquipmentPool;
using Bannerlord.ExpandedTemplate.Domain.EquipmentPool.Model;
using Bannerlord.ExpandedTemplate.Domain.EquipmentPool.Util;
using Moq;
using NUnit.Framework;

namespace Bannerlord.ExpandedTemplate.Domain.Tests.EquipmentPool;

public class GetEquipmentShould
{
private Mock<IRandom> _random;

private IGetEquipment _getEquipment;

[SetUp]
public void SetUp()
{
_random = new Mock<IRandom>(MockBehavior.Strict);
_getEquipment = new GetEquipment(_random.Object);
}

[Test]
public void GetFirstEquipmentWhenTwoEquipmentTemplatesAndRandomReturnsZero()
{
var equipment = new List<Equipment>
{
new(XDocument.Parse("<Equipment1/>")),
new(XDocument.Parse("<Equipment2/>"))
};
var equipmentPool = new Domain.EquipmentPool.Model.EquipmentPool(equipment, 0);
_random.Setup(random => random.Next(0, 2)).Returns(0);

var actualEquipment = _getEquipment.GetEquipmentFromEquipmentPool(equipmentPool);

Assert.That(actualEquipment, Is.EqualTo(equipment[0]));
}

[Test]
public void GetSecondEquipmentWhenTwoEquipmentTemplatesAndRandomReturnsOne()
{
var equipment = new List<Equipment>
{
new(XDocument.Parse("<Equipment1/>")),
new(XDocument.Parse("<Equipment2/>"))
};
var equipmentPool = new Domain.EquipmentPool.Model.EquipmentPool(equipment, 0);
_random.Setup(random => random.Next(0, 2)).Returns(1);

var actualEquipment = _getEquipment.GetEquipmentFromEquipmentPool(equipmentPool);

Assert.That(actualEquipment, Is.EqualTo(equipment[1]));
}

[Test]
public void ReturnsNullWhenNoEquipmentTemplates()
{
var equipmentPool = new Domain.EquipmentPool.Model.EquipmentPool(new List<Equipment>(), 0);

var actualEquipment = _getEquipment.GetEquipmentFromEquipmentPool(equipmentPool);

Assert.That(actualEquipment, Is.Null);
}

[Test]
public void ReturnsNullWhenNoEquipmentPool()
{
var actualEquipment = _getEquipment.GetEquipmentFromEquipmentPool(null!);

Assert.That(actualEquipment, Is.Null);
}
}
16 changes: 16 additions & 0 deletions Bannerlord.ExpandedTemplate.Domain/EquipmentPool/GetEquipment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Bannerlord.ExpandedTemplate.Domain.EquipmentPool.Model;
using Bannerlord.ExpandedTemplate.Domain.EquipmentPool.Util;

namespace Bannerlord.ExpandedTemplate.Domain.EquipmentPool;

public class GetEquipment(IRandom random) : IGetEquipment
{
public Equipment GetEquipmentFromEquipmentPool(Model.EquipmentPool equipmentPool)
{
if (equipmentPool is null || equipmentPool.GetEquipmentLoadouts().Count == 0) return null;

Check warning on line 10 in Bannerlord.ExpandedTemplate.Domain/EquipmentPool/GetEquipment.cs

View workflow job for this annotation

GitHub Actions / integration-tests

Possible null reference return.

Check warning on line 10 in Bannerlord.ExpandedTemplate.Domain/EquipmentPool/GetEquipment.cs

View workflow job for this annotation

GitHub Actions / integration-tests

Possible null reference return.

var randomIndex = random.Next(0, equipmentPool.GetEquipmentLoadouts().Count);

return equipmentPool.GetEquipmentLoadouts()[randomIndex];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Bannerlord.ExpandedTemplate.Domain.EquipmentPool.Model;

namespace Bannerlord.ExpandedTemplate.Domain.EquipmentPool;

public interface IGetEquipment
{
Equipment? GetEquipmentFromEquipmentPool(Model.EquipmentPool equipmentPool);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Xml;
using System.Xml.Linq;
using Bannerlord.ExpandedTemplate.Domain.Logging.Port;
using TaleWorlds.Core;
using TaleWorlds.ObjectSystem;

namespace Bannerlord.ExpandedTemplate.Integration.SetSpawnEquipment.EquipmentPools.Mappers;

public class EquipmentMapper(MBObjectManager mbObjectManager, ILoggerFactory loggerFactory)
{
private readonly ILogger _logger = loggerFactory.CreateLogger<EquipmentMapper>();

public Equipment Map(Domain.EquipmentPool.Model.Equipment equipment, MBEquipmentRoster bannerlordEquipmentPool)
{
XmlNode? xmlEquipmentNode = MapEquipmentNode(equipment.GetEquipmentNode());
if (xmlEquipmentNode is null) return null;

Check warning on line 17 in Bannerlord.ExpandedTemplate.Integration/SetSpawnEquipment/EquipmentPools/Mappers/EquipmentMapper.cs

View workflow job for this annotation

GitHub Actions / integration-tests

Possible null reference return.

if (xmlEquipmentNode.Name.Equals("EquipmentRoster", StringComparison.InvariantCultureIgnoreCase))
return AddEquipmentNodeToEquipmentRoster(xmlEquipmentNode, bannerlordEquipmentPool);

return null;

Check warning on line 22 in Bannerlord.ExpandedTemplate.Integration/SetSpawnEquipment/EquipmentPools/Mappers/EquipmentMapper.cs

View workflow job for this annotation

GitHub Actions / integration-tests

Possible null reference return.
}

private XmlNode? MapEquipmentNode(XNode node)
{
if (node is null) return null;
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(node.ToString());
return xmlDocument.DocumentElement;
}

private Equipment AddEquipmentNodeToEquipmentRoster(XmlNode equipmentRosterNode,
MBEquipmentRoster bannerlordEquipmentPool)
{
var equipmentLoadout =
new Equipment(bool.Parse(equipmentRosterNode.Attributes?["civilian"]?.Value ?? "false"));
equipmentLoadout.Deserialize(mbObjectManager, equipmentRosterNode);

var nativeEquipmentLoadout =
FindMatchingDomainEquipmentInBannerlordEquipmentPool(bannerlordEquipmentPool, equipmentLoadout);

if (nativeEquipmentLoadout is null)
{
_logger.Error(
$"Could not find {equipmentLoadout} among native '{bannerlordEquipmentPool.StringId}' equipment roster");
return null;

Check warning on line 47 in Bannerlord.ExpandedTemplate.Integration/SetSpawnEquipment/EquipmentPools/Mappers/EquipmentMapper.cs

View workflow job for this annotation

GitHub Actions / integration-tests

Possible null reference return.
}

return nativeEquipmentLoadout;
}

private Equipment? FindMatchingDomainEquipmentInBannerlordEquipmentPool(MBEquipmentRoster bannerlordEquipmentPool,
Equipment equipment)
{
if (bannerlordEquipmentPool is null) return null;

return bannerlordEquipmentPool.AllEquipments.Find(nativeEquipmentLoadout =>
nativeEquipmentLoadout.IsEquipmentEqualTo(equipment));
}
}
Original file line number Diff line number Diff line change
@@ -1,105 +1,40 @@
using System;

using System.Linq;
using System.Reflection;
using System.Xml;
using System.Xml.Linq;
using Bannerlord.ExpandedTemplate.Domain.EquipmentPool.Model;
using Bannerlord.ExpandedTemplate.Domain.Logging.Port;
using TaleWorlds.Core;
using TaleWorlds.Library;
using TaleWorlds.ObjectSystem;
using Equipment = TaleWorlds.Core.Equipment;

namespace Bannerlord.ExpandedTemplate.Integration.SetSpawnEquipment.EquipmentPools.Mappers
{
public class EquipmentPoolsMapper
{
private readonly MBObjectManager _mbObjectManager;
private readonly ILogger _logger;

private readonly FieldInfo _mbEquipmentRosterEquipmentsField =
typeof(MBEquipmentRoster).GetField("_equipments", BindingFlags.NonPublic | BindingFlags.Instance);

public EquipmentPoolsMapper(MBObjectManager mbObjectManager, ILoggerFactory loggerFactory)
{
_mbObjectManager = mbObjectManager;
_logger = loggerFactory.CreateLogger<EquipmentPoolsMapper>();
}

public MBEquipmentRoster MapEquipmentPool(EquipmentPool equipmentPool,
string equipmentId)
{
var mbEquipmentLoadouts = new MBEquipmentRoster();
var equipmentNodes = equipmentPool.GetEquipmentLoadouts()
.Select(equipmentLoadout => equipmentLoadout.GetEquipmentNode());

foreach (var equipmentLoadoutNode in equipmentNodes)
{
var node = MapNode(equipmentLoadoutNode);
if (node is null) continue;

if (node.Name.Equals("EquipmentRoster", StringComparison.InvariantCultureIgnoreCase))
AddEquipmentNodeToEquipmentRoster(node, mbEquipmentLoadouts, equipmentId);
else if (node.Name.Equals("EquipmentSet", StringComparison.InvariantCultureIgnoreCase))
AddReferencedEquipmentsToPool(node, mbEquipmentLoadouts, equipmentId);
}

return mbEquipmentLoadouts;
}
namespace Bannerlord.ExpandedTemplate.Integration.SetSpawnEquipment.EquipmentPools.Mappers;

private XmlNode? MapNode(XNode node)
{
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(node.ToString());
return xmlDocument.DocumentElement;
}

private void AddEquipmentNodeToEquipmentRoster(XmlNode equipmentRosterNode, MBEquipmentRoster equipmentRoster,
string equipmentId)
{
var equipmentLoadout =
new Equipment(bool.Parse(equipmentRosterNode.Attributes?["civilian"]?.Value ?? "false"));
equipmentLoadout.Deserialize(_mbObjectManager, equipmentRosterNode);

var nativeEquipmentLoadout = FindMatchingEquipment(equipmentId, equipmentLoadout);

if (nativeEquipmentLoadout is null)
{
_logger.Error($"Could not find {equipmentLoadout} among native '{equipmentId}' equipment roster");
return;
}
public class EquipmentPoolsMapper
{
private readonly EquipmentMapper _equipmentMapper;

var equipment = (MBList<Equipment>)_mbEquipmentRosterEquipmentsField.GetValue(equipmentRoster);
equipment.Add(nativeEquipmentLoadout);
}
private readonly FieldInfo? _mbEquipmentRosterEquipmentsField =
typeof(MBEquipmentRoster).GetField("_equipments", BindingFlags.NonPublic | BindingFlags.Instance);

private Equipment? FindMatchingEquipment(string equipmentId, Equipment equipment)
{
var nativeEquipmentPool = _mbObjectManager.GetObject<MBEquipmentRoster>(equipmentId);
public EquipmentPoolsMapper(EquipmentMapper equipmentMapper, ILoggerFactory loggerFactory)
{
ILogger logger = loggerFactory.CreateLogger<EquipmentMapper>();
if (_mbEquipmentRosterEquipmentsField is null)
logger.Error("Could not find the _equipment field in the MBEquipmentRoster class via reflection.");
_equipmentMapper = equipmentMapper;
}

if (nativeEquipmentPool is null) return null;
public MBEquipmentRoster MapEquipmentPool(EquipmentPool equipmentPool,
MBEquipmentRoster equipmentPoolWithAllEquipment)
{
if (_mbEquipmentRosterEquipmentsField is null) return new MBEquipmentRoster();

// TODO: handle use case when nativeEquipmentPool is not found
return nativeEquipmentPool.AllEquipments.Find(nativeEquipmentLoadout =>
nativeEquipmentLoadout.IsEquipmentEqualTo(equipment));
}

private void AddReferencedEquipmentsToPool(XmlNode referencedEquipmentNode, MBEquipmentRoster equipmentRoster,
string equipmentId)
{
var id = referencedEquipmentNode.Attributes?["id"]?.Value;
if (string.IsNullOrWhiteSpace(id))
{
AddEquipmentNodeToEquipmentRoster(referencedEquipmentNode, equipmentRoster, equipmentId);
return;
}
var mbEquipmentLoadouts = new MBEquipmentRoster();
var equipments = (MBList<Equipment>)_mbEquipmentRosterEquipmentsField.GetValue(mbEquipmentLoadouts);

var referencedId = _mbObjectManager.GetObject<MBEquipmentRoster>(id);
if (referencedId is null) return;
equipmentPool.GetEquipmentLoadouts().ToList().ForEach(equipment =>
equipments.Add(_equipmentMapper.Map(equipment, equipmentPoolWithAllEquipment)));

bool.TryParse(referencedEquipmentNode.Attributes?["civilian"]?.Value, out var isCivilian);
// add all referenced equipments from the EquipmentSet node to the roster
equipmentRoster.AddEquipmentRoster(referencedId, isCivilian);
}
return mbEquipmentLoadouts;
}
}
Loading

0 comments on commit 7670e22

Please sign in to comment.