diff --git a/src/AcDream.Core/Items/ClientObjectTable.cs b/src/AcDream.Core/Items/ClientObjectTable.cs index 356fa7b6..5ed850a8 100644 --- a/src/AcDream.Core/Items/ClientObjectTable.cs +++ b/src/AcDream.Core/Items/ClientObjectTable.cs @@ -163,6 +163,32 @@ public sealed class ClientObjectTable return true; } + /// + /// Apply a patch, creating the object if it does not + /// exist yet. Used for the local player's own properties from PlayerDescription + /// (0x0013), which may arrive BEFORE the player's CreateObject — unlike + /// , which no-ops on an unknown object. Fires + /// ObjectAdded on create, else ObjectUpdated. + /// + public void UpsertProperties(uint guid, PropertyBundle incoming) + { + ArgumentNullException.ThrowIfNull(incoming); + bool existed = _objects.TryGetValue(guid, out var item); + if (!existed || item is null) + { + item = new ClientObject { ObjectId = guid }; + _objects[guid] = item; + } + foreach (var kv in incoming.Ints) item.Properties.Ints[kv.Key] = kv.Value; + foreach (var kv in incoming.Int64s) item.Properties.Int64s[kv.Key] = kv.Value; + foreach (var kv in incoming.Bools) item.Properties.Bools[kv.Key] = kv.Value; + foreach (var kv in incoming.Floats) item.Properties.Floats[kv.Key] = kv.Value; + foreach (var kv in incoming.Strings) item.Properties.Strings[kv.Key] = kv.Value; + foreach (var kv in incoming.DataIds) item.Properties.DataIds[kv.Key] = kv.Value; + foreach (var kv in incoming.InstanceIds) item.Properties.InstanceIds[kv.Key] = kv.Value; + if (!existed) ObjectAdded?.Invoke(item); else ObjectUpdated?.Invoke(item); + } + /// /// Apply a single PropertyInt update (from PublicUpdatePropertyInt 0x02CE) to an /// object: store it in the bundle and, for known typed ints, mirror to the typed diff --git a/tests/AcDream.Core.Tests/Items/ClientObjectTableUpdateTests.cs b/tests/AcDream.Core.Tests/Items/ClientObjectTableUpdateTests.cs new file mode 100644 index 00000000..b5ec1c78 --- /dev/null +++ b/tests/AcDream.Core.Tests/Items/ClientObjectTableUpdateTests.cs @@ -0,0 +1,41 @@ +using AcDream.Core.Items; +using Xunit; + +namespace AcDream.Core.Tests.Items; + +public sealed class ClientObjectTableUpdateTests +{ + [Fact] + public void UpsertProperties_unknownObject_createsThenMerges() + { + var t = new ClientObjectTable(); + bool added = false; + t.ObjectAdded += _ => added = true; + + var bundle = new PropertyBundle(); + bundle.Ints[5] = 1234; // EncumbranceVal + + t.UpsertProperties(0x50000001u, bundle); + + var o = t.Get(0x50000001u); + Assert.NotNull(o); + Assert.Equal(1234, o!.Properties.Ints[5]); + Assert.True(added); + } + + [Fact] + public void UpsertProperties_existingObject_mergesAndFiresUpdated() + { + var t = new ClientObjectTable(); + t.AddOrUpdate(new ClientObject { ObjectId = 0x50000001u }); + bool updated = false; + t.ObjectUpdated += _ => updated = true; + + var bundle = new PropertyBundle(); + bundle.Ints[5] = 99; + t.UpsertProperties(0x50000001u, bundle); + + Assert.Equal(99, t.Get(0x50000001u)!.Properties.Ints[5]); + Assert.True(updated); + } +}