Skip to content

Commit 5040784

Browse files
committed
fix: respawn equipment desync
1 parent 02b8024 commit 5040784

File tree

2 files changed

+128
-17
lines changed

2 files changed

+128
-17
lines changed

Code/client/Services/Generic/PlayerService.cpp

Lines changed: 120 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include <Events/PartyJoinedEvent.h>
1717
#include <Events/PartyLeftEvent.h>
1818
#include <Events/BeastFormChangeEvent.h>
19+
#include <Events/EquipmentChangeEvent.h>
1920

2021
#include <Messages/PlayerRespawnRequest.h>
2122
#include <Messages/NotifyPlayerRespawn.h>
@@ -35,6 +36,7 @@
3536
#include <Games/References.h>
3637
#include <AI/AIProcess.h>
3738
#include <EquipManager.h>
39+
#include <DefaultObjectManager.h>
3840
#include <Forms/TESRace.h>
3941

4042
PlayerService::PlayerService(World& aWorld, entt::dispatcher& aDispatcher, TransportService& aTransport) noexcept
@@ -208,9 +210,32 @@ void PlayerService::RunRespawnUpdates(const double acDeltaTime) noexcept
208210
PlayerCharacter* pPlayer = PlayerCharacter::Get();
209211
if (!pPlayer->actorState.IsBleedingOut())
210212
{
211-
// Cache equipped spells and shouts so we can restore them after respawn.
212-
m_cachedMainSpellId = pPlayer->magicItems[0] ? pPlayer->magicItems[0]->formID : 0;
213-
m_cachedSecondarySpellId = pPlayer->magicItems[1] ? pPlayer->magicItems[1]->formID : 0;
213+
// Cache equipped items, spells, and shouts so we can restore them after respawn.
214+
m_cachedLeftHandSpellId = pPlayer->magicItems[0] ? pPlayer->magicItems[0]->formID : 0;
215+
m_cachedRightHandSpellId = pPlayer->magicItems[1] ? pPlayer->magicItems[1]->formID : 0;
216+
217+
TESForm* pLeftEquipped = nullptr;
218+
TESForm* pRightEquipped = nullptr;
219+
220+
if (m_cachedLeftHandSpellId == 0)
221+
pLeftEquipped = pPlayer->GetEquippedWeapon(0);
222+
if (m_cachedRightHandSpellId == 0)
223+
pRightEquipped = pPlayer->GetEquippedWeapon(1);
224+
225+
m_cachedLeftHandItemId = pLeftEquipped ? pLeftEquipped->formID : 0;
226+
m_cachedRightHandItemId = pRightEquipped ? pRightEquipped->formID : 0;
227+
228+
// Detect two-handed weapons so we can equip them via the either-hand slot.
229+
if (pLeftEquipped && pRightEquipped && pLeftEquipped == pRightEquipped)
230+
m_cachedTwoHandedItemId = pLeftEquipped->formID;
231+
else
232+
m_cachedTwoHandedItemId = 0;
233+
234+
if (auto* pAmmo = pPlayer->GetEquippedAmmo())
235+
m_cachedAmmoId = pAmmo->formID;
236+
else
237+
m_cachedAmmoId = 0;
238+
214239
m_cachedPowerId = pPlayer->equippedShout ? pPlayer->equippedShout->formID : 0;
215240

216241
s_startTimer = false;
@@ -318,24 +343,57 @@ void PlayerService::RunRespawnUpdates(const double acDeltaTime) noexcept
318343
m_knockdownTimer = 1.5;
319344
m_knockdownStart = true;
320345

346+
// Restore cached equipment
347+
auto* pEquipManager = EquipManager::Get();
348+
auto& defaultObjects = DefaultObjectManager::Get();
349+
350+
if (m_cachedLeftHandSpellId)
351+
{
352+
if (TESForm* pSpell = TESForm::GetById(m_cachedLeftHandSpellId))
353+
pEquipManager->EquipSpell(pPlayer, pSpell, 0);
354+
}
355+
else if (m_cachedLeftHandItemId && m_cachedTwoHandedItemId == 0)
356+
{
357+
if (TESForm* pItem = TESForm::GetById(m_cachedLeftHandItemId))
358+
pEquipManager->Equip(pPlayer, pItem, nullptr, 1, defaultObjects.leftEquipSlot, false, true, false, false);
359+
}
360+
361+
if (m_cachedRightHandSpellId)
362+
{
363+
if (TESForm* pSpell = TESForm::GetById(m_cachedRightHandSpellId))
364+
pEquipManager->EquipSpell(pPlayer, pSpell, 1);
365+
}
366+
else if (m_cachedTwoHandedItemId)
367+
{
368+
if (TESForm* pItem = TESForm::GetById(m_cachedTwoHandedItemId))
369+
pEquipManager->Equip(pPlayer, pItem, nullptr, 1, defaultObjects.eitherEquipSlot, false, true, false, false);
370+
}
371+
else if (m_cachedRightHandItemId)
372+
{
373+
if (TESForm* pItem = TESForm::GetById(m_cachedRightHandItemId))
374+
pEquipManager->Equip(pPlayer, pItem, nullptr, 1, defaultObjects.rightEquipSlot, false, true, false, false);
375+
}
376+
377+
if (m_cachedAmmoId)
378+
{
379+
if (TESForm* pAmmo = TESForm::GetById(m_cachedAmmoId))
380+
pEquipManager->Equip(pPlayer, pAmmo, nullptr, 1, defaultObjects.rightEquipSlot, false, true, false, false);
381+
}
382+
383+
if (m_cachedPowerId)
384+
{
385+
if (TESForm* pShout = TESForm::GetById(m_cachedPowerId))
386+
pEquipManager->EquipShout(pPlayer, pShout);
387+
}
388+
389+
SyncCachedEquipment(pPlayer);
390+
321391
m_transport.Send(PlayerRespawnRequest());
322392

323393
PartyMemberDownedRequest revivedRequest{};
324394
revivedRequest.IsDowned = false;
325395
m_transport.Send(revivedRequest);
326396

327-
// Restore spells and shouts
328-
auto* pEquipManager = EquipManager::Get();
329-
TESForm* pSpell = TESForm::GetById(m_cachedMainSpellId);
330-
if (pSpell)
331-
pEquipManager->EquipSpell(pPlayer, pSpell, 0);
332-
pSpell = TESForm::GetById(m_cachedSecondarySpellId);
333-
if (pSpell)
334-
pEquipManager->EquipSpell(pPlayer, pSpell, 1);
335-
pSpell = TESForm::GetById(m_cachedPowerId);
336-
if (pSpell)
337-
pEquipManager->EquipShout(pPlayer, pSpell);
338-
339397
m_waitingForRespawn = false;
340398
m_canRespawn = false;
341399
m_respawnTimer = 0.0;
@@ -364,6 +422,53 @@ void PlayerService::RunRespawnUpdates(const double acDeltaTime) noexcept
364422
}
365423
}
366424

