Skip to content

Commit 02b8024

Browse files
committed
fix: item drop sync
1 parent 20637b4 commit 02b8024

File tree

9 files changed

+427
-20
lines changed

9 files changed

+427
-20
lines changed

Code/client/Events/InventoryChangeEvent.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
#pragma once
22

3-
#include <Structs/Inventory.h>
43
#include <ExtraData/ExtraDataList.h>
4+
#include <Structs/Inventory.h>
5+
#include <Games/Primitives.h>
6+
7+
#include <optional>
58

69
/**
710
* @brief Dispatched when the contents of an object or actor inventory changes locally.
@@ -23,16 +26,22 @@ struct InventoryChangeEvent
2326
{
2427
}
2528

26-
InventoryChangeEvent(const uint32_t aFormId, Inventory::Entry arItem, bool aDrop, bool aUpdateClients)
29+
InventoryChangeEvent(const uint32_t aFormId, Inventory::Entry arItem, bool aDrop, bool aUpdateClients, std::optional<NiPoint3> aDropLocation = std::nullopt, std::optional<NiPoint3> aDropRotation = std::nullopt, std::optional<uint32_t> aDropInstanceId = std::nullopt)
2730
: FormId(aFormId)
2831
, Item(std::move(arItem))
2932
, Drop(aDrop)
3033
, UpdateClients(aUpdateClients)
34+
, DropLocation(std::move(aDropLocation))
35+
, DropRotation(std::move(aDropRotation))
36+
, DropInstanceId(std::move(aDropInstanceId))
3137
{
3238
}
3339

3440
uint32_t FormId{};
3541
Inventory::Entry Item{};
3642
bool Drop = false;
3743
bool UpdateClients = true;
44+
std::optional<NiPoint3> DropLocation{};
45+
std::optional<NiPoint3> DropRotation{};
46+
std::optional<uint32_t> DropInstanceId{};
3847
};

Code/client/Games/Skyrim/Actor.cpp

Lines changed: 231 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include <TiltedOnlinePCH.h>
2+
13
#include <Games/References.h>
24
#include <Games/Skyrim/EquipManager.h>
35
#include <AI/AIProcess.h>
@@ -31,6 +33,9 @@
3133
#include <Games/Skyrim/Misc/InventoryEntry.h>
3234
#include <Games/Skyrim/ExtraData/ExtraCount.h>
3335
#include <Games/Misc/ActorKnowledge.h>
36+
#include <Games/Skyrim/TESObjectREFR.h>
37+
38+
#include <optional>
3439

3540
#include <ExtraData/ExtraDataList.h>
3641
#include <ExtraData/ExtraCharge.h>
@@ -55,6 +60,144 @@
5560

5661
#include <ModCompat/BehaviorVar.h>
5762

63+
namespace
64+
{
65+
constexpr float kDropSearchRadiusSquared = 200.0f * 200.0f;
66+
using DropHandleMap = TiltedPhoques::Map<uint32_t, TiltedPhoques::Map<uint32_t, uint32_t>>;
67+
DropHandleMap s_actorDropHandles;
68+
TiltedPhoques::Map<uint32_t, uint32_t> s_actorNextDropId;
69+
70+
TESObjectREFR* FindDroppedReferenceNear(const TESBoundObject* apObject, const NiPoint3& acCenter)
71+
{
72+
if (!apObject)
73+
return nullptr;
74+
75+
TES* pTes = TES::Get();
76+
if (!pTes || !pTes->cells || !pTes->cells->arr)
77+
return nullptr;
78+
79+
const int dimension = pTes->cells->dimension;
80+
if (dimension <= 0)
81+
return nullptr;
82+
83+
auto evaluateCell = [&](TESObjectCELL* apCell, TESObjectREFR*& apBestMatch, float& aBestDistanceSq) {
84+
if (!apCell || !apCell->IsValid())
85+
return;
86+
87+
auto* pReferences = apCell->refData.refArray;
88+
if (!pReferences)
89+
return;
90+
91+
const uint32_t referenceCount = apCell->refData.Count();
92+
for (uint32_t j = 0; j < referenceCount; ++j)
93+
{
94+
TESObjectREFR* pCandidate = pReferences[j].Get();
95+
if (!pCandidate || pCandidate->baseForm != apObject || pCandidate->formType == Actor::Type)
96+
continue;
97+
98+
const float diffX = pCandidate->position.x - acCenter.x;
99+
const float diffY = pCandidate->position.y - acCenter.y;
100+
const float diffZ = pCandidate->position.z - acCenter.z;
101+
const float distanceSq = diffX * diffX + diffY * diffY + diffZ * diffZ;
102+
103+
if (distanceSq < aBestDistanceSq)
104+
{
105+
aBestDistanceSq = distanceSq;
106+
apBestMatch = pCandidate;
107+
}
108+
}
109+
};
110+
111+
TESObjectREFR* pClosest = nullptr;
112+
float closestDistanceSq = kDropSearchRadiusSquared;
113+
114+
const int cellCount = dimension * dimension;
115+
for (int i = 0; i < cellCount; ++i)
116+
{
117+
TESObjectCELL* pCell = pTes->cells->arr[i];
118+
evaluateCell(pCell, pClosest, closestDistanceSq);
119+
}
120+
121+
if (!pClosest)
122+
evaluateCell(pTes->interiorCell, pClosest, closestDistanceSq);
123+
124+
return pClosest;
125+
}
126+
} // namespace
127+
128+
namespace DropSync
129+
{
130+
thread_local std::optional<uint32_t> PendingDropId{};
131+
thread_local uint32_t PendingDropActorFormId = 0;
132+
} // namespace DropSync
133+
134+
uint32_t Actor::RegisterLocalDrop(uint32_t aActorFormId, uint32_t aHandleBits) noexcept
135+
{
136+
if (aHandleBits == 0)
137+
return 0;
138+
139+
auto& nextId = s_actorNextDropId[aActorFormId];
140+
uint32_t dropId = ++nextId;
141+
s_actorDropHandles[aActorFormId][dropId] = aHandleBits;
142+
return dropId;
143+
}
144+
145+
void Actor::TrackRemoteDrop(uint32_t aActorFormId, uint32_t aDropId, uint32_t aHandleBits) noexcept
146+
{
147+
if (aDropId == 0 || aHandleBits == 0)
148+
return;
149+
150+
s_actorDropHandles[aActorFormId][aDropId] = aHandleBits;
151+
auto& nextId = s_actorNextDropId[aActorFormId];
152+
if (aDropId > nextId)
153+
nextId = aDropId;
154+
}
155+
156+
std::optional<uint32_t> Actor::ConsumeTrackedDrop(uint32_t aActorFormId, uint32_t aDropId) noexcept
157+
{
158+
if (aDropId == 0)
159+
return std::nullopt;
160+
161+
if (s_actorDropHandles.find(aActorFormId) == s_actorDropHandles.end())
162+
return std::nullopt;
163+
164+
auto& bucket = s_actorDropHandles.at(aActorFormId);
165+
auto handleIt = bucket.find(aDropId);
166+
if (handleIt == bucket.end())
167+
return std::nullopt;
168+
169+
uint32_t handleBits = handleIt->second;
170+
bucket.erase(handleIt);
171+
if (bucket.empty())
172+
s_actorDropHandles.erase(aActorFormId);
173+
174+
return handleBits;
175+
}
176+
177+
std::optional<uint32_t> Actor::ConsumeTrackedDropByHandle(uint32_t aActorFormId, uint32_t aHandleBits) noexcept
178+
{
179+
if (aHandleBits == 0)
180+
return std::nullopt;
181+
182+
if (s_actorDropHandles.find(aActorFormId) == s_actorDropHandles.end())
183+
return std::nullopt;
184+
185+
auto& bucket = s_actorDropHandles.at(aActorFormId);
186+
for (auto it = bucket.begin(); it != bucket.end(); ++it)
187+
{
188+
if (it->second == aHandleBits)
189+
{
190+
uint32_t dropId = it->first;
191+
bucket.erase(dropId);
192+
if (bucket.empty())
193+
s_actorDropHandles.erase(aActorFormId);
194+
return dropId;
195+
}
196+
}
197+
198+
return std::nullopt;
199+
}
200+
58201
#ifdef SAVE_STUFF
59202

60203
#include <Games/Skyrim/SaveLoad.h>
@@ -1098,7 +1241,19 @@ void* TP_MAKE_THISCALL(HookPickUpObject, Actor, TESObjectREFR* apObject, int32_t
10981241
// The inventory change event should always be sent to the server, otherwise the server inventory won't be updated.
10991242
bool shouldUpdateClients = apObject->IsTemporary() && !ScopedActivateOverride::IsOverriden();
11001243

1101-
World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID, std::move(item), false, shouldUpdateClients));
1244+
std::optional<NiPoint3> pickupLocation{};
1245+
std::optional<NiPoint3> pickupRotation{};
1246+
std::optional<uint32_t> dropInstanceId{};
1247+
if (apObject)
1248+
{
1249+
pickupLocation.emplace(apObject->position);
1250+
pickupRotation.emplace(apObject->rotation);
1251+
auto handle = apObject->GetHandle();
1252+
if (handle && handle.handle.iBits)
1253+
dropInstanceId = Actor::ConsumeTrackedDropByHandle(apThis->formID, handle.handle.iBits);
1254+
}
1255+
1256+
World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID, std::move(item), false, shouldUpdateClients, std::move(pickupLocation), std::move(pickupRotation), dropInstanceId));
11021257
}
11031258

11041259
return TiltedPhoques::ThisCall(RealPickUpObject, apThis, apObject, aCount, aUnk1, aUnk2);
@@ -1120,11 +1275,66 @@ void* TP_MAKE_THISCALL(HookDropObject, Actor, void* apResult, TESBoundObject* ap
11201275
if (apExtraData)
11211276
apThis->GetItemFromExtraData(item, apExtraData);
11221277

1123-
World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID, std::move(item), true));
1278+
const bool shouldSend = !ScopedInventoryOverride::IsOverriden();
1279+
1280+
void* pReturn = nullptr;
1281+
{
1282+
ScopedInventoryOverride _;
1283+
pReturn = TiltedPhoques::ThisCall(RealDropObject, apThis, apResult, apObject, apExtraData, aCount, apLocation, apRotation);
1284+
}
1285+
1286+
uint32_t handleBits = 0;
1287+
TESObjectREFR* pDroppedRef = nullptr;
1288+
if (auto* pHandle = static_cast<BSPointerHandle<TESObjectREFR>*>(apResult); pHandle && *pHandle)
1289+
{
1290+
handleBits = pHandle->handle.iBits;
1291+
if (handleBits)
1292+
pDroppedRef = TESObjectREFR::GetByHandle(handleBits);
1293+
}
1294+
1295+
if (DropSync::PendingDropId)
1296+
{
1297+
if (handleBits)
1298+
Actor::TrackRemoteDrop(DropSync::PendingDropActorFormId, *DropSync::PendingDropId, handleBits);
1299+
DropSync::PendingDropId.reset();
1300+
DropSync::PendingDropActorFormId = 0;
1301+
}
1302+
1303+
std::optional<NiPoint3> dropLocation{};
1304+
std::optional<NiPoint3> dropRotation{};
1305+
1306+
if (pDroppedRef)
1307+
{
1308+
dropLocation.emplace(pDroppedRef->position);
1309+
dropRotation.emplace(pDroppedRef->rotation);
1310+
}
1311+
1312+
if (!dropLocation)
1313+
{
1314+
if (apLocation)
1315+
dropLocation.emplace(*apLocation);
1316+
else
1317+
dropLocation.emplace(apThis->position);
1318+
}
1319+
1320+
if (!dropRotation)
1321+
{
1322+
if (apRotation)
1323+
dropRotation.emplace(*apRotation);
1324+
else
1325+
dropRotation.emplace(apThis->rotation);
1326+
}
1327+
1328+
std::optional<uint32_t> dropInstanceId{};
1329+
if (shouldSend && handleBits)
1330+
dropInstanceId = Actor::RegisterLocalDrop(apThis->formID, handleBits);
11241331

1125-
ScopedInventoryOverride _;
1332+
if (shouldSend)
1333+
{
1334+
World::Get().GetRunner().Trigger(InventoryChangeEvent(apThis->formID, std::move(item), true, true, std::move(dropLocation), std::move(dropRotation), dropInstanceId));
1335+
}
11261336

1127-
return TiltedPhoques::ThisCall(RealDropObject, apThis, apResult, apObject, apExtraData, aCount, apLocation, apRotation);
1337+
return pReturn;
11281338
}
11291339

11301340
void Actor::DropOrPickUpObject(const Inventory::Entry& arEntry, NiPoint3* apLocation, NiPoint3* apRotation) noexcept
@@ -1143,7 +1353,23 @@ void Actor::DropOrPickUpObject(const Inventory::Entry& arEntry, NiPoint3* apLoca
11431353

11441354
if (arEntry.Count < 0)
11451355
DropObject(pObject, pExtraData, -arEntry.Count, apLocation, apRotation);
1146-
// TODO: pick up
1356+
else if (arEntry.Count > 0)
1357+
{
1358+
NiPoint3 searchLocation = apLocation ? *apLocation : position;
1359+
TESObjectREFR* pDroppedRef = FindDroppedReferenceNear(pObject, searchLocation);
1360+
1361+
if (!pDroppedRef && apLocation)
1362+
pDroppedRef = FindDroppedReferenceNear(pObject, position);
1363+
1364+
if (!pDroppedRef)
1365+
{
1366+
spdlog::warn("Object to pick up not found near target location, {:X}:{:X}.", arEntry.BaseId.ModId, arEntry.BaseId.BaseId);
1367+
return;
1368+
}
1369+
1370+
spdlog::debug("Picking up object, form id: {:X}, count: {}, actor: {:X}", pObject->formID, arEntry.Count, formID);
1371+
PickUpObject(pDroppedRef, arEntry.Count, false, 0.0f);
1372+
}
11471373
}
11481374

11491375
void Actor::DropObject(TESBoundObject* apObject, ExtraDataList* apExtraData, int32_t aCount, NiPoint3* apLocation, NiPoint3* apRotation) noexcept

Code/client/Games/Skyrim/Actor.h

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#include <Games/Events.h>
44
#include <TESObjectREFR.h>
55

6+
#include <optional>
7+
68
#include <Magic/MagicTarget.h>
79
#include <Forms/TESActorBase.h>
810
#include <Misc/ActorState.h>
@@ -241,8 +243,12 @@ struct Actor : TESObjectREFR
241243
void Kill() noexcept;
242244
void Respawn() noexcept;
243245
void PickUpObject(TESObjectREFR* apObject, int32_t aCount, bool aUnk1, float aUnk2) noexcept;
244-
void DropObject(TESBoundObject* apObject, ExtraDataList* apExtraData, int32_t aCount, NiPoint3* apLocation, NiPoint3* apRotation) noexcept;
245246
void DropOrPickUpObject(const Inventory::Entry& arEntry, NiPoint3* apPoint, NiPoint3* apRotate) noexcept;
247+
void DropObject(TESBoundObject* apObject, ExtraDataList* apExtraData, int32_t aCount, NiPoint3* apLocation, NiPoint3* apRotation) noexcept;
248+
static uint32_t RegisterLocalDrop(uint32_t aActorFormId, uint32_t aHandleBits) noexcept;
249+
static void TrackRemoteDrop(uint32_t aActorFormId, uint32_t aDropId, uint32_t aHandleBits) noexcept;
250+
static std::optional<uint32_t> ConsumeTrackedDrop(uint32_t aActorFormId, uint32_t aDropId) noexcept;
251+
static std::optional<uint32_t> ConsumeTrackedDropByHandle(uint32_t aActorFormId, uint32_t aHandleBits) noexcept;
246252
void SpeakSound(const char* pFile);
247253
void StartCombatEx(Actor* apTarget) noexcept;
248254
void SetCombatTargetEx(Actor* apTarget) noexcept;
@@ -366,7 +372,6 @@ struct Actor : TESObjectREFR
366372
};
367373

368374
static_assert(offsetof(Actor, currentProcess) == 0xF8);
369-
static_assert(offsetof(Actor, flags1) == 0xE8);
370375
static_assert(offsetof(Actor, actorValueOwner) == 0xB8);
371376
static_assert(offsetof(Actor, actorState) == 0xC0);
372377
static_assert(offsetof(Actor, flags2) == 0x204);
@@ -380,3 +385,9 @@ static_assert(offsetof(Actor, equippedShout) == 0x1E8);
380385
static_assert(offsetof(Actor, actorLock) == 0x284);
381386
static_assert(sizeof(Actor) == 0x2B8);
382387
static_assert(sizeof(Actor::SpellItemEntry) == 0x18);
388+
389+
namespace DropSync
390+
{
391+
extern thread_local std::optional<uint32_t> PendingDropId;
392+
extern thread_local uint32_t PendingDropActorFormId;
393+
}

0 commit comments

Comments
 (0)