diff --git a/src/AcDream.Core/Items/ClientObjectTable.cs b/src/AcDream.Core/Items/ClientObjectTable.cs index 94fa574f..356fa7b6 100644 --- a/src/AcDream.Core/Items/ClientObjectTable.cs +++ b/src/AcDream.Core/Items/ClientObjectTable.cs @@ -274,6 +274,35 @@ public sealed class ClientObjectTable _containerIndex.TryGetValue(containerId, out var l) ? l.ToArray() : System.Array.Empty(); + /// + /// Σ Burden over every object carried by : items + /// whose container chain roots at the owner (pack + side-bag contents, retail + /// hierarchy is 2-deep) plus items wielded by the owner. The client-side + /// equivalent of the server's EncumbranceVal (PropertyInt 5) — used by + /// the inventory burden bar until the wire value is parsed (B-Wire). The hop + /// cap (8) is a cycle guard; real chains are ≤2. + /// + public int SumCarriedBurden(uint ownerGuid) + { + int total = 0; + foreach (var o in _objects.Values) + if (IsCarriedBy(o, ownerGuid)) + total += o.Burden; + return total; + } + + private bool IsCarriedBy(ClientObject o, uint ownerGuid) + { + if (o.WielderId == ownerGuid) return true; + uint c = o.ContainerId; + for (int hops = 0; c != 0 && hops < 8; hops++) + { + if (c == ownerGuid) return true; + c = _objects.TryGetValue(c, out var parent) ? parent.ContainerId : 0u; + } + return false; + } + /// /// Flush the table — typically called on logoff or teleport /// that drops the session's object state. diff --git a/tests/AcDream.Core.Tests/Items/ClientObjectTableBurdenTests.cs b/tests/AcDream.Core.Tests/Items/ClientObjectTableBurdenTests.cs new file mode 100644 index 00000000..13e348c4 --- /dev/null +++ b/tests/AcDream.Core.Tests/Items/ClientObjectTableBurdenTests.cs @@ -0,0 +1,32 @@ +using AcDream.Core.Items; +using Xunit; + +namespace AcDream.Core.Tests.Items; + +public class ClientObjectTableBurdenTests +{ + private const uint Player = 0x50000001u; + + [Fact] + public void SumCarriedBurden_sums_pack_sidebag_and_wielded_but_not_unrelated() + { + var t = new ClientObjectTable(); + // loose pack item + t.AddOrUpdate(new ClientObject { ObjectId = 0xA, ContainerId = Player, Burden = 100 }); + // side bag in pack + t.AddOrUpdate(new ClientObject { ObjectId = 0xB, ContainerId = Player, Burden = 50, + Type = ItemType.Container }); + // item inside the side bag (2-deep) + t.AddOrUpdate(new ClientObject { ObjectId = 0xC, ContainerId = 0xB, Burden = 30 }); + // wielded (ContainerId 0, WielderId = player) + t.AddOrUpdate(new ClientObject { ObjectId = 0xD, WielderId = Player, Burden = 200 }); + // unrelated object in the world + t.AddOrUpdate(new ClientObject { ObjectId = 0xE, ContainerId = 0, Burden = 999 }); + + Assert.Equal(380, t.SumCarriedBurden(Player)); // 100+50+30+200 + } + + [Fact] + public void SumCarriedBurden_unknown_owner_is_zero() + => Assert.Equal(0, new ClientObjectTable().SumCarriedBurden(Player)); +}