From c03a2acdc51bfe789b72f7fa0019a024c3f04af1 Mon Sep 17 00:00:00 2001 From: Leonid Pospelov Date: Sun, 15 Dec 2024 13:08:28 +0000 Subject: [PATCH] wip --- .../cpp/server_guest_lib/ActionListener.cpp | 14 +- .../server_guest_lib/MpObjectReference.cpp | 12 +- .../cpp/server_guest_lib/PartOne.cpp | 132 +++++++++++------- skymp5-server/cpp/server_guest_lib/PartOne.h | 18 ++- .../cpp/server_guest_lib/SpSnippet.cpp | 30 ++-- .../cpp/server_guest_lib/SpSnippet.h | 9 +- .../server_guest_lib/SpSnippetFunctionGen.cpp | 68 +++++---- .../server_guest_lib/SpSnippetFunctionGen.h | 13 +- 8 files changed, 181 insertions(+), 115 deletions(-) diff --git a/skymp5-server/cpp/server_guest_lib/ActionListener.cpp b/skymp5-server/cpp/server_guest_lib/ActionListener.cpp index 4c83d0df68..d61a2a7169 100644 --- a/skymp5-server/cpp/server_guest_lib/ActionListener.cpp +++ b/skymp5-server/cpp/server_guest_lib/ActionListener.cpp @@ -23,6 +23,8 @@ #include #include +#include "HostStartMessage.h" +#include "HostStopMessage.h" #include "UpdateEquipmentMessage.h" MpActor* ActionListener::SendToNeighbours( @@ -614,9 +616,9 @@ void ActionListener::OnHostAttempt(const RawMessageData& rawMsgData, longFormId += 0x100000000; } - Networking::SendFormatted(&partOne.GetSendTarget(), rawMsgData.userId, - R"({ "type": "hostStart", "target": %llu })", - longFormId); + HostStartMessage message; + message.target = longFormId; + partOne.GetSendTarget()->Send(rawMsgData.userId, message, true); // Otherwise, health percentage would remain unsynced until someone hits // npc @@ -642,9 +644,9 @@ void ActionListener::OnHostAttempt(const RawMessageData& rawMsgData, auto prevHosterUser = partOne.serverState.UserByActor(prevHosterActor); if (prevHosterUser != Networking::InvalidUserId && prevHosterUser != rawMsgData.userId) { - Networking::SendFormatted(&partOne.GetSendTarget(), prevHosterUser, - R"({ "type": "hostStop", "target": %llu })", - longFormId); + HostStopMessage message; + message.target = longFormId; + partOne.GetSendTarget()->Send(prevHosterUser, message, true); } } } diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index 2b906f6aa4..fe09acaccb 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp @@ -31,6 +31,7 @@ #include #include "OpenContainerMessage.h" +#include "SetInventoryMessage.h" #include "TeleportMessage.h" #include "script_classes/PapyrusObjectReference.h" // kOriginalNameExpression @@ -1761,14 +1762,9 @@ void MpObjectReference::SendInventoryUpdate() constexpr int kChannelSetInventory = 0; auto actor = AsActor(); if (actor) { - std::string msg; - msg += Networking::MinPacketId; - msg += nlohmann::json{ - { "inventory", actor->GetInventory().ToJson() }, - { "type", "setInventory" } - }.dump(); - actor->SendToUserDeferred(msg.data(), msg.size(), true, - kChannelSetInventory, true); + SetInventoryMessage message; + message.inventory = actor->GetInventory(); + actor->SendToUserDeferred(message, true, kChannelSetInventory, true); } } diff --git a/skymp5-server/cpp/server_guest_lib/PartOne.cpp b/skymp5-server/cpp/server_guest_lib/PartOne.cpp index e3ad404423..83a08844b0 100644 --- a/skymp5-server/cpp/server_guest_lib/PartOne.cpp +++ b/skymp5-server/cpp/server_guest_lib/PartOne.cpp @@ -13,15 +13,53 @@ #include #include +#include "CreateActorMessage.h" +#include "CustomPacketMessage.h" +#include "HostStopMessage.h" +#include "SetRaceMenuOpenMessage.h" + +namespace { +MessageSerializer& GetMessageSerializerInstance() +{ + static auto g_serializer = + MessageSerializerFactory::CreateMessageSerializer(); + return *g_serializer; +} +} + +PartOneSendTargetWrapper::PartOneSendTargetWrapper( + Networking::ISendTarget& underlyingSendTarget_) + : underlyingSendTarget(underlyingSendTarget_) +{ +} + +void PartOneSendTargetWrapper::Send(Networking::UserId targetUserId, + Networking::PacketData data, size_t length, + bool reliable) +{ + return underlyingSendTarget.Send(targetUserId, data, length, reliable); +} + +void PartOneSendTargetWrapper::Send(Networking::UserId targetUserId, + const IMessageBase& message, bool reliable) +{ + SLNet::BitStream stream; + + GetMessageSerializerInstance().Serialize(message, stream); + + Send(targetUserId, + reinterpret_cast(stream.GetData()), + stream.GetNumberOfBytesUsed(), reliable); +} + class FakeSendTarget : public Networking::ISendTarget { public: void Send(Networking::UserId targetUserId, Networking::PacketData data, size_t length, bool reliable) override { - static auto g_serializer = - MessageSerializerFactory::CreateMessageSerializer(); - auto deserializeResult = g_serializer->Deserialize(data, length); + auto deserializeResult = + GetMessageSerializerInstance().Deserialize(data, length); nlohmann::json j; if (deserializeResult) { deserializeResult->message->WriteJson(j); @@ -52,7 +90,7 @@ struct PartOne::Impl std::shared_ptr logger; - Networking::ISendTarget* sendTarget = nullptr; + std::unique_ptr sendTarget; std::unique_ptr damageFormula{}; FakeSendTarget fakeSendTarget; @@ -83,7 +121,11 @@ PartOne::~PartOne() void PartOne::SetSendTarget(Networking::ISendTarget* sendTarget) { - pImpl->sendTarget = sendTarget ? sendTarget : &pImpl->fakeSendTarget; + Networking::ISendTarget* underlyingSendTargetToSet = + sendTarget ? sendTarget : &pImpl->fakeSendTarget; + + pImpl->sendTarget.reset( + new PartOneSendTargetWrapper(*underlyingSendTargetToSet)); } void PartOne::SetDamageFormula(std::unique_ptr dmgFormula) @@ -218,28 +260,31 @@ void PartOne::SetRaceMenuOpen(uint32_t actorFormId, bool open) { auto& actor = worldState.GetFormAt(actorFormId); - if (actor.IsRaceMenuOpen() == open) + if (actor.IsRaceMenuOpen() == open) { return; + } actor.SetRaceMenuOpen(open); auto userId = serverState.UserByActor(&actor); if (userId == Networking::InvalidUserId) { - throw std::runtime_error(fmt::format( - "Actor with id {:#x} is not attached to any of users", actorFormId)); + spdlog::warn( + "PartOne::SetRaceMenuOpen {:x} - actor is not attached to any of users", + actorFormId); + return; } - Networking::SendFormatted(pImpl->sendTarget, userId, - R"({"type": "setRaceMenuOpen", "open": %s})", - open ? "true" : "false"); + SetRaceMenuOpenMessage message; + message.open = open; + pImpl->sendTarget->Send(userId, message, true); } void PartOne::SendCustomPacket(Networking::UserId userId, const std::string& jContent) { - Networking::SendFormatted(pImpl->sendTarget, userId, - R"({"type": "customPacket", "content":%s})", - jContent.data()); + CustomPacketMessage message; + message.contentJsonDump = jContent; + pImpl->sendTarget->Send(userId, message, true); } std::string PartOne::GetActorName(uint32_t actorFormId) @@ -415,7 +460,7 @@ void PartOne::HandlePacket(void* partOneInstance, Networking::UserId userId, } } -Networking::ISendTarget& PartOne::GetSendTarget() const +PartOneSendTargetWrapper& PartOne::GetSendTarget() const { if (!pImpl->sendTarget) { throw std::runtime_error("No send target found"); @@ -532,16 +577,13 @@ void PartOne::SendHostStop(Networking::UserId badHosterUserId, longFormId += 0x100000000; } - Networking::SendFormatted(&GetSendTarget(), badHosterUserId, - R"({ "type": "hostStop", "target": %llu })", - longFormId); + HostStopMessage message; + message.target = longFormId; + GetSendTarget()->Send(badHosterUserId, message, true); } FormCallbacks PartOne::CreateFormCallbacks() { - static auto g_serializer = - MessageSerializerFactory::CreateMessageSerializer(); - auto st = &serverState; FormCallbacks::SubscribeCallback @@ -557,12 +599,14 @@ FormCallbacks PartOne::CreateFormCallbacks() FormCallbacks::SendToUserFn sendToUser = [this, st](MpActor* actor, const IMessageBase& message, bool reliable) { SLNet::BitStream stream; - g_serializer->Serialize(message, stream); + GetMessageSerializerInstance().Serialize(message, stream); bool isOffline = st->UserByActor(actor) == Networking::InvalidUserId; // Only send to hoster if actor is offline (no active user) // This fixes December 2023 Update "invisible chat" bug + // TODO: make send-to-hoster mechanism explicit, instead of implicitly + // redirecting packets if (isOffline) { auto hosterIterator = worldState.hosters.find(actor->GetFormId()); if (hosterIterator != worldState.hosters.end()) { @@ -673,58 +717,40 @@ void PartOne::Init() MpActor* emitterAsActor = emitter->AsActor(); - std::string jEquipment, jAppearance, jAnimation; + CreateActorMessage message; + + std::string jAnimation; - const char *appearancePrefix = "", *appearance = ""; if (emitterAsActor) { - jAppearance = emitterAsActor->GetAppearanceAsJson(); - if (!jAppearance.empty()) { - appearancePrefix = R"(, "appearance": )"; - appearance = jAppearance.data(); - } + auto appearance = emitterAsActor->GetAppearance(); + message.appearance = appearance + ? std::optional(*appearance) + : std::optional(std::nullopt); } - const char *equipmentPrefix = "", *equipment = ""; if (emitterAsActor) { - jEquipment = emitterAsActor->GetEquipmentAsJson(); - if (!jEquipment.empty()) { - equipmentPrefix = R"(, "equipment": )"; - equipment = jEquipment.data(); - } + message.equipment = emitterAsActor->GetEquipment(); } - const char *animationPrefix = "", *animation = ""; if (emitterAsActor) { - jAnimation = emitterAsActor->GetLastAnimEventAsJson(); - if (!jAnimation.empty()) { - animationPrefix = R"(, "animation": )"; - animation = jAnimation.data(); - } + message.animation = emitterAsActor->GetLastAnimEvent(); } - const char* refrIdPrefix = ""; - char refrId[32] = { 0 }; - refrIdPrefix = R"(, "refrId": )"; - - long long unsigned int longFormId = emitter->GetFormId(); + uint64_t longFormId = emitter->GetFormId(); if (emitterAsActor && longFormId < 0xff000000) { longFormId += 0x100000000; } - sprintf(refrId, "%llu", longFormId); + message.refrId = longFormId; const char* baseIdPrefix = ""; char baseId[32] = { 0 }; if (emitter->GetBaseId() != 0x00000000 && emitter->GetBaseId() != 0x00000007) { - baseIdPrefix = R"(, "baseId": )"; - sprintf(baseId, "%u", emitter->GetBaseId()); + message.baseId = emitter->GetBaseId(); } - const char* isDeadPrefix = ""; - const char* isDead = ""; if (emitterAsActor && emitterAsActor->IsDead()) { - isDeadPrefix = R"(, "isDead": )"; - isDead = "\"true\""; + message.= true; } const bool isOwner = emitter == listener; diff --git a/skymp5-server/cpp/server_guest_lib/PartOne.h b/skymp5-server/cpp/server_guest_lib/PartOne.h index d13ca55027..41c2fd9cbe 100644 --- a/skymp5-server/cpp/server_guest_lib/PartOne.h +++ b/skymp5-server/cpp/server_guest_lib/PartOne.h @@ -23,6 +23,22 @@ class ActionListener; struct HitData; +class PartOneSendTargetWrapper : public Networking::ISendTarget +{ +public: + explicit PartOneSendTargetWrapper( + Networking::ISendTarget& underlyingSendTarget_); + + void Send(Networking::UserId targetUserId, Networking::PacketData data, + size_t length, bool reliable) override; + + void Send(Networking::UserId targetUserId, const IMessageBase& message, + bool reliable); + +private: + Networking::ISendTarget& underlyingSendTarget; +}; + class PartOne { public: @@ -82,7 +98,7 @@ class PartOne ServerState serverState; AnimationSystem animationSystem; - Networking::ISendTarget& GetSendTarget() const; + PartOneSendTargetWrapper& GetSendTarget() const; float CalculateDamage(const MpActor& aggressor, const MpActor& target, const HitData& hitData) const; diff --git a/skymp5-server/cpp/server_guest_lib/SpSnippet.cpp b/skymp5-server/cpp/server_guest_lib/SpSnippet.cpp index 206c6436a9..b62bcfb2a8 100644 --- a/skymp5-server/cpp/server_guest_lib/SpSnippet.cpp +++ b/skymp5-server/cpp/server_guest_lib/SpSnippet.cpp @@ -2,9 +2,14 @@ #include "MpActor.h" #include "NetworkingInterface.h" // Format +#include "SpSnippetMessage.h" -SpSnippet::SpSnippet(const char* cl_, const char* func_, const char* args_, - uint32_t selfId_) +SpSnippet::SpSnippet( + const char* cl_, const char* func_, + + const std::vector>>& args_, + uint32_t selfId_) : cl(cl_) , func(func_) , args(args_) @@ -33,16 +38,17 @@ Viet::Promise SpSnippet::Execute(MpActor* actor, SpSnippetMode mode) auto targetSelfId = (selfId < 0xff000000 || selfId != actor->GetFormId()) ? selfId : 0x14; - 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 kChannelSpSnippet = 1; - actor->SendToUserDeferred(data, len, true, kChannelSpSnippet, false); - }, - R"({"type": "spSnippet", "class": "%s", "function": "%s", "arguments": %s, "selfId": %u, "snippetIdx": %u})", - cl, func, args, targetSelfId, snippetIdx); + SpSnippetMessage message; + message.class_ = cl; + message.function = func; + message.args = args; + message.selfId = targetSelfId; + message.snippetIdx = static_cast(snippetIdx); + + // TODO: change to SendToUser, probably was deferred only for ability to send + // text packets + constexpr int kChannelSpSnippet = 1; + actor->SendToUserDeferred(message, true, kChannelSpSnippet, false); return promise; } diff --git a/skymp5-server/cpp/server_guest_lib/SpSnippet.h b/skymp5-server/cpp/server_guest_lib/SpSnippet.h index 4ebd0ce437..d67ab2c7d7 100644 --- a/skymp5-server/cpp/server_guest_lib/SpSnippet.h +++ b/skymp5-server/cpp/server_guest_lib/SpSnippet.h @@ -15,12 +15,17 @@ enum class SpSnippetMode class SpSnippet { public: - SpSnippet(const char* cl_, const char* func_, const char* args_, + SpSnippet(const char* cl_, const char* func_, + const std::vector>>& args_, uint32_t selfId_ = 0); Viet::Promise Execute(MpActor* actor, SpSnippetMode mode); private: - const char *const cl, *const func, *const args; + const char* const cl; + const char* const func; + const std::vector>>& args; const uint32_t selfId; }; diff --git a/skymp5-server/cpp/server_guest_lib/SpSnippetFunctionGen.cpp b/skymp5-server/cpp/server_guest_lib/SpSnippetFunctionGen.cpp index 054f176a9f..a2cfbbe06b 100644 --- a/skymp5-server/cpp/server_guest_lib/SpSnippetFunctionGen.cpp +++ b/skymp5-server/cpp/server_guest_lib/SpSnippetFunctionGen.cpp @@ -5,30 +5,40 @@ #include "script_objects/EspmGameObject.h" #include "script_objects/MpFormGameObject.h" #include -#include +#include -uint32_t SpSnippetFunctionGen::GetFormId(VarValue varValue) +uint32_t SpSnippetFunctionGen::GetFormId(const VarValue& varValue) { - if (auto form = GetFormPtr(varValue)) + if (varValue == VarValue::None()) { + return 0; + } + + if (auto form = GetFormPtr(varValue)) { return form->GetFormId(); - if (auto record = GetRecordPtr(varValue); record.rec) + } + + if (auto record = GetRecordPtr(varValue); record.rec) { return record.ToGlobalId(record.rec->GetId()); - if (varValue == VarValue::None()) - return 0; - std::stringstream ss; - ss << varValue << " is not a valid Papyrus object"; - throw std::runtime_error(ss.str()); + } + + spdlog::error("SpSnippetFunctionGen::GetFormId - VarValue {} is not a valid " + "Papyrus object", + varValue.ToString()); + + return 0; } -std::string SpSnippetFunctionGen::SerializeArguments( +std::vector>> +SpSnippetFunctionGen::SerializeArguments( const std::vector& arguments, MpActor* actor) { - std::stringstream ss; - ss << '['; - for (auto& arg : arguments) { - if (&arg != &arguments[0]) - ss << ", "; + std::vector>> + result; + result.reserve(arguments.size()); + for (auto& arg : arguments) { switch (arg.GetType()) { case VarValue::kType_Object: { auto formId = GetFormId(arg); @@ -42,31 +52,33 @@ std::string SpSnippetFunctionGen::SerializeArguments( auto obj = static_cast(arg); auto type = obj ? obj->GetParentNativeScript() : ""; - ss - << nlohmann::json({ { "formId", formId }, { "type", type } }).dump(); + + SpSnippetObjectArgument objectArg; + objectArg.formId = formId; + objectArg.type = type; + result.push_back(objectArg); break; } case VarValue::kType_String: - ss << nlohmann::json(static_cast(arg)).dump(); + result.push_back(std::string(static_cast(arg))); break; case VarValue::kType_Bool: - ss << (static_cast(arg) ? "true" : "false"); + result.push_back(static_cast(arg)); break; case VarValue::kType_Integer: - ss << static_cast(arg); + result.push_back(static_cast(static_cast(arg))); break; case VarValue::kType_Float: - ss << static_cast(arg); + result.push_back(static_cast(arg)); break; default: { - std::stringstream err; - err << "Unable to serialize VarValue " << arg - << " due to unsupported type (" << static_cast(arg.GetType()) - << ")"; - throw std::runtime_error(err.str()); + spdlog::error("SpSnippetFunctionGen::SerializeArguments - Unable to " + "serialize VarValue {} due to unsupported type ({})", + arg.ToString(), static_cast(arg.GetType())); + result.push_back(std::nullopt); + break; } } } - ss << ']'; - return ss.str(); + return result; } diff --git a/skymp5-server/cpp/server_guest_lib/SpSnippetFunctionGen.h b/skymp5-server/cpp/server_guest_lib/SpSnippetFunctionGen.h index 481be00b4c..13da9ddbdd 100644 --- a/skymp5-server/cpp/server_guest_lib/SpSnippetFunctionGen.h +++ b/skymp5-server/cpp/server_guest_lib/SpSnippetFunctionGen.h @@ -2,16 +2,19 @@ #include "MpActor.h" #include "SpSnippet.h" #include "SpSnippetFunctionGen.h" +#include "SpSnippetMessage.h" // SpSnippetObjectArgument #include "papyrus-vm/VirtualMachine.h" #include class SpSnippetFunctionGen { public: - static std::string SerializeArguments(const std::vector& arguments, - MpActor* actor = nullptr); + static std::vector>> + SerializeArguments(const std::vector& arguments, + MpActor* actor = nullptr); - static uint32_t GetFormId(VarValue varValue); + static uint32_t GetFormId(const VarValue& varValue); }; // TODO: unhardcode mode @@ -21,7 +24,7 @@ class SpSnippetFunctionGen if (auto actor = compatibilityPolicy->GetDefaultActor( \ GetName(), #name, self.GetMetaStackId())) { \ auto s = SpSnippetFunctionGen::SerializeArguments(arguments, actor); \ - SpSnippet(GetName(), (#name), s.data()) \ + SpSnippet(GetName(), (#name), s) \ .Execute(actor, SpSnippetMode::kNoReturnResult); \ } \ return VarValue::None(); \ @@ -34,7 +37,7 @@ class SpSnippetFunctionGen if (auto actor = compatibilityPolicy->GetDefaultActor( \ GetName(), #name, self.GetMetaStackId())) { \ auto s = SpSnippetFunctionGen::SerializeArguments(arguments, actor); \ - auto promise = SpSnippet(GetName(), (#name), s.data(), \ + auto promise = SpSnippet(GetName(), (#name), s, \ SpSnippetFunctionGen::GetFormId(self)) \ .Execute(actor, SpSnippetMode::kReturnResult); \ return VarValue(promise); \