From 64c2766bec389ed50a83bbde6e2ef0ed4c7acd37 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 25 May 2024 14:13:45 +0200 Subject: [PATCH 1/7] fix: vampire lord transformation --- Code/client/Games/References.cpp | 6 +++ Code/client/Games/Skyrim/Actor.h | 2 + Code/client/Games/Skyrim/TESObjectREFR.cpp | 5 ++ Code/client/Games/Skyrim/TESObjectREFR.h | 1 + Code/client/Services/Debug/DebugService.cpp | 26 +++++++--- .../Services/Generic/CharacterService.cpp | 1 + .../Services/Generic/InventoryService.cpp | 2 + Code/client/Services/Generic/MagicService.cpp | 49 +++++++++++++++++++ Code/client/Services/MagicService.h | 3 ++ 9 files changed, 89 insertions(+), 6 deletions(-) diff --git a/Code/client/Games/References.cpp b/Code/client/Games/References.cpp index c143d901c..7a6d33098 100644 --- a/Code/client/Games/References.cpp +++ b/Code/client/Games/References.cpp @@ -846,6 +846,12 @@ void Actor::StopCombat() noexcept s_pStopCombat(this); } +bool Actor::PlayIdle(TESIdleForm* apIdle) noexcept +{ + PAPYRUS_FUNCTION(bool, Actor, PlayIdle, TESIdleForm*); + return s_pPlayIdle(this, apIdle); +} + bool Actor::HasPerk(uint32_t aPerkFormId) const noexcept { return GetPerkRank(aPerkFormId) != 0; diff --git a/Code/client/Games/Skyrim/Actor.h b/Code/client/Games/Skyrim/Actor.h index 8ef8e5459..619ad4e31 100644 --- a/Code/client/Games/Skyrim/Actor.h +++ b/Code/client/Games/Skyrim/Actor.h @@ -21,6 +21,7 @@ struct ExPlayerCharacter; struct ActorExtension; struct AIProcess; struct CombatController; +struct TESIdleForm; struct Actor : TESObjectREFR { @@ -246,6 +247,7 @@ struct Actor : TESObjectREFR void SetCombatTargetEx(Actor* apTarget) noexcept; void StartCombat(Actor* apTarget) noexcept; void StopCombat() noexcept; + bool PlayIdle(TESIdleForm* apIdle) noexcept; enum ActorFlags { diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index a1e93bcfa..5a444405f 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -578,6 +578,11 @@ bool TESObjectREFR::PlayAnimation(BSFixedString* apEventName) noexcept return result; } +bool TESObjectREFR::SendAnimationEvent(BSFixedString* apEventName) noexcept +{ + return animationGraphHolder.SendAnimationEvent(apEventName); +} + bool TP_MAKE_THISCALL(HookPlayAnimation, void, uint32_t auiStackID, TESObjectREFR* apSelf, BSFixedString* apEventName) { spdlog::debug("EventName: {}", apEventName->AsAscii()); diff --git a/Code/client/Games/Skyrim/TESObjectREFR.h b/Code/client/Games/Skyrim/TESObjectREFR.h index 6e1d856d6..de0d9d63c 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.h +++ b/Code/client/Games/Skyrim/TESObjectREFR.h @@ -175,6 +175,7 @@ struct TESObjectREFR : TESForm void MoveTo(TESObjectCELL* apCell, const NiPoint3& acPosition) const noexcept; void PayGold(int32_t aAmount) noexcept; void PayGoldToContainer(TESObjectREFR* pContainer, int32_t aAmount) noexcept; + bool SendAnimationEvent(BSFixedString* apEventName) noexcept; bool Activate(TESObjectREFR* apActivator, uint8_t aUnk1, TESBoundObject* apObjectToGet, int32_t aCount, char aDefaultProcessing) noexcept; diff --git a/Code/client/Services/Debug/DebugService.cpp b/Code/client/Services/Debug/DebugService.cpp index 1bb8fd4f5..bf950c4cc 100644 --- a/Code/client/Services/Debug/DebugService.cpp +++ b/Code/client/Services/Debug/DebugService.cpp @@ -147,6 +147,8 @@ void DebugService::OnMoveActor(const MoveActorEvent& acEvent) noexcept moveData.position = acEvent.Position; } +extern thread_local bool g_forceAnimation; + void DebugService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { if (!BSGraphics::GetMainWindow()->IsForeground()) @@ -197,11 +199,18 @@ void DebugService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { s_f7Pressed = true; - static char s_address[256] = "de.playtogether.gg:10100"; - if (!m_transport.IsOnline()) - m_transport.Connect(s_address); - else - m_transport.Close(); + auto* pIdle = Cast(TESForm::GetById(0x20023fe)); + + g_forceAnimation = true; + auto* pActor = Cast(TESForm::GetById(m_formId)); + BSFixedString str("LevitationToggle"); + //bool res = pActor->PlayAnimation(&str); + bool res = pActor->SendAnimationEvent(&str); + spdlog::error("{}", res); + g_forceAnimation = false; + + //auto* pIdle = Cast(TESForm::GetById(0x20023fe)); + //PlayerCharacter::Get()->PlayIdle(pIdle); } } else @@ -213,7 +222,12 @@ void DebugService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { s_f8Pressed = true; - PlaceActorInWorld(); + //PlaceActorInWorld(); + auto* pObjForm = TESForm::GetById(0x2011a86); + if (!pObjForm) + spdlog::error("Not found"); + + EquipManager::Get()->Equip(PlayerCharacter::Get(), pObjForm, nullptr, 1, nullptr, false, true, false, false); } } else diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index c43734949..ce07ce12a 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -650,6 +650,7 @@ void CharacterService::OnNotifyRespawn(const NotifyRespawn& acMessage) const noe m_transport.Send(request); } +// TODO: delete this shit void CharacterService::OnLeaveBeastForm(const LeaveBeastFormEvent& acEvent) const noexcept { auto view = m_world.view(); diff --git a/Code/client/Services/Generic/InventoryService.cpp b/Code/client/Services/Generic/InventoryService.cpp index 02dc51639..fbf71b01e 100644 --- a/Code/client/Services/Generic/InventoryService.cpp +++ b/Code/client/Services/Generic/InventoryService.cpp @@ -121,6 +121,8 @@ void InventoryService::OnEquipmentChangeEvent(const EquipmentChangeEvent& acEven request.CurrentInventory = pActor->GetEquipment(); m_transport.Send(request); + + spdlog::info("Sending equipment request, item: {:X}, count: {}, target object: {:X}", acEvent.ItemId, acEvent.Count, acEvent.ActorId); } void InventoryService::OnNotifyInventoryChanges(const NotifyInventoryChanges& acMessage) noexcept diff --git a/Code/client/Services/Generic/MagicService.cpp b/Code/client/Services/Generic/MagicService.cpp index 26eefb476..56e639cac 100644 --- a/Code/client/Services/Generic/MagicService.cpp +++ b/Code/client/Services/Generic/MagicService.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -54,6 +55,7 @@ void MagicService::OnUpdate(const UpdateEvent& acEvent) noexcept return; ApplyQueuedEffects(); + RunVampireLordTransformationFinish(acEvent.Delta); UpdateRevealOtherPlayersEffect(); } @@ -418,7 +420,10 @@ void MagicService::OnNotifyAddTarget(const NotifyAddTarget& acMessage) noexcept pActor->GetExtension()->GraphDescriptorHash = AnimationGraphDescriptor_WerewolfBehavior::m_key; if (pEffect->IsVampireLordEffect()) + { pActor->GetExtension()->GraphDescriptorHash = AnimationGraphDescriptor_VampireLordBehavior::m_key; + m_queuedVampireLords[pActor->formID] = 0.0; + } // TODO: ft, check if this bug also occurs in fallout 4 // This hack is here because slow time seems to be twice as slow when cast by an npc @@ -487,6 +492,50 @@ void MagicService::ApplyQueuedEffects() noexcept m_queuedRemoteEffects.erase(serverId); } +extern thread_local bool g_forceAnimation; + +void MagicService::RunVampireLordTransformationFinish(double aDelta) noexcept +{ + Vector markedForRemoval{}; + + for (auto& [formId, _] : m_queuedVampireLords) + { + auto& timer = m_queuedVampireLords[formId]; + + timer += aDelta; + if (timer < 5.0) + continue; + + markedForRemoval.push_back(formId); + + Actor* pActor = Cast(TESForm::GetById(formId)); + if (pActor == nullptr) + continue; + + auto* pObjForm = TESForm::GetById(0x2011a84); + TESBoundObject* pObject = Cast(pObjForm); + + { + ScopedInventoryOverride _; + pActor->AddObjectToContainer(pObject, nullptr, 1, nullptr); + } + + EquipManager::Get()->Equip(pActor, pObject, nullptr, 1, nullptr, false, true, false, false); + + auto* pIdle = Cast(TESForm::GetById(0x20023fe)); + + g_forceAnimation = true; + BSFixedString levitation("LevitationToggle"); + pActor->SendAnimationEvent(&levitation); + BSFixedString weapEquip("WeapEquip"); + pActor->SendAnimationEvent(&weapEquip); + g_forceAnimation = false; + } + + for (uint32_t formId : markedForRemoval) + m_queuedVampireLords.erase(formId); +} + void MagicService::UpdateRevealOtherPlayersEffect() noexcept { #if TP_SKYRIM64 diff --git a/Code/client/Services/MagicService.h b/Code/client/Services/MagicService.h index 034b267b5..f72b928fd 100644 --- a/Code/client/Services/MagicService.h +++ b/Code/client/Services/MagicService.h @@ -72,6 +72,8 @@ struct MagicService */ void ApplyQueuedEffects() noexcept; + void RunVampireLordTransformationFinish(double aDelta) noexcept; + /** * Apply the "reveal players" effect on remote players. */ @@ -87,6 +89,7 @@ struct MagicService */ Map m_queuedEffects; Map m_queuedRemoteEffects; + Map m_queuedVampireLords; bool m_revealOtherPlayers = false; From c0746ec645b370c9a4ee81ffa44d16d8e31c5277 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 25 May 2024 17:53:41 +0200 Subject: [PATCH 2/7] tweak: notify server of beast form --- Code/client/Events/BeastFormChangeEvent.h | 8 ++++ Code/client/Events/LeaveBeastFormEvent.h | 8 ---- Code/client/Games/Skyrim/PlayerCharacter.cpp | 8 ++-- Code/client/Services/CharacterService.h | 7 ++-- .../Services/Generic/CharacterService.cpp | 38 +++++++++++++------ Code/encoding/Messages/RequestRespawn.cpp | 4 ++ Code/encoding/Messages/RequestRespawn.h | 6 ++- Code/server/Services/CharacterService.cpp | 10 ++++- 8 files changed, 60 insertions(+), 29 deletions(-) create mode 100644 Code/client/Events/BeastFormChangeEvent.h delete mode 100644 Code/client/Events/LeaveBeastFormEvent.h diff --git a/Code/client/Events/BeastFormChangeEvent.h b/Code/client/Events/BeastFormChangeEvent.h new file mode 100644 index 000000000..cc7576207 --- /dev/null +++ b/Code/client/Events/BeastFormChangeEvent.h @@ -0,0 +1,8 @@ +#pragma once + +/** + * @brief Dispatched when the local player enters or leaves beast form (vampire lord, werewolf). + */ +struct BeastFormChangeEvent +{ +}; diff --git a/Code/client/Events/LeaveBeastFormEvent.h b/Code/client/Events/LeaveBeastFormEvent.h deleted file mode 100644 index 211dac5b3..000000000 --- a/Code/client/Events/LeaveBeastFormEvent.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once - -/** - * @brief Dispatched when the local player leaves beast form (vampire lord, werewolf). - */ -struct LeaveBeastFormEvent -{ -}; diff --git a/Code/client/Games/Skyrim/PlayerCharacter.cpp b/Code/client/Games/Skyrim/PlayerCharacter.cpp index 650276f7e..52f78d216 100644 --- a/Code/client/Games/Skyrim/PlayerCharacter.cpp +++ b/Code/client/Games/Skyrim/PlayerCharacter.cpp @@ -2,11 +2,12 @@ #include #include +#include #include #include -#include +#include #include #include #include @@ -160,10 +161,9 @@ char TP_MAKE_THISCALL(HookPickUpObject, PlayerCharacter, TESObjectREFR* apObject void TP_MAKE_THISCALL(HookSetBeastForm, void, void* apUnk1, void* apUnk2, bool aEntering) { if (!aEntering) - { PlayerCharacter::Get()->GetExtension()->GraphDescriptorHash = AnimationGraphDescriptor_Master_Behavior::m_key; - World::Get().GetRunner().Trigger(LeaveBeastFormEvent()); - } + + World::Get().GetRunner().Trigger(BeastFormChangeEvent()); TiltedPhoques::ThisCall(RealSetBeastForm, apThis, apUnk1, apUnk2, aEntering); } diff --git a/Code/client/Services/CharacterService.h b/Code/client/Services/CharacterService.h index 8f098a701..7ae13a0e6 100644 --- a/Code/client/Services/CharacterService.h +++ b/Code/client/Services/CharacterService.h @@ -31,7 +31,7 @@ struct NotifyMount; struct InitPackageEvent; struct NotifyNewPackage; struct NotifyRespawn; -struct LeaveBeastFormEvent; +struct BeastFormChangeEvent; struct AddExperienceEvent; struct NotifySyncExperience; struct DialogueEvent; @@ -78,7 +78,7 @@ struct CharacterService void OnInitPackageEvent(const InitPackageEvent& acEvent) const noexcept; void OnNotifyNewPackage(const NotifyNewPackage& acMessage) const noexcept; void OnNotifyRespawn(const NotifyRespawn& acMessage) const noexcept; - void OnLeaveBeastForm(const LeaveBeastFormEvent& acEvent) const noexcept; + void OnBeastFormChange(const BeastFormChangeEvent& acEvent) const noexcept; void OnAddExperienceEvent(const AddExperienceEvent& acEvent) noexcept; void OnNotifySyncExperience(const NotifySyncExperience& acMessage) noexcept; void OnDialogueEvent(const DialogueEvent& acEvent) noexcept; @@ -114,6 +114,7 @@ struct CharacterService float m_cachedExperience = 0.f; + // TODO: revamp this, read the local anim var like vampire lord? struct WeaponDrawData { WeaponDrawData() = default; @@ -147,7 +148,7 @@ struct CharacterService entt::scoped_connection m_initPackageConnection; entt::scoped_connection m_newPackageConnection; entt::scoped_connection m_notifyRespawnConnection; - entt::scoped_connection m_leaveBeastFormConnection; + entt::scoped_connection m_beastFormChangeConnection; entt::scoped_connection m_addExperienceEventConnection; entt::scoped_connection m_syncExperienceConnection; entt::scoped_connection m_dialogueEventConnection; diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index ce07ce12a..74f466b34 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include #include @@ -95,7 +95,7 @@ CharacterService::CharacterService(World& aWorld, entt::dispatcher& aDispatcher, m_newPackageConnection = m_dispatcher.sink().connect<&CharacterService::OnNotifyNewPackage>(this); m_notifyRespawnConnection = m_dispatcher.sink().connect<&CharacterService::OnNotifyRespawn>(this); - m_leaveBeastFormConnection = m_dispatcher.sink().connect<&CharacterService::OnLeaveBeastForm>(this); + m_beastFormChangeConnection = m_dispatcher.sink().connect<&CharacterService::OnBeastFormChange>(this); m_addExperienceEventConnection = m_dispatcher.sink().connect<&CharacterService::OnAddExperienceEvent>(this); m_syncExperienceConnection = m_dispatcher.sink().connect<&CharacterService::OnNotifySyncExperience>(this); @@ -634,24 +634,33 @@ void CharacterService::OnRemoveCharacter(const NotifyRemoveCharacter& acMessage) void CharacterService::OnNotifyRespawn(const NotifyRespawn& acMessage) const noexcept { - Actor* pActor = Utils::GetByServerId(acMessage.ActorId); - if (!pActor) + auto view = m_world.view(); + const auto entityIt = std::find_if(view.begin(), view.end(), [view, id = acMessage.ActorId](auto aEntity) { return view.get(aEntity).Id == id; }); + + if (entityIt == view.end()) { - spdlog::error("{}: could not find actor server id {:X}", __FUNCTION__, acMessage.ActorId); + spdlog::error("Actor to respawn not found in: {:X}", acMessage.ActorId); return; } - pActor->Delete(); + const auto cId = *entityIt; - // TODO: delete components? + auto& formIdComponent = view.get(cId); + CancelServerAssignment(*entityIt, formIdComponent.Id); + + if (m_world.all_of(cId)) + m_world.remove(cId); + + if (m_world.orphan(cId)) + m_world.destroy(cId); RequestRespawn request; request.ActorId = acMessage.ActorId; + m_transport.Send(request); } -// TODO: delete this shit -void CharacterService::OnLeaveBeastForm(const LeaveBeastFormEvent& acEvent) const noexcept +void CharacterService::OnBeastFormChange(const BeastFormChangeEvent& acEvent) const noexcept { auto view = m_world.view(); @@ -670,8 +679,15 @@ void CharacterService::OnLeaveBeastForm(const LeaveBeastFormEvent& acEvent) cons request.ActorId = serverId; Actor* pActor = Utils::GetByServerId(serverId); - if (pActor) - pActor->Delete(); + if (!pActor) + return; + + TESNPC* pNpc = Cast(pActor->baseForm); + if (!pNpc) + return; + + pNpc->Serialize(&request.AppearanceBuffer); + request.ChangeFlags = pNpc->GetChangeFlags(); m_transport.Send(request); } diff --git a/Code/encoding/Messages/RequestRespawn.cpp b/Code/encoding/Messages/RequestRespawn.cpp index 19b956fc9..052f86d79 100644 --- a/Code/encoding/Messages/RequestRespawn.cpp +++ b/Code/encoding/Messages/RequestRespawn.cpp @@ -3,6 +3,8 @@ void RequestRespawn::SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter) const noexcept { Serialization::WriteVarInt(aWriter, ActorId); + Serialization::WriteString(aWriter, AppearanceBuffer); + Serialization::WriteVarInt(aWriter, ChangeFlags); } void RequestRespawn::DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept @@ -10,4 +12,6 @@ void RequestRespawn::DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noex ClientMessage::DeserializeRaw(aReader); ActorId = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + AppearanceBuffer = Serialization::ReadString(aReader); + ChangeFlags = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; } diff --git a/Code/encoding/Messages/RequestRespawn.h b/Code/encoding/Messages/RequestRespawn.h index 50cf7d81c..877aec2f3 100644 --- a/Code/encoding/Messages/RequestRespawn.h +++ b/Code/encoding/Messages/RequestRespawn.h @@ -14,7 +14,9 @@ struct RequestRespawn final : ClientMessage void SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter) const noexcept override; void DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept override; - bool operator==(const RequestRespawn& acRhs) const noexcept { return GetOpcode() == acRhs.GetOpcode() && ActorId == acRhs.ActorId; } + bool operator==(const RequestRespawn& acRhs) const noexcept { return GetOpcode() == acRhs.GetOpcode() && ActorId == acRhs.ActorId && AppearanceBuffer == acRhs.AppearanceBuffer && ChangeFlags == acRhs.ChangeFlags; } - uint32_t ActorId; + uint32_t ActorId{}; + String AppearanceBuffer{}; + uint32_t ChangeFlags{0}; }; diff --git a/Code/server/Services/CharacterService.cpp b/Code/server/Services/CharacterService.cpp index f54c031eb..c21db88d2 100644 --- a/Code/server/Services/CharacterService.cpp +++ b/Code/server/Services/CharacterService.cpp @@ -480,7 +480,7 @@ void CharacterService::OnNewPackageRequest(const PacketEvent& void CharacterService::OnRequestRespawn(const PacketEvent& acMessage) const noexcept { - auto view = m_world.view(); + auto view = m_world.view(); auto it = view.find(static_cast(acMessage.Packet.ActorId)); if (it == view.end()) { @@ -491,6 +491,14 @@ void CharacterService::OnRequestRespawn(const PacketEvent& acMes auto& ownerComponent = view.get(*it); if (ownerComponent.GetOwner() == acMessage.pPlayer) { + if (!acMessage.Packet.AppearanceBuffer.empty()) + { + auto& characterComponent = view.get(*it); + characterComponent.SaveBuffer = acMessage.Packet.AppearanceBuffer; + characterComponent.ChangeFlags = acMessage.Packet.ChangeFlags; + spdlog::info("Respawning with new buffer"); + } + NotifyRespawn notify; notify.ActorId = acMessage.Packet.ActorId; From 1b0f698d2076ef23f8c585ebc7d7401ca06066a7 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 25 May 2024 18:31:46 +0200 Subject: [PATCH 3/7] fix: late-inform server --- Code/client/Games/Skyrim/PlayerCharacter.cpp | 5 ++-- .../Services/Generic/CharacterService.cpp | 2 +- .../client/Services/Generic/PlayerService.cpp | 28 +++++++++++++++++++ Code/client/Services/PlayerService.h | 1 + 4 files changed, 33 insertions(+), 3 deletions(-) diff --git a/Code/client/Games/Skyrim/PlayerCharacter.cpp b/Code/client/Games/Skyrim/PlayerCharacter.cpp index 52f78d216..9800851ef 100644 --- a/Code/client/Games/Skyrim/PlayerCharacter.cpp +++ b/Code/client/Games/Skyrim/PlayerCharacter.cpp @@ -161,9 +161,10 @@ char TP_MAKE_THISCALL(HookPickUpObject, PlayerCharacter, TESObjectREFR* apObject void TP_MAKE_THISCALL(HookSetBeastForm, void, void* apUnk1, void* apUnk2, bool aEntering) { if (!aEntering) + { PlayerCharacter::Get()->GetExtension()->GraphDescriptorHash = AnimationGraphDescriptor_Master_Behavior::m_key; - - World::Get().GetRunner().Trigger(BeastFormChangeEvent()); + World::Get().GetRunner().Trigger(BeastFormChangeEvent()); + } TiltedPhoques::ThisCall(RealSetBeastForm, apThis, apUnk1, apUnk2, aEntering); } diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index 74f466b34..3b08b59e9 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -404,7 +404,7 @@ void CharacterService::OnCharacterSpawn(const CharacterSpawnRequest& acMessage) if (waitingItor != std::end(waitingView)) { - spdlog::info("Character with form id {:X} already has a spawn request in progress.", acMessage.FormId); + spdlog::info("Character with form id {:X} already has a spawn request in progress.", cActorId); return; } diff --git a/Code/client/Services/Generic/PlayerService.cpp b/Code/client/Services/Generic/PlayerService.cpp index 9e11138b8..397f586f8 100644 --- a/Code/client/Services/Generic/PlayerService.cpp +++ b/Code/client/Services/Generic/PlayerService.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -29,6 +30,7 @@ #include #include #include +#include PlayerService::PlayerService(World& aWorld, entt::dispatcher& aDispatcher, TransportService& aTransport) noexcept : m_world(aWorld) @@ -54,6 +56,7 @@ void PlayerService::OnUpdate(const UpdateEvent& acEvent) noexcept RunPostDeathUpdates(acEvent.Delta); RunDifficultyUpdates(); RunLevelUpdates(); + RunBeastFormDetection(); } void PlayerService::OnConnected(const ConnectedEvent& acEvent) noexcept @@ -354,6 +357,31 @@ void PlayerService::RunLevelUpdates() const noexcept } } +void PlayerService::RunBeastFormDetection() const noexcept +{ + static uint32_t lastRaceFormID = 0; + static std::chrono::steady_clock::time_point lastSendTimePoint; + constexpr auto cDelayBetweenUpdates = 250ms; + + const auto now = std::chrono::steady_clock::now(); + if (now - lastSendTimePoint < cDelayBetweenUpdates) + return; + + lastSendTimePoint = now; + + PlayerCharacter* pPlayer = PlayerCharacter::Get(); + if (!pPlayer->race) + return; + + if (pPlayer->race->formID == lastRaceFormID) + return; + + if (pPlayer->race->formID == 0x200283A || pPlayer->race->formID == 0xCDD84) + m_world.GetDispatcher().trigger(BeastFormChangeEvent()); + + lastRaceFormID = pPlayer->race->formID; +} + void PlayerService::ToggleDeathSystem(bool aSet) noexcept { m_isDeathSystemEnabled = aSet; diff --git a/Code/client/Services/PlayerService.h b/Code/client/Services/PlayerService.h index add0488c3..468a051cc 100644 --- a/Code/client/Services/PlayerService.h +++ b/Code/client/Services/PlayerService.h @@ -50,6 +50,7 @@ struct PlayerService */ void RunDifficultyUpdates() const noexcept; void RunLevelUpdates() const noexcept; + void RunBeastFormDetection() const noexcept; void ToggleDeathSystem(bool aSet) noexcept; From f4cc55f3b4d74b590117af3e9f62eaab5a74368a Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 25 May 2024 19:09:01 +0200 Subject: [PATCH 4/7] tweak: fix up vampire lord after spawning --- Code/client/Games/Animation.cpp | 1 + Code/client/Games/References.cpp | 9 ++++ Code/client/Games/Skyrim/Actor.cpp | 41 +++++++++++++++++++ Code/client/Games/Skyrim/Actor.h | 2 + Code/client/Games/Skyrim/TESObjectREFR.h | 1 + Code/client/Services/Debug/DebugService.cpp | 7 ++++ .../Services/Generic/CharacterService.cpp | 3 ++ Code/client/Services/Generic/MagicService.cpp | 2 +- 8 files changed, 65 insertions(+), 1 deletion(-) diff --git a/Code/client/Games/Animation.cpp b/Code/client/Games/Animation.cpp index 557e44b3c..0c243f436 100644 --- a/Code/client/Games/Animation.cpp +++ b/Code/client/Games/Animation.cpp @@ -17,6 +17,7 @@ TP_THIS_FUNCTION(TPerformAction, uint8_t, ActorMediator, TESActionData* apAction); static TPerformAction* RealPerformAction; +// TODO: make scoped override thread_local bool g_forceAnimation = false; uint8_t TP_MAKE_THISCALL(HookPerformAction, ActorMediator, TESActionData* apAction) diff --git a/Code/client/Games/References.cpp b/Code/client/Games/References.cpp index 7a6d33098..efbc15a13 100644 --- a/Code/client/Games/References.cpp +++ b/Code/client/Games/References.cpp @@ -456,6 +456,15 @@ void TESObjectREFR::Enable() const noexcept s_pEnable(this, true); } +uint32_t TESObjectREFR::GetAnimationVariableInt(BSFixedString* apVariableName) noexcept +{ + using ObjectReference = TESObjectREFR; + + PAPYRUS_FUNCTION(uint32_t, ObjectReference, GetAnimationVariableInt, BSFixedString*); + + return s_pGetAnimationVariableInt(this, apVariableName); +} + // Skyrim: MoveTo() can fail, causing the object to be deleted void TESObjectREFR::MoveTo(TESObjectCELL* apCell, const NiPoint3& acPosition) const noexcept { diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index ad6207a01..17a11051f 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -21,6 +21,7 @@ #include #include +#include #include @@ -585,6 +586,46 @@ void Actor::Respawn() noexcept Reset(); } +bool Actor::IsVampireLord() const noexcept +{ + return race && race->formID == 0x200283A; +} + +extern thread_local bool g_forceAnimation; + +void Actor::FixVampireLordModel() noexcept +{ + TESBoundObject* pObject = Cast(TESForm::GetById(0x2011a84)); + if (!pObject) + return; + + { + ScopedInventoryOverride _; + AddObjectToContainer(pObject, nullptr, 1, nullptr); + } + + EquipManager::Get()->Equip(this, pObject, nullptr, 1, nullptr, false, true, false, false); + + g_forceAnimation = true; + + BSFixedString str("isLevitating"); + uint32_t isLevitating = GetAnimationVariableInt(&str); + spdlog::critical("isLevitating {}", isLevitating); + + // By default, a loaded vampire lord is not levitating. + if (isLevitating) + { + BSFixedString levitation("LevitationToggle"); + SendAnimationEvent(&levitation); + } + + // TODO: weapon draw code does not seem to take care of this + //BSFixedString weapEquip("WeapEquip"); + //SendAnimationEvent(&weapEquip); + + g_forceAnimation = false; +} + TP_THIS_FUNCTION(TForceState, void, Actor, const NiPoint3&, float, float, TESObjectCELL*, TESWorldSpace*, bool); static TForceState* RealForceState = nullptr; diff --git a/Code/client/Games/Skyrim/Actor.h b/Code/client/Games/Skyrim/Actor.h index 619ad4e31..ba9effc70 100644 --- a/Code/client/Games/Skyrim/Actor.h +++ b/Code/client/Games/Skyrim/Actor.h @@ -209,6 +209,7 @@ struct Actor : TESObjectREFR [[nodiscard]] uint8_t GetPerkRank(uint32_t aPerkFormId) const noexcept; [[nodiscard]] bool IsWearingBodyPiece() const noexcept; [[nodiscard]] bool ShouldWearBodyPiece() const noexcept; + [[nodiscard]] bool IsVampireLord() const noexcept; // Setters void SetSpeed(float aSpeed) noexcept; @@ -248,6 +249,7 @@ struct Actor : TESObjectREFR void StartCombat(Actor* apTarget) noexcept; void StopCombat() noexcept; bool PlayIdle(TESIdleForm* apIdle) noexcept; + void FixVampireLordModel() noexcept; enum ActorFlags { diff --git a/Code/client/Games/Skyrim/TESObjectREFR.h b/Code/client/Games/Skyrim/TESObjectREFR.h index de0d9d63c..d6d2aec91 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.h +++ b/Code/client/Games/Skyrim/TESObjectREFR.h @@ -167,6 +167,7 @@ struct TESObjectREFR : TESForm void SaveAnimationVariables(AnimationVariables& aWriter) const noexcept; void LoadAnimationVariables(const AnimationVariables& aReader) const noexcept; + uint32_t GetAnimationVariableInt(BSFixedString* apVariableName) noexcept; void RemoveAllItems() noexcept; void Delete() const noexcept; diff --git a/Code/client/Services/Debug/DebugService.cpp b/Code/client/Services/Debug/DebugService.cpp index bf950c4cc..b96c6318c 100644 --- a/Code/client/Services/Debug/DebugService.cpp +++ b/Code/client/Services/Debug/DebugService.cpp @@ -223,11 +223,18 @@ void DebugService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept s_f8Pressed = true; //PlaceActorInWorld(); + + BSFixedString str("isLevitating"); + uint32_t res = PlayerCharacter::Get()->GetAnimationVariableInt(&str); + spdlog::error("{}", res); + + #if 0 auto* pObjForm = TESForm::GetById(0x2011a86); if (!pObjForm) spdlog::error("Not found"); EquipManager::Get()->Equip(PlayerCharacter::Get(), pObjForm, nullptr, 1, nullptr, false, true, false, false); + #endif } } else diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index 3b08b59e9..c33734a64 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -1529,6 +1529,9 @@ void CharacterService::RunRemoteUpdates() noexcept if (pActor->IsDead() != waitingFor3D.SpawnRequest.IsDead) waitingFor3D.SpawnRequest.IsDead ? pActor->Kill() : pActor->Respawn(); + if (pActor->IsVampireLord()) + pActor->FixVampireLordModel(); + toRemove.push_back(entity); spdlog::info("Applied 3D for actor, form id: {:X}", pActor->formID); diff --git a/Code/client/Services/Generic/MagicService.cpp b/Code/client/Services/Generic/MagicService.cpp index 56e639cac..8906bb847 100644 --- a/Code/client/Services/Generic/MagicService.cpp +++ b/Code/client/Services/Generic/MagicService.cpp @@ -55,7 +55,7 @@ void MagicService::OnUpdate(const UpdateEvent& acEvent) noexcept return; ApplyQueuedEffects(); - RunVampireLordTransformationFinish(acEvent.Delta); + //RunVampireLordTransformationFinish(acEvent.Delta); UpdateRevealOtherPlayersEffect(); } From 7c60d0a2257bcbda9b9d35251fec125fc2eb1e44 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 25 May 2024 19:14:07 +0200 Subject: [PATCH 5/7] tweak: removed old vampire lord fix code --- Code/client/Services/Generic/MagicService.cpp | 48 ------------------- Code/client/Services/MagicService.h | 1 - 2 files changed, 49 deletions(-) diff --git a/Code/client/Services/Generic/MagicService.cpp b/Code/client/Services/Generic/MagicService.cpp index 8906bb847..3f1f500a0 100644 --- a/Code/client/Services/Generic/MagicService.cpp +++ b/Code/client/Services/Generic/MagicService.cpp @@ -55,7 +55,6 @@ void MagicService::OnUpdate(const UpdateEvent& acEvent) noexcept return; ApplyQueuedEffects(); - //RunVampireLordTransformationFinish(acEvent.Delta); UpdateRevealOtherPlayersEffect(); } @@ -420,10 +419,7 @@ void MagicService::OnNotifyAddTarget(const NotifyAddTarget& acMessage) noexcept pActor->GetExtension()->GraphDescriptorHash = AnimationGraphDescriptor_WerewolfBehavior::m_key; if (pEffect->IsVampireLordEffect()) - { pActor->GetExtension()->GraphDescriptorHash = AnimationGraphDescriptor_VampireLordBehavior::m_key; - m_queuedVampireLords[pActor->formID] = 0.0; - } // TODO: ft, check if this bug also occurs in fallout 4 // This hack is here because slow time seems to be twice as slow when cast by an npc @@ -492,50 +488,6 @@ void MagicService::ApplyQueuedEffects() noexcept m_queuedRemoteEffects.erase(serverId); } -extern thread_local bool g_forceAnimation; - -void MagicService::RunVampireLordTransformationFinish(double aDelta) noexcept -{ - Vector markedForRemoval{}; - - for (auto& [formId, _] : m_queuedVampireLords) - { - auto& timer = m_queuedVampireLords[formId]; - - timer += aDelta; - if (timer < 5.0) - continue; - - markedForRemoval.push_back(formId); - - Actor* pActor = Cast(TESForm::GetById(formId)); - if (pActor == nullptr) - continue; - - auto* pObjForm = TESForm::GetById(0x2011a84); - TESBoundObject* pObject = Cast(pObjForm); - - { - ScopedInventoryOverride _; - pActor->AddObjectToContainer(pObject, nullptr, 1, nullptr); - } - - EquipManager::Get()->Equip(pActor, pObject, nullptr, 1, nullptr, false, true, false, false); - - auto* pIdle = Cast(TESForm::GetById(0x20023fe)); - - g_forceAnimation = true; - BSFixedString levitation("LevitationToggle"); - pActor->SendAnimationEvent(&levitation); - BSFixedString weapEquip("WeapEquip"); - pActor->SendAnimationEvent(&weapEquip); - g_forceAnimation = false; - } - - for (uint32_t formId : markedForRemoval) - m_queuedVampireLords.erase(formId); -} - void MagicService::UpdateRevealOtherPlayersEffect() noexcept { #if TP_SKYRIM64 diff --git a/Code/client/Services/MagicService.h b/Code/client/Services/MagicService.h index f72b928fd..26181cfd4 100644 --- a/Code/client/Services/MagicService.h +++ b/Code/client/Services/MagicService.h @@ -89,7 +89,6 @@ struct MagicService */ Map m_queuedEffects; Map m_queuedRemoteEffects; - Map m_queuedVampireLords; bool m_revealOtherPlayers = false; From b8a13f6fd7e95ded5ef6c1fe011d815478850f09 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 25 May 2024 19:19:32 +0200 Subject: [PATCH 6/7] tweak: cleanup --- Code/client/Services/Debug/DebugService.cpp | 25 +-------------------- Code/client/Services/MagicService.h | 2 -- Code/server/Services/CharacterService.cpp | 1 - 3 files changed, 1 insertion(+), 27 deletions(-) diff --git a/Code/client/Services/Debug/DebugService.cpp b/Code/client/Services/Debug/DebugService.cpp index b96c6318c..00c6ef9cb 100644 --- a/Code/client/Services/Debug/DebugService.cpp +++ b/Code/client/Services/Debug/DebugService.cpp @@ -199,18 +199,7 @@ void DebugService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { s_f7Pressed = true; - auto* pIdle = Cast(TESForm::GetById(0x20023fe)); - - g_forceAnimation = true; - auto* pActor = Cast(TESForm::GetById(m_formId)); - BSFixedString str("LevitationToggle"); - //bool res = pActor->PlayAnimation(&str); - bool res = pActor->SendAnimationEvent(&str); - spdlog::error("{}", res); - g_forceAnimation = false; - - //auto* pIdle = Cast(TESForm::GetById(0x20023fe)); - //PlayerCharacter::Get()->PlayIdle(pIdle); + // } } else @@ -223,18 +212,6 @@ void DebugService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept s_f8Pressed = true; //PlaceActorInWorld(); - - BSFixedString str("isLevitating"); - uint32_t res = PlayerCharacter::Get()->GetAnimationVariableInt(&str); - spdlog::error("{}", res); - - #if 0 - auto* pObjForm = TESForm::GetById(0x2011a86); - if (!pObjForm) - spdlog::error("Not found"); - - EquipManager::Get()->Equip(PlayerCharacter::Get(), pObjForm, nullptr, 1, nullptr, false, true, false, false); - #endif } } else diff --git a/Code/client/Services/MagicService.h b/Code/client/Services/MagicService.h index 26181cfd4..034b267b5 100644 --- a/Code/client/Services/MagicService.h +++ b/Code/client/Services/MagicService.h @@ -72,8 +72,6 @@ struct MagicService */ void ApplyQueuedEffects() noexcept; - void RunVampireLordTransformationFinish(double aDelta) noexcept; - /** * Apply the "reveal players" effect on remote players. */ diff --git a/Code/server/Services/CharacterService.cpp b/Code/server/Services/CharacterService.cpp index c21db88d2..690dae088 100644 --- a/Code/server/Services/CharacterService.cpp +++ b/Code/server/Services/CharacterService.cpp @@ -496,7 +496,6 @@ void CharacterService::OnRequestRespawn(const PacketEvent& acMes auto& characterComponent = view.get(*it); characterComponent.SaveBuffer = acMessage.Packet.AppearanceBuffer; characterComponent.ChangeFlags = acMessage.Packet.ChangeFlags; - spdlog::info("Respawning with new buffer"); } NotifyRespawn notify; From 04f7e03a2f610829f9a5fdcca6449a512ef1d2d0 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 26 May 2024 08:39:55 +0200 Subject: [PATCH 7/7] fix: ft build --- Code/client/Games/References.cpp | 15 --------------- Code/client/Games/Skyrim/Actor.cpp | 6 ++++++ Code/client/Games/Skyrim/TESObjectREFR.cpp | 9 +++++++++ Code/client/Services/Generic/CharacterService.cpp | 2 ++ Code/client/Services/Generic/PlayerService.cpp | 2 ++ 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/Code/client/Games/References.cpp b/Code/client/Games/References.cpp index efbc15a13..c143d901c 100644 --- a/Code/client/Games/References.cpp +++ b/Code/client/Games/References.cpp @@ -456,15 +456,6 @@ void TESObjectREFR::Enable() const noexcept s_pEnable(this, true); } -uint32_t TESObjectREFR::GetAnimationVariableInt(BSFixedString* apVariableName) noexcept -{ - using ObjectReference = TESObjectREFR; - - PAPYRUS_FUNCTION(uint32_t, ObjectReference, GetAnimationVariableInt, BSFixedString*); - - return s_pGetAnimationVariableInt(this, apVariableName); -} - // Skyrim: MoveTo() can fail, causing the object to be deleted void TESObjectREFR::MoveTo(TESObjectCELL* apCell, const NiPoint3& acPosition) const noexcept { @@ -855,12 +846,6 @@ void Actor::StopCombat() noexcept s_pStopCombat(this); } -bool Actor::PlayIdle(TESIdleForm* apIdle) noexcept -{ - PAPYRUS_FUNCTION(bool, Actor, PlayIdle, TESIdleForm*); - return s_pPlayIdle(this, apIdle); -} - bool Actor::HasPerk(uint32_t aPerkFormId) const noexcept { return GetPerkRank(aPerkFormId) != 0; diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index 17a11051f..f587dcab1 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -580,6 +580,12 @@ void Actor::Reset() noexcept s_pReset(this, 0, nullptr); } +bool Actor::PlayIdle(TESIdleForm* apIdle) noexcept +{ + PAPYRUS_FUNCTION(bool, Actor, PlayIdle, TESIdleForm*); + return s_pPlayIdle(this, apIdle); +} + void Actor::Respawn() noexcept { Resurrect(false); diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 5a444405f..eb56bd92f 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -536,6 +536,15 @@ void TESObjectREFR::EnableImpl() noexcept TiltedPhoques::ThisCall(s_enable, this, false); } +uint32_t TESObjectREFR::GetAnimationVariableInt(BSFixedString* apVariableName) noexcept +{ + using ObjectReference = TESObjectREFR; + + PAPYRUS_FUNCTION(uint32_t, ObjectReference, GetAnimationVariableInt, BSFixedString*); + + return s_pGetAnimationVariableInt(this, apVariableName); +} + static thread_local bool s_cancelAnimationWaitEvent = false; bool TESObjectREFR::PlayAnimationAndWait(BSFixedString* apAnimation, BSFixedString* apEventName) noexcept diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index c33734a64..0bb9f705f 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -1529,8 +1529,10 @@ void CharacterService::RunRemoteUpdates() noexcept if (pActor->IsDead() != waitingFor3D.SpawnRequest.IsDead) waitingFor3D.SpawnRequest.IsDead ? pActor->Kill() : pActor->Respawn(); +#if TP_SKYRIM64 if (pActor->IsVampireLord()) pActor->FixVampireLordModel(); +#endif toRemove.push_back(entity); diff --git a/Code/client/Services/Generic/PlayerService.cpp b/Code/client/Services/Generic/PlayerService.cpp index 397f586f8..bf4def43b 100644 --- a/Code/client/Services/Generic/PlayerService.cpp +++ b/Code/client/Services/Generic/PlayerService.cpp @@ -359,6 +359,7 @@ void PlayerService::RunLevelUpdates() const noexcept void PlayerService::RunBeastFormDetection() const noexcept { +#if TP_SKYRIM64 static uint32_t lastRaceFormID = 0; static std::chrono::steady_clock::time_point lastSendTimePoint; constexpr auto cDelayBetweenUpdates = 250ms; @@ -380,6 +381,7 @@ void PlayerService::RunBeastFormDetection() const noexcept m_world.GetDispatcher().trigger(BeastFormChangeEvent()); lastRaceFormID = pPlayer->race->formID; +#endif } void PlayerService::ToggleDeathSystem(bool aSet) noexcept