fix(interaction): restore retail loot placement and world-drop projection
This commit is contained in:
parent
4d095be286
commit
921712f412
24 changed files with 1581 additions and 34 deletions
|
|
@ -214,6 +214,40 @@ public sealed class ClientObjectTable
|
|||
public ClientObject? Get(uint objectId) =>
|
||||
_objects.TryGetValue(objectId, out var item) ? item : null;
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>ACCWeenieObject::IsOwnedByObject @ 0x0058CEB0</c>.
|
||||
/// Direct containment/wielding counts, as does an item inside one of the
|
||||
/// owner's authored pack containers or on one of the owner's locations.
|
||||
/// </summary>
|
||||
public bool IsOwnedByObject(uint objectId, uint ownerId)
|
||||
{
|
||||
if (ownerId == 0u
|
||||
|| !_objects.TryGetValue(objectId, out ClientObject? item))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.ObjectId == ownerId
|
||||
|| item.ContainerId == ownerId
|
||||
|| item.WielderId == ownerId)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!_objects.ContainsKey(ownerId))
|
||||
return false;
|
||||
|
||||
if (item.ContainerId != 0u
|
||||
&& _containerIndex.TryGetValue(ownerId, out List<uint>? ownerContents)
|
||||
&& ownerContents.Contains(item.ContainerId))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return _equipmentIndex.TryGetValue(ownerId, out List<uint>? ownerLocations)
|
||||
&& ownerLocations.Contains(item.ObjectId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Look up a container by object id, creating a lightweight stub if
|
||||
/// the id doesn't match any known container (defensive — avoids losing
|
||||
|
|
@ -297,26 +331,43 @@ public sealed class ClientObjectTable
|
|||
newWielderId,
|
||||
newEquipLocation),
|
||||
containerTypeHint,
|
||||
ClientObjectMoveOrigin.AuthoritativeResponse);
|
||||
ClientObjectMoveOrigin.AuthoritativeResponse,
|
||||
retailContainerInsert: true);
|
||||
}
|
||||
|
||||
private bool ApplyPlacement(
|
||||
ClientObject item,
|
||||
ClientObjectPlacement current,
|
||||
uint? containerTypeHint,
|
||||
ClientObjectMoveOrigin origin)
|
||||
ClientObjectMoveOrigin origin,
|
||||
bool retailContainerInsert = false)
|
||||
{
|
||||
ClientObjectPlacement previous = ClientObjectPlacement.From(item);
|
||||
List<uint>? changedContainers = RemoveFromOtherContainerIndexes(
|
||||
item.ObjectId,
|
||||
current.ContainerId);
|
||||
bool previousWasContainer = IsContainerListMember(item);
|
||||
List<uint>? changedContainers;
|
||||
if (retailContainerInsert)
|
||||
{
|
||||
changedContainers = RemoveFromAllContainerIndexes(
|
||||
item.ObjectId,
|
||||
current.ContainerId,
|
||||
previousWasContainer);
|
||||
}
|
||||
else
|
||||
{
|
||||
changedContainers = RemoveFromOtherContainerIndexes(
|
||||
item.ObjectId,
|
||||
current.ContainerId);
|
||||
}
|
||||
item.ContainerId = current.ContainerId;
|
||||
item.ContainerSlot = current.ContainerSlot;
|
||||
item.WielderId = current.WielderId;
|
||||
item.CurrentlyEquippedLocation = current.EquipLocation;
|
||||
if (containerTypeHint is { } hint)
|
||||
item.ContainerTypeHint = hint;
|
||||
Reindex(item, previous.ContainerId);
|
||||
if (retailContainerInsert && item.ContainerId != 0u)
|
||||
InsertContainerMember(item, current.ContainerSlot);
|
||||
else
|
||||
Reindex(item, previous.ContainerId);
|
||||
PublishPlacementChange(item, previous, origin);
|
||||
PublishContainerContentsChanges(changedContainers);
|
||||
return true;
|
||||
|
|
@ -927,6 +978,101 @@ public sealed class ClientObjectTable
|
|||
return changed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail <c>ACCWeenieObject::ServerSaysMoveItem @ 0x0058DBB0</c>
|
||||
/// removes the object from its old <c>IDList</c> before
|
||||
/// <c>AddContent @ 0x0058CCE0</c> inserts it at the server-supplied list
|
||||
/// index. Remove the canonical member as well as stale ViewContents
|
||||
/// projections so an authoritative same-container move can perform that
|
||||
/// exact remove-then-insert transition.
|
||||
/// </summary>
|
||||
private List<uint>? RemoveFromAllContainerIndexes(
|
||||
uint itemId,
|
||||
uint destinationContainerId,
|
||||
bool wasContainer)
|
||||
{
|
||||
List<uint>? changed = null;
|
||||
foreach ((uint containerId, List<uint> members) in _containerIndex)
|
||||
{
|
||||
if (!members.Remove(itemId))
|
||||
continue;
|
||||
|
||||
RenumberContainerCategory(members, wasContainer);
|
||||
if (containerId != destinationContainerId)
|
||||
(changed ??= new List<uint>()).Add(containerId);
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Port of retail <c>ACCWeenieObject::AddContent @ 0x0058CCE0</c>:
|
||||
/// items and child containers have separate ordered <c>IDList</c>s and
|
||||
/// <c>IDList::AddAtNum</c> clamps the requested index to the list length.
|
||||
/// Our compact projection stores both categories in one list, so insertion
|
||||
/// and renumbering operate only inside the matching retail category.
|
||||
/// </summary>
|
||||
private void InsertContainerMember(ClientObject item, int requestedSlot)
|
||||
{
|
||||
if (!_containerIndex.TryGetValue(item.ContainerId, out List<uint>? members))
|
||||
_containerIndex[item.ContainerId] = members = new List<uint>();
|
||||
|
||||
bool isContainer = IsContainerListMember(item);
|
||||
int categoryCount = 0;
|
||||
int insertionIndex = members.Count;
|
||||
int requestedIndex = requestedSlot < 0 ? int.MaxValue : requestedSlot;
|
||||
for (int i = 0; i < members.Count; i++)
|
||||
{
|
||||
if (!_objects.TryGetValue(members[i], out ClientObject? existing)
|
||||
|| IsContainerListMember(existing) != isContainer)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (categoryCount == requestedIndex)
|
||||
{
|
||||
insertionIndex = i;
|
||||
break;
|
||||
}
|
||||
|
||||
categoryCount++;
|
||||
insertionIndex = i + 1;
|
||||
}
|
||||
|
||||
members.Insert(insertionIndex, item.ObjectId);
|
||||
int publishedSlot = requestedSlot < 0 ? categoryCount : requestedSlot;
|
||||
RenumberContainerCategory(
|
||||
members,
|
||||
isContainer,
|
||||
preservedItemId: item.ObjectId,
|
||||
preservedSlot: publishedSlot);
|
||||
}
|
||||
|
||||
private void RenumberContainerCategory(
|
||||
List<uint> members,
|
||||
bool isContainer,
|
||||
uint preservedItemId = 0u,
|
||||
int preservedSlot = -1)
|
||||
{
|
||||
int slot = 0;
|
||||
for (int i = 0; i < members.Count; i++)
|
||||
{
|
||||
if (_objects.TryGetValue(members[i], out ClientObject? member)
|
||||
&& IsContainerListMember(member) == isContainer)
|
||||
{
|
||||
member.ContainerSlot = member.ObjectId == preservedItemId
|
||||
? preservedSlot
|
||||
: slot;
|
||||
slot++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsContainerListMember(ClientObject item) =>
|
||||
item.ContainerTypeHint != 0u
|
||||
|| (item.Type & ItemType.Container) != 0
|
||||
|| item.ItemsCapacity > 0;
|
||||
|
||||
private void PublishContainerContentsChanges(List<uint>? changed)
|
||||
{
|
||||
if (changed is null || changed.Count == 0)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue