From ef448c0a3a8a0f4040595ada6f4c338049b3b1e4 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Tue, 6 Feb 2024 09:34:44 +0600 Subject: [PATCH] Actor activation support improvements --- .../cpp/server_guest_lib/MpActor.cpp | 32 +++++ skymp5-server/cpp/server_guest_lib/MpActor.h | 2 + .../server_guest_lib/MpObjectReference.cpp | 112 +++++++++++++++++- .../script_classes/PapyrusActor.cpp | 24 +--- 4 files changed, 145 insertions(+), 25 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.cpp b/skymp5-server/cpp/server_guest_lib/MpActor.cpp index b4a2d02e6b..15be201cca 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpActor.cpp @@ -174,6 +174,38 @@ void MpActor::RemoveSpell(const uint32_t spellId) }); } +espm::LookupResult MpActor::GetRace() const +{ + uint32_t raceId = 0; + + if (auto appearance = GetAppearance()) { + raceId = appearance->raceId; + } else { + raceId = EvaluateTemplate( + GetParent(), GetBaseId(), GetTemplateChain(), + [](const auto& npcLookupResult, const auto& npcData) { + return npcLookupResult.ToGlobalId(npcData.race); + }); + } + + auto lookupRes = GetParent()->GetEspm().GetBrowser().LookupById(raceId); + + if (!lookupRes.rec) { + spdlog::error("MpActor::GetRace - Race with id {:x} not found in espm", + raceId); + return {}; + } + + if (!(lookupRes.rec->GetType() == espm::RACE::kType)) { + spdlog::error( + "MpActor::GetRace - Expected record {:x} to be RACE, but it is {}", + raceId, lookupRes.rec->GetType().ToString()); + return {}; + } + + return lookupRes; +} + void MpActor::SetRaceMenuOpen(bool isOpen) { EditChangeForm( diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.h b/skymp5-server/cpp/server_guest_lib/MpActor.h index 2ac7c96b32..6862c899a1 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.h +++ b/skymp5-server/cpp/server_guest_lib/MpActor.h @@ -137,6 +137,8 @@ class MpActor : public MpObjectReference void AddSpell(uint32_t spellId); void RemoveSpell(uint32_t spellId); + espm::LookupResult GetRace() const; + private: struct Impl; std::shared_ptr pImpl; diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index 7678e76367..8852abac14 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -9,6 +9,7 @@ #include "Primitive.h" #include "ScopedTask.h" #include "ScriptVariablesHolder.h" +#include "SpSnippet.h" #include "TimeUtils.h" #include "WorldState.h" #include "libespm/GroupUtils.h" @@ -287,8 +288,15 @@ void MpObjectReference::VisitProperties(const PropertiesVisitor& visitor, visitor("isOpen", "true"); } - if (auto actor = dynamic_cast(this); actor && actor->IsDead()) { - visitor("isDead", "true"); + if (auto actor = dynamic_cast(this)) { + if (actor->IsDead()) { + visitor("isDead", "true"); + } + + if (this->occupant) { + auto occupantFormIdStr = std::to_string(this->occupant->GetFormId()); + visitor("occupant", occupantFormIdStr.data()); + } } if (mode == VisitPropertiesMode::All && !GetInventory().IsEmpty()) { @@ -1338,6 +1346,106 @@ void MpObjectReference::ProcessActivate(MpObjectReference& activationSource) this->occupant->RemoveEventSink(this->occupantDestroySink); this->occupant = nullptr; } + } else if (t == espm::NPC_::kType && actorActivator) { + if (auto thisActor = dynamic_cast(this)) { + if (auto race = thisActor->GetRace(); race.rec) { + bool spSnippetSendActivate = false; + + auto raceId = race.ToGlobalId(race.rec->GetId()); + if (raceId == 0x000131fd) { + auto occupantPos = + this->occupant ? this->occupant->GetPos() : NiPoint3(); + auto occupantCellOrWorld = + this->occupant ? this->occupant->GetCellOrWorld() : FormDesc(); + + constexpr float kOccupationReach = 512.f; + auto distanceToOccupant = (occupantPos - GetPos()).Length(); + + if (!this->occupant || this->occupant->IsDisabled() || + distanceToOccupant > kOccupationReach || + occupantCellOrWorld != GetCellOrWorld()) { + if (this->occupant) { + this->occupant->RemoveEventSink(this->occupantDestroySink); + } + spSnippetSendActivate = true; + } else if (this->occupant == &activationSource) { + this->occupant->RemoveEventSink(this->occupantDestroySink); + this->occupant = nullptr; + spSnippetSendActivate = true; + } + } + + if (spSnippetSendActivate) { + auto j = nlohmann::json::array(); + // akActivator + j.push_back( + nlohmann::json({ { "formId", actorActivator->GetFormId() }, + { "type", "ObjectReference" } })); + // abDefaultProcessingOnly + j.push_back(true); + + std::string dump = j.dump(); + SpSnippet("ObjectReference", "Activate", dump.data(), + this->GetFormId()) + .Execute(actorActivator); + + SendPropertyToListeners( + "occupant", this->occupant ? this->occupant->GetFormId() : 0); + + // Prepare variables + auto& remote = *this; + auto remoteId = GetFormId(); + auto me = actorActivator; + auto remoteAsActor = dynamic_cast(&remote); + + // Implementation based on ActionListener::OnHostAttempt + auto& hoster = GetParent()->hosters[remoteId]; + const uint32_t prevHoster = hoster; + hoster = me->GetFormId(); + remote.UpdateHoster(hoster); + + uint64_t longFormId = remote.GetFormId(); + if (remoteAsActor && longFormId < 0xff000000) { + longFormId += 0x100000000; + } + + Networking::Format( + [&](Networking::PacketData data, size_t len) { + // The only reason for deferred here is that it still supports + // raw binary data send + // TODO: change to SendToUser + constexpr int kChannelHost = 2; + me->SendToUserDeferred(data, len, true, kChannelHost, false); + }, + R"({ "type": "hostStart", "target": %llu })", longFormId); + + if (MpActor* prevHosterActor = dynamic_cast( + GetParent()->LookupFormById(prevHoster).get())) { + if (prevHosterActor != me) { + Networking::Format( + [&](Networking::PacketData data, size_t len) { + // The only reason for deferred here is that it still + // supports raw binary data send + // TODO: change to SendToUser + constexpr int kChannelHost = 2; + prevHosterActor->SendToUserDeferred(data, len, true, + kChannelHost, false); + }, + R"({ "type": "hostStop", "target": %llu })", longFormId); + } + } + } + } else { + spdlog::error( + "MpObjectReference::ProcessActivate {:x} - Unable to find race", + GetFormId()); + } + } else { + spdlog::error( + "MpObjectReference::ProcessActivate {:x} - NPC_ base type, but " + "not an MpActor", + GetFormId()); + } } } diff --git a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusActor.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusActor.cpp index e1f4b3f2e0..0559a63794 100644 --- a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusActor.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusActor.cpp @@ -380,31 +380,9 @@ VarValue PapyrusActor::GetRace(VarValue self, return VarValue::None(); } - uint32_t raceId = 0; - - if (auto appearance = actor->GetAppearance()) { - raceId = appearance->raceId; - } else { - raceId = EvaluateTemplate( - actor->GetParent(), actor->GetBaseId(), actor->GetTemplateChain(), - [](const auto& npcLookupResult, const auto& npcData) { - return npcLookupResult.ToGlobalId(npcData.race); - }); - } - - auto lookupRes = - actor->GetParent()->GetEspm().GetBrowser().LookupById(raceId); + auto lookupRes = actor->GetRace(); if (!lookupRes.rec) { - spdlog::error("Actor.GetRace - Race with id {:x} not found in espm", - raceId); - return VarValue::None(); - } - - if (!(lookupRes.rec->GetType() == espm::RACE::kType)) { - spdlog::error( - "Actor.GetRace - Expected record {:x} to be RACE, but it is {}", raceId, - lookupRes.rec->GetType().ToString()); return VarValue::None(); }