From be4aafcde1ae8d131b1a7daad7fb06cc2ffc2fd2 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Thu, 26 May 2022 14:03:02 +0200 Subject: [PATCH 01/39] WIP: transfer actors to quest leader (does not work) --- Code/client/Services/CharacterService.h | 7 +- .../Services/Generic/CharacterService.cpp | 73 ++++++++++++++++++- Code/client/Services/Generic/PartyService.cpp | 17 ++++- Code/client/Services/PartyService.h | 4 +- Code/client/World.cpp | 4 +- Code/client/World.h | 3 + .../Messages/NotifyRelinquishControl.cpp | 13 ++++ .../Messages/NotifyRelinquishControl.h | 23 ++++++ Code/encoding/Messages/ServerMessageFactory.h | 4 +- Code/encoding/Opcodes.h | 1 + Code/server/Services/CharacterService.cpp | 7 +- 11 files changed, 142 insertions(+), 14 deletions(-) create mode 100644 Code/encoding/Messages/NotifyRelinquishControl.cpp create mode 100644 Code/encoding/Messages/NotifyRelinquishControl.h diff --git a/Code/client/Services/CharacterService.h b/Code/client/Services/CharacterService.h index e0ebf3cb7..daf47d8cd 100644 --- a/Code/client/Services/CharacterService.h +++ b/Code/client/Services/CharacterService.h @@ -37,6 +37,7 @@ struct DialogueEvent; struct NotifyDialogue; struct SubtitleEvent; struct NotifySubtitle; +struct NotifyRelinquishControl; struct Actor; struct World; @@ -79,10 +80,11 @@ struct CharacterService void OnNotifyDialogue(const NotifyDialogue& acMessage) noexcept; void OnSubtitleEvent(const SubtitleEvent& acEvent) noexcept; void OnNotifySubtitle(const NotifySubtitle& acMessage) noexcept; - -private: + void OnNotifyRelinquishControl(const NotifyRelinquishControl& acMessage) noexcept; void ProcessNewEntity(entt::entity aEntity) const noexcept; + +private: void RequestServerAssignment(entt::entity aEntity) const noexcept; void CancelServerAssignment(entt::entity aEntity, uint32_t aFormId) const noexcept; @@ -130,4 +132,5 @@ struct CharacterService entt::scoped_connection m_dialogueSyncConnection; entt::scoped_connection m_subtitleEventConnection; entt::scoped_connection m_subtitleSyncConnection; + entt::scoped_connection m_relinquishConnection; }; diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index 497f489d7..9a5de0196 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -62,6 +62,7 @@ #include #include #include +#include #include #include @@ -112,6 +113,8 @@ CharacterService::CharacterService(World& aWorld, entt::dispatcher& aDispatcher, m_subtitleEventConnection = m_dispatcher.sink().connect<&CharacterService::OnSubtitleEvent>(this); m_subtitleSyncConnection = m_dispatcher.sink().connect<&CharacterService::OnNotifySubtitle>(this); + + m_relinquishConnection = m_dispatcher.sink().connect<&CharacterService::OnNotifyRelinquishControl>(this); } void CharacterService::OnActorAdded(const ActorAddedEvent& acEvent) noexcept @@ -904,6 +907,7 @@ void CharacterService::OnNotifyMount(const NotifyMount& acMessage) const noexcep auto formView = m_world.view(); Vector entities(formView.begin(), formView.end()); + // TODO(cosideci): remove this, cause of NotifyRelinquishControl? for (auto entity : entities) { std::optional serverIdRes = Utils::GetServerId(entity); @@ -1099,6 +1103,47 @@ void CharacterService::OnSubtitleEvent(const SubtitleEvent& acEvent) noexcept m_transport.Send(request); } +void CharacterService::OnNotifyRelinquishControl(const NotifyRelinquishControl& acMessage) noexcept +{ + auto formView = m_world.view(); + Vector entities(formView.begin(), formView.end()); + + for (auto entity : entities) + { + std::optional serverIdRes = Utils::GetServerId(entity); + if (!serverIdRes.has_value()) + { + spdlog::error("{}: failed to find server id", __FUNCTION__); + continue; + } + + uint32_t serverId = serverIdRes.value(); + + if (serverId == acMessage.ServerId) + { + auto& formIdComponent = m_world.get(entity); + + if (m_world.all_of(entity)) + { + m_world.remove(entity); + m_world.emplace_or_replace(entity, acMessage.ServerId, formIdComponent.Id); + } + + Actor* pActor = Cast(TESForm::GetById(formIdComponent.Id)); + pActor->GetExtension()->SetRemote(true); + + InterpolationSystem::Setup(m_world, entity); + AnimationSystem::Setup(m_world, entity); + + spdlog::warn("Relinquished control of actor {:X} with server id {:X}", pActor->formID, acMessage.ServerId); + + return; + } + } + + spdlog::error("Did not find actor to relinquish control to, server id {:X}", acMessage.ServerId); +} + void CharacterService::OnNotifySubtitle(const NotifySubtitle& acMessage) noexcept { auto remoteView = m_world.view(); @@ -1142,9 +1187,31 @@ void CharacterService::ProcessNewEntity(entt::entity aEntity) const noexcept if (auto* pRemoteComponent = m_world.try_get(aEntity); pRemoteComponent) { - RequestSpawnData requestSpawnData; - requestSpawnData.Id = pRemoteComponent->Id; - m_transport.Send(requestSpawnData); + if (m_world.GetPartyService().IsLeader() && !pActor->IsTemporary()) + { + pActor->GetExtension()->SetRemote(false); + + m_world.emplace(aEntity, pRemoteComponent->Id); + m_world.emplace(aEntity); + m_world.remove(aEntity); + + spdlog::critical("Sending ownership claim for actor {:X} with server id {:X}", pActor->formID, + pRemoteComponent->Id); + + // TODO(cosideci): send current local data of actor with it(?) + RequestOwnershipClaim request; + request.ServerId = pRemoteComponent->Id; + m_transport.Send(request); + return; + } + else + { + RequestSpawnData requestSpawnData; + requestSpawnData.Id = pRemoteComponent->Id; + m_transport.Send(requestSpawnData); + return; + } } if (m_world.any_of(aEntity)) diff --git a/Code/client/Services/Generic/PartyService.cpp b/Code/client/Services/Generic/PartyService.cpp index fa1e8f745..1f112deef 100644 --- a/Code/client/Services/Generic/PartyService.cpp +++ b/Code/client/Services/Generic/PartyService.cpp @@ -1,6 +1,5 @@ #include -#include #include #include @@ -18,8 +17,8 @@ #include #include -PartyService::PartyService(entt::dispatcher& aDispatcher, TransportService& aTransportService) noexcept - : m_transportService(aTransportService) +PartyService::PartyService(World& aWorld, entt::dispatcher& aDispatcher, TransportService& aTransportService) noexcept + : m_world(aWorld), m_transportService(aTransportService) { m_updateConnection = aDispatcher.sink().connect<&PartyService::OnUpdate>(this); m_disconnectConnection = aDispatcher.sink().connect<&PartyService::OnDisconnected>(this); @@ -91,6 +90,18 @@ void PartyService::OnPartyJoined(const NotifyPartyJoined& acPartyJoined) noexcep m_isLeader = acPartyJoined.IsLeader; m_leaderPlayerId = acPartyJoined.LeaderPlayerId; m_partyMembers = acPartyJoined.PlayerIds; + + // Takes ownership of all actors + if (m_isLeader) + { + auto view = m_world.view(entt::exclude); + Vector entities(view.begin(), view.end()); + + for (auto entity : entities) + { + m_world.GetCharacterService().ProcessNewEntity(entity); + } + } } void PartyService::DestroyParty() noexcept diff --git a/Code/client/Services/PartyService.h b/Code/client/Services/PartyService.h index 1cd4d8fb0..9d5759791 100644 --- a/Code/client/Services/PartyService.h +++ b/Code/client/Services/PartyService.h @@ -1,5 +1,6 @@ #pragma once +struct World; struct ImguiService; struct TransportService; struct UpdateEvent; @@ -15,7 +16,7 @@ struct NotifyPartyLeft; */ struct PartyService { - PartyService(entt::dispatcher& aDispatcher, TransportService& aTransportService) noexcept; + PartyService(World& aWorld, entt::dispatcher& aDispatcher, TransportService& aTransportService) noexcept; ~PartyService() = default; TP_NOCOPYMOVE(PartyService); @@ -69,6 +70,7 @@ struct PartyService uint32_t m_leaderPlayerId; Vector m_partyMembers; + World& m_world; TransportService& m_transportService; entt::scoped_connection m_updateConnection; diff --git a/Code/client/World.cpp b/Code/client/World.cpp index 5058dcc27..ff4b1d245 100644 --- a/Code/client/World.cpp +++ b/Code/client/World.cpp @@ -3,7 +3,6 @@ #include "World.h" #include -#include #include #include #include @@ -12,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -41,7 +39,7 @@ World::World() ctx().emplace(*this, m_dispatcher, m_transport); ctx().emplace(*this, m_dispatcher, m_transport); ctx().emplace(*this, m_dispatcher); - ctx().emplace(m_dispatcher, m_transport); + ctx().emplace(*this, m_dispatcher, m_transport); ctx().emplace(*this, m_dispatcher, m_transport); ctx().emplace(*this, m_dispatcher, m_transport); ctx().emplace(*this, m_dispatcher, m_transport); diff --git a/Code/client/World.h b/Code/client/World.h index 1da2b447f..c54df6b96 100644 --- a/Code/client/World.h +++ b/Code/client/World.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include @@ -23,6 +24,8 @@ struct World : entt::registry PartyService& GetPartyService() noexcept { return ctx().at(); } const PartyService& GetPartyService() const noexcept { return ctx().at(); } + CharacterService& GetCharacterService() noexcept { return ctx().at(); } + const CharacterService& GetCharacterService() const noexcept { return ctx().at(); } OverlayService& GetOverlayService() noexcept { return ctx().at(); } const OverlayService& GetOverlayService() const noexcept { return ctx().at(); } DebugService& GetDebugService() noexcept { return ctx().at(); } diff --git a/Code/encoding/Messages/NotifyRelinquishControl.cpp b/Code/encoding/Messages/NotifyRelinquishControl.cpp new file mode 100644 index 000000000..48060a193 --- /dev/null +++ b/Code/encoding/Messages/NotifyRelinquishControl.cpp @@ -0,0 +1,13 @@ +#include + +void NotifyRelinquishControl::SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter) const noexcept +{ + Serialization::WriteVarInt(aWriter, ServerId); +} + +void NotifyRelinquishControl::DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept +{ + ServerMessage::DeserializeRaw(aReader); + + ServerId = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; +} diff --git a/Code/encoding/Messages/NotifyRelinquishControl.h b/Code/encoding/Messages/NotifyRelinquishControl.h new file mode 100644 index 000000000..58420fd8f --- /dev/null +++ b/Code/encoding/Messages/NotifyRelinquishControl.h @@ -0,0 +1,23 @@ +#pragma once + +#include "Message.h" + +struct NotifyRelinquishControl final : ServerMessage +{ + static constexpr ServerOpcode Opcode = kNotifyRelinquishControl; + + NotifyRelinquishControl() : ServerMessage(Opcode) + { + } + + void SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter) const noexcept override; + void DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept override; + + bool operator==(const NotifyRelinquishControl& acRhs) const noexcept + { + return GetOpcode() == acRhs.GetOpcode() && + ServerId == acRhs.ServerId; + } + + uint32_t ServerId; +}; diff --git a/Code/encoding/Messages/ServerMessageFactory.h b/Code/encoding/Messages/ServerMessageFactory.h index 1bb17a35f..a51efa4a1 100644 --- a/Code/encoding/Messages/ServerMessageFactory.h +++ b/Code/encoding/Messages/ServerMessageFactory.h @@ -46,6 +46,7 @@ #include #include #include +#include using TiltedPhoques::UniquePtr; @@ -65,7 +66,8 @@ struct ServerMessageFactory NotifyObjectInventoryChanges, NotifySpellCast, NotifyProjectileLaunch, NotifyInterruptCast, NotifyAddTarget, NotifyScriptAnimation, NotifyDrawWeapon, NotifyMount, NotifyNewPackage, NotifyRespawn, NotifySyncExperience, NotifyEquipmentChanges, NotifyChatMessageBroadcast, - TeleportCommandResponse, NotifyPlayerRespawn, NotifyDialogue, NotifySubtitle, NotifyPlayerDialogue>; + TeleportCommandResponse, NotifyPlayerRespawn, NotifyDialogue, NotifySubtitle, NotifyPlayerDialogue, + NotifyRelinquishControl>; return s_visitor(std::forward(func)); } diff --git a/Code/encoding/Opcodes.h b/Code/encoding/Opcodes.h index d8575e4f4..a2d201b5a 100644 --- a/Code/encoding/Opcodes.h +++ b/Code/encoding/Opcodes.h @@ -93,5 +93,6 @@ enum ServerOpcode : unsigned char kNotifyDialogue, kNotifySubtitle, kNotifyPlayerDialogue, + kNotifyRelinquishControl, kServerOpcodeMax }; diff --git a/Code/server/Services/CharacterService.cpp b/Code/server/Services/CharacterService.cpp index 4a83d4756..a63644e73 100644 --- a/Code/server/Services/CharacterService.cpp +++ b/Code/server/Services/CharacterService.cpp @@ -40,6 +40,7 @@ #include #include #include +#include CharacterService::CharacterService(World& aWorld, entt::dispatcher& aDispatcher) noexcept : m_world(aWorld) @@ -234,7 +235,7 @@ void CharacterService::OnOwnershipTransferRequest(const PacketEvent(message.ServerId)); if (it == view.end()) { - spdlog::warn("Client {:X} requested travel of an entity that doesn't exist !", acMessage.pPlayer->GetConnectionId()); + spdlog::warn("Client {:X} requested travel of an entity that doesn't exist, server id {:X}", acMessage.pPlayer->GetConnectionId(), message.ServerId); return; } @@ -323,6 +324,10 @@ void CharacterService::OnOwnershipClaimRequest(const PacketEvent(*it); + NotifyRelinquishControl notify; + notify.ServerId = message.ServerId; + characterOwnerComponent.pOwner->Send(notify); + characterOwnerComponent.SetOwner(acMessage.pPlayer); characterOwnerComponent.InvalidOwners.clear(); From 7222a5a2e569c57ccac6d5eba362c5af375c69dc Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 28 May 2022 17:01:01 +0200 Subject: [PATCH 02/39] WIP: ai background process list debugger --- Code/client/Games/TES.h | 28 +++--- Code/client/Services/Debug/DebugService.cpp | 4 + .../Services/Debug/Views/ProcessView.cpp | 91 +++++++++++++++++++ Code/client/Services/DebugService.h | 1 + .../Services/Generic/DiscoveryService.cpp | 4 +- 5 files changed, 114 insertions(+), 14 deletions(-) create mode 100644 Code/client/Services/Debug/Views/ProcessView.cpp diff --git a/Code/client/Games/TES.h b/Code/client/Games/TES.h index 91d9d8e2a..d84114831 100644 --- a/Code/client/Games/TES.h +++ b/Code/client/Games/TES.h @@ -64,25 +64,29 @@ struct ProcessLists static ProcessLists* Get() noexcept; #if TP_SKYRIM - uint8_t pad0[0x30]; #else + // TODO: fallout 4 offsets are wrong now uint8_t pad0[0x40]; #endif - GameArray HighActorHandleArray; - - // TODO: re-reverse stuff below -#if TP_SKYRIM - uint8_t pad48[0x90 - 0x48]; -#else - -#endif - - GameArray* actorBuckets[4]; // 0 is HighActorHandleArray, others are not investigated + uint8_t pad00[0x8]; + bool bProcessHigh; + bool bProcessLow; + bool bProcessMHigh; + bool bProcessMLow; + bool bProcessSchedule; + uint8_t padD[0x3]; + int32_t numberHighActors; + uint8_t pad14[0x30 - 0x14]; + GameArray highActorHandleArray; + GameArray lowActorHandleArray; + GameArray middleHighActorHandleArray; + GameArray middleLowActorHandleArray; + GameArray* actorBuckets[4]; }; #if TP_SKYRIM -static_assert(offsetof(ProcessLists, HighActorHandleArray) == 0x30); +static_assert(offsetof(ProcessLists, highActorHandleArray) == 0x30); static_assert(offsetof(ProcessLists, actorBuckets) == 0x90); #elif TP_FALLOUT4 static_assert(offsetof(ProcessLists, HighActorHandleArray) == 0x40); diff --git a/Code/client/Services/Debug/DebugService.cpp b/Code/client/Services/Debug/DebugService.cpp index dfc5955de..1a23cb26f 100644 --- a/Code/client/Services/Debug/DebugService.cpp +++ b/Code/client/Services/Debug/DebugService.cpp @@ -210,6 +210,7 @@ static bool g_enablePartyWindow{false}; static bool g_enableActorValuesWindow{false}; static bool g_enableQuestWindow{false}; static bool g_enableCellWindow{false}; +static bool g_enableProcessesWindow{false}; void DebugService::OnDraw() noexcept { @@ -282,6 +283,7 @@ void DebugService::OnDraw() noexcept ImGui::MenuItem("Party", nullptr, &g_enablePartyWindow); ImGui::MenuItem("Quests", nullptr, &g_enableQuestWindow); ImGui::MenuItem("Cell", nullptr, &g_enableCellWindow); + ImGui::MenuItem("Processes", nullptr, &g_enableProcessesWindow); ImGui::EndMenu(); } @@ -316,6 +318,8 @@ void DebugService::OnDraw() noexcept DrawQuestDebugView(); if (g_enableCellWindow) DrawCellView(); + if (g_enableProcessesWindow) + DrawProcessView(); if (m_drawComponentsInWorldSpace) DrawComponentDebugView(); diff --git a/Code/client/Services/Debug/Views/ProcessView.cpp b/Code/client/Services/Debug/Views/ProcessView.cpp new file mode 100644 index 000000000..22987137a --- /dev/null +++ b/Code/client/Services/Debug/Views/ProcessView.cpp @@ -0,0 +1,91 @@ +#include + +#include + +#include +#include + +void DebugService::DrawProcessView() +{ + ImGui::Begin("Processes"); + + ImGui::PushItemWidth(100.f); + + int id = 0; + + ProcessLists* pProcesses = ProcessLists::Get(); + + ImGui::InputScalar("Processing high?", ImGuiDataType_U8, &pProcesses->bProcessHigh, 0, 0, "%" PRIx8, + ImGuiInputTextFlags_CharsHexadecimal); + ImGui::SameLine(); + + ImGui::PushID(id); + if (ImGui::Button("Toggle")) + { + m_world.GetRunner().Queue([pProcesses]() { + pProcesses->bProcessHigh = !pProcesses->bProcessHigh; + }); + } + ImGui::PopID(); + id++; + + ImGui::InputScalar("Processing Low?", ImGuiDataType_U8, &pProcesses->bProcessLow, 0, 0, "%" PRIx8, + ImGuiInputTextFlags_CharsHexadecimal); + ImGui::SameLine(); + + ImGui::PushID(id); + if (ImGui::Button("Toggle")) + { + m_world.GetRunner().Queue([pProcesses]() { + pProcesses->bProcessLow = !pProcesses->bProcessLow; + }); + } + ImGui::PopID(); + id++; + + ImGui::InputScalar("Processing middle high?", ImGuiDataType_U8, &pProcesses->bProcessMHigh, 0, 0, "%" PRIx8, + ImGuiInputTextFlags_CharsHexadecimal); + ImGui::SameLine(); + + ImGui::PushID(id); + if (ImGui::Button("Toggle")) + { + m_world.GetRunner().Queue([pProcesses]() { + pProcesses->bProcessMHigh = !pProcesses->bProcessMHigh; + }); + } + ImGui::PopID(); + id++; + + ImGui::InputScalar("Processing middle low?", ImGuiDataType_U8, &pProcesses->bProcessMLow, 0, 0, "%" PRIx8, + ImGuiInputTextFlags_CharsHexadecimal); + ImGui::SameLine(); + + ImGui::PushID(id); + if (ImGui::Button("Toggle")) + { + m_world.GetRunner().Queue([pProcesses]() { + pProcesses->bProcessMLow = !pProcesses->bProcessMLow; + }); + } + ImGui::PopID(); + id++; + + ImGui::InputScalar("Processing schedule?", ImGuiDataType_U8, &pProcesses->bProcessSchedule, 0, 0, "%" PRIx8, + ImGuiInputTextFlags_CharsHexadecimal); + ImGui::SameLine(); + + ImGui::PushID(id); + if (ImGui::Button("Toggle")) + { + m_world.GetRunner().Queue([pProcesses]() { + pProcesses->bProcessSchedule = !pProcesses->bProcessSchedule; + }); + } + ImGui::PopID(); + id++; + + ImGui::PopItemWidth(); + + ImGui::End(); +} diff --git a/Code/client/Services/DebugService.h b/Code/client/Services/DebugService.h index dd8269597..5b1b81332 100644 --- a/Code/client/Services/DebugService.h +++ b/Code/client/Services/DebugService.h @@ -54,6 +54,7 @@ struct DebugService void DrawActorValuesView(); void DrawQuestDebugView(); void DrawCellView(); + void DrawProcessView(); entt::dispatcher& m_dispatcher; TransportService& m_transport; diff --git a/Code/client/Services/Generic/DiscoveryService.cpp b/Code/client/Services/Generic/DiscoveryService.cpp index dfff4c84f..9003763c1 100644 --- a/Code/client/Services/Generic/DiscoveryService.cpp +++ b/Code/client/Services/Generic/DiscoveryService.cpp @@ -208,9 +208,9 @@ void DiscoveryService::VisitForms() noexcept if (!pProcessLists) return; - for (uint32_t i = 0; i < pProcessLists->HighActorHandleArray.length; ++i) + for (uint32_t i = 0; i < pProcessLists->highActorHandleArray.length; ++i) { - TESObjectREFR* const pRefr = TESObjectREFR::GetByHandle(pProcessLists->HighActorHandleArray[i]); + TESObjectREFR* const pRefr = TESObjectREFR::GetByHandle(pProcessLists->highActorHandleArray[i]); if (pRefr) { if (pRefr->GetNiNode()) From 6cd70c544745bbdb7804cafb0780f22be35578d4 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sat, 28 May 2022 23:28:39 +0200 Subject: [PATCH 03/39] fix: actor stuck on traveling through door --- .../Services/Generic/CharacterService.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index 497f489d7..2e3ef20bf 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -269,21 +269,24 @@ void CharacterService::OnAssignCharacter(const AssignCharacterResponse& acMessag return; } + auto* const pForm = TESForm::GetById(formIdComponent->Id); + auto* pActor = Cast(pForm); + if (!pActor) + { + spdlog::error(__FUNCTION__ ": actor not found, form id: {:X}", formIdComponent->Id); + m_world.destroy(cEntity); + return; + } + if (acMessage.Owner) { m_world.emplace(cEntity, acMessage.ServerId); m_world.emplace(cEntity); + + pActor->GetExtension()->SetRemote(false); } else { - auto* const pForm = TESForm::GetById(formIdComponent->Id); - auto* pActor = Cast(pForm); - if (!pActor) - { - m_world.destroy(cEntity); - return; - } - m_world.emplace_or_replace(cEntity, acMessage.ServerId, formIdComponent->Id); pActor->GetExtension()->SetRemote(true); From 77c009864eee163a2721d0c8976259a8ad781970 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 01:49:33 +0200 Subject: [PATCH 04/39] fix: the door problem (again) --- Code/client/Services/CharacterService.h | 6 ++ .../Services/Generic/CharacterService.cpp | 83 ++++++++++++++----- .../encoding/Messages/NotifyActorTeleport.cpp | 19 +++++ Code/encoding/Messages/NotifyActorTeleport.h | 31 +++++++ .../Messages/RequestOwnershipTransfer.cpp | 6 ++ .../Messages/RequestOwnershipTransfer.h | 14 +++- Code/encoding/Messages/ServerMessageFactory.h | 4 +- Code/encoding/Opcodes.h | 1 + Code/server/Services/CharacterService.cpp | 26 +++++- 9 files changed, 165 insertions(+), 25 deletions(-) create mode 100644 Code/encoding/Messages/NotifyActorTeleport.cpp create mode 100644 Code/encoding/Messages/NotifyActorTeleport.h diff --git a/Code/client/Services/CharacterService.h b/Code/client/Services/CharacterService.h index e0ebf3cb7..c108c3e40 100644 --- a/Code/client/Services/CharacterService.h +++ b/Code/client/Services/CharacterService.h @@ -37,6 +37,7 @@ struct DialogueEvent; struct NotifyDialogue; struct SubtitleEvent; struct NotifySubtitle; +struct NotifyActorTeleport; struct Actor; struct World; @@ -79,9 +80,13 @@ struct CharacterService void OnNotifyDialogue(const NotifyDialogue& acMessage) noexcept; void OnSubtitleEvent(const SubtitleEvent& acEvent) noexcept; void OnNotifySubtitle(const NotifySubtitle& acMessage) noexcept; + void OnNotifyActorTeleport(const NotifyActorTeleport& acMessage) noexcept; private: + void MoveActor(const Actor* apActor, const GameId& acWorldSpaceId, const GameId& acCellId, + const Vector3_NetQuantize& acPosition) const noexcept; + void ProcessNewEntity(entt::entity aEntity) const noexcept; void RequestServerAssignment(entt::entity aEntity) const noexcept; void CancelServerAssignment(entt::entity aEntity, uint32_t aFormId) const noexcept; @@ -130,4 +135,5 @@ struct CharacterService entt::scoped_connection m_dialogueSyncConnection; entt::scoped_connection m_subtitleEventConnection; entt::scoped_connection m_subtitleSyncConnection; + entt::scoped_connection m_actorTeleportConnection; }; diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index 2e3ef20bf..1a1414373 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -62,6 +62,7 @@ #include #include #include +#include #include #include @@ -112,6 +113,8 @@ CharacterService::CharacterService(World& aWorld, entt::dispatcher& aDispatcher, m_subtitleEventConnection = m_dispatcher.sink().connect<&CharacterService::OnSubtitleEvent>(this); m_subtitleSyncConnection = m_dispatcher.sink().connect<&CharacterService::OnNotifySubtitle>(this); + + m_actorTeleportConnection = m_dispatcher.sink().connect<&CharacterService::OnNotifyActorTeleport>(this); } void CharacterService::OnActorAdded(const ActorAddedEvent& acEvent) noexcept @@ -302,23 +305,7 @@ void CharacterService::OnAssignCharacter(const AssignCharacterResponse& acMessag if (pActor->actorState.IsWeaponDrawn() != acMessage.IsWeaponDrawn) m_weaponDrawUpdates[pActor->formID] = {0, acMessage.IsWeaponDrawn}; - const uint32_t cCellId = m_world.GetModSystem().GetGameId(acMessage.CellId); - TESObjectCELL* pCell = Cast(TESForm::GetById(cCellId)); - - // In case of lazy-loading of exterior cells - if (!pCell) - { - const uint32_t cWorldSpaceId = m_world.GetModSystem().GetGameId(acMessage.WorldSpaceId); - TESWorldSpace* const pWorldSpace = Cast(TESForm::GetById(cWorldSpaceId)); - if (pWorldSpace) - { - GridCellCoords coordinates = GridCellCoords::CalculateGridCellCoords(acMessage.Position); - pCell = pWorldSpace->LoadCell(coordinates.X, coordinates.Y); - } - } - - if (pCell) - pActor->MoveTo(pCell, acMessage.Position); + MoveActor(pActor, acMessage.WorldSpaceId, acMessage.CellId, acMessage.Position); } } @@ -586,7 +573,7 @@ void CharacterService::OnOwnershipTransfer(const NotifyOwnershipTransfer& acMess spdlog::warn("Actor for ownership transfer not found {:X}", acMessage.ServerId); - RequestOwnershipTransfer request; + RequestOwnershipTransfer request{}; request.ServerId = acMessage.ServerId; m_transport.Send(request); @@ -1126,6 +1113,42 @@ void CharacterService::OnNotifySubtitle(const NotifySubtitle& acMessage) noexcep SubtitleManager::Get()->ShowSubtitle(pActor, acMessage.Text.c_str()); } +void CharacterService::OnNotifyActorTeleport(const NotifyActorTeleport& acMessage) noexcept +{ + auto& modSystem = m_world.GetModSystem(); + + const uint32_t cActorId = World::Get().GetModSystem().GetGameId(acMessage.FormId); + Actor* pActor = Cast(TESForm::GetById(cActorId)); + if (!pActor) + { + spdlog::error(__FUNCTION__ ": failed to retrieve actor to teleport."); + return; + } + + MoveActor(pActor, acMessage.WorldSpaceId, acMessage.CellId, acMessage.Position); +} + +void CharacterService::MoveActor(const Actor* apActor, const GameId& acWorldSpaceId, const GameId& acCellId, const Vector3_NetQuantize& acPosition) const noexcept +{ + const uint32_t cCellId = m_world.GetModSystem().GetGameId(acCellId); + TESObjectCELL* pCell = Cast(TESForm::GetById(cCellId)); + + // In case of lazy-loading of exterior cells + if (!pCell) + { + const uint32_t cWorldSpaceId = m_world.GetModSystem().GetGameId(acWorldSpaceId); + TESWorldSpace* const pWorldSpace = Cast(TESForm::GetById(cWorldSpaceId)); + if (pWorldSpace) + { + GridCellCoords coordinates = GridCellCoords::CalculateGridCellCoords(acPosition); + pCell = pWorldSpace->LoadCell(coordinates.X, coordinates.Y); + } + } + + if (pCell) + apActor->MoveTo(pCell, acPosition); +} + void CharacterService::ProcessNewEntity(entt::entity aEntity) const noexcept { if (!m_transport.IsOnline()) @@ -1338,9 +1361,31 @@ void CharacterService::CancelServerAssignment(const entt::entity aEntity, const { auto& localComponent = m_world.get(aEntity); - RequestOwnershipTransfer request; + RequestOwnershipTransfer request{}; request.ServerId = localComponent.Id; + if (Actor* pActor = Cast(TESForm::GetById(aFormId))) + { + if (pActor->IsTemporary()) + { + auto& modSystem = m_world.GetModSystem(); + + if (TESWorldSpace* pWorldSpace = pActor->GetWorldSpace()) + { + if (!modSystem.GetServerModId(pWorldSpace->formID, request.WorldSpaceId)) + spdlog::error("World space id not found, despite having a world space, {:X}", pWorldSpace->formID); + } + + if (TESObjectCELL* pCell = pActor->GetParentCell()) + { + if (!modSystem.GetServerModId(pCell->formID, request.CellId)) + spdlog::error("Cell id not found, despite having a cell, {:X}", pCell->formID); + } + + request.Position = pActor->position; + } + } + m_transport.Send(request); m_world.remove(aEntity); diff --git a/Code/encoding/Messages/NotifyActorTeleport.cpp b/Code/encoding/Messages/NotifyActorTeleport.cpp new file mode 100644 index 000000000..9ce2da41d --- /dev/null +++ b/Code/encoding/Messages/NotifyActorTeleport.cpp @@ -0,0 +1,19 @@ +#include + +void NotifyActorTeleport::SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter) const noexcept +{ + FormId.Serialize(aWriter); + WorldSpaceId.Serialize(aWriter); + CellId.Serialize(aWriter); + Position.Serialize(aWriter); +} + +void NotifyActorTeleport::DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept +{ + ServerMessage::DeserializeRaw(aReader); + + FormId.Deserialize(aReader); + WorldSpaceId.Deserialize(aReader); + CellId.Deserialize(aReader); + Position.Deserialize(aReader); +} diff --git a/Code/encoding/Messages/NotifyActorTeleport.h b/Code/encoding/Messages/NotifyActorTeleport.h new file mode 100644 index 000000000..f1e3c1ca0 --- /dev/null +++ b/Code/encoding/Messages/NotifyActorTeleport.h @@ -0,0 +1,31 @@ +#pragma once + +#include "Message.h" +#include +#include + +struct NotifyActorTeleport final : ServerMessage +{ + static constexpr ServerOpcode Opcode = kNotifyActorTeleport; + + NotifyActorTeleport() : ServerMessage(Opcode) + { + } + + void SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter) const noexcept override; + void DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept override; + + bool operator==(const NotifyActorTeleport& acRhs) const noexcept + { + return GetOpcode() == acRhs.GetOpcode() && + FormId == acRhs.FormId && + WorldSpaceId == acRhs.WorldSpaceId && + CellId == acRhs.CellId && + Position == acRhs.Position; + } + + GameId FormId{}; + GameId WorldSpaceId{}; + GameId CellId{}; + Vector3_NetQuantize Position{}; +}; diff --git a/Code/encoding/Messages/RequestOwnershipTransfer.cpp b/Code/encoding/Messages/RequestOwnershipTransfer.cpp index 6027cbb2c..b3e060d36 100644 --- a/Code/encoding/Messages/RequestOwnershipTransfer.cpp +++ b/Code/encoding/Messages/RequestOwnershipTransfer.cpp @@ -3,6 +3,9 @@ void RequestOwnershipTransfer::SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter) const noexcept { Serialization::WriteVarInt(aWriter, ServerId); + WorldSpaceId.Serialize(aWriter); + CellId.Serialize(aWriter); + Position.Serialize(aWriter); } void RequestOwnershipTransfer::DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept @@ -10,4 +13,7 @@ void RequestOwnershipTransfer::DeserializeRaw(TiltedPhoques::Buffer::Reader& aRe ClientMessage::DeserializeRaw(aReader); ServerId = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; + WorldSpaceId.Deserialize(aReader); + CellId.Deserialize(aReader); + Position.Deserialize(aReader); } diff --git a/Code/encoding/Messages/RequestOwnershipTransfer.h b/Code/encoding/Messages/RequestOwnershipTransfer.h index 86bfdb1dc..d1ac8fa61 100644 --- a/Code/encoding/Messages/RequestOwnershipTransfer.h +++ b/Code/encoding/Messages/RequestOwnershipTransfer.h @@ -2,6 +2,9 @@ #include "Message.h" +#include +#include + struct RequestOwnershipTransfer final : ClientMessage { static constexpr ClientOpcode Opcode = kRequestOwnershipTransfer; @@ -17,8 +20,15 @@ struct RequestOwnershipTransfer final : ClientMessage bool operator==(const RequestOwnershipTransfer& achRhs) const noexcept { - return ServerId == achRhs.ServerId && GetOpcode() == achRhs.GetOpcode(); + return GetOpcode() == achRhs.GetOpcode() && + ServerId == achRhs.ServerId && + WorldSpaceId == achRhs.WorldSpaceId && + CellId == achRhs.CellId && + Position == achRhs.Position; } - uint32_t ServerId; + uint32_t ServerId{}; + GameId WorldSpaceId{}; + GameId CellId{}; + Vector3_NetQuantize Position{}; }; diff --git a/Code/encoding/Messages/ServerMessageFactory.h b/Code/encoding/Messages/ServerMessageFactory.h index 1bb17a35f..6772859f6 100644 --- a/Code/encoding/Messages/ServerMessageFactory.h +++ b/Code/encoding/Messages/ServerMessageFactory.h @@ -46,6 +46,7 @@ #include #include #include +#include using TiltedPhoques::UniquePtr; @@ -65,7 +66,8 @@ struct ServerMessageFactory NotifyObjectInventoryChanges, NotifySpellCast, NotifyProjectileLaunch, NotifyInterruptCast, NotifyAddTarget, NotifyScriptAnimation, NotifyDrawWeapon, NotifyMount, NotifyNewPackage, NotifyRespawn, NotifySyncExperience, NotifyEquipmentChanges, NotifyChatMessageBroadcast, - TeleportCommandResponse, NotifyPlayerRespawn, NotifyDialogue, NotifySubtitle, NotifyPlayerDialogue>; + TeleportCommandResponse, NotifyPlayerRespawn, NotifyDialogue, NotifySubtitle, NotifyPlayerDialogue, + NotifyActorTeleport>; return s_visitor(std::forward(func)); } diff --git a/Code/encoding/Opcodes.h b/Code/encoding/Opcodes.h index d8575e4f4..5f6ace77a 100644 --- a/Code/encoding/Opcodes.h +++ b/Code/encoding/Opcodes.h @@ -93,5 +93,6 @@ enum ServerOpcode : unsigned char kNotifyDialogue, kNotifySubtitle, kNotifyPlayerDialogue, + kNotifyActorTeleport, kServerOpcodeMax }; diff --git a/Code/server/Services/CharacterService.cpp b/Code/server/Services/CharacterService.cpp index 4a83d4756..0968b9c16 100644 --- a/Code/server/Services/CharacterService.cpp +++ b/Code/server/Services/CharacterService.cpp @@ -40,6 +40,7 @@ #include #include #include +#include CharacterService::CharacterService(World& aWorld, entt::dispatcher& aDispatcher) noexcept : m_world(aWorld) @@ -229,7 +230,7 @@ void CharacterService::OnOwnershipTransferRequest(const PacketEvent view(m_world, acMessage.GetSender()); + OwnerView view(m_world, acMessage.GetSender()); const auto it = view.find(static_cast(message.ServerId)); if (it == view.end()) @@ -238,6 +239,26 @@ void CharacterService::OnOwnershipTransferRequest(const PacketEvent(*it); + + NotifyActorTeleport notify{}; + notify.FormId = formIdComponent.Id; + notify.WorldSpaceId = message.WorldSpaceId; + notify.CellId = message.CellId; + notify.Position = message.Position; + + auto& cellIdComponent = view.get(*it); + cellIdComponent.WorldSpaceId = message.WorldSpaceId; + cellIdComponent.Cell = message.CellId; + cellIdComponent.CenterCoords = GridCellCoords::CalculateGridCellCoords(message.Position); + + auto& movementComponent = view.get(*it); + movementComponent.Position = message.Position; + movementComponent.Sent = true; + } + auto& characterOwnerComponent = view.get(*it); characterOwnerComponent.InvalidOwners.push_back(acMessage.pPlayer); @@ -613,8 +634,7 @@ void CharacterService::CreateCharacter(const PacketEvent if (message.WorldSpaceId != GameId{}) { cellIdComponent.WorldSpaceId = message.WorldSpaceId; - auto coords = GridCellCoords::CalculateGridCellCoords(message.Position.x, message.Position.y); - cellIdComponent.CenterCoords = coords; + cellIdComponent.CenterCoords = GridCellCoords::CalculateGridCellCoords(message.Position); } auto& characterComponent = m_world.emplace(cEntity); From 156262146da4edad2c483731aa696b135543a759 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 01:55:13 +0200 Subject: [PATCH 05/39] tweak: actually send the thing --- Code/server/Services/CharacterService.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Code/server/Services/CharacterService.cpp b/Code/server/Services/CharacterService.cpp index 0968b9c16..fb04756fd 100644 --- a/Code/server/Services/CharacterService.cpp +++ b/Code/server/Services/CharacterService.cpp @@ -257,6 +257,8 @@ void CharacterService::OnOwnershipTransferRequest(const PacketEvent(*it); movementComponent.Position = message.Position; movementComponent.Sent = true; + + GameServer::Get()->SendToPlayers(notify, acMessage.pPlayer); } auto& characterOwnerComponent = view.get(*it); From 81b338bfecf7e316205b5d9473a1baa7b60818a1 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 02:17:41 +0200 Subject: [PATCH 06/39] fix: MoveActor bug --- .../Services/Generic/CharacterService.cpp | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index 1a1414373..14a9e0f96 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -1130,11 +1130,14 @@ void CharacterService::OnNotifyActorTeleport(const NotifyActorTeleport& acMessag void CharacterService::MoveActor(const Actor* apActor, const GameId& acWorldSpaceId, const GameId& acCellId, const Vector3_NetQuantize& acPosition) const noexcept { - const uint32_t cCellId = m_world.GetModSystem().GetGameId(acCellId); - TESObjectCELL* pCell = Cast(TESForm::GetById(cCellId)); - + TESObjectCELL* pCell = nullptr; + if (!acWorldSpaceId) + { + const uint32_t cCellId = m_world.GetModSystem().GetGameId(acCellId); + pCell = Cast(TESForm::GetById(cCellId)); + } // In case of lazy-loading of exterior cells - if (!pCell) + else { const uint32_t cWorldSpaceId = m_world.GetModSystem().GetGameId(acWorldSpaceId); TESWorldSpace* const pWorldSpace = Cast(TESForm::GetById(cWorldSpaceId)); @@ -1145,8 +1148,15 @@ void CharacterService::MoveActor(const Actor* apActor, const GameId& acWorldSpac } } - if (pCell) - apActor->MoveTo(pCell, acPosition); + if (!pCell) + { + spdlog::error(__FUNCTION__ + ": failed to fetch cell to teleport, actor: {:X}, worldspace: {:X}, cell: {:X}, position: {}, {}, {}", + apActor->formID, acWorldSpaceId.BaseId, acCellId.BaseId, acPosition.x, acPosition.y, acPosition.z); + return; + } + + apActor->MoveTo(pCell, acPosition); } void CharacterService::ProcessNewEntity(entt::entity aEntity) const noexcept From b1912d8085005a863d18235d0c8f1d8c87130a68 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 11:09:18 +0200 Subject: [PATCH 07/39] fix: don't tp temporaries --- .../Services/Generic/CharacterService.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index 14a9e0f96..4727ff7f9 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -1126,6 +1126,8 @@ void CharacterService::OnNotifyActorTeleport(const NotifyActorTeleport& acMessag } MoveActor(pActor, acMessage.WorldSpaceId, acMessage.CellId, acMessage.Position); + + spdlog::warn("Successfully teleported actor"); } void CharacterService::MoveActor(const Actor* apActor, const GameId& acWorldSpaceId, const GameId& acCellId, const Vector3_NetQuantize& acPosition) const noexcept @@ -1341,11 +1343,17 @@ void CharacterService::CancelServerAssignment(const entt::entity aEntity, const TESForm* const pForm = TESForm::GetById(aFormId); Actor* const pActor = Cast(pForm); - if (pActor && ((pActor->formID & 0xFF000000) == 0xFF000000)) + if (pActor) { - spdlog::info("Temporary Remote Deleted {:X}", aFormId); - - pActor->Delete(); + if (pActor->IsTemporary()) + { + spdlog::info("Temporary Remote Deleted {:X}", aFormId); + pActor->Delete(); + } + else + { + pActor->GetExtension()->SetRemote(false); + } } m_world.remove(TESForm::GetById(aFormId))) { - if (pActor->IsTemporary()) + if (!pActor->IsTemporary()) { auto& modSystem = m_world.GetModSystem(); From b93cb7f59f193c98e18d218450586b88fbaae1fe Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 11:22:12 +0200 Subject: [PATCH 08/39] fix: server crash --- Code/server/Game/OwnerView.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Code/server/Game/OwnerView.h b/Code/server/Game/OwnerView.h index 78c77e5f9..c0aea416d 100644 --- a/Code/server/Game/OwnerView.h +++ b/Code/server/Game/OwnerView.h @@ -87,6 +87,10 @@ template decltype(auto) OwnerView::find(entt::entity aEntity) const { auto it = m_view.find(aEntity); + + if (it == m_view.end()) + return m_view.end(); + if (m_view.template get(*it).GetOwner() == m_pPlayer) return iterator(m_pPlayer, it, std::end(m_view), m_view); From 677daaaffcc3ccecbd41bc00ad0a0052f277755b Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 11:25:22 +0200 Subject: [PATCH 09/39] fix: build error --- Code/server/Game/OwnerView.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/server/Game/OwnerView.h b/Code/server/Game/OwnerView.h index c0aea416d..cb3934aa6 100644 --- a/Code/server/Game/OwnerView.h +++ b/Code/server/Game/OwnerView.h @@ -89,7 +89,7 @@ decltype(auto) OwnerView::find(entt::entity aEntity) const auto it = m_view.find(aEntity); if (it == m_view.end()) - return m_view.end(); + return end(); if (m_view.template get(*it).GetOwner() == m_pPlayer) return iterator(m_pPlayer, it, std::end(m_view), m_view); From e0baaa457e97adae7af1ff31ece2b14a0ab3595b Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 13:22:02 +0200 Subject: [PATCH 10/39] tweak: delete all old crash dumps --- Code/client/CrashHandler.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/Code/client/CrashHandler.cpp b/Code/client/CrashHandler.cpp index fe0a36d8d..d53a1174a 100644 --- a/Code/client/CrashHandler.cpp +++ b/Code/client/CrashHandler.cpp @@ -84,7 +84,6 @@ void CrashHandler::RemovePreviousDump(std::filesystem::path path) if (entry.path().string().find("crash") != std::string::npos) { DeleteFileA(entry.path().string().c_str()); - break; } } } From b208a1c142a6aa0899f63f754dbffb28e3eaf0e2 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 13:25:11 +0200 Subject: [PATCH 11/39] tweak: don't deselect current quest on connect --- Code/client/Services/Generic/QuestService.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Code/client/Services/Generic/QuestService.cpp b/Code/client/Services/Generic/QuestService.cpp index f4840fa60..b3b72ff5a 100644 --- a/Code/client/Services/Generic/QuestService.cpp +++ b/Code/client/Services/Generic/QuestService.cpp @@ -49,6 +49,8 @@ QuestService::QuestService(World& aWorld, entt::dispatcher& aDispatcher) void QuestService::OnConnected(const ConnectedEvent&) noexcept { + // TODO: this should be followed with whatever the quest leader selected + /* // deselect any active quests auto* pPlayer = PlayerCharacter::Get(); for (auto& objective : pPlayer->objectives) @@ -56,6 +58,7 @@ void QuestService::OnConnected(const ConnectedEvent&) noexcept if (auto* pQuest = objective.instance->quest) pQuest->SetActive(false); } + */ } BSTEventResult QuestService::OnEvent(const TESQuestStartStopEvent* apEvent, const EventDispatcher*) From 8631066992c7ebc20edcba0541bb31d4413cd2d9 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 13:57:57 +0200 Subject: [PATCH 12/39] tweak: take ownership debug button --- Code/client/Services/CharacterService.h | 2 + .../Services/Debug/Views/EntitiesView.cpp | 15 ++++- Code/client/Services/DebugService.h | 2 +- .../Services/Generic/CharacterService.cpp | 60 +++++++++---------- Code/client/World.h | 2 + 5 files changed, 44 insertions(+), 37 deletions(-) diff --git a/Code/client/Services/CharacterService.h b/Code/client/Services/CharacterService.h index c108c3e40..787c6bce2 100644 --- a/Code/client/Services/CharacterService.h +++ b/Code/client/Services/CharacterService.h @@ -53,6 +53,8 @@ struct CharacterService TP_NOCOPYMOVE(CharacterService); + bool TakeOwnership(const uint32_t acFormId, const uint32_t acServerId, const entt::entity acEntity) const noexcept; + void OnActorAdded(const ActorAddedEvent& acEvent) noexcept; void OnActorRemoved(const ActorRemovedEvent& acEvent) noexcept; void OnUpdate(const UpdateEvent& acUpdateEvent) noexcept; diff --git a/Code/client/Services/Debug/Views/EntitiesView.cpp b/Code/client/Services/Debug/Views/EntitiesView.cpp index 825182d7e..191d46344 100644 --- a/Code/client/Services/Debug/Views/EntitiesView.cpp +++ b/Code/client/Services/Debug/Views/EntitiesView.cpp @@ -1,5 +1,7 @@ #include +#include + #include #include @@ -123,7 +125,7 @@ void DebugService::DisplayEntityPanel(entt::entity aEntity) noexcept if (pFormIdComponent) DisplayFormComponent(*pFormIdComponent); if (pLocalComponent) DisplayLocalComponent(*pLocalComponent); - if (pRemoteComponent) DisplayRemoteComponent(*pRemoteComponent); + if (pRemoteComponent) DisplayRemoteComponent(*pRemoteComponent, aEntity, pFormIdComponent ? pFormIdComponent->Id : 0); } void DebugService::DisplayFormComponent(FormIdComponent& aFormComponent) const noexcept @@ -184,10 +186,17 @@ void DebugService::DisplayLocalComponent(LocalComponent& aLocalComponent) const ImGui::InputScalarN("State", ImGuiDataType_U32, &action.State1, 2, nullptr, nullptr, "%x", ImGuiInputTextFlags_ReadOnly); } -void DebugService::DisplayRemoteComponent(RemoteComponent& aLocalComponent) const noexcept +void DebugService::DisplayRemoteComponent(RemoteComponent& aRemoteComponent, const entt::entity acEntity, const uint32_t acFormId) const noexcept { if (!ImGui::CollapsingHeader("Remote Component", ImGuiTreeNodeFlags_DefaultOpen)) return; - ImGui::InputInt("Server Id", (int*)&aLocalComponent.Id, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); + ImGui::InputInt("Server Id", (int*)&aRemoteComponent.Id, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); + if (ImGui::Button("Take ownership")) + { + m_world.GetRunner().Queue([acEntity, acFormId]() { + if (auto* pRemoteCompoment = World::Get().try_get(acEntity)) + World::Get().GetCharacterService().TakeOwnership(acFormId, pRemoteCompoment->Id, acEntity); + }); + } } diff --git a/Code/client/Services/DebugService.h b/Code/client/Services/DebugService.h index 5b1b81332..863562c54 100644 --- a/Code/client/Services/DebugService.h +++ b/Code/client/Services/DebugService.h @@ -40,7 +40,7 @@ struct DebugService void DisplayEntityPanel(entt::entity aEntity) noexcept; void DisplayFormComponent(FormIdComponent& aFormComponent) const noexcept; void DisplayLocalComponent(LocalComponent& aLocalComponent) const noexcept; - void DisplayRemoteComponent(RemoteComponent& aLocalComponent) const noexcept; + void DisplayRemoteComponent(RemoteComponent& aLocalComponent, const entt::entity acEntity, const uint32_t acFormId) const noexcept; void DrawEntitiesView(); void DrawComponentDebugView(); diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index 4727ff7f9..dafa523be 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -117,6 +117,30 @@ CharacterService::CharacterService(World& aWorld, entt::dispatcher& aDispatcher, m_actorTeleportConnection = m_dispatcher.sink().connect<&CharacterService::OnNotifyActorTeleport>(this); } +bool CharacterService::TakeOwnership(const uint32_t acFormId, const uint32_t acServerId, const entt::entity acEntity) const noexcept +{ + Actor* const pActor = Cast(TESForm::GetById(acFormId)); + if (!pActor) + return false; + + pActor->GetExtension()->SetRemote(false); + + // TODO(cosideci): this should be done differently. + // Send an ownership claim request, and have the server broadcast the result. + // Only then should components be added or removed. + m_world.emplace(acEntity, acServerId); + m_world.emplace(acEntity); + m_world.remove(acEntity); + + RequestOwnershipClaim request; + request.ServerId = acServerId; + + m_transport.Send(request); + + return true; +} + void CharacterService::OnActorAdded(const ActorAddedEvent& acEvent) noexcept { if (acEvent.FormId == 0x14) @@ -548,25 +572,9 @@ void CharacterService::OnOwnershipTransfer(const NotifyOwnershipTransfer& acMess { auto& formIdComponent = view.get(*itor); - auto* const pActor = Cast(TESForm::GetById(formIdComponent.Id)); - if (pActor) + if (TakeOwnership(formIdComponent.Id, acMessage.ServerId, *itor)) { - pActor->GetExtension()->SetRemote(false); - - // TODO(cosideci): this should be done differently. - // Send an ownership claim request, and have the server broadcast the result. - // Only then should components be added or removed. - m_world.emplace(*itor, acMessage.ServerId); - m_world.emplace(*itor); - m_world.remove(*itor); - - RequestOwnershipClaim request; - request.ServerId = acMessage.ServerId; - - m_transport.Send(request); - spdlog::info("Ownership claimed {:X}", request.ServerId); - + spdlog::info("Ownership claimed {:X}", acMessage.ServerId); return; } } @@ -845,21 +853,7 @@ void CharacterService::OnMountEvent(const MountEvent& acEvent) const noexcept } if (m_world.try_get(cMountEntity)) - { - const TESForm* pMountForm = TESForm::GetById(acEvent.MountID); - Actor* pMount = Cast(pMountForm); - pMount->GetExtension()->SetRemote(false); - - m_world.emplace(cMountEntity, mountServerIdRes.value()); - m_world.emplace(cMountEntity); - m_world.remove(cMountEntity); - - RequestOwnershipClaim request; - request.ServerId = mountServerIdRes.value(); - - m_transport.Send(request); - } + TakeOwnership(acEvent.MountID, *mountServerIdRes, cMountEntity); MountRequest request; request.MountId = mountServerIdRes.value(); diff --git a/Code/client/World.h b/Code/client/World.h index 1da2b447f..5b65a26d8 100644 --- a/Code/client/World.h +++ b/Code/client/World.h @@ -25,6 +25,8 @@ struct World : entt::registry const PartyService& GetPartyService() const noexcept { return ctx().at(); } OverlayService& GetOverlayService() noexcept { return ctx().at(); } const OverlayService& GetOverlayService() const noexcept { return ctx().at(); } + CharacterService& GetCharacterService() noexcept { return ctx().at(); } + const CharacterService& GetCharacterService() const noexcept { return ctx().at(); } DebugService& GetDebugService() noexcept { return ctx().at(); } const DebugService& GetDebugService() const noexcept { return ctx().at(); } From 497d6792d9fc901ae4eef1f89fc1e2665f1ce8fc Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 14:08:13 +0200 Subject: [PATCH 13/39] fix: build error --- Code/client/World.cpp | 1 - Code/client/World.h | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/Code/client/World.cpp b/Code/client/World.cpp index 5058dcc27..a95ba0532 100644 --- a/Code/client/World.cpp +++ b/Code/client/World.cpp @@ -3,7 +3,6 @@ #include "World.h" #include -#include #include #include #include diff --git a/Code/client/World.h b/Code/client/World.h index 5b65a26d8..ca420fee2 100644 --- a/Code/client/World.h +++ b/Code/client/World.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include From b64d0c6a9b1c85ded0917172e26df6329a05cfc4 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 14:18:19 +0200 Subject: [PATCH 14/39] tweak: added logging --- Code/client/Services/Generic/CharacterService.cpp | 5 +++++ Code/server/Services/CharacterService.cpp | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index dafa523be..b9d98a2e2 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -1398,6 +1398,11 @@ void CharacterService::CancelServerAssignment(const entt::entity aEntity, const } } + spdlog::warn("Transferring ownership of local actor, server id: {:X}, worldspace: {:X}, cell: {:X}, position: " + "({}, {}, {})", + request.ServerId, request.WorldSpaceId.BaseId, request.CellId.BaseId, request.Position.x, + request.Position.y, request.Position.z); + m_transport.Send(request); m_world.remove(aEntity); diff --git a/Code/server/Services/CharacterService.cpp b/Code/server/Services/CharacterService.cpp index fb04756fd..c3ee87e7a 100644 --- a/Code/server/Services/CharacterService.cpp +++ b/Code/server/Services/CharacterService.cpp @@ -235,7 +235,7 @@ void CharacterService::OnOwnershipTransferRequest(const PacketEvent(message.ServerId)); if (it == view.end()) { - spdlog::warn("Client {:X} requested travel of an entity that doesn't exist !", acMessage.pPlayer->GetConnectionId()); + spdlog::warn("Client {:X} requested travel of an entity that doesn't exist, server id: {:X}", acMessage.pPlayer->GetConnectionId(), message.ServerId); return; } From 4695f952cc4dbb3714ffafbce5f7a501e07f8536 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 14:40:28 +0200 Subject: [PATCH 15/39] tweak: entity validity debugging --- Code/client/Services/Debug/Views/EntitiesView.cpp | 6 ++++++ Code/client/Services/Generic/PartyService.cpp | 2 ++ 2 files changed, 8 insertions(+) diff --git a/Code/client/Services/Debug/Views/EntitiesView.cpp b/Code/client/Services/Debug/Views/EntitiesView.cpp index 191d46344..d435bae51 100644 --- a/Code/client/Services/Debug/Views/EntitiesView.cpp +++ b/Code/client/Services/Debug/Views/EntitiesView.cpp @@ -105,9 +105,15 @@ void DebugService::DisplayObjects() noexcept char name[256]; sprintf_s(name, std::size(name), "%s (%x)", pRefr->baseForm->GetName(), formComponent.Id); + if (!m_world.all_of(it)) + ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255)); + if (ImGui::Selectable(name, m_formId == formComponent.Id)) m_formId = formComponent.Id; + if (!m_world.all_of(it)) + ImGui::PopStyleColor(); + if(m_formId == formComponent.Id) s_selected = i; diff --git a/Code/client/Services/Generic/PartyService.cpp b/Code/client/Services/Generic/PartyService.cpp index 1f112deef..1b4149856 100644 --- a/Code/client/Services/Generic/PartyService.cpp +++ b/Code/client/Services/Generic/PartyService.cpp @@ -92,6 +92,7 @@ void PartyService::OnPartyJoined(const NotifyPartyJoined& acPartyJoined) noexcep m_partyMembers = acPartyJoined.PlayerIds; // Takes ownership of all actors + /* if (m_isLeader) { auto view = m_world.view(entt::exclude); @@ -102,6 +103,7 @@ void PartyService::OnPartyJoined(const NotifyPartyJoined& acPartyJoined) noexcep m_world.GetCharacterService().ProcessNewEntity(entity); } } + */ } void PartyService::DestroyParty() noexcept From b77fbf3a251830c74a9f3cb368095cae769f7cfc Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 14:56:16 +0200 Subject: [PATCH 16/39] tweak: improved entities debugger --- Code/client/Services/Debug/Views/EntitiesView.cpp | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Code/client/Services/Debug/Views/EntitiesView.cpp b/Code/client/Services/Debug/Views/EntitiesView.cpp index d435bae51..4685c4201 100644 --- a/Code/client/Services/Debug/Views/EntitiesView.cpp +++ b/Code/client/Services/Debug/Views/EntitiesView.cpp @@ -55,16 +55,18 @@ void DebugService::DisplayEntities() noexcept auto& formComponent = view.get(it); const auto pActor = Cast(TESForm::GetById(formComponent.Id)); - if (!pActor || !pActor->baseForm) + if (!pActor) continue; char name[256]; + + if (!pActor->baseForm) + strncpy_s(name, "UNNAMED", sizeof(name)); + sprintf_s(name, std::size(name), "%s (%x)", pActor->baseForm->GetName(), formComponent.Id); if (ImGui::Selectable(name, m_formId == formComponent.Id)) - { m_formId = formComponent.Id; - } if(m_formId == formComponent.Id) s_selected = i; @@ -105,15 +107,9 @@ void DebugService::DisplayObjects() noexcept char name[256]; sprintf_s(name, std::size(name), "%s (%x)", pRefr->baseForm->GetName(), formComponent.Id); - if (!m_world.all_of(it)) - ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 0, 0, 255)); - if (ImGui::Selectable(name, m_formId == formComponent.Id)) m_formId = formComponent.Id; - if (!m_world.all_of(it)) - ImGui::PopStyleColor(); - if(m_formId == formComponent.Id) s_selected = i; From 991337bb01762ed6ab7b6c9192ce2f5db71530a4 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 15:16:10 +0200 Subject: [PATCH 17/39] fix: wrongful ownership transfer --- Code/server/Services/CharacterService.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Code/server/Services/CharacterService.cpp b/Code/server/Services/CharacterService.cpp index e30d5f49a..92a766d67 100644 --- a/Code/server/Services/CharacterService.cpp +++ b/Code/server/Services/CharacterService.cpp @@ -347,9 +347,12 @@ void CharacterService::OnOwnershipClaimRequest(const PacketEvent(*it); - NotifyRelinquishControl notify; - notify.ServerId = message.ServerId; - characterOwnerComponent.pOwner->Send(notify); + if (characterOwnerComponent.GetOwner() != acMessage.pPlayer) + { + NotifyRelinquishControl notify; + notify.ServerId = message.ServerId; + characterOwnerComponent.pOwner->Send(notify); + } characterOwnerComponent.SetOwner(acMessage.pPlayer); characterOwnerComponent.InvalidOwners.clear(); From 7c6a9ed45e5786266357ec21176310719bf92753 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 15:38:09 +0200 Subject: [PATCH 18/39] feat: transfer ownership to party leader on creation --- .../Services/Generic/CharacterService.cpp | 30 ++++++++++--------- Code/client/Services/Generic/PartyService.cpp | 2 -- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index ae5d9b46d..051a75c11 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -122,11 +122,21 @@ CharacterService::CharacterService(World& aWorld, entt::dispatcher& aDispatcher, bool CharacterService::TakeOwnership(const uint32_t acFormId, const uint32_t acServerId, const entt::entity acEntity) const noexcept { - Actor* const pActor = Cast(TESForm::GetById(acFormId)); + Actor* pActor = Cast(TESForm::GetById(acFormId)); if (!pActor) + { + spdlog::error("Cannot find actor to take control over, form id: {:X}, server id: {:X}", acFormId, acServerId); + return false; + } + + ActorExtension* pExtension = pActor->GetExtension(); + if (pExtension->IsRemotePlayer()) + { + spdlog::error("Cannot take control over remote player actor, form id: {:X}, server id: {:X}", acFormId, acServerId); return false; + } - pActor->GetExtension()->SetRemote(false); + pExtension->SetRemote(false); // TODO(cosideci): this should be done differently. // Send an ownership claim request, and have the server broadcast the result. @@ -136,6 +146,7 @@ bool CharacterService::TakeOwnership(const uint32_t acFormId, const uint32_t acS m_world.remove(acEntity); + // TODO(cosideci): send current local data of actor with it(?) RequestOwnershipClaim request; request.ServerId = acServerId; @@ -1221,20 +1232,11 @@ void CharacterService::ProcessNewEntity(entt::entity aEntity) const noexcept { if (m_world.GetPartyService().IsLeader() && !pActor->IsTemporary()) { - pActor->GetExtension()->SetRemote(false); - - m_world.emplace(aEntity, pRemoteComponent->Id); - m_world.emplace(aEntity); - m_world.remove(aEntity); - - spdlog::critical("Sending ownership claim for actor {:X} with server id {:X}", pActor->formID, + spdlog::info("Sending ownership claim for actor {:X} with server id {:X}", pActor->formID, pRemoteComponent->Id); - // TODO(cosideci): send current local data of actor with it(?) - RequestOwnershipClaim request; - request.ServerId = pRemoteComponent->Id; - m_transport.Send(request); + TakeOwnership(pActor->formID, pRemoteComponent->Id, aEntity); + return; } else diff --git a/Code/client/Services/Generic/PartyService.cpp b/Code/client/Services/Generic/PartyService.cpp index 1b4149856..1f112deef 100644 --- a/Code/client/Services/Generic/PartyService.cpp +++ b/Code/client/Services/Generic/PartyService.cpp @@ -92,7 +92,6 @@ void PartyService::OnPartyJoined(const NotifyPartyJoined& acPartyJoined) noexcep m_partyMembers = acPartyJoined.PlayerIds; // Takes ownership of all actors - /* if (m_isLeader) { auto view = m_world.view(entt::exclude); @@ -103,7 +102,6 @@ void PartyService::OnPartyJoined(const NotifyPartyJoined& acPartyJoined) noexcep m_world.GetCharacterService().ProcessNewEntity(entity); } } - */ } void PartyService::DestroyParty() noexcept From 0bb89bc7bae1e66bcb63838db4bcf6e1271f573b Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 16:12:35 +0200 Subject: [PATCH 19/39] feat: party leader hosts all actors --- Code/server/Services/CharacterService.cpp | 65 ++++++++++++++--------- Code/server/Services/CharacterService.h | 1 + Code/server/Services/PartyService.cpp | 28 ++++++++++ Code/server/Services/PartyService.h | 3 ++ Code/server/World.cpp | 2 - Code/server/World.h | 3 ++ 6 files changed, 75 insertions(+), 27 deletions(-) diff --git a/Code/server/Services/CharacterService.cpp b/Code/server/Services/CharacterService.cpp index 92a766d67..a542b49b0 100644 --- a/Code/server/Services/CharacterService.cpp +++ b/Code/server/Services/CharacterService.cpp @@ -186,7 +186,7 @@ void CharacterService::OnAssignCharacterRequest(const PacketEvent(); + auto view = m_world.view(); const auto itor = std::find_if(std::begin(view), std::end(view), [view, refId](auto entity) { @@ -206,6 +206,19 @@ void CharacterService::OnAssignCharacterRequest(const PacketEvent(*itor); auto& movementComponent = view.get(*itor); auto& cellIdComponent = view.get(*itor); + auto& ownerComponent = view.get(*itor); + + auto& partyService = m_world.GetPartyService(); + + if (partyService.IsPlayerInParty(acMessage.pPlayer) && partyService.IsPlayerLeader(acMessage.pPlayer)) + { + PartyService::Party* pParty = partyService.GetPlayerParty(acMessage.pPlayer); + Player* pOwningPlayer = view.get(*itor).GetOwner(); + + // Transfer ownership if owning player is in the same party as the owner + if (std::find(pParty->Members.begin(), pParty->Members.end(), pOwningPlayer) != pParty->Members.end()) + TransferOwnership(acMessage.pPlayer, World::ToInteger(*itor)); + } AssignCharacterResponse response; response.Cookie = message.Cookie; @@ -334,30 +347,7 @@ void CharacterService::OnCharacterRemoveEvent(const CharacterRemoveEvent& acEven void CharacterService::OnOwnershipClaimRequest(const PacketEvent& acMessage) const noexcept { - auto& message = acMessage.Packet; - - //const OwnerView view(m_world, acMessage.GetSender()); - auto view = m_world.view(); - const auto it = view.find(static_cast(message.ServerId)); - if (it == view.end()) - { - spdlog::warn("Client {:X} requested ownership of an entity that doesn't exist ({:X})!", acMessage.pPlayer->GetConnectionId(), message.ServerId); - return; - } - - auto& characterOwnerComponent = view.get(*it); - - if (characterOwnerComponent.GetOwner() != acMessage.pPlayer) - { - NotifyRelinquishControl notify; - notify.ServerId = message.ServerId; - characterOwnerComponent.pOwner->Send(notify); - } - - characterOwnerComponent.SetOwner(acMessage.pPlayer); - characterOwnerComponent.InvalidOwners.clear(); - - spdlog::info("\tOwnership claimed {:X}", message.ServerId); + TransferOwnership(acMessage.pPlayer, acMessage.Packet.ServerId); } void CharacterService::OnCharacterSpawned(const CharacterSpawnedEvent& acEvent) const noexcept @@ -699,6 +689,31 @@ void CharacterService::CreateCharacter(const PacketEvent dispatcher.trigger(CharacterSpawnedEvent(cEntity)); } +void CharacterService::TransferOwnership(Player* apPlayer, const uint32_t acServerId) const noexcept +{ + //const OwnerView view(m_world, acMessage.GetSender()); + auto view = m_world.view(); + const auto it = view.find(static_cast(acServerId)); + if (it == view.end()) + { + spdlog::warn("Client {:X} requested ownership of an entity that doesn't exist ({:X})!", apPlayer->GetConnectionId(), acServerId); + return; + } + + auto& characterOwnerComponent = view.get(*it); + + if (characterOwnerComponent.GetOwner() != apPlayer) + { + NotifyRelinquishControl notify; + notify.ServerId = acServerId; + characterOwnerComponent.pOwner->Send(notify); + } + + characterOwnerComponent.SetOwner(apPlayer); + characterOwnerComponent.InvalidOwners.clear(); + + spdlog::info("\tOwnership claimed {:X}", acServerId); +} void CharacterService::ProcessFactionsChanges() const noexcept { diff --git a/Code/server/Services/CharacterService.h b/Code/server/Services/CharacterService.h index c31360d45..0de6b08ad 100644 --- a/Code/server/Services/CharacterService.h +++ b/Code/server/Services/CharacterService.h @@ -60,6 +60,7 @@ struct CharacterService void OnSubtitleRequest(const PacketEvent& acMessage) const noexcept; void CreateCharacter(const PacketEvent& acMessage) const noexcept; + void TransferOwnership(Player* apPlayer, const uint32_t acServerId) const noexcept; void ProcessFactionsChanges() const noexcept; void ProcessMovementChanges() const noexcept; diff --git a/Code/server/Services/PartyService.cpp b/Code/server/Services/PartyService.cpp index 4f15a1e81..b37a6a355 100644 --- a/Code/server/Services/PartyService.cpp +++ b/Code/server/Services/PartyService.cpp @@ -41,6 +41,34 @@ const PartyService::Party* PartyService::GetById(uint32_t aId) const noexcept return nullptr; } +bool PartyService::IsPlayerInParty(Player* const apPlayer) const noexcept +{ + return apPlayer->GetParty().JoinedPartyId.has_value(); +} + +bool PartyService::IsPlayerLeader(Player* const apPlayer) noexcept +{ + auto& inviterPartyComponent = apPlayer->GetParty(); + if (inviterPartyComponent.JoinedPartyId) + { + Party& party = m_parties[*inviterPartyComponent.JoinedPartyId]; + return party.LeaderPlayerId == apPlayer->GetId(); + } + + return false; +} + +PartyService::Party* PartyService::GetPlayerParty(Player* const apPlayer) noexcept +{ + auto& inviterPartyComponent = apPlayer->GetParty(); + if (inviterPartyComponent.JoinedPartyId) + { + return &m_parties[*inviterPartyComponent.JoinedPartyId]; + } + + return nullptr; +} + void PartyService::OnUpdate(const UpdateEvent& acEvent) noexcept { const auto cCurrentTick = GameServer::Get()->GetTick(); diff --git a/Code/server/Services/PartyService.h b/Code/server/Services/PartyService.h index 9e56c8ba3..e9fefaf3b 100644 --- a/Code/server/Services/PartyService.h +++ b/Code/server/Services/PartyService.h @@ -31,6 +31,9 @@ struct PartyService TP_NOCOPYMOVE(PartyService); const Party* GetById(uint32_t aId) const noexcept; + bool IsPlayerInParty(Player* const apPlayer) const noexcept; + bool IsPlayerLeader(Player* const apPlayer) noexcept; + Party* GetPlayerParty(Player* const apPlayer) noexcept; protected: diff --git a/Code/server/World.cpp b/Code/server/World.cpp index 45f6e6c4b..405e8236d 100644 --- a/Code/server/World.cpp +++ b/Code/server/World.cpp @@ -2,11 +2,9 @@ #include #include -#include #include #include #include -#include #include #include #include diff --git a/Code/server/World.h b/Code/server/World.h index f4253c635..b4347f32b 100644 --- a/Code/server/World.h +++ b/Code/server/World.h @@ -3,6 +3,7 @@ #include "Services/AdminService.h" #include +#include #include #include #include @@ -24,6 +25,8 @@ struct World : entt::registry const CharacterService& GetCharacterService() const noexcept { return ctx().at(); } PlayerService& GetPlayerService() noexcept { return ctx().at(); } const PlayerService& GetPlayerService() const noexcept { return ctx().at(); } + PartyService& GetPartyService() noexcept { return ctx().at(); } + const PartyService& GetPartyService() const noexcept { return ctx().at(); } CalendarService& GetCalendarService() noexcept { return ctx().at(); } const CalendarService& GetCalendarService() const noexcept { return ctx().at(); } QuestService& GetQuestService() noexcept { return ctx().at(); } From c543200a12d18e4cfc9c3dbc1b1c590ad5540a98 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 17:13:48 +0200 Subject: [PATCH 20/39] tweak: don't transfer ownership of mounts to quest leader --- Code/client/Games/Skyrim/Actor.h | 6 ++++++ Code/client/Services/Generic/CharacterService.cpp | 8 ++++++-- Code/encoding/Messages/AssignCharacterRequest.cpp | 2 ++ Code/encoding/Messages/AssignCharacterRequest.h | 6 ++++-- Code/server/Components/CharacterComponent.h | 12 ++++++++++++ Code/server/Services/CharacterService.cpp | 4 +++- 6 files changed, 33 insertions(+), 5 deletions(-) diff --git a/Code/client/Games/Skyrim/Actor.h b/Code/client/Games/Skyrim/Actor.h index f5fed2516..c7061b087 100644 --- a/Code/client/Games/Skyrim/Actor.h +++ b/Code/client/Games/Skyrim/Actor.h @@ -233,9 +233,15 @@ struct Actor : TESObjectREFR enum ActorFlags { + IS_A_MOUNT = 1 << 1, IS_ESSENTIAL = 1 << 18, }; + bool IsMount() const noexcept + { + return flags2 & ActorFlags::IS_A_MOUNT; + } + bool IsEssential() const noexcept { return flags2 & ActorFlags::IS_ESSENTIAL; diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index 051a75c11..d05596db8 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -1103,6 +1103,7 @@ void CharacterService::OnNotifyRelinquishControl(const NotifyRelinquishControl& auto formView = m_world.view(); Vector entities(formView.begin(), formView.end()); + // TODO(cosideci): this entity iteration shouldn't technically be necessary, just look for the local component for (auto entity : entities) { std::optional serverIdRes = Utils::GetServerId(entity); @@ -1136,7 +1137,7 @@ void CharacterService::OnNotifyRelinquishControl(const NotifyRelinquishControl& } } - spdlog::error("Did not find actor to relinquish control to, server id {:X}", acMessage.ServerId); + spdlog::error("Did not find actor to relinquish control of, server id {:X}", acMessage.ServerId); } void CharacterService::OnNotifySubtitle(const NotifySubtitle& acMessage) noexcept @@ -1230,7 +1231,9 @@ void CharacterService::ProcessNewEntity(entt::entity aEntity) const noexcept if (auto* pRemoteComponent = m_world.try_get(aEntity); pRemoteComponent) { - if (m_world.GetPartyService().IsLeader() && !pActor->IsTemporary()) + // TODO(cosideci): don't just take all actors (i.e. from other parties), + // maybe check it server side, add a variable to the request. + if (m_world.GetPartyService().IsLeader() && !pActor->IsTemporary() && !pActor->IsMount()) { spdlog::info("Sending ownership claim for actor {:X} with server id {:X}", pActor->formID, pRemoteComponent->Id); @@ -1373,6 +1376,7 @@ void CharacterService::RequestServerAssignment(const entt::entity aEntity) const message.IsDead = pActor->IsDead(); message.IsDragon = pActor->IsDragon(); message.IsWeaponDrawn = pActor->actorState.IsWeaponFullyDrawn(); + message.IsMount = pActor->IsMount(); if (isTemporary /* && !isNpcTemporary */) { diff --git a/Code/encoding/Messages/AssignCharacterRequest.cpp b/Code/encoding/Messages/AssignCharacterRequest.cpp index 23505affd..414bd699e 100644 --- a/Code/encoding/Messages/AssignCharacterRequest.cpp +++ b/Code/encoding/Messages/AssignCharacterRequest.cpp @@ -20,6 +20,7 @@ void AssignCharacterRequest::SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter Serialization::WriteBool(aWriter, IsDead); Serialization::WriteBool(aWriter, IsWeaponDrawn); Serialization::WriteBool(aWriter, IsDragon); + Serialization::WriteBool(aWriter, IsMount); } void AssignCharacterRequest::DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept @@ -55,4 +56,5 @@ void AssignCharacterRequest::DeserializeRaw(TiltedPhoques::Buffer::Reader& aRead IsDead = Serialization::ReadBool(aReader); IsWeaponDrawn = Serialization::ReadBool(aReader); IsDragon = Serialization::ReadBool(aReader); + IsMount = Serialization::ReadBool(aReader); } diff --git a/Code/encoding/Messages/AssignCharacterRequest.h b/Code/encoding/Messages/AssignCharacterRequest.h index 6cc9485a7..da7a7e4dc 100644 --- a/Code/encoding/Messages/AssignCharacterRequest.h +++ b/Code/encoding/Messages/AssignCharacterRequest.h @@ -29,7 +29,8 @@ struct AssignCharacterRequest final : ClientMessage bool operator==(const AssignCharacterRequest& acRhs) const noexcept { - return Cookie == acRhs.Cookie && + return GetOpcode() == acRhs.GetOpcode() && + Cookie == acRhs.Cookie && ReferenceId == acRhs.ReferenceId && FormId == acRhs.FormId && CellId == acRhs.CellId && @@ -47,7 +48,7 @@ struct AssignCharacterRequest final : ClientMessage IsDead == acRhs.IsDead && IsWeaponDrawn == acRhs.IsWeaponDrawn && IsDragon == acRhs.IsDragon && - GetOpcode() == acRhs.GetOpcode(); + IsMount == acRhs.IsMount; } uint32_t Cookie{}; @@ -68,4 +69,5 @@ struct AssignCharacterRequest final : ClientMessage bool IsDead{}; bool IsWeaponDrawn{}; bool IsDragon{}; + bool IsMount{}; }; diff --git a/Code/server/Components/CharacterComponent.h b/Code/server/Components/CharacterComponent.h index a857f2c5d..c4e62100d 100644 --- a/Code/server/Components/CharacterComponent.h +++ b/Code/server/Components/CharacterComponent.h @@ -16,6 +16,7 @@ struct CharacterComponent kIsPlayer = 1 << 2, kIsWeaponDrawn = 1 << 3, kIsDragon = 1 << 4, + kIsMount = 1 << 5, }; [[nodiscard]] bool IsDirtyFactions() const @@ -38,6 +39,10 @@ struct CharacterComponent { return Flags & kIsDragon; } + [[nodiscard]] bool IsMount() const + { + return Flags & kIsMount; + } void SetDirtyFactions(bool aSet) { @@ -74,6 +79,13 @@ struct CharacterComponent else Flags &= ~kIsDragon; } + void SetMount(bool aSet) + { + if (aSet) + Flags |= kIsMount; + else + Flags &= ~kIsMount; + } uint32_t ChangeFlags{ 0 }; String SaveBuffer{}; diff --git a/Code/server/Services/CharacterService.cpp b/Code/server/Services/CharacterService.cpp index a542b49b0..48a87be7d 100644 --- a/Code/server/Services/CharacterService.cpp +++ b/Code/server/Services/CharacterService.cpp @@ -210,7 +210,8 @@ void CharacterService::OnAssignCharacterRequest(const PacketEvent(*itor).GetOwner(); @@ -647,6 +648,7 @@ void CharacterService::CreateCharacter(const PacketEvent characterComponent.SetPlayer(isPlayer); characterComponent.SetWeaponDrawn(message.IsWeaponDrawn); characterComponent.SetDragon(message.IsDragon); + characterComponent.SetMount(message.IsMount); auto& inventoryComponent = m_world.emplace(cEntity); inventoryComponent.Content = message.InventoryContent; From 563cf472df2b8eb99e46fbdca92f4119c43f5fc7 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 17:29:14 +0200 Subject: [PATCH 21/39] fix: isOwner bool on assignment --- Code/server/Services/CharacterService.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Code/server/Services/CharacterService.cpp b/Code/server/Services/CharacterService.cpp index 48a87be7d..b062edcad 100644 --- a/Code/server/Services/CharacterService.cpp +++ b/Code/server/Services/CharacterService.cpp @@ -210,6 +210,8 @@ void CharacterService::OnAssignCharacterRequest(const PacketEventMembers.begin(), pParty->Members.end(), pOwningPlayer) != pParty->Members.end()) + { TransferOwnership(acMessage.pPlayer, World::ToInteger(*itor)); + isOwner = true; + } } AssignCharacterResponse response; response.Cookie = message.Cookie; response.ServerId = World::ToInteger(*itor); - response.Owner = false; + response.Owner = isOwner; response.AllActorValues = actorValuesComponent.CurrentActorValues; response.IsDead = characterComponent.IsDead(); response.IsWeaponDrawn = characterComponent.IsWeaponDrawn(); From 2ca9a43c4cb1797cab8bd21c6d81c3928c24cabb Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 18:29:14 +0200 Subject: [PATCH 22/39] tweak: teleport actor to me button --- .../Services/Debug/Views/EntitiesView.cpp | 26 ++++++------- Code/client/Services/DebugService.h | 2 +- .../Services/Generic/CharacterService.cpp | 38 ++++++++++++------- 3 files changed, 38 insertions(+), 28 deletions(-) diff --git a/Code/client/Services/Debug/Views/EntitiesView.cpp b/Code/client/Services/Debug/Views/EntitiesView.cpp index 4685c4201..c87ba5b57 100644 --- a/Code/client/Services/Debug/Views/EntitiesView.cpp +++ b/Code/client/Services/Debug/Views/EntitiesView.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -126,7 +127,7 @@ void DebugService::DisplayEntityPanel(entt::entity aEntity) noexcept const auto pRemoteComponent = m_world.try_get(aEntity); if (pFormIdComponent) DisplayFormComponent(*pFormIdComponent); - if (pLocalComponent) DisplayLocalComponent(*pLocalComponent); + if (pLocalComponent) DisplayLocalComponent(*pLocalComponent, pFormIdComponent ? pFormIdComponent->Id : 0); if (pRemoteComponent) DisplayRemoteComponent(*pRemoteComponent, aEntity, pFormIdComponent ? pFormIdComponent->Id : 0); } @@ -153,34 +154,31 @@ void DebugService::DisplayFormComponent(FormIdComponent& aFormComponent) const n }); } - if (ImGui::Button("Stop dialogue")) - { - m_world.GetRunner().Queue([id = aFormComponent.Id]() { - Actor* pActor = Cast(TESForm::GetById(id)); - pActor->StopCurrentDialogue(true); - }); - } - ImGui::InputInt("Game Id", (int*)&aFormComponent.Id, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); ImGui::InputFloat3("Position", pActor->position.AsArray(), "%.3f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3("Rotation", pActor->rotation.AsArray(), "%.3f", ImGuiInputTextFlags_ReadOnly); int isDead = int(pActor->IsDead()); ImGui::InputInt("Is dead?", &isDead, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); - int isWeaponDrawn = int(pActor->actorState.IsWeaponDrawn()); - ImGui::InputInt("Is weapon drawn?", &isWeaponDrawn, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); - int isBleedingOut = int(pActor->actorState.IsBleedingOut()); - ImGui::InputInt("Is bleeding out?", &isBleedingOut, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); #if TP_SKYRIM64 float attributes[3] {pActor->GetActorValue(24), pActor->GetActorValue(25), pActor->GetActorValue(26)}; ImGui::InputFloat3("Attributes (H/M/S)", attributes, "%.3f", ImGuiInputTextFlags_ReadOnly); #endif } -void DebugService::DisplayLocalComponent(LocalComponent& aLocalComponent) const noexcept +void DebugService::DisplayLocalComponent(LocalComponent& aLocalComponent, const uint32_t acFormId) const noexcept { if (!ImGui::CollapsingHeader("Local Component", ImGuiTreeNodeFlags_DefaultOpen)) return; + if (ImGui::Button("Teleport to me")) + { + m_world.GetRunner().Queue([acFormId]() { + Actor* pActor = Cast(TESForm::GetById(acFormId)); + PlayerCharacter* pPlayer = PlayerCharacter::Get(); + pActor->MoveTo(pPlayer->parentCell, pPlayer->position); + }); + } + auto& action = aLocalComponent.CurrentAction; ImGui::InputInt("Net Id", (int*)&aLocalComponent.Id, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); ImGui::InputInt("Action Id", (int*)&action.ActionId, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); diff --git a/Code/client/Services/DebugService.h b/Code/client/Services/DebugService.h index 863562c54..54f27d7c2 100644 --- a/Code/client/Services/DebugService.h +++ b/Code/client/Services/DebugService.h @@ -39,7 +39,7 @@ struct DebugService void DisplayObjects() noexcept; void DisplayEntityPanel(entt::entity aEntity) noexcept; void DisplayFormComponent(FormIdComponent& aFormComponent) const noexcept; - void DisplayLocalComponent(LocalComponent& aLocalComponent) const noexcept; + void DisplayLocalComponent(LocalComponent& aLocalComponent, const uint32_t acFormId) const noexcept; void DisplayRemoteComponent(RemoteComponent& aLocalComponent, const entt::entity acEntity, const uint32_t acFormId) const noexcept; void DrawEntitiesView(); diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index d05596db8..6f7b5b692 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -210,6 +210,8 @@ void CharacterService::OnActorRemoved(const ActorRemovedEvent& acEvent) noexcept if (m_world.orphan(cId)) m_world.destroy(cId); + + spdlog::info("Actor removed, form id: {:X}", acEvent.FormId); } void CharacterService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept @@ -267,7 +269,7 @@ void CharacterService::OnDisconnected(const DisconnectedEvent& acDisconnectedEve void CharacterService::OnAssignCharacter(const AssignCharacterResponse& acMessage) noexcept { - spdlog::info("Received for cookie {:X}", acMessage.Cookie); + spdlog::info("Received for cookie {:X}, server id {:X}", acMessage.Cookie, acMessage.ServerId); auto view = m_world.view(); const auto itor = std::find_if(std::begin(view), std::end(view), [view, cookie = acMessage.Cookie](auto entity) @@ -288,18 +290,20 @@ void CharacterService::OnAssignCharacter(const AssignCharacterResponse& acMessag const auto formIdComponent = m_world.try_get(cEntity); if (!formIdComponent) { - spdlog::error("CharacterService::OnAssignCharacter(): form id component doesn't exist, cookie: {:X}", acMessage.Cookie); + spdlog::error(__FUNCTION__ ": form id component doesn't exist, cookie: {:X}", acMessage.Cookie); return; } // This code path triggers when the character has been spawned through CharacterSpawnRequest if (m_world.any_of(cEntity)) { - TESForm* const pForm = TESForm::GetById(formIdComponent->Id); - Actor* const pActor = Cast(pForm); + Actor* pActor = Cast(TESForm::GetById(formIdComponent->Id)); if (!pActor) + { + spdlog::error(__FUNCTION__ ": could not find actor for already owned entity, form id: {:X}", formIdComponent->Id); return; + } if (pActor->IsDead() != acMessage.IsDead) acMessage.IsDead ? pActor->Kill() : pActor->Respawn(); @@ -307,11 +311,12 @@ void CharacterService::OnAssignCharacter(const AssignCharacterResponse& acMessag if (pActor->actorState.IsWeaponDrawn() != acMessage.IsWeaponDrawn) m_weaponDrawUpdates[formIdComponent->Id] = {0, acMessage.IsWeaponDrawn}; + spdlog::info("Applied updates on assignment response, form id: {:X}", formIdComponent->Id); + return; } - auto* const pForm = TESForm::GetById(formIdComponent->Id); - auto* pActor = Cast(pForm); + Actor* pActor = Cast(TESForm::GetById(formIdComponent->Id)); if (!pActor) { spdlog::error(__FUNCTION__ ": actor not found, form id: {:X}", formIdComponent->Id); @@ -321,13 +326,17 @@ void CharacterService::OnAssignCharacter(const AssignCharacterResponse& acMessag if (acMessage.Owner) { - m_world.emplace(cEntity, acMessage.ServerId); - m_world.emplace(cEntity); + spdlog::info("Received local actor, form id: {:X}", pActor->formID); + + m_world.emplace_or_replace(cEntity, acMessage.ServerId); + m_world.emplace_or_replace(cEntity); pActor->GetExtension()->SetRemote(false); } else { + spdlog::info("Received remote actor, form id: {:X}", pActor->formID); + m_world.emplace_or_replace(cEntity, acMessage.ServerId, formIdComponent->Id); pActor->GetExtension()->SetRemote(true); @@ -424,6 +433,8 @@ void CharacterService::OnCharacterSpawn(const CharacterSpawnRequest& acMessage) spdlog::error("Actor object {:X} could not be created.", acMessage.ServerId); return; } + + spdlog::info("CharacterSpawnRequest, server id: {:X}, form id: {:X}", acMessage.ServerId, pActor->formID); if (pActor->IsDisabled()) pActor->Enable(); @@ -1178,7 +1189,9 @@ void CharacterService::OnNotifyActorTeleport(const NotifyActorTeleport& acMessag MoveActor(pActor, acMessage.WorldSpaceId, acMessage.CellId, acMessage.Position); - spdlog::warn("Successfully teleported actor"); + spdlog::info("Successfully teleported actor, form id: {:X}, world space: {:X}, cell: {:X}, position: ({}, {}, {})", + pActor->formID, acMessage.WorldSpaceId.BaseId, acMessage.CellId.BaseId, acMessage.Position.x, + acMessage.Position.y, acMessage.Position.z); } void CharacterService::MoveActor(const Actor* apActor, const GameId& acWorldSpaceId, const GameId& acCellId, const Vector3_NetQuantize& acPosition) const noexcept @@ -1393,7 +1406,7 @@ void CharacterService::RequestServerAssignment(const entt::entity aEntity) const message.LatestAction = pExtension->LatestAnimation; pActor->SaveAnimationVariables(message.LatestAction.Variables); - spdlog::info("Request id: {:X}, cookie: {:X}, {:X}", formIdComponent.Id, sCookieSeed, to_integral(aEntity)); + spdlog::info("Request id: {:X}, cookie: {:X}, entity: {:X}", formIdComponent.Id, sCookieSeed, to_integral(aEntity)); if (m_transport.Send(message)) { @@ -1407,8 +1420,7 @@ void CharacterService::CancelServerAssignment(const entt::entity aEntity, const { if (m_world.all_of(aEntity)) { - TESForm* const pForm = TESForm::GetById(aFormId); - Actor* const pActor = Cast(pForm); + Actor* pActor = Cast(TESForm::GetById(aFormId)); if (pActor) { @@ -1471,7 +1483,7 @@ void CharacterService::CancelServerAssignment(const entt::entity aEntity, const } } - spdlog::warn("Transferring ownership of local actor, server id: {:X}, worldspace: {:X}, cell: {:X}, position: " + spdlog::info("Transferring ownership of local actor, server id: {:X}, worldspace: {:X}, cell: {:X}, position: " "({}, {}, {})", request.ServerId, request.WorldSpaceId.BaseId, request.CellId.BaseId, request.Position.x, request.Position.y, request.Position.z); From c6bf19c7d460c191293a685f2cef71a86c1ece5d Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 18:38:38 +0200 Subject: [PATCH 23/39] fix: double applying of RemoteComponent --- .../Services/Generic/CharacterService.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index 6f7b5b692..5ebf1dbf6 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -295,6 +295,7 @@ void CharacterService::OnAssignCharacter(const AssignCharacterResponse& acMessag } // This code path triggers when the character has been spawned through CharacterSpawnRequest + // TODO(cosideci): since I check for WaitingForAssignmentComponent now, this path shouldn't ever trigger if (m_world.any_of(cEntity)) { Actor* pActor = Cast(TESForm::GetById(formIdComponent->Id)); @@ -311,7 +312,7 @@ void CharacterService::OnAssignCharacter(const AssignCharacterResponse& acMessag if (pActor->actorState.IsWeaponDrawn() != acMessage.IsWeaponDrawn) m_weaponDrawUpdates[formIdComponent->Id] = {0, acMessage.IsWeaponDrawn}; - spdlog::info("Applied updates on assignment response, form id: {:X}", formIdComponent->Id); + spdlog::critical("Applied updates on assignment response, form id: {:X}", formIdComponent->Id); return; } @@ -405,6 +406,18 @@ void CharacterService::OnCharacterSpawn(const CharacterSpawnRequest& acMessage) } else { + auto waitingView = m_world.view(); + const auto waitingItor = std::find_if(std::begin(waitingView), std::end(waitingView), [waitingView, Id = acMessage.FormId](auto entity) + { + return waitingView.get(entity).Id == Id; + }); + + if (waitingItor != std::end(waitingView)) + { + spdlog::info("Character with form id {:X} already has a spawn request in progress.", acMessage.FormId); + return; + } + const auto cActorId = World::Get().GetModSystem().GetGameId(acMessage.FormId); auto* const pForm = TESForm::GetById(cActorId); @@ -1289,7 +1302,7 @@ void CharacterService::RequestServerAssignment(const entt::entity aEntity) const if (!pNpc) return; - AssignCharacterRequest message; + AssignCharacterRequest message{}; message.Cookie = sCookieSeed; From a11649ed87b2ab96d10ad9a0d365d60d0af235d5 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Sun, 29 May 2022 18:50:23 +0200 Subject: [PATCH 24/39] fix: form id comparison --- Code/client/Services/Generic/CharacterService.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index 5ebf1dbf6..6fd1356f0 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -406,10 +406,12 @@ void CharacterService::OnCharacterSpawn(const CharacterSpawnRequest& acMessage) } else { + const uint32_t cActorId = World::Get().GetModSystem().GetGameId(acMessage.FormId); + auto waitingView = m_world.view(); - const auto waitingItor = std::find_if(std::begin(waitingView), std::end(waitingView), [waitingView, Id = acMessage.FormId](auto entity) + const auto waitingItor = std::find_if(std::begin(waitingView), std::end(waitingView), [waitingView, cActorId](auto entity) { - return waitingView.get(entity).Id == Id; + return waitingView.get(entity).Id == cActorId; }); if (waitingItor != std::end(waitingView)) @@ -418,8 +420,6 @@ void CharacterService::OnCharacterSpawn(const CharacterSpawnRequest& acMessage) return; } - const auto cActorId = World::Get().GetModSystem().GetGameId(acMessage.FormId); - auto* const pForm = TESForm::GetById(cActorId); pActor = Cast(pForm); From c1870b9af94f50dc2765e1704d5ee08134e0dc4c Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Mon, 30 May 2022 01:03:00 +0200 Subject: [PATCH 25/39] fix: potential disconnect crash --- Code/client/Services/Debug/Views/EntitiesView.cpp | 3 +++ Code/client/Services/Generic/ActorValueService.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Code/client/Services/Debug/Views/EntitiesView.cpp b/Code/client/Services/Debug/Views/EntitiesView.cpp index c87ba5b57..a9d7c7fe0 100644 --- a/Code/client/Services/Debug/Views/EntitiesView.cpp +++ b/Code/client/Services/Debug/Views/EntitiesView.cpp @@ -4,6 +4,7 @@ #include #include +#include #include #include @@ -159,6 +160,8 @@ void DebugService::DisplayFormComponent(FormIdComponent& aFormComponent) const n ImGui::InputFloat3("Rotation", pActor->rotation.AsArray(), "%.3f", ImGuiInputTextFlags_ReadOnly); int isDead = int(pActor->IsDead()); ImGui::InputInt("Is dead?", &isDead, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); + int isRemote = int(pActor->GetExtension()->IsRemote()); + ImGui::InputInt("Is remote?", &isRemote, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); #if TP_SKYRIM64 float attributes[3] {pActor->GetActorValue(24), pActor->GetActorValue(25), pActor->GetActorValue(26)}; ImGui::InputFloat3("Attributes (H/M/S)", attributes, "%.3f", ImGuiInputTextFlags_ReadOnly); diff --git a/Code/client/Services/Generic/ActorValueService.cpp b/Code/client/Services/Generic/ActorValueService.cpp index 35d5ee65d..4a32f4cb0 100644 --- a/Code/client/Services/Generic/ActorValueService.cpp +++ b/Code/client/Services/Generic/ActorValueService.cpp @@ -41,7 +41,7 @@ ActorValueService::ActorValueService(World& aWorld, entt::dispatcher& aDispatche void ActorValueService::CreateActorValuesComponent(const entt::entity aEntity, Actor* apActor) noexcept { - auto& actorValuesComponent = m_world.emplace(aEntity); + auto& actorValuesComponent = m_world.emplace_or_replace(aEntity); for (int i = 0; i < ActorValueInfo::kActorValueCount; i++) { From d32f31ea727a2bc4fb3309161e9fbcaca5b256ef Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Mon, 30 May 2022 15:36:17 +0200 Subject: [PATCH 26/39] tweak: improved equipment sync --- .../Services/Generic/ActorValueService.cpp | 3 +- .../Services/Generic/CharacterService.cpp | 32 +++++++------------ 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/Code/client/Services/Generic/ActorValueService.cpp b/Code/client/Services/Generic/ActorValueService.cpp index 4a32f4cb0..57a497521 100644 --- a/Code/client/Services/Generic/ActorValueService.cpp +++ b/Code/client/Services/Generic/ActorValueService.cpp @@ -61,8 +61,7 @@ void ActorValueService::CreateActorValuesComponent(const entt::entity aEntity, A void ActorValueService::OnLocalComponentAdded(entt::registry& aRegistry, const entt::entity aEntity) noexcept { const auto& formIdComponent = aRegistry.get(aEntity); - const auto* pForm = TESForm::GetById(formIdComponent.Id); - auto* pActor = Cast(pForm); + Actor* pActor = Cast(TESForm::GetById(formIdComponent.Id)); if (pActor != NULL) { diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index 6fd1356f0..bc720d1b5 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -141,8 +141,8 @@ bool CharacterService::TakeOwnership(const uint32_t acFormId, const uint32_t acS // TODO(cosideci): this should be done differently. // Send an ownership claim request, and have the server broadcast the result. // Only then should components be added or removed. - m_world.emplace(acEntity, acServerId); - m_world.emplace(acEntity); + m_world.emplace_or_replace(acEntity, acServerId); + m_world.emplace_or_replace(acEntity); m_world.remove(acEntity); @@ -476,13 +476,13 @@ void CharacterService::OnCharacterSpawn(const CharacterSpawnRequest& acMessage) AnimationSystem::Setup(m_world, *entity); - m_world.emplace(*entity); + m_world.emplace_or_replace(*entity); auto& remoteAnimationComponent = m_world.get(*entity); remoteAnimationComponent.TimePoints.push_back(acMessage.LatestAction); } -// TODO: verify/simplify this spawn data stuff +// TODO(cosideci): this is probably not necessary anymore void CharacterService::OnRemoteSpawnDataReceived(const NotifySpawnData& acMessage) noexcept { auto view = m_world.view(); @@ -504,8 +504,7 @@ void CharacterService::OnRemoteSpawnDataReceived(const NotifySpawnData& acMessag remoteComponent.SpawnRequest.IsWeaponDrawn = acMessage.IsWeaponDrawn; auto& formIdComponent = view.get(*itor); - auto* const pForm = TESForm::GetById(formIdComponent.Id); - auto* pActor = Cast(pForm); + Actor* pActor = Cast(TESForm::GetById(formIdComponent.Id)); if (!pActor) return; @@ -1245,14 +1244,11 @@ void CharacterService::ProcessNewEntity(entt::entity aEntity) const noexcept auto& formIdComponent = m_world.get(aEntity); - auto* const pForm = TESForm::GetById(formIdComponent.Id); - auto* const pActor = Cast(pForm); + Actor* const pActor = Cast(TESForm::GetById(formIdComponent.Id)); if (!pActor) - return; - - if (pActor->GetExtension()->IsRemote()) { - spdlog::info("New entity remotely managed? {:X}", pActor->formID); + spdlog::warn(__FUNCTION__ ": actor for new entity not found, form id: {:X}", formIdComponent.Id); + return; } if (auto* pRemoteComponent = m_world.try_get(aEntity); pRemoteComponent) @@ -1265,16 +1261,12 @@ void CharacterService::ProcessNewEntity(entt::entity aEntity) const noexcept pRemoteComponent->Id); TakeOwnership(pActor->formID, pRemoteComponent->Id, aEntity); - - return; } else - { - RequestSpawnData requestSpawnData; - requestSpawnData.Id = pRemoteComponent->Id; - m_transport.Send(requestSpawnData); - return; - } + spdlog::info("New entity remotely managed, form id: {:X}, server id: {:X}", pActor->formID, + pRemoteComponent->Id); + + return; } if (m_world.any_of(aEntity)) From 197f0cc3050abc62571898204b99d35ed6769704 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Mon, 30 May 2022 15:59:51 +0200 Subject: [PATCH 27/39] fix: properly update magic equipment --- Code/client/Games/Skyrim/Actor.cpp | 11 +++++++++++ Code/client/Games/Skyrim/Actor.h | 1 + Code/client/Games/Skyrim/TESObjectREFR.cpp | 7 ------- Code/client/Games/Skyrim/TESObjectREFR.h | 1 - Code/client/Services/Generic/InventoryService.cpp | 2 +- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index 2bec95727..c1014df8f 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -303,6 +303,14 @@ MagicEquipment Actor::GetMagicEquipment() const noexcept return equipment; } +Inventory Actor::GetEquipment() const noexcept +{ + Inventory inventory = GetInventory(); + inventory.RemoveByFilter([](const auto& entry) { return !entry.IsWorn(); }); + inventory.CurrentMagicEquipment = GetMagicEquipment(); + return inventory; +} + int32_t Actor::GetGoldAmount() noexcept { TP_THIS_FUNCTION(TGetGoldAmount, int32_t, Actor); @@ -328,18 +336,21 @@ void Actor::SetMagicEquipment(const MagicEquipment& acEquipment) noexcept if (acEquipment.LeftHandSpell) { uint32_t mainHandWeaponId = modSystem.GetGameId(acEquipment.LeftHandSpell); + spdlog::debug("Setting left hand spell: {:X}", mainHandWeaponId); pEquipManager->EquipSpell(this, TESForm::GetById(mainHandWeaponId), 0); } if (acEquipment.RightHandSpell) { uint32_t secondaryHandWeaponId = modSystem.GetGameId(acEquipment.RightHandSpell); + spdlog::debug("Setting right hand spell: {:X}", secondaryHandWeaponId); pEquipManager->EquipSpell(this, TESForm::GetById(secondaryHandWeaponId), 1); } if (acEquipment.Shout) { uint32_t shoutId = modSystem.GetGameId(acEquipment.Shout); + spdlog::debug("Setting shout: {:X}", shoutId); pEquipManager->EquipShout(this, TESForm::GetById(shoutId)); } } diff --git a/Code/client/Games/Skyrim/Actor.h b/Code/client/Games/Skyrim/Actor.h index c7061b087..949a267a2 100644 --- a/Code/client/Games/Skyrim/Actor.h +++ b/Code/client/Games/Skyrim/Actor.h @@ -192,6 +192,7 @@ struct Actor : TESObjectREFR float GetActorPermanentValue(uint32_t aId) const noexcept; Inventory GetActorInventory() const noexcept; MagicEquipment GetMagicEquipment() const noexcept; + Inventory GetEquipment() const noexcept; int32_t GetGoldAmount() noexcept; Factions GetFactions() const noexcept; diff --git a/Code/client/Games/Skyrim/TESObjectREFR.cpp b/Code/client/Games/Skyrim/TESObjectREFR.cpp index 1f06ab626..142f07290 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.cpp +++ b/Code/client/Games/Skyrim/TESObjectREFR.cpp @@ -462,13 +462,6 @@ Inventory TESObjectREFR::GetWornArmor() const noexcept return wornArmor; } -Inventory TESObjectREFR::GetEquippedItems() const noexcept -{ - Inventory inventory = GetInventory(); - inventory.RemoveByFilter([](const auto& entry) { return !entry.IsWorn(); }); - return inventory; -} - bool TESObjectREFR::IsItemInInventory(uint32_t aFormID) const noexcept { Inventory inventory = GetInventory([aFormID](TESForm& aForm) { return aForm.formID == aFormID; }); diff --git a/Code/client/Games/Skyrim/TESObjectREFR.h b/Code/client/Games/Skyrim/TESObjectREFR.h index 38473ceba..dee42dd8a 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.h +++ b/Code/client/Games/Skyrim/TESObjectREFR.h @@ -195,7 +195,6 @@ struct TESObjectREFR : TESForm Inventory GetInventory(std::function aFilter) const noexcept; Inventory GetArmor() const noexcept; Inventory GetWornArmor() const noexcept; - Inventory GetEquippedItems() const noexcept; bool IsItemInInventory(uint32_t aFormID) const noexcept; diff --git a/Code/client/Services/Generic/InventoryService.cpp b/Code/client/Services/Generic/InventoryService.cpp index 908a77dfa..939614fae 100644 --- a/Code/client/Services/Generic/InventoryService.cpp +++ b/Code/client/Services/Generic/InventoryService.cpp @@ -116,7 +116,7 @@ void InventoryService::OnEquipmentChangeEvent(const EquipmentChangeEvent& acEven request.IsSpell = acEvent.IsSpell; request.IsShout = acEvent.IsShout; request.IsAmmo = acEvent.IsAmmo; - request.CurrentInventory = pActor->GetEquippedItems(); + request.CurrentInventory = pActor->GetEquipment(); m_transport.Send(request); } From 0e9d36b908d2fdc1d9ca7d235a847f82a3b9a8de Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Mon, 30 May 2022 16:09:39 +0200 Subject: [PATCH 28/39] tweak: worn armor debugger --- .../Services/Debug/Views/PlayerDebugView.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Code/client/Services/Debug/Views/PlayerDebugView.cpp b/Code/client/Services/Debug/Views/PlayerDebugView.cpp index c7e7b0210..d37e7f37a 100644 --- a/Code/client/Services/Debug/Views/PlayerDebugView.cpp +++ b/Code/client/Services/Debug/Views/PlayerDebugView.cpp @@ -95,5 +95,19 @@ void DebugService::DrawPlayerDebugView() ImGui::InputInt2("Center grid", centerGrid, ImGuiInputTextFlags_ReadOnly); } + auto& modSystem = m_world.GetModSystem(); + + if (ImGui::CollapsingHeader("Worn armor")) + { + Inventory wornArmor{}; + wornArmor = pPlayer->GetWornArmor(); + for (const auto& armor : wornArmor.Entries) + { + const uint32_t armorId = armor.BaseId.BaseId; + ImGui::InputScalar("Item id", ImGuiDataType_U32, (void*)&armorId, nullptr, nullptr, "%" PRIx32, + ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); + } + } + ImGui::End(); } From 4bf7cdb3353797ed8a7f68fca10619e2cd2942ec Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Mon, 30 May 2022 18:02:49 +0200 Subject: [PATCH 29/39] tweak: apply remote inventory for claimed actor --- Code/client/Components/RemoteComponent.h | 3 - Code/client/Components/WaitingFor3D.h | 9 ++- .../Services/Generic/CharacterService.cpp | 66 +++++++++---------- Code/server/Services/CharacterService.cpp | 2 - 4 files changed, 39 insertions(+), 41 deletions(-) diff --git a/Code/client/Components/RemoteComponent.h b/Code/client/Components/RemoteComponent.h index b18f2eebc..05263365a 100644 --- a/Code/client/Components/RemoteComponent.h +++ b/Code/client/Components/RemoteComponent.h @@ -4,8 +4,6 @@ #error Include Components.h instead #endif -#include - #include struct RemoteComponent @@ -14,5 +12,4 @@ struct RemoteComponent uint32_t Id; uint32_t CachedRefId; - CharacterSpawnRequest SpawnRequest{}; }; diff --git a/Code/client/Components/WaitingFor3D.h b/Code/client/Components/WaitingFor3D.h index ac9ab9a4e..c0b5f0f4f 100644 --- a/Code/client/Components/WaitingFor3D.h +++ b/Code/client/Components/WaitingFor3D.h @@ -4,10 +4,13 @@ #error Include Components.h instead #endif +#include + struct WaitingFor3D { - WaitingFor3D() = default; + WaitingFor3D(const CharacterSpawnRequest& aSpawnRequest) + : SpawnRequest(aSpawnRequest) + {} - // This rather hacky thing is here because entt components seemingly can't be empty - uint8_t Placeholder{}; + CharacterSpawnRequest SpawnRequest{}; }; diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index bc720d1b5..bda627823 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -144,7 +144,7 @@ bool CharacterService::TakeOwnership(const uint32_t acFormId, const uint32_t acS m_world.emplace_or_replace(acEntity, acServerId); m_world.emplace_or_replace(acEntity); m_world.remove(acEntity); + FaceGenComponent, CacheComponent>(acEntity); // TODO(cosideci): send current local data of actor with it(?) RequestOwnershipClaim request; @@ -453,6 +453,7 @@ void CharacterService::OnCharacterSpawn(const CharacterSpawnRequest& acMessage) pActor->Enable(); pActor->GetExtension()->SetRemote(true); + pActor->rotation.x = acMessage.Rotation.x; pActor->rotation.z = acMessage.Rotation.y; pActor->MoveTo(PlayerCharacter::Get()->parentCell, acMessage.Position); @@ -469,14 +470,13 @@ void CharacterService::OnCharacterSpawn(const CharacterSpawnRequest& acMessage) acMessage.IsDead ? pActor->Kill() : pActor->Respawn(); auto& remoteComponent = m_world.emplace_or_replace(*entity, acMessage.ServerId, pActor->formID); - remoteComponent.SpawnRequest = acMessage; auto& interpolationComponent = InterpolationSystem::Setup(m_world, *entity); interpolationComponent.Position = acMessage.Position; AnimationSystem::Setup(m_world, *entity); - m_world.emplace_or_replace(*entity); + m_world.emplace_or_replace(*entity, acMessage); auto& remoteAnimationComponent = m_world.get(*entity); remoteAnimationComponent.TimePoints.push_back(acMessage.LatestAction); @@ -485,23 +485,24 @@ void CharacterService::OnCharacterSpawn(const CharacterSpawnRequest& acMessage) // TODO(cosideci): this is probably not necessary anymore void CharacterService::OnRemoteSpawnDataReceived(const NotifySpawnData& acMessage) noexcept { - auto view = m_world.view(); - - const auto id = acMessage.Id; + auto view = m_world.view(); - const auto itor = std::find_if(std::begin(view), std::end(view), [view, id](auto entity) { - const auto& remoteComponent = view.get(entity); - - return remoteComponent.Id == id; + const auto itor = std::find_if(std::begin(view), std::end(view), [view, id = acMessage.Id](auto entity) { + if (auto serverId = Utils::GetServerId(entity)) + { + if (*serverId == id) + return true; + } + return false; }); if (itor != std::end(view)) { - auto& remoteComponent = view.get(*itor); - remoteComponent.SpawnRequest.InitialActorValues = acMessage.InitialActorValues; - remoteComponent.SpawnRequest.InventoryContent = acMessage.InitialInventory; - remoteComponent.SpawnRequest.IsDead = acMessage.IsDead; - remoteComponent.SpawnRequest.IsWeaponDrawn = acMessage.IsWeaponDrawn; + auto& waitingFor3D = view.get(*itor); + waitingFor3D.SpawnRequest.InitialActorValues = acMessage.InitialActorValues; + waitingFor3D.SpawnRequest.InventoryContent = acMessage.InitialInventory; + waitingFor3D.SpawnRequest.IsDead = acMessage.IsDead; + waitingFor3D.SpawnRequest.IsWeaponDrawn = acMessage.IsWeaponDrawn; auto& formIdComponent = view.get(*itor); Actor* pActor = Cast(TESForm::GetById(formIdComponent.Id)); @@ -509,8 +510,8 @@ void CharacterService::OnRemoteSpawnDataReceived(const NotifySpawnData& acMessag if (!pActor) return; - pActor->SetActorValues(remoteComponent.SpawnRequest.InitialActorValues); - pActor->SetActorInventory(remoteComponent.SpawnRequest.InventoryContent); + pActor->SetActorValues(waitingFor3D.SpawnRequest.InitialActorValues); + pActor->SetActorInventory(waitingFor3D.SpawnRequest.InventoryContent); m_weaponDrawUpdates[pActor->formID] = {0, acMessage.IsWeaponDrawn}; if (pActor->IsDead() != acMessage.IsDead) @@ -1501,13 +1502,13 @@ void CharacterService::CancelServerAssignment(const entt::entity aEntity, const Actor* CharacterService::CreateCharacterForEntity(entt::entity aEntity) const noexcept { - auto* pRemoteComponent = m_world.try_get(aEntity); + auto* pWaitingFor3D = m_world.try_get(aEntity); auto* pInterpolationComponent = m_world.try_get(aEntity); - if (!pRemoteComponent || !pInterpolationComponent) + if (!pWaitingFor3D || !pInterpolationComponent) return nullptr; - auto& acMessage = pRemoteComponent->SpawnRequest; + auto& acMessage = pWaitingFor3D->SpawnRequest; Actor* pActor = nullptr; @@ -1556,8 +1557,6 @@ Actor* CharacterService::CreateCharacterForEntity(entt::entity aEntity) const no if (pActor->IsDead() != acMessage.IsDead) acMessage.IsDead ? pActor->Kill() : pActor->Respawn(); - m_world.emplace_or_replace(aEntity); - return pActor; } @@ -1643,28 +1642,29 @@ void CharacterService::RunRemoteUpdates() noexcept FaceGenSystem::Update(m_world, pActor, faceGenComponent); } - auto waitingView = m_world.view(); + auto waitingView = m_world.view(); Vector toRemove; for (auto entity : waitingView) { auto& formIdComponent = waitingView.get(entity); - auto& remoteComponent = waitingView.get(entity); + auto& waitingFor3D = waitingView.get(entity); - const auto* pForm = TESForm::GetById(formIdComponent.Id); - auto* pActor = Cast(pForm); + Actor* pActor = Cast(TESForm::GetById(formIdComponent.Id)); if (!pActor || !pActor->GetNiNode()) continue; - pActor->SetActorInventory(remoteComponent.SpawnRequest.InventoryContent); - pActor->SetFactions(remoteComponent.SpawnRequest.FactionsContent); - pActor->LoadAnimationVariables(remoteComponent.SpawnRequest.LatestAction.Variables); - m_weaponDrawUpdates[pActor->formID] = {0, remoteComponent.SpawnRequest.IsWeaponDrawn}; + pActor->SetActorInventory(waitingFor3D.SpawnRequest.InventoryContent); + pActor->SetFactions(waitingFor3D.SpawnRequest.FactionsContent); + pActor->LoadAnimationVariables(waitingFor3D.SpawnRequest.LatestAction.Variables); + m_weaponDrawUpdates[pActor->formID] = {0, waitingFor3D.SpawnRequest.IsWeaponDrawn}; - if (pActor->IsDead() != remoteComponent.SpawnRequest.IsDead) - remoteComponent.SpawnRequest.IsDead ? pActor->Kill() : pActor->Respawn(); + if (pActor->IsDead() != waitingFor3D.SpawnRequest.IsDead) + waitingFor3D.SpawnRequest.IsDead ? pActor->Kill() : pActor->Respawn(); toRemove.push_back(entity); + + spdlog::info("Applied 3D for actor, form id: {:X}", pActor->formID); } for (auto entity : toRemove) @@ -1714,7 +1714,7 @@ void CharacterService::RunFactionsUpdates() const noexcept void CharacterService::RunSpawnUpdates() const noexcept { - auto invisibleView = m_world.view(entt::exclude); + auto invisibleView = m_world.view(entt::exclude); Vector entities(invisibleView.begin(), invisibleView.end()); for (const auto entity : entities) diff --git a/Code/server/Services/CharacterService.cpp b/Code/server/Services/CharacterService.cpp index b062edcad..380bc5589 100644 --- a/Code/server/Services/CharacterService.cpp +++ b/Code/server/Services/CharacterService.cpp @@ -200,8 +200,6 @@ void CharacterService::OnAssignCharacterRequest(const PacketEvent(*itor); auto& characterComponent = view.get(*itor); auto& movementComponent = view.get(*itor); From f0c6f25d219e5d8a6bb8798583ed2d57b3585eeb Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Mon, 30 May 2022 20:10:15 +0200 Subject: [PATCH 30/39] tweak: send current inventory on assigned char --- Code/client/Games/References.cpp | 2 +- Code/client/Games/Skyrim/Actor.cpp | 4 +- Code/client/Games/Skyrim/Actor.h | 2 +- Code/client/Games/Skyrim/TESObjectREFR.h | 2 +- Code/client/Services/Debug/DebugService.cpp | 2 +- .../Services/Generic/CharacterService.cpp | 45 +++++++++++-------- .../Services/Generic/InventoryService.cpp | 6 ++- .../Messages/AssignCharacterResponse.cpp | 6 ++- .../Messages/AssignCharacterResponse.h | 7 ++- Code/server/Services/CharacterService.cpp | 10 +++-- 10 files changed, 51 insertions(+), 35 deletions(-) diff --git a/Code/client/Games/References.cpp b/Code/client/Games/References.cpp index cb69096c1..112db440d 100644 --- a/Code/client/Games/References.cpp +++ b/Code/client/Games/References.cpp @@ -557,7 +557,7 @@ void TESObjectREFR::LockChange() noexcept ThisCall(RealLockChange, this); } -const float TESObjectREFR::GetHeight() noexcept +float TESObjectREFR::GetHeight() noexcept { auto boundMax = GetBoundMax(); return boundMax.z - GetBoundMin().z; diff --git a/Code/client/Games/Skyrim/Actor.cpp b/Code/client/Games/Skyrim/Actor.cpp index c1014df8f..0df0301a6 100644 --- a/Code/client/Games/Skyrim/Actor.cpp +++ b/Code/client/Games/Skyrim/Actor.cpp @@ -318,9 +318,9 @@ int32_t Actor::GetGoldAmount() noexcept return ThisCall(s_getGoldAmount, this); } -void Actor::SetActorInventory(Inventory& aInventory) noexcept +void Actor::SetActorInventory(const Inventory& aInventory) noexcept { - spdlog::debug("Setting inventory for actor {:X}", formID); + spdlog::info("Setting inventory for actor {:X}", formID); UnEquipAll(); diff --git a/Code/client/Games/Skyrim/Actor.h b/Code/client/Games/Skyrim/Actor.h index 949a267a2..bfd4a31d7 100644 --- a/Code/client/Games/Skyrim/Actor.h +++ b/Code/client/Games/Skyrim/Actor.h @@ -209,7 +209,7 @@ struct Actor : TESObjectREFR void ForcePosition(const NiPoint3& acPosition) noexcept; void SetWeaponDrawnEx(bool aDraw) noexcept; void SetPackage(TESPackage* apPackage) noexcept; - void SetActorInventory(Inventory& aInventory) noexcept; + void SetActorInventory(const Inventory& aInventory) noexcept; void SetMagicEquipment(const MagicEquipment& acEquipment) noexcept; void SetEssentialEx(bool aSet) noexcept; void SetNoBleedoutRecovery(bool aSet) noexcept; diff --git a/Code/client/Games/Skyrim/TESObjectREFR.h b/Code/client/Games/Skyrim/TESObjectREFR.h index dee42dd8a..cf0c7684d 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.h +++ b/Code/client/Games/Skyrim/TESObjectREFR.h @@ -188,7 +188,7 @@ struct TESObjectREFR : TESForm Lock* CreateLock() noexcept; void LockChange() noexcept; - const float GetHeight() noexcept; + float GetHeight() noexcept; void EnableImpl() noexcept; Inventory GetInventory() const noexcept; diff --git a/Code/client/Services/Debug/DebugService.cpp b/Code/client/Services/Debug/DebugService.cpp index 1a23cb26f..4c282f94f 100644 --- a/Code/client/Services/Debug/DebugService.cpp +++ b/Code/client/Services/Debug/DebugService.cpp @@ -81,7 +81,7 @@ void __declspec(noinline) DebugService::PlaceActorInWorld() noexcept auto pActor = Actor::Create(pPlayerBaseForm); - Inventory inventory = PlayerCharacter::Get()->GetActorInventory(); + const Inventory inventory = PlayerCharacter::Get()->GetActorInventory(); pActor->SetActorInventory(inventory); pActor->GetExtension()->SetPlayer(true); diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index bda627823..f843097ae 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -333,10 +333,13 @@ void CharacterService::OnAssignCharacter(const AssignCharacterResponse& acMessag m_world.emplace_or_replace(cEntity); pActor->GetExtension()->SetRemote(false); + + // TODO: apply those actor values and inventory stuff here too + // or, alternatively, update the values remotely, broadcast them } else { - spdlog::info("Received remote actor, form id: {:X}", pActor->formID); + spdlog::info("Received remote actor, form id: {:X}, isweapondrawn: {}", pActor->formID, acMessage.IsWeaponDrawn); m_world.emplace_or_replace(cEntity, acMessage.ServerId, formIdComponent->Id); @@ -346,11 +349,13 @@ void CharacterService::OnAssignCharacter(const AssignCharacterResponse& acMessag AnimationSystem::Setup(m_world, cEntity); pActor->SetActorValues(acMessage.AllActorValues); + pActor->SetActorInventory(acMessage.CurrentInventory); if (pActor->IsDead() != acMessage.IsDead) acMessage.IsDead ? pActor->Kill() : pActor->Respawn(); - if (pActor->actorState.IsWeaponDrawn() != acMessage.IsWeaponDrawn) + // TODO(cosideci): might be better if you don't do this check + //if (pActor->actorState.IsWeaponDrawn() != acMessage.IsWeaponDrawn) m_weaponDrawUpdates[pActor->formID] = {0, acMessage.IsWeaponDrawn}; MoveActor(pActor, acMessage.WorldSpaceId, acMessage.CellId, acMessage.Position); @@ -485,7 +490,7 @@ void CharacterService::OnCharacterSpawn(const CharacterSpawnRequest& acMessage) // TODO(cosideci): this is probably not necessary anymore void CharacterService::OnRemoteSpawnDataReceived(const NotifySpawnData& acMessage) noexcept { - auto view = m_world.view(); + auto view = m_world.view(entt::exclude); const auto itor = std::find_if(std::begin(view), std::end(view), [view, id = acMessage.Id](auto entity) { if (auto serverId = Utils::GetServerId(entity)) @@ -496,27 +501,29 @@ void CharacterService::OnRemoteSpawnDataReceived(const NotifySpawnData& acMessag return false; }); - if (itor != std::end(view)) + if (itor == std::end(view)) + return; + + if (auto* pWaitingFor3D = m_world.try_get(*itor)) { - auto& waitingFor3D = view.get(*itor); - waitingFor3D.SpawnRequest.InitialActorValues = acMessage.InitialActorValues; - waitingFor3D.SpawnRequest.InventoryContent = acMessage.InitialInventory; - waitingFor3D.SpawnRequest.IsDead = acMessage.IsDead; - waitingFor3D.SpawnRequest.IsWeaponDrawn = acMessage.IsWeaponDrawn; + pWaitingFor3D->SpawnRequest.InitialActorValues = acMessage.InitialActorValues; + pWaitingFor3D->SpawnRequest.InventoryContent = acMessage.InitialInventory; + pWaitingFor3D->SpawnRequest.IsDead = acMessage.IsDead; + pWaitingFor3D->SpawnRequest.IsWeaponDrawn = acMessage.IsWeaponDrawn; + } - auto& formIdComponent = view.get(*itor); - Actor* pActor = Cast(TESForm::GetById(formIdComponent.Id)); + auto& formIdComponent = view.get(*itor); + Actor* pActor = Cast(TESForm::GetById(formIdComponent.Id)); - if (!pActor) - return; + if (!pActor) + return; - pActor->SetActorValues(waitingFor3D.SpawnRequest.InitialActorValues); - pActor->SetActorInventory(waitingFor3D.SpawnRequest.InventoryContent); - m_weaponDrawUpdates[pActor->formID] = {0, acMessage.IsWeaponDrawn}; + pActor->SetActorValues(acMessage.InitialActorValues); + pActor->SetActorInventory(acMessage.InitialInventory); + m_weaponDrawUpdates[pActor->formID] = {0, acMessage.IsWeaponDrawn}; - if (pActor->IsDead() != acMessage.IsDead) - acMessage.IsDead ? pActor->Kill() : pActor->Respawn(); - } + if (pActor->IsDead() != acMessage.IsDead) + acMessage.IsDead ? pActor->Kill() : pActor->Respawn(); } void CharacterService::OnReferencesMoveRequest(const ServerReferencesMoveRequest& acMessage) const noexcept diff --git a/Code/client/Services/Generic/InventoryService.cpp b/Code/client/Services/Generic/InventoryService.cpp index 939614fae..7862adf8a 100644 --- a/Code/client/Services/Generic/InventoryService.cpp +++ b/Code/client/Services/Generic/InventoryService.cpp @@ -60,7 +60,8 @@ void InventoryService::OnInventoryChangeEvent(const InventoryChangeEvent& acEven std::optional serverIdRes = Utils::GetServerId(*iter); if (!serverIdRes.has_value()) { - spdlog::error("{}: failed to find server id", __FUNCTION__); + spdlog::error(__FUNCTION__ ": failed to find server id, target form id: {:X}, item id: {:X}, count: {}", + acEvent.FormId, acEvent.Item.BaseId.BaseId, acEvent.Item.Count); return; } @@ -93,7 +94,8 @@ void InventoryService::OnEquipmentChangeEvent(const EquipmentChangeEvent& acEven std::optional serverIdRes = Utils::GetServerId(*iter); if (!serverIdRes.has_value()) { - spdlog::error("{}: failed to find server id", __FUNCTION__); + spdlog::error(__FUNCTION__ ": failed to find server id, actor id: {:X}, item id: {:X}, unequip: {}, slot: {:X}", + acEvent.ActorId, acEvent.IsAmmo, acEvent.Unequip, acEvent.EquipSlotId); return; } diff --git a/Code/encoding/Messages/AssignCharacterResponse.cpp b/Code/encoding/Messages/AssignCharacterResponse.cpp index 5353efe20..4bb8bd3b2 100644 --- a/Code/encoding/Messages/AssignCharacterResponse.cpp +++ b/Code/encoding/Messages/AssignCharacterResponse.cpp @@ -2,26 +2,28 @@ void AssignCharacterResponse::SerializeRaw(TiltedPhoques::Buffer::Writer& aWriter) const noexcept { - Serialization::WriteBool(aWriter, Owner); Serialization::WriteVarInt(aWriter, Cookie); Serialization::WriteVarInt(aWriter, ServerId); Position.Serialize(aWriter); CellId.Serialize(aWriter); WorldSpaceId.Serialize(aWriter); AllActorValues.Serialize(aWriter); + CurrentInventory.Serialize(aWriter); + Serialization::WriteBool(aWriter, Owner); Serialization::WriteBool(aWriter, IsDead); Serialization::WriteBool(aWriter, IsWeaponDrawn); } void AssignCharacterResponse::DeserializeRaw(TiltedPhoques::Buffer::Reader& aReader) noexcept { - Owner = Serialization::ReadBool(aReader); Cookie = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; ServerId = Serialization::ReadVarInt(aReader) & 0xFFFFFFFF; Position.Deserialize(aReader); CellId.Deserialize(aReader); WorldSpaceId.Deserialize(aReader); AllActorValues.Deserialize(aReader); + CurrentInventory.Deserialize(aReader); + Owner = Serialization::ReadBool(aReader); IsDead = Serialization::ReadBool(aReader); IsWeaponDrawn = Serialization::ReadBool(aReader); } diff --git a/Code/encoding/Messages/AssignCharacterResponse.h b/Code/encoding/Messages/AssignCharacterResponse.h index 5f0d0bb46..3448a2ebe 100644 --- a/Code/encoding/Messages/AssignCharacterResponse.h +++ b/Code/encoding/Messages/AssignCharacterResponse.h @@ -5,6 +5,7 @@ #include #include #include +#include struct AssignCharacterResponse final : ServerMessage { @@ -20,24 +21,26 @@ struct AssignCharacterResponse final : ServerMessage bool operator==(const AssignCharacterResponse& achRhs) const noexcept { return GetOpcode() == achRhs.GetOpcode() && - Owner == achRhs.Owner && Cookie == achRhs.Cookie && ServerId == achRhs.ServerId && Position == achRhs.Position && CellId == achRhs.CellId && WorldSpaceId == achRhs.WorldSpaceId && AllActorValues == achRhs.AllActorValues && + CurrentInventory == achRhs.CurrentInventory && + Owner == achRhs.Owner && IsDead == achRhs.IsDead && IsWeaponDrawn == achRhs.IsWeaponDrawn; } - bool Owner{ false }; uint32_t Cookie{}; uint32_t ServerId{}; Vector3_NetQuantize Position{}; GameId CellId{}; GameId WorldSpaceId{}; ActorValues AllActorValues{}; + Inventory CurrentInventory{}; + bool Owner{ false }; bool IsDead{}; bool IsWeaponDrawn{}; }; diff --git a/Code/server/Services/CharacterService.cpp b/Code/server/Services/CharacterService.cpp index 380bc5589..163158ba0 100644 --- a/Code/server/Services/CharacterService.cpp +++ b/Code/server/Services/CharacterService.cpp @@ -186,7 +186,7 @@ void CharacterService::OnAssignCharacterRequest(const PacketEvent(); + auto view = m_world.view(); const auto itor = std::find_if(std::begin(view), std::end(view), [view, refId](auto entity) { @@ -201,6 +201,7 @@ void CharacterService::OnAssignCharacterRequest(const PacketEvent(*itor); + auto& inventoryComponent = view.get(*itor); auto& characterComponent = view.get(*itor); auto& movementComponent = view.get(*itor); auto& cellIdComponent = view.get(*itor); @@ -224,11 +225,12 @@ void CharacterService::OnAssignCharacterRequest(const PacketEventSend(response); + return; } } @@ -682,11 +685,10 @@ void CharacterService::CreateCharacter(const PacketEvent dispatcher.trigger(PlayerEnterWorldEvent(pPlayer)); } - AssignCharacterResponse response; + AssignCharacterResponse response{}; response.Cookie = message.Cookie; response.ServerId = World::ToInteger(cEntity); response.Owner = true; - response.AllActorValues = message.AllActorValues; pServer->Send(acMessage.pPlayer->GetConnectionId(), response); From 5b084742519b6972e850ed526adfb4dd325442f3 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Mon, 30 May 2022 22:54:03 +0200 Subject: [PATCH 31/39] tweak: weapon draw impovements --- Code/client/Games/References.cpp | 2 +- Code/client/Games/Skyrim/TESObjectREFR.h | 2 +- Code/client/Services/CharacterService.h | 14 +++++++++- .../Services/Debug/Views/EntitiesView.cpp | 24 +++++++++++++++-- .../Services/Generic/CharacterService.cpp | 27 +++++++++++-------- 5 files changed, 53 insertions(+), 16 deletions(-) diff --git a/Code/client/Games/References.cpp b/Code/client/Games/References.cpp index 112db440d..cb69096c1 100644 --- a/Code/client/Games/References.cpp +++ b/Code/client/Games/References.cpp @@ -557,7 +557,7 @@ void TESObjectREFR::LockChange() noexcept ThisCall(RealLockChange, this); } -float TESObjectREFR::GetHeight() noexcept +const float TESObjectREFR::GetHeight() noexcept { auto boundMax = GetBoundMax(); return boundMax.z - GetBoundMin().z; diff --git a/Code/client/Games/Skyrim/TESObjectREFR.h b/Code/client/Games/Skyrim/TESObjectREFR.h index cf0c7684d..dee42dd8a 100644 --- a/Code/client/Games/Skyrim/TESObjectREFR.h +++ b/Code/client/Games/Skyrim/TESObjectREFR.h @@ -188,7 +188,7 @@ struct TESObjectREFR : TESForm Lock* CreateLock() noexcept; void LockChange() noexcept; - float GetHeight() noexcept; + const float GetHeight() noexcept; void EnableImpl() noexcept; Inventory GetInventory() const noexcept; diff --git a/Code/client/Services/CharacterService.h b/Code/client/Services/CharacterService.h index b2727f29b..e773f14b6 100644 --- a/Code/client/Services/CharacterService.h +++ b/Code/client/Services/CharacterService.h @@ -111,7 +111,19 @@ struct CharacterService float m_cachedExperience = 0.f; - Map> m_weaponDrawUpdates{}; + struct WeaponDrawData + { + WeaponDrawData() = default; + WeaponDrawData(bool aDrawWeapon) + : m_drawWeapon(aDrawWeapon) + {} + + double m_timer = 0.0; + bool m_drawWeapon = false; + bool m_isFirstPass = true; + }; + + Map m_weaponDrawUpdates{}; entt::scoped_connection m_referenceAddedConnection; entt::scoped_connection m_referenceRemovedConnection; diff --git a/Code/client/Services/Debug/Views/EntitiesView.cpp b/Code/client/Services/Debug/Views/EntitiesView.cpp index a9d7c7fe0..257f7a15c 100644 --- a/Code/client/Services/Debug/Views/EntitiesView.cpp +++ b/Code/client/Services/Debug/Views/EntitiesView.cpp @@ -6,8 +6,11 @@ #include #include +#include + #include #include +#include void DebugService::DrawEntitiesView() { @@ -159,9 +162,13 @@ void DebugService::DisplayFormComponent(FormIdComponent& aFormComponent) const n ImGui::InputFloat3("Position", pActor->position.AsArray(), "%.3f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3("Rotation", pActor->rotation.AsArray(), "%.3f", ImGuiInputTextFlags_ReadOnly); int isDead = int(pActor->IsDead()); - ImGui::InputInt("Is dead?", &isDead, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); + ImGui::InputScalar("Is dead?", ImGuiDataType_U8, &isDead, 0, 0, "%" PRIx8, ImGuiInputTextFlags_ReadOnly); int isRemote = int(pActor->GetExtension()->IsRemote()); - ImGui::InputInt("Is remote?", &isRemote, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); + ImGui::InputScalar("Is remote?", ImGuiDataType_U8, &isRemote, 0, 0, "%" PRIx8, ImGuiInputTextFlags_ReadOnly); + int isWeaponDrawn = int(pActor->actorState.IsWeaponDrawn()); + ImGui::InputScalar("Is weapon drawn?", ImGuiDataType_U8, &isWeaponDrawn, 0, 0, "%" PRIx8, ImGuiInputTextFlags_ReadOnly); + int isWeaponFullyDrawn = int(pActor->actorState.IsWeaponFullyDrawn()); + ImGui::InputScalar("Is weapon fully drawn?", ImGuiDataType_U8, &isWeaponFullyDrawn, 0, 0, "%" PRIx8, ImGuiInputTextFlags_ReadOnly); #if TP_SKYRIM64 float attributes[3] {pActor->GetActorValue(24), pActor->GetActorValue(25), pActor->GetActorValue(26)}; ImGui::InputFloat3("Attributes (H/M/S)", attributes, "%.3f", ImGuiInputTextFlags_ReadOnly); @@ -195,6 +202,7 @@ void DebugService::DisplayRemoteComponent(RemoteComponent& aRemoteComponent, con return; ImGui::InputInt("Server Id", (int*)&aRemoteComponent.Id, 0, 0, ImGuiInputTextFlags_ReadOnly | ImGuiInputTextFlags_CharsHexadecimal); + if (ImGui::Button("Take ownership")) { m_world.GetRunner().Queue([acEntity, acFormId]() { @@ -202,4 +210,16 @@ void DebugService::DisplayRemoteComponent(RemoteComponent& aRemoteComponent, con World::Get().GetCharacterService().TakeOwnership(acFormId, pRemoteCompoment->Id, acEntity); }); } + + if (ImGui::Button("Get spawn data")) + { + m_world.GetRunner().Queue([this, acEntity, acFormId]() { + if (auto* pRemoteCompoment = World::Get().try_get(acEntity)) + { + RequestSpawnData request{}; + request.Id = pRemoteCompoment->Id; + m_transport.Send(request); + } + }); + } } diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index f843097ae..a8a6d7352 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -310,7 +310,7 @@ void CharacterService::OnAssignCharacter(const AssignCharacterResponse& acMessag acMessage.IsDead ? pActor->Kill() : pActor->Respawn(); if (pActor->actorState.IsWeaponDrawn() != acMessage.IsWeaponDrawn) - m_weaponDrawUpdates[formIdComponent->Id] = {0, acMessage.IsWeaponDrawn}; + m_weaponDrawUpdates[formIdComponent->Id] = {acMessage.IsWeaponDrawn}; spdlog::critical("Applied updates on assignment response, form id: {:X}", formIdComponent->Id); @@ -354,9 +354,7 @@ void CharacterService::OnAssignCharacter(const AssignCharacterResponse& acMessag if (pActor->IsDead() != acMessage.IsDead) acMessage.IsDead ? pActor->Kill() : pActor->Respawn(); - // TODO(cosideci): might be better if you don't do this check - //if (pActor->actorState.IsWeaponDrawn() != acMessage.IsWeaponDrawn) - m_weaponDrawUpdates[pActor->formID] = {0, acMessage.IsWeaponDrawn}; + m_weaponDrawUpdates[pActor->formID] = {acMessage.IsWeaponDrawn}; MoveActor(pActor, acMessage.WorldSpaceId, acMessage.CellId, acMessage.Position); } @@ -487,7 +485,6 @@ void CharacterService::OnCharacterSpawn(const CharacterSpawnRequest& acMessage) remoteAnimationComponent.TimePoints.push_back(acMessage.LatestAction); } -// TODO(cosideci): this is probably not necessary anymore void CharacterService::OnRemoteSpawnDataReceived(const NotifySpawnData& acMessage) noexcept { auto view = m_world.view(entt::exclude); @@ -520,10 +517,12 @@ void CharacterService::OnRemoteSpawnDataReceived(const NotifySpawnData& acMessag pActor->SetActorValues(acMessage.InitialActorValues); pActor->SetActorInventory(acMessage.InitialInventory); - m_weaponDrawUpdates[pActor->formID] = {0, acMessage.IsWeaponDrawn}; + m_weaponDrawUpdates[pActor->formID] = {acMessage.IsWeaponDrawn}; if (pActor->IsDead() != acMessage.IsDead) acMessage.IsDead ? pActor->Kill() : pActor->Respawn(); + + spdlog::info("Applied remote spawn data, actor form id: {:X}", pActor->formID); } void CharacterService::OnReferencesMoveRequest(const ServerReferencesMoveRequest& acMessage) const noexcept @@ -1664,7 +1663,7 @@ void CharacterService::RunRemoteUpdates() noexcept pActor->SetActorInventory(waitingFor3D.SpawnRequest.InventoryContent); pActor->SetFactions(waitingFor3D.SpawnRequest.FactionsContent); pActor->LoadAnimationVariables(waitingFor3D.SpawnRequest.LatestAction.Variables); - m_weaponDrawUpdates[pActor->formID] = {0, waitingFor3D.SpawnRequest.IsWeaponDrawn}; + m_weaponDrawUpdates[pActor->formID] = {waitingFor3D.SpawnRequest.IsWeaponDrawn}; if (pActor->IsDead() != waitingFor3D.SpawnRequest.IsDead) waitingFor3D.SpawnRequest.IsDead ? pActor->Kill() : pActor->Respawn(); @@ -1793,17 +1792,23 @@ void CharacterService::ApplyCachedWeaponDraws(const UpdateEvent& acUpdateEvent) { auto& data = m_weaponDrawUpdates[cId]; - data.first += acUpdateEvent.Delta; - if (data.first <= 0.5) + data.m_timer += acUpdateEvent.Delta; + + // We do 2 passes because Skyrim's weapon drawing is the most finnicky thing in existance + double maxTime = data.m_isFirstPass ? 0.5 : 2.0; + if (data.m_timer <= maxTime) continue; Actor* pActor = Cast(TESForm::GetById(cId)); if (!pActor) continue; - pActor->SetWeaponDrawnEx(data.second); + pActor->SetWeaponDrawnEx(data.m_drawWeapon); + + if (!data.m_isFirstPass) + toRemove.push_back(cId); - toRemove.push_back(cId); + data.m_isFirstPass = false; } for (uint32_t id : toRemove) From 97bd3dc719854e0f4fc703a5d21523162149849b Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Mon, 30 May 2022 23:21:26 +0200 Subject: [PATCH 32/39] tweak: request spawn data after relinquish control --- Code/client/Services/Debug/Views/EntitiesView.cpp | 4 ---- Code/client/Services/Generic/CharacterService.cpp | 10 ++++++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/Code/client/Services/Debug/Views/EntitiesView.cpp b/Code/client/Services/Debug/Views/EntitiesView.cpp index 257f7a15c..120cd60a8 100644 --- a/Code/client/Services/Debug/Views/EntitiesView.cpp +++ b/Code/client/Services/Debug/Views/EntitiesView.cpp @@ -165,10 +165,6 @@ void DebugService::DisplayFormComponent(FormIdComponent& aFormComponent) const n ImGui::InputScalar("Is dead?", ImGuiDataType_U8, &isDead, 0, 0, "%" PRIx8, ImGuiInputTextFlags_ReadOnly); int isRemote = int(pActor->GetExtension()->IsRemote()); ImGui::InputScalar("Is remote?", ImGuiDataType_U8, &isRemote, 0, 0, "%" PRIx8, ImGuiInputTextFlags_ReadOnly); - int isWeaponDrawn = int(pActor->actorState.IsWeaponDrawn()); - ImGui::InputScalar("Is weapon drawn?", ImGuiDataType_U8, &isWeaponDrawn, 0, 0, "%" PRIx8, ImGuiInputTextFlags_ReadOnly); - int isWeaponFullyDrawn = int(pActor->actorState.IsWeaponFullyDrawn()); - ImGui::InputScalar("Is weapon fully drawn?", ImGuiDataType_U8, &isWeaponFullyDrawn, 0, 0, "%" PRIx8, ImGuiInputTextFlags_ReadOnly); #if TP_SKYRIM64 float attributes[3] {pActor->GetActorValue(24), pActor->GetActorValue(25), pActor->GetActorValue(26)}; ImGui::InputFloat3("Attributes (H/M/S)", attributes, "%.3f", ImGuiInputTextFlags_ReadOnly); diff --git a/Code/client/Services/Generic/CharacterService.cpp b/Code/client/Services/Generic/CharacterService.cpp index a8a6d7352..69da09875 100644 --- a/Code/client/Services/Generic/CharacterService.cpp +++ b/Code/client/Services/Generic/CharacterService.cpp @@ -1161,7 +1161,11 @@ void CharacterService::OnNotifyRelinquishControl(const NotifyRelinquishControl& InterpolationSystem::Setup(m_world, entity); AnimationSystem::Setup(m_world, entity); - spdlog::warn("Relinquished control of actor {:X} with server id {:X}", pActor->formID, acMessage.ServerId); + spdlog::info("Relinquished control of actor {:X} with server id {:X}", pActor->formID, acMessage.ServerId); + + RequestSpawnData request{}; + request.Id = serverId; + m_transport.Send(request); return; } @@ -1714,7 +1718,7 @@ void CharacterService::RunFactionsUpdates() const noexcept message.Changes[localComponent.Id] = factions; } - if(!message.Changes.empty()) + if (!message.Changes.empty()) m_transport.Send(message); } @@ -1744,9 +1748,7 @@ void CharacterService::RunSpawnUpdates() const noexcept { pActor = CreateCharacterForEntity(entity); if (!pActor) - { continue; - } remoteComponent.CachedRefId = pActor->formID; } From 5651b655d662323fed2a80a5a642564a3aadd060 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Mon, 30 May 2022 23:57:11 +0200 Subject: [PATCH 33/39] fix: conjure familiar ghost effect apply --- Code/client/Services/Generic/MagicService.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Code/client/Services/Generic/MagicService.cpp b/Code/client/Services/Generic/MagicService.cpp index 17326f98b..852d13ad6 100644 --- a/Code/client/Services/Generic/MagicService.cpp +++ b/Code/client/Services/Generic/MagicService.cpp @@ -344,7 +344,7 @@ void MagicService::OnAddTargetEvent(const AddTargetEvent& acEvent) noexcept if (it == std::end(view)) { - spdlog::warn("Target not found for magic add target, form id: {:X}", acEvent.TargetID); + spdlog::warn("Form id not found for magic add target, form id: {:X}", acEvent.TargetID); m_queuedEffects[acEvent.TargetID] = request; return; } @@ -352,7 +352,8 @@ void MagicService::OnAddTargetEvent(const AddTargetEvent& acEvent) noexcept std::optional serverIdRes = Utils::GetServerId(*it); if (!serverIdRes.has_value()) { - spdlog::error("{}: failed to find server id", __FUNCTION__); + spdlog::warn("Server id not found for magic add target, form id: {:X}", acEvent.TargetID); + m_queuedEffects[acEvent.TargetID] = request; return; } @@ -463,10 +464,7 @@ void MagicService::ApplyQueuedEffects() noexcept std::optional serverIdRes = Utils::GetServerId(entity); if (!serverIdRes.has_value()) - { - spdlog::error("{}: failed to find server id", __FUNCTION__); continue; - } request.TargetId = serverIdRes.value(); From a5c3ad72e180cbbd592ba0931f4b4bb8c93b6aa5 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Tue, 31 May 2022 00:15:54 +0200 Subject: [PATCH 34/39] fix: transfer temp actors --- Code/server/Services/CharacterService.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Code/server/Services/CharacterService.cpp b/Code/server/Services/CharacterService.cpp index 163158ba0..0f5e27502 100644 --- a/Code/server/Services/CharacterService.cpp +++ b/Code/server/Services/CharacterService.cpp @@ -251,18 +251,17 @@ void CharacterService::OnOwnershipTransferRequest(const PacketEvent view(m_world, acMessage.GetSender()); + const entt::entity cEntity = static_cast(message.ServerId); - const auto it = view.find(static_cast(message.ServerId)); - if (it == view.end()) + if (!m_world.valid(cEntity)) { - spdlog::warn("Client {:X} requested travel of an entity that doesn't exist, server id: {:X}", acMessage.pPlayer->GetConnectionId(), message.ServerId); + spdlog::warn("Client {:X} requested ownership transfer of an entity that doesn't exist, server id: {:X}", acMessage.pPlayer->GetConnectionId(), message.ServerId); return; } if (message.WorldSpaceId || message.CellId) { - auto& formIdComponent = view.get(*it); + auto& formIdComponent = m_world.get(cEntity); NotifyActorTeleport notify{}; notify.FormId = formIdComponent.Id; @@ -270,22 +269,22 @@ void CharacterService::OnOwnershipTransferRequest(const PacketEvent(*it); + auto& cellIdComponent = m_world.get(cEntity); cellIdComponent.WorldSpaceId = message.WorldSpaceId; cellIdComponent.Cell = message.CellId; cellIdComponent.CenterCoords = GridCellCoords::CalculateGridCellCoords(message.Position); - auto& movementComponent = view.get(*it); + auto& movementComponent = m_world.get(cEntity); movementComponent.Position = message.Position; movementComponent.Sent = true; GameServer::Get()->SendToPlayers(notify, acMessage.pPlayer); } - auto& characterOwnerComponent = view.get(*it); + auto& characterOwnerComponent = m_world.get(cEntity); characterOwnerComponent.InvalidOwners.push_back(acMessage.pPlayer); - m_world.GetDispatcher().trigger(OwnershipTransferEvent(*it)); + m_world.GetDispatcher().trigger(OwnershipTransferEvent(cEntity)); } void CharacterService::OnOwnershipTransferEvent(const OwnershipTransferEvent& acEvent) const noexcept From 397e9717a980332d6418437d19636b66870439d2 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Tue, 31 May 2022 02:02:29 +0200 Subject: [PATCH 35/39] feat: improved quest debugger --- Code/client/Events/MoveActorEvent.h | 13 +++++ Code/client/Games/Skyrim/Forms/TESQuest.cpp | 11 ++++ Code/client/Games/Skyrim/Forms/TESQuest.h | 10 +++- Code/client/Services/Debug/DebugService.cpp | 36 +++++++++++- .../Services/Debug/Views/EntitiesView.cpp | 3 +- .../Services/Debug/Views/QuestDebugView.cpp | 56 +++++++++++++++---- Code/client/Services/DebugService.h | 2 + 7 files changed, 118 insertions(+), 13 deletions(-) create mode 100644 Code/client/Events/MoveActorEvent.h diff --git a/Code/client/Events/MoveActorEvent.h b/Code/client/Events/MoveActorEvent.h new file mode 100644 index 000000000..7d8fc70ec --- /dev/null +++ b/Code/client/Events/MoveActorEvent.h @@ -0,0 +1,13 @@ +#pragma once + +struct MoveActorEvent +{ + MoveActorEvent(uint32_t aFormId, uint32_t aCellId, NiPoint3 aPosition) + : FormId(aFormId), CellId(aCellId), Position(aPosition) + { + } + + uint32_t FormId; + uint32_t CellId; + NiPoint3 Position; +}; diff --git a/Code/client/Games/Skyrim/Forms/TESQuest.cpp b/Code/client/Games/Skyrim/Forms/TESQuest.cpp index de27d1814..daa17239d 100644 --- a/Code/client/Games/Skyrim/Forms/TESQuest.cpp +++ b/Code/client/Games/Skyrim/Forms/TESQuest.cpp @@ -4,6 +4,17 @@ #include +TESObjectREFR* TESQuest::GetAliasedRef(uint32_t aAliasID) noexcept +{ + TP_THIS_FUNCTION(TGetAliasedRef, BSPointerHandle*, TESQuest, BSPointerHandle*, uint32_t); + POINTER_SKYRIMSE(TGetAliasedRef, getAliasedRef, 25066); + + BSPointerHandle result{}; + ThisCall(getAliasedRef, this, &result, aAliasID); + + return TESObjectREFR::GetByHandle(result.handle.iBits); +} + TESQuest::State TESQuest::getState() { if (flags >= 0) diff --git a/Code/client/Games/Skyrim/Forms/TESQuest.h b/Code/client/Games/Skyrim/Forms/TESQuest.h index 02df81d30..27e8fcbdb 100644 --- a/Code/client/Games/Skyrim/Forms/TESQuest.h +++ b/Code/client/Games/Skyrim/Forms/TESQuest.h @@ -5,6 +5,12 @@ #include #include +struct BGSScene : TESForm +{ + GameArray phases; + GameArray actorIds; +}; + struct TESQuest : BGSStoryManagerTreeForm { enum class State : uint8_t @@ -91,7 +97,7 @@ struct TESQuest : BGSStoryManagerTreeForm */ GameList objectives; // 0x00F8 char pad108[0x100]; // 0x0108 - GameArray scenes; // 0x0208 + GameArray scenes; // 0x0208 char pad210[8]; // 0x0210 uint16_t currentStage; // 0x0228 bool alreadyRun; // 0x022A @@ -101,6 +107,8 @@ struct TESQuest : BGSStoryManagerTreeForm uint64_t unkFlags; char pad250[24]; + TESObjectREFR* GetAliasedRef(uint32_t aiAliasID) noexcept; + bool IsStageDone(uint16_t stageIndex); void SetCompleted(bool force); diff --git a/Code/client/Services/Debug/DebugService.cpp b/Code/client/Services/Debug/DebugService.cpp index 4c282f94f..3b3068157 100644 --- a/Code/client/Services/Debug/DebugService.cpp +++ b/Code/client/Services/Debug/DebugService.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include @@ -97,6 +98,7 @@ DebugService::DebugService(entt::dispatcher& aDispatcher, World& aWorld, Transpo m_drawImGuiConnection = aImguiService.OnDraw.connect<&DebugService::OnDraw>(this); m_dialogueConnection = m_dispatcher.sink().connect<&DebugService::OnDialogue>(this); m_dispatcher.sink().connect<&DebugService::OnSubtitle>(this); + m_dispatcher.sink().connect<&DebugService::OnMoveActor>(this); } void DebugService::OnDialogue(const DialogueEvent& acEvent) noexcept @@ -115,11 +117,40 @@ void DebugService::OnSubtitle(const SubtitleEvent& acEvent) noexcept SubtitleText = acEvent.Text; } +// TODO: yeah, i'm aware of how dumb this looks, but things crash if +// you do it directly by adding an event to the queue, no symbols for tiltedcore when debugging, +// so this'll do for now +struct MoveData +{ + Actor* pActor = nullptr; + TESObjectCELL* pCell = nullptr; + NiPoint3 position; +} moveData; + +void DebugService::OnMoveActor(const MoveActorEvent& acEvent) noexcept +{ + Actor* pActor = Cast(TESForm::GetById(acEvent.FormId)); + TESObjectCELL* pCell = Cast(TESForm::GetById(acEvent.CellId)); + + if (!pActor || !pCell) + return; + + moveData.pActor = pActor; + moveData.pCell = pCell; + moveData.position = acEvent.Position; +} + void DebugService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { if (!BSGraphics::GetMainWindow()->IsForeground()) return; + if (moveData.pActor) + { + moveData.pActor->MoveTo(moveData.pCell, moveData.position); + moveData.pActor = nullptr; + } + static std::atomic s_f8Pressed = false; static std::atomic s_f7Pressed = false; static std::atomic s_f6Pressed = false; @@ -167,13 +198,16 @@ void DebugService::OnUpdate(const UpdateEvent& acUpdateEvent) noexcept { s_f8Pressed = true; + Actor* pActor = Cast(TESForm::GetById(0x1a677)); + pActor->MoveTo(PlayerCharacter::Get()->parentCell, PlayerCharacter::Get()->position); + + #if 0 static bool s_enabled = true; FadeOutGame(s_enabled, true, 1.f, true, 0.f); s_enabled = !s_enabled; - #if 0 static bool s_enabled = true; static bool s_firstPerson = false; diff --git a/Code/client/Services/Debug/Views/EntitiesView.cpp b/Code/client/Services/Debug/Views/EntitiesView.cpp index 120cd60a8..f2ef3f2ce 100644 --- a/Code/client/Services/Debug/Views/EntitiesView.cpp +++ b/Code/client/Services/Debug/Views/EntitiesView.cpp @@ -181,7 +181,8 @@ void DebugService::DisplayLocalComponent(LocalComponent& aLocalComponent, const m_world.GetRunner().Queue([acFormId]() { Actor* pActor = Cast(TESForm::GetById(acFormId)); PlayerCharacter* pPlayer = PlayerCharacter::Get(); - pActor->MoveTo(pPlayer->parentCell, pPlayer->position); + if (pActor) + pActor->MoveTo(pPlayer->parentCell, pPlayer->position); }); } diff --git a/Code/client/Services/Debug/Views/QuestDebugView.cpp b/Code/client/Services/Debug/Views/QuestDebugView.cpp index 777e9507f..4bf60302f 100644 --- a/Code/client/Services/Debug/Views/QuestDebugView.cpp +++ b/Code/client/Services/Debug/Views/QuestDebugView.cpp @@ -4,6 +4,9 @@ #include #include +#include + +#include #include @@ -30,25 +33,58 @@ void DebugService::DrawQuestDebugView() foundQuests.insert(pQuest->formID); - if (pQuest->IsActive()) - { - ImGui::TextColored({255.f, 0.f, 255.f, 255.f}, "%s|%x|%s", pQuest->idName.AsAscii(), - pQuest->flags, pQuest->fullName.value.AsAscii()); + if (!pQuest->IsActive()) + continue; - for (auto* stage : pQuest->stages) + char questName[256]; + sprintf_s(questName, std::size(questName), "%s (%s)", pQuest->fullName.value.AsAscii(), pQuest->idName.AsAscii()); + if (!ImGui::CollapsingHeader(questName)) + continue; + + if (ImGui::CollapsingHeader("Stages")) + { + for (auto* pStage : pQuest->stages) { - ImGui::TextColored({0.f, 255.f, 255.f, 255.f}, "Stage: %d, is done? %s", stage->stageIndex, stage->IsDone() ? "true" : "false"); + ImGui::TextColored({0.f, 255.f, 255.f, 255.f}, "Stage: %d, is done? %s", pStage->stageIndex, pStage->IsDone() ? "true" : "false"); char setStage[64]; - sprintf_s(setStage, std::size(setStage), "Set stage (%d)", stage->stageIndex); + sprintf_s(setStage, std::size(setStage), "Set stage (%d)", pStage->stageIndex); if (ImGui::Button(setStage)) - pQuest->ScriptSetStage(stage->stageIndex); + pQuest->ScriptSetStage(pStage->stageIndex); } } - else + + if (ImGui::CollapsingHeader("Actors")) { - ImGui::Text("%s|%x|%s", pQuest->idName.AsAscii(), pQuest->flags, pQuest->fullName.value.AsAscii()); + Set foundActors{}; + + for (BGSScene* pScene : pQuest->scenes) + { + for (uint32_t actorId : pScene->actorIds) + { + Actor* pActor = Cast(pQuest->GetAliasedRef(actorId)); + if (!pActor) + continue; + + if (foundActors.contains(pActor->formID)) + continue; + + foundActors.insert(pActor->formID); + + char name[256]; + sprintf_s(name, std::size(name), "%s (%x)", pActor->baseForm->GetName(), pActor->formID); + ImGui::BulletText(name); + + ImGui::PushID(pActor->formID); + if (ImGui::Button("Teleport to me")) + { + auto* pPlayer = PlayerCharacter::Get(); + m_world.GetRunner().Trigger(MoveActorEvent(pActor->formID, pPlayer->parentCell->formID, pPlayer->position)); + } + ImGui::PopID(); + } + } } } diff --git a/Code/client/Services/DebugService.h b/Code/client/Services/DebugService.h index 54f27d7c2..3517f248d 100644 --- a/Code/client/Services/DebugService.h +++ b/Code/client/Services/DebugService.h @@ -8,6 +8,7 @@ struct ImguiService; struct UpdateEvent; struct DialogueEvent; struct SubtitleEvent; +struct MoveActorEvent; struct TransportService; struct BSAnimationGraphManager; @@ -25,6 +26,7 @@ struct DebugService void OnUpdate(const UpdateEvent&) noexcept; void OnDialogue(const DialogueEvent&) noexcept; void OnSubtitle(const SubtitleEvent&) noexcept; + void OnMoveActor(const MoveActorEvent&) noexcept; void SetDebugId(const uint32_t aFormId) noexcept; From 046aadd8ff7b2fca7e2992d473be5c9e6372eacc Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Tue, 31 May 2022 02:05:07 +0200 Subject: [PATCH 36/39] fix: entity actor mover --- Code/client/Services/Debug/Views/EntitiesView.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Code/client/Services/Debug/Views/EntitiesView.cpp b/Code/client/Services/Debug/Views/EntitiesView.cpp index f2ef3f2ce..ef8c8f54b 100644 --- a/Code/client/Services/Debug/Views/EntitiesView.cpp +++ b/Code/client/Services/Debug/Views/EntitiesView.cpp @@ -5,9 +5,12 @@ #include #include #include +#include #include +#include + #include #include #include @@ -178,11 +181,9 @@ void DebugService::DisplayLocalComponent(LocalComponent& aLocalComponent, const if (ImGui::Button("Teleport to me")) { - m_world.GetRunner().Queue([acFormId]() { - Actor* pActor = Cast(TESForm::GetById(acFormId)); - PlayerCharacter* pPlayer = PlayerCharacter::Get(); - if (pActor) - pActor->MoveTo(pPlayer->parentCell, pPlayer->position); + m_world.GetRunner().Queue([this, acFormId]() { + auto* pPlayer = PlayerCharacter::Get(); + m_world.GetRunner().Trigger(MoveActorEvent(acFormId, pPlayer->parentCell->formID, pPlayer->position)); }); } From 3f53434457eae479415597d0f021a04f7ef91ec2 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Tue, 31 May 2022 12:44:53 +0200 Subject: [PATCH 37/39] fix: double queue of event --- Code/client/Services/Debug/Views/EntitiesView.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Code/client/Services/Debug/Views/EntitiesView.cpp b/Code/client/Services/Debug/Views/EntitiesView.cpp index ef8c8f54b..474745567 100644 --- a/Code/client/Services/Debug/Views/EntitiesView.cpp +++ b/Code/client/Services/Debug/Views/EntitiesView.cpp @@ -181,10 +181,8 @@ void DebugService::DisplayLocalComponent(LocalComponent& aLocalComponent, const if (ImGui::Button("Teleport to me")) { - m_world.GetRunner().Queue([this, acFormId]() { - auto* pPlayer = PlayerCharacter::Get(); - m_world.GetRunner().Trigger(MoveActorEvent(acFormId, pPlayer->parentCell->formID, pPlayer->position)); - }); + auto* pPlayer = PlayerCharacter::Get(); + m_world.GetRunner().Trigger(MoveActorEvent(acFormId, pPlayer->parentCell->formID, pPlayer->position)); } auto& action = aLocalComponent.CurrentAction; From c6341b5a2dafc65032d3bfc2b27c29d0c6c712f9 Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Tue, 31 May 2022 13:13:43 +0200 Subject: [PATCH 38/39] tweak: TiltedCore debug symbols --- Code/client/Services/Debug/DebugService.cpp | 2 ++ Code/client/xmake.lua | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Code/client/Services/Debug/DebugService.cpp b/Code/client/Services/Debug/DebugService.cpp index 3b3068157..e5a4a1c44 100644 --- a/Code/client/Services/Debug/DebugService.cpp +++ b/Code/client/Services/Debug/DebugService.cpp @@ -135,6 +135,8 @@ void DebugService::OnMoveActor(const MoveActorEvent& acEvent) noexcept if (!pActor || !pCell) return; + //pActor->MoveTo(pCell, acEvent.Position); + moveData.pActor = pActor; moveData.pCell = pCell; moveData.position = acEvent.Position; diff --git a/Code/client/xmake.lua b/Code/client/xmake.lua index c214d0456..57a7f135c 100644 --- a/Code/client/xmake.lua +++ b/Code/client/xmake.lua @@ -79,5 +79,7 @@ target(name) "kernel32") end +add_requires("tiltedcore", {debug = true}) + build_client("SkyrimTogetherClient", "TP_SKYRIM=1") --build_client("FalloutTogetherClient", "TP_FALLOUT=1") From 6d1de7c354d12aa65066b928a22b89e2c1c94a7a Mon Sep 17 00:00:00 2001 From: Robbe Bryssinck Date: Tue, 31 May 2022 13:21:33 +0200 Subject: [PATCH 39/39] tweak: limit quest debugger to quest leader --- .../Services/Debug/Views/QuestDebugView.cpp | 111 ++++++++++-------- 1 file changed, 60 insertions(+), 51 deletions(-) diff --git a/Code/client/Services/Debug/Views/QuestDebugView.cpp b/Code/client/Services/Debug/Views/QuestDebugView.cpp index 4bf60302f..24142c5ce 100644 --- a/Code/client/Services/Debug/Views/QuestDebugView.cpp +++ b/Code/client/Services/Debug/Views/QuestDebugView.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -15,74 +16,82 @@ void DebugService::DrawQuestDebugView() auto* pPlayer = PlayerCharacter::Get(); if (!pPlayer) return; - ImGui::Begin("Quest log"); + ImGui::Begin("Quests"); - Set foundQuests{}; - - for (auto &objective : pPlayer->objectives) + // TODO(cosideci): yes I'll refactor this + if (!m_world.GetPartyService().IsLeader()) { - TESQuest* pQuest = objective.instance->quest; - if (!pQuest) - continue; - - if (QuestService::IsNonSyncableQuest(pQuest)) - continue; - - if (foundQuests.contains(pQuest->formID)) - continue; - - foundQuests.insert(pQuest->formID); + ImGui::Text("You need to be the quest leader to access the quest debugger."); + } + else + { + Set foundQuests{}; - if (!pQuest->IsActive()) - continue; + for (auto &objective : pPlayer->objectives) + { + TESQuest* pQuest = objective.instance->quest; + if (!pQuest) + continue; - char questName[256]; - sprintf_s(questName, std::size(questName), "%s (%s)", pQuest->fullName.value.AsAscii(), pQuest->idName.AsAscii()); - if (!ImGui::CollapsingHeader(questName)) - continue; + if (QuestService::IsNonSyncableQuest(pQuest)) + continue; - if (ImGui::CollapsingHeader("Stages")) - { - for (auto* pStage : pQuest->stages) - { - ImGui::TextColored({0.f, 255.f, 255.f, 255.f}, "Stage: %d, is done? %s", pStage->stageIndex, pStage->IsDone() ? "true" : "false"); + if (foundQuests.contains(pQuest->formID)) + continue; - char setStage[64]; - sprintf_s(setStage, std::size(setStage), "Set stage (%d)", pStage->stageIndex); + foundQuests.insert(pQuest->formID); - if (ImGui::Button(setStage)) - pQuest->ScriptSetStage(pStage->stageIndex); - } - } + if (!pQuest->IsActive()) + continue; - if (ImGui::CollapsingHeader("Actors")) - { - Set foundActors{}; + char questName[256]; + sprintf_s(questName, std::size(questName), "%s (%s)", pQuest->fullName.value.AsAscii(), pQuest->idName.AsAscii()); + if (!ImGui::CollapsingHeader(questName)) + continue; - for (BGSScene* pScene : pQuest->scenes) + if (ImGui::CollapsingHeader("Stages")) { - for (uint32_t actorId : pScene->actorIds) + for (auto* pStage : pQuest->stages) { - Actor* pActor = Cast(pQuest->GetAliasedRef(actorId)); - if (!pActor) - continue; + ImGui::TextColored({0.f, 255.f, 255.f, 255.f}, "Stage: %d, is done? %s", pStage->stageIndex, pStage->IsDone() ? "true" : "false"); - if (foundActors.contains(pActor->formID)) - continue; + char setStage[64]; + sprintf_s(setStage, std::size(setStage), "Set stage (%d)", pStage->stageIndex); - foundActors.insert(pActor->formID); + if (ImGui::Button(setStage)) + pQuest->ScriptSetStage(pStage->stageIndex); + } + } - char name[256]; - sprintf_s(name, std::size(name), "%s (%x)", pActor->baseForm->GetName(), pActor->formID); - ImGui::BulletText(name); + if (ImGui::CollapsingHeader("Actors")) + { + Set foundActors{}; - ImGui::PushID(pActor->formID); - if (ImGui::Button("Teleport to me")) + for (BGSScene* pScene : pQuest->scenes) + { + for (uint32_t actorId : pScene->actorIds) { - auto* pPlayer = PlayerCharacter::Get(); - m_world.GetRunner().Trigger(MoveActorEvent(pActor->formID, pPlayer->parentCell->formID, pPlayer->position)); + Actor* pActor = Cast(pQuest->GetAliasedRef(actorId)); + if (!pActor) + continue; + + if (foundActors.contains(pActor->formID)) + continue; + + foundActors.insert(pActor->formID); + + char name[256]; + sprintf_s(name, std::size(name), "%s (%x)", pActor->baseForm->GetName(), pActor->formID); + ImGui::BulletText(name); + + ImGui::PushID(pActor->formID); + if (ImGui::Button("Teleport to me")) + { + auto* pPlayer = PlayerCharacter::Get(); + m_world.GetRunner().Trigger(MoveActorEvent(pActor->formID, pPlayer->parentCell->formID, pPlayer->position)); + } + ImGui::PopID(); } - ImGui::PopID(); } } }