b451f83174
Captures the pre-Godot-port state of the codebase. This is the rollback anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md). All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
131 lines
4.8 KiB
C#
131 lines
4.8 KiB
C#
using Theriapolis.Core.Data;
|
|
using Theriapolis.Core.Items;
|
|
using Theriapolis.Core.Rules.Stats;
|
|
using Xunit;
|
|
|
|
namespace Theriapolis.Tests.Items;
|
|
|
|
public sealed class InventoryTests
|
|
{
|
|
private readonly ContentResolver _content = new(new ContentLoader(TestHelpers.DataDirectory));
|
|
|
|
[Fact]
|
|
public void Add_AppendsItemAndIncreasesWeight()
|
|
{
|
|
var inv = new Inventory();
|
|
var sword = inv.Add(_content.Items["rend_sword"]);
|
|
Assert.Single(inv.Items);
|
|
Assert.Equal(_content.Items["rend_sword"].WeightLb, inv.TotalWeightLb);
|
|
Assert.Equal(1, sword.Qty);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryEquip_PutsItemInRequestedSlot()
|
|
{
|
|
var inv = new Inventory();
|
|
var sword = inv.Add(_content.Items["rend_sword"]);
|
|
bool ok = inv.TryEquip(sword, EquipSlot.MainHand, out var err);
|
|
Assert.True(ok, err);
|
|
Assert.Equal(EquipSlot.MainHand, sword.EquippedAt);
|
|
Assert.Same(sword, inv.GetEquipped(EquipSlot.MainHand));
|
|
}
|
|
|
|
[Fact]
|
|
public void TryEquip_RefusesIfItemNotInInventory()
|
|
{
|
|
var inv = new Inventory();
|
|
var sword = new ItemInstance(_content.Items["rend_sword"]); // not added to inv
|
|
bool ok = inv.TryEquip(sword, EquipSlot.MainHand, out var err);
|
|
Assert.False(ok);
|
|
Assert.Contains("not in this inventory", err);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryEquip_TwoHandedWeaponBlocksWhenOffHandOccupied()
|
|
{
|
|
var inv = new Inventory();
|
|
var shield = inv.Add(_content.Items["buckler"]);
|
|
var lance = inv.Add(_content.Items["gore_lance"]); // two_handed
|
|
Assert.True(inv.TryEquip(shield, EquipSlot.OffHand, out _));
|
|
bool ok = inv.TryEquip(lance, EquipSlot.MainHand, out var err);
|
|
Assert.False(ok);
|
|
Assert.Contains("two-handed", err.ToLowerInvariant());
|
|
}
|
|
|
|
[Fact]
|
|
public void TryEquip_OffHandBlockedWhenMainHandHoldsTwoHanded()
|
|
{
|
|
var inv = new Inventory();
|
|
var lance = inv.Add(_content.Items["gore_lance"]);
|
|
var shield = inv.Add(_content.Items["buckler"]);
|
|
Assert.True(inv.TryEquip(lance, EquipSlot.MainHand, out _));
|
|
bool ok = inv.TryEquip(shield, EquipSlot.OffHand, out var err);
|
|
Assert.False(ok);
|
|
Assert.Contains("two-handed", err.ToLowerInvariant());
|
|
}
|
|
|
|
[Fact]
|
|
public void TryEquip_NaturalWeaponEnhancerRequiresMatchingSlot()
|
|
{
|
|
var inv = new Inventory();
|
|
var fangCaps = inv.Add(_content.Items["fang_caps_steel"]);
|
|
// Wrong slot:
|
|
bool ok = inv.TryEquip(fangCaps, EquipSlot.NaturalWeaponClaw, out var err);
|
|
Assert.False(ok);
|
|
Assert.Contains("fits", err.ToLowerInvariant());
|
|
// Correct slot:
|
|
Assert.True(inv.TryEquip(fangCaps, EquipSlot.NaturalWeaponFang, out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void TryUnequip_ClearsSlot()
|
|
{
|
|
var inv = new Inventory();
|
|
var sword = inv.Add(_content.Items["rend_sword"]);
|
|
inv.TryEquip(sword, EquipSlot.MainHand, out _);
|
|
Assert.True(inv.TryUnequip(EquipSlot.MainHand, out _));
|
|
Assert.Null(sword.EquippedAt);
|
|
Assert.Null(inv.GetEquipped(EquipSlot.MainHand));
|
|
}
|
|
|
|
[Fact]
|
|
public void Remove_AlsoUnregistersFromEquippedSlot()
|
|
{
|
|
var inv = new Inventory();
|
|
var sword = inv.Add(_content.Items["rend_sword"]);
|
|
inv.TryEquip(sword, EquipSlot.MainHand, out _);
|
|
Assert.True(inv.Remove(sword));
|
|
Assert.Empty(inv.Items);
|
|
Assert.Null(inv.GetEquipped(EquipSlot.MainHand));
|
|
}
|
|
|
|
// ── SizeMatch ────────────────────────────────────────────────────────
|
|
|
|
[Fact]
|
|
public void SizeMatch_DirectFitReturnsMatch()
|
|
{
|
|
var rendSword = _content.Items["rend_sword"]; // medium + large
|
|
Assert.Equal(SizeMatch.MatchResult.Match, SizeMatch.Check(rendSword, SizeCategory.Medium));
|
|
}
|
|
|
|
[Fact]
|
|
public void SizeMatch_AdaptivePropertyOverridesMissingSize()
|
|
{
|
|
var pack = _content.Items["adaptive_pack"];
|
|
// adaptive_pack lists s/m/l explicitly, so match — but we test the Adaptive
|
|
// path by querying with a Tiny wearer that's not in the list.
|
|
var result = SizeMatch.Check(pack, SizeCategory.Tiny);
|
|
// adaptive_pack lists "small" so Tiny falls through to "tiny" not in list,
|
|
// but its "adaptive" property kicks in for the Adaptive branch.
|
|
Assert.NotEqual(SizeMatch.MatchResult.WrongSize, result);
|
|
}
|
|
|
|
[Fact]
|
|
public void SizeMatch_NonAdaptiveWrongSizeIsFlagged()
|
|
{
|
|
var rendSword = _content.Items["rend_sword"]; // medium + large only
|
|
// Small wearer: not in sizes, no adaptive property → WrongSize.
|
|
Assert.Equal(SizeMatch.MatchResult.WrongSize, SizeMatch.Check(rendSword, SizeCategory.Small));
|
|
}
|
|
}
|