425+
void PlayerService::SyncCachedEquipment(PlayerCharacter* apPlayer) noexcept
426+
{
427+
if (!apPlayer)
428+
return;
429+
430+
auto& defaultObjects = DefaultObjectManager::Get();
431+
432+
const auto dispatch = [&](uint32_t itemId, TESForm* pSlot, bool isSpell, bool isShout, bool isAmmo = false)
433+
{
434+
if (!itemId)
435+
return;
436+
437+
if (!TESForm::GetById(itemId))
438+
return;
439+
440+
EquipmentChangeEvent evt{};
441+
evt.ActorId = apPlayer->formID;
442+
evt.ItemId = itemId;
443+
evt.EquipSlotId = pSlot ? pSlot->formID : 0;
444+
evt.IsSpell = isSpell;
445+
evt.IsShout = isShout;
446+
evt.IsAmmo = isAmmo;
447+
if (!isSpell && !isShout)
448+
evt.Count = 1;
449+
450+
m_world.GetRunner().Trigger(evt);
451+
};
452+
453+
dispatch(m_cachedLeftHandSpellId, defaultObjects.leftEquipSlot, true, false);
454+
dispatch(m_cachedRightHandSpellId, defaultObjects.rightEquipSlot, true, false);
455+
456+
if (m_cachedTwoHandedItemId)
457+
{
458+
dispatch(m_cachedTwoHandedItemId, defaultObjects.eitherEquipSlot, false, false);
459+
}
460+
else
461+
{
462+
if (m_cachedLeftHandSpellId == 0)
463+
dispatch(m_cachedLeftHandItemId, defaultObjects.leftEquipSlot, false, false);
464+
if (m_cachedRightHandSpellId == 0)
465+
dispatch(m_cachedRightHandItemId, defaultObjects.rightEquipSlot, false, false);
466+
}
467+
468+
dispatch(m_cachedAmmoId, defaultObjects.rightEquipSlot, false, false, true);
469+
dispatch(m_cachedPowerId, nullptr, false, true);
470+
}
471+
367472
void PlayerService::RequestManualRespawn() noexcept
368473
{
369474
try

Code/client/Services/PlayerService.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
struct World;
44
struct TransportService;
5+
struct PlayerCharacter;
56

67
struct UpdateEvent;
78
struct ConnectedEvent;
@@ -57,6 +58,7 @@ struct PlayerService
5758
void RunDifficultyUpdates() const noexcept;
5859
void RunLevelUpdates() const noexcept;
5960
void RunBeastFormDetection() const noexcept;
61+
void SyncCachedEquipment(PlayerCharacter* apPlayer) noexcept;
6062

6163
void ToggleDeathSystem(bool aSet) noexcept;
6264

@@ -84,8 +86,12 @@ struct PlayerService
8486
bool m_godmodeStart = false;
8587
double m_godmodeTimer = 0.0;
8688

87-
uint32_t m_cachedMainSpellId = 0;
88-
uint32_t m_cachedSecondarySpellId = 0;
89+
uint32_t m_cachedLeftHandSpellId = 0;
90+
uint32_t m_cachedRightHandSpellId = 0;
91+
uint32_t m_cachedLeftHandItemId = 0;
92+
uint32_t m_cachedRightHandItemId = 0;
93+
uint32_t m_cachedTwoHandedItemId = 0;
94+
uint32_t m_cachedAmmoId = 0;
8995
uint32_t m_cachedPowerId = 0;
9096

9197
entt::scoped_connection m_updateConnection;

0 commit comments

Comments
 (0)