diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index cfb5df8c3e..27f954a1e9 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,9 +1,26 @@ FROM mcr.microsoft.com/devcontainers/javascript-node:22 -RUN apt-get update \ - && apt-get install --no-install-recommends -y \ - clang-format-15 \ - # Clean cache - && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ - && apt-get clean -y && rm -rf /var/lib/apt/lists/* \ - && mv /usr/bin/clang-format-15 /usr/bin/clang-format +RUN \ + curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarnkey.gpg \ + && echo "deb [signed-by=/usr/share/keyrings/yarnkey.gpg] https://dl.yarnpkg.com/debian stable main" > /etc/apt/sources.list.d/yarn.list \ + && curl -fsSL https://apt.kitware.com/keys/kitware-archive-latest.asc | gpg --dearmor - > /usr/share/keyrings/kitware-archive-keyring.gpg \ + && echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ jammy main' > /etc/apt/sources.list.d/kitware.list \ + && apt-get update \ + && apt-get install -y \ + nodejs \ + yarn \ + libicu-dev \ + git \ + cmake \ + curl \ + unzip \ + tar \ + make \ + zip \ + pkg-config \ + cmake \ + clang-15 \ + clang-format-15 \ + ninja-build \ + && rm -rf /var/lib/apt/lists/* \ + && mv /usr/bin/clang-format-15 /usr/bin/clang-format diff --git a/libespm/src/WRLD.cpp b/libespm/src/WRLD.cpp index b5de80cafe..599cd123cb 100644 --- a/libespm/src/WRLD.cpp +++ b/libespm/src/WRLD.cpp @@ -1,5 +1,6 @@ #include "libespm/WRLD.h" #include "libespm/RecordHeaderAccess.h" +#include namespace espm { diff --git a/misc/git-hooks/clang-format-hook.js b/misc/git-hooks/clang-format-hook.js index b5a018d4b7..3113464c41 100644 --- a/misc/git-hooks/clang-format-hook.js +++ b/misc/git-hooks/clang-format-hook.js @@ -3,8 +3,9 @@ const simpleGit = require("simple-git"); const fs = require("fs"); const path = require("path"); -const extensions = [".cpp", ".h", ".hpp", ".cxx", ".cc"]; - +/** + * Utility: Recursively find all files in a directory. + */ const findFiles = (dir, fileList = []) => { const files = fs.readdirSync(dir); files.forEach((file) => { @@ -18,48 +19,159 @@ const findFiles = (dir, fileList = []) => { return fileList; }; -const formatFiles = (files) => { - const filesToFormat = files.filter((file) => - extensions.some((ext) => file.endsWith(ext)) +/** + * Check Registry: Define custom checks here. + * Each check should implement `lint` and `fix` methods. + */ +const checks = [ + { + name: "Clang Format", + appliesTo: (file) => [".cpp", ".h", ".hpp", ".cxx", ".cc"].some((ext) => file.endsWith(ext)), + lint: (file) => { + // Example: Use clang-format to lint + const lintCommand = `clang-format --dry-run --Werror ${file}`; + try { + execSync(lintCommand, { stdio: "inherit" }); + console.log(`[PASS] ${file}`); + return true; + } catch (error) { + console.error(`[FAIL] ${file}`); + return false; + } + }, + fix: (file) => { + // Use clang-format to autofix + const fixCommand = `clang-format -i ${file}`; + execSync(fixCommand, { stdio: "inherit" }); + console.log(`[FIXED] ${file}`); + }, + }, + { + name: "Header/TypeScript Pair Check", + // Applies to files that reside in the specified parent directories + appliesTo: (file) => { + const serverDir = "skymp5-server/cpp/messages"; + const clientDir = "skymp5-client/src/services/messages"; + const validDirs = [serverDir, clientDir]; + + // Check if the file belongs to one of the valid parent directories + return validDirs.some((dir) => file.includes(path.sep + dir + path.sep)) + && !file.endsWith(path.sep + "anyMessage.ts") + && !file.endsWith(path.sep + "refrIdMessageBase.ts") + && !file.endsWith(path.sep + "MessageBase.h") + && !file.endsWith(path.sep + "MessageSerializerFactory.cpp") + && !file.endsWith(path.sep + "MessageSerializerFactory.h") + && !file.endsWith(path.sep + "Messages.h") + && !file.endsWith(path.sep + "MinPacketId.h") + && !file.endsWith(path.sep + "MsgType.h"); + }, + lint: (file) => { + const serverDir = "skymp5-server/cpp/messages"; + const clientDir = "skymp5-client/src/services/messages"; + const ext = path.extname(file); + const baseName = path.basename(file, ext); + + // Determine the pair file's extension and directory + const pairExt = ext === ".h" ? ".ts" : ".h"; + const pairDir = file.includes(path.sep + serverDir + path.sep) + ? clientDir + : serverDir; + + const pairFiles = fs.readdirSync(pairDir); + + // Find a case-insensitive match + const pairFile = pairFiles.find( + (candidate) => candidate.toLowerCase() === `${baseName}${pairExt}`.toLowerCase() + ); + + if (!pairFile) { + console.error(`[FAIL] Pair file not found for ${file}: ${pairFile}`); + return false; + } else { + console.log(`[PASS] Pair file found for ${file}: ${pairFile}`); + return true; + } + }, + fix() { } + } +]; + +/** + * Core: Run checks (lint or fix) on given files. + */ +const runChecks = (files, { lintOnly = false }) => { + const filesToCheck = files.filter((file) => + checks.some((check) => check.appliesTo(file)) ); - if (filesToFormat.length === 0) { - console.log("No files to format."); + if (filesToCheck.length === 0) { + console.log("No matching files found for checks."); return; } - console.log("Formatting files:"); - filesToFormat.forEach((file) => { - console.log(` - ${file}`); - execSync(`clang-format -i ${file}`, { stdio: "inherit" }); + console.log(`${lintOnly ? "Linting" : "Fixing"} files:`); + + let fail = false; + + filesToCheck.forEach((file) => { + checks.forEach((check) => { + if (check.appliesTo(file)) { + try { + const res = lintOnly ? check.lint(file) : check.fix(file); + if (res === false) { + fail = true; + } + } catch (err) { + if (lintOnly) { + console.error(`Error in ${check.name}:`, err); + process.exit(1); + } + else { + throw err; + } + } + } + }); }); + + if (fail) { + process.exit(1); + } + + console.log(`${lintOnly ? "Linting" : "Fixing"} completed.`); }; +/** + * CLI Entry Point + */ (async () => { const args = process.argv.slice(2); + const lintOnly = args.includes("--lint"); + const allFiles = args.includes("--all"); try { - if (args.includes("--all")) { - console.log("Formatting all files in the repository..."); - const allFiles = findFiles(process.cwd()); - formatFiles(allFiles); + let files = []; + + if (allFiles) { + console.log("Processing all files in the repository..."); + files = findFiles(process.cwd()); } else { - console.log("Formatting staged files..."); + console.log("Processing staged files..."); const git = simpleGit(); const changedFiles = await git.diff(["--name-only", "--cached"]); - - const filesToFormat = changedFiles + files = changedFiles .split("\n") .filter((file) => file.trim() !== "") - .filter((file) => fs.existsSync(file)); // Do not try validate deleted files - formatFiles(filesToFormat); - - filesToFormat.forEach((file) => execSync(`git add ${file}`)); + .filter((file) => fs.existsSync(file)); // Exclude deleted files } - console.log("Formatting completed."); + runChecks(files, { lintOnly }); + + if (!lintOnly && !allFiles) { + files.forEach((file) => execSync(`git add ${file}`)); + } } catch (err) { - console.error("Error during formatting:", err.message); + console.error("Error during processing:", err.message); process.exit(1); } })(); diff --git a/serialization/include/archives/BitStreamInputArchive.h b/serialization/include/archives/BitStreamInputArchive.h index a8957eacac..1d08a38fa2 100644 --- a/serialization/include/archives/BitStreamInputArchive.h +++ b/serialization/include/archives/BitStreamInputArchive.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "../impl/BitStreamUtil.h" @@ -96,6 +97,43 @@ class BitStreamInputArchive return *this; } + template + BitStreamInputArchive& Serialize(const char* key, + std::variant& value) + { + uint32_t typeIndex = 0; + + Serialize("typeIndex", typeIndex); + + if (typeIndex >= sizeof...(Types)) { + throw std::runtime_error( + "Invalid type index for std::variant deserialization"); + } + + // Helper lambda to visit and deserialize the correct type + auto deserializeVisitor = [this](auto indexTag, + std::variant& variant) { + using SelectedType = + typename std::variant_alternative>::type; + SelectedType value; + Serialize("value", value); + variant = std::move(value); + }; + + // Visit the type corresponding to the typeIndex + [&](std::index_sequence) + { + ((typeIndex == Is ? deserializeVisitor( + std::integral_constant{}, value) + : void()), + ...); + } + (std::make_index_sequence{}); + + return *this; + } + template BitStreamInputArchive& Serialize(const char* key, T& value) { diff --git a/serialization/include/archives/BitStreamOutputArchive.h b/serialization/include/archives/BitStreamOutputArchive.h index 52d2ca5f2d..c779e60447 100644 --- a/serialization/include/archives/BitStreamOutputArchive.h +++ b/serialization/include/archives/BitStreamOutputArchive.h @@ -1,5 +1,6 @@ #pragma once #include "concepts/Concepts.h" +#include #include #include #include @@ -84,6 +85,21 @@ class BitStreamOutputArchive return *this; } + template + BitStreamOutputArchive& Serialize(const char* key, + std::variant& value) + { + uint32_t typeIndex = static_cast(value.index()); + + Serialize("typeIndex", typeIndex); + + auto serializeVisitor = [&](auto& v) { Serialize("value", v); }; + + std::visit(serializeVisitor, value); + + return *this; + } + template BitStreamOutputArchive& Serialize(const char* key, T& value) { diff --git a/serialization/include/archives/JsonInputArchive.h b/serialization/include/archives/JsonInputArchive.h index e27794a671..3162528684 100644 --- a/serialization/include/archives/JsonInputArchive.h +++ b/serialization/include/archives/JsonInputArchive.h @@ -4,6 +4,7 @@ #include #include #include +#include #include class JsonInputArchive @@ -86,6 +87,54 @@ class JsonInputArchive return *this; } + template + JsonInputArchive& Serialize(const char* key, std::variant& value) + { + const auto& jsonValue = j.at(key); + + // Helper lambda to attempt deserialization for a specific type + auto tryDeserialize = [&](auto typeTag) -> bool { + using SelectedType = decltype(typeTag); + + nlohmann::json childArchiveInput = nlohmann::json::object(); + childArchiveInput["candidate"] = jsonValue; + SelectedType deserializedValue; + JsonInputArchive childArchive(childArchiveInput); + + bool deserializationSuccessful = false; + try { + childArchive.Serialize("candidate", deserializedValue); + deserializationSuccessful = true; + } catch (const std::exception&) { + deserializationSuccessful = false; + } + + if (deserializationSuccessful) { + value = std::move(deserializedValue); + } + + return deserializationSuccessful; + }; + + // Iterate through the variant types and attempt deserialization + bool success = false; + [&](std::index_sequence) + { + ((success = success || + tryDeserialize(std::declval>::type>())), + ...); + } + (std::make_index_sequence{}); + + if (!success) { + throw std::runtime_error( + "Unable to deserialize JSON into any variant type"); + } + + return *this; + } + template JsonInputArchive& Serialize(const char* key, T& value) { diff --git a/serialization/include/archives/JsonOutputArchive.h b/serialization/include/archives/JsonOutputArchive.h index 2817f423ae..bf18fbd34f 100644 --- a/serialization/include/archives/JsonOutputArchive.h +++ b/serialization/include/archives/JsonOutputArchive.h @@ -1,6 +1,8 @@ #pragma once #include "concepts/Concepts.h" +#include #include +#include #include class JsonOutputArchive @@ -52,6 +54,21 @@ class JsonOutputArchive return *this; } + template + JsonOutputArchive& Serialize(const char* key, + const std::variant& value) + { + auto serializeVisitor = [&](auto& v) { + JsonOutputArchive childArchive; + childArchive.Serialize("element", v); + return childArchive.j["element"]; + }; + + j[key] = std::visit(serializeVisitor, value); + + return *this; + } + template JsonOutputArchive& Serialize(const char* key, const T& value) { diff --git a/serialization/include/archives/SimdJsonInputArchive.h b/serialization/include/archives/SimdJsonInputArchive.h index 907780921c..176baf2ad1 100644 --- a/serialization/include/archives/SimdJsonInputArchive.h +++ b/serialization/include/archives/SimdJsonInputArchive.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "concepts/Concepts.h" @@ -52,7 +53,6 @@ class SimdJsonInputArchive { } - // TODO(#2250): it's probably useless and should be removed template SimdJsonInputArchive& Serialize(const char* key, T& output) { @@ -65,6 +65,7 @@ class SimdJsonInputArchive SimdJsonInputArchive& Serialize(T& output) { static_assert(!sizeof(T), "can only parse to std::string"); + return *this; } SimdJsonInputArchive& Serialize(std::string& output) @@ -188,6 +189,49 @@ class SimdJsonInputArchive return *this; } + template + SimdJsonInputArchive& Serialize(std::variant& output) + { + // Helper lambda to attempt deserialization for a specific type + auto tryDeserialize = [&](auto typeTag) -> bool { + using SelectedType = decltype(typeTag); + + SelectedType deserializedValue; + + bool deserializationSuccessful = false; + try { + Serialize(deserializedValue); + deserializationSuccessful = true; + } catch (const std::exception&) { + deserializationSuccessful = false; + } + + if (deserializationSuccessful) { + output = std::move(deserializedValue); + } + + return deserializationSuccessful; + }; + + // Iterate through the variant types and attempt deserialization + bool success = false; + [&](std::index_sequence) + { + ((success = success || + tryDeserialize(typename std::variant_alternative< + Is, std::variant>::type{})), + ...); + } + (std::make_index_sequence{}); + + if (!success) { + throw std::runtime_error( + "Unable to deserialize JSON into any variant type"); + } + + return *this; + } + // This function is called when for non-trivial types. // It's expected that a special function will handle this, implemented by the // said type @@ -204,6 +248,17 @@ class SimdJsonInputArchive return *this; } + template + SimdJsonInputArchive& Serialize(std::optional& output) + { + T outputItem; + SimdJsonInputArchive itemArchive(input); + itemArchive.Serialize(outputItem); + output.emplace(outputItem); + + return *this; + } + template SimdJsonInputArchive& Serialize(const char* key, std::optional& output) { diff --git a/serialization/include/impl/BitStreamUtil.h b/serialization/include/impl/BitStreamUtil.h index 28dc200cbf..7add936682 100644 --- a/serialization/include/impl/BitStreamUtil.h +++ b/serialization/include/impl/BitStreamUtil.h @@ -26,6 +26,7 @@ DECLARE_RAKNET_SAFETY_WRAPPERS(int32_t) DECLARE_RAKNET_SAFETY_WRAPPERS(int16_t) DECLARE_RAKNET_SAFETY_WRAPPERS(int8_t) DECLARE_RAKNET_SAFETY_WRAPPERS(float) +DECLARE_RAKNET_SAFETY_WRAPPERS(double) DECLARE_RAKNET_SAFETY_WRAPPERS(bool) DECLARE_RAKNET_SAFETY_WRAPPERS(char) diff --git a/skymp5-client/src/messages.ts b/skymp5-client/src/messages.ts index 2c69087948..a7be8a574d 100644 --- a/skymp5-client/src/messages.ts +++ b/skymp5-client/src/messages.ts @@ -23,4 +23,15 @@ export enum MsgType { PlayerBowShot = 22, SpellCast = 23, UpdateAnimVariables = 24, + + // ex-strings + DestroyActor = 25, + HostStart = 26, + HostStop = 27, + SetInventory = 28, + SetRaceMenuOpen = 29, + SpSnippet = 30, + Teleport2 = 31, + UpdateGamemodeData = 32, + CreateActor = 33 } diff --git a/skymp5-client/src/services/events/events.ts b/skymp5-client/src/services/events/events.ts index ea5c797102..e136b48b6d 100644 --- a/skymp5-client/src/services/events/events.ts +++ b/skymp5-client/src/services/events/events.ts @@ -13,9 +13,8 @@ import { HostStartMessage } from "../messages/hostStartMessage"; import { HostStopMessage } from "../messages/hostStopMessage"; import { SetInventoryMessage } from "../messages/setInventoryMessage"; import { OpenContainerMessage } from "../messages/openContainerMessage"; -import { ChangeValuesMessage } from "../messages/changeValues"; +import { ChangeValuesMessage } from "../messages/changeValuesMessage"; import { CreateActorMessage } from "../messages/createActorMessage"; -import { CustomPacketMessage2 } from "../messages/customPacketMessage2"; import { DestroyActorMessage } from "../messages/destroyActorMessage"; import { SetRaceMenuOpenMessage } from "../messages/setRaceMenuOpenMessage"; import { SpSnippetMessage } from "../messages/spSnippetMessage"; @@ -36,6 +35,7 @@ import { QueryBlockSetInventoryEvent } from "./queryBlockSetInventoryEvent"; import { QueryKeyCodeBindings } from "./queryKeyCodeBindings"; import { SpellCastMessage } from "../messages/spellCastMessage"; import { UpdateAnimVariablesMessage } from "../messages/updateAnimVariablesMessage"; +import { CustomPacketMessage } from "../messages/customPacketMessage"; type EventTypes = { @@ -64,14 +64,14 @@ type EventTypes = { 'hostStopMessage': [ConnectionMessage], 'setInventoryMessage': [ConnectionMessage], 'createActorMessage': [ConnectionMessage], - 'customPacketMessage2': [ConnectionMessage], 'destroyActorMessage': [ConnectionMessage], 'setRaceMenuOpenMessage': [ConnectionMessage], 'spSnippetMessage': [ConnectionMessage], 'updateGamemodeDataMessage': [ConnectionMessage], 'updatePropertyMessage': [ConnectionMessage], 'deathStateContainerMessage': [ConnectionMessage], - 'teleportMessage2': [ConnectionMessage] + 'teleportMessage2': [ConnectionMessage], + 'customPacketMessage': [ConnectionMessage] 'browserWindowLoaded': [BrowserWindowLoadedEvent], 'authAttempt': [AuthAttemptEvent], diff --git a/skymp5-client/src/services/messages/anyMessage.ts b/skymp5-client/src/services/messages/anyMessage.ts index 6fbc46b966..2d4d0304ee 100644 --- a/skymp5-client/src/services/messages/anyMessage.ts +++ b/skymp5-client/src/services/messages/anyMessage.ts @@ -1,12 +1,11 @@ import { ActivateMessage } from "./activateMessage"; -import { ChangeValuesMessage } from "./changeValues"; +import { ChangeValuesMessage } from "./changeValuesMessage"; import { ConsoleCommandMessage } from "./consoleCommandMessage"; import { CraftItemMessage } from "./craftItemMessage"; import { CreateActorMessage } from "./createActorMessage"; import { CustomEventMessage } from "./customEventMessage"; import { CustomPacketMessage } from "./customPacketMessage"; -import { CustomPacketMessage2 } from "./customPacketMessage2"; import { DeathStateContainerMessage } from "./deathStateContainerMessage"; import { DestroyActorMessage } from "./destroyActorMessage"; import { DropItemMessage } from "./dropItemMessage"; @@ -59,7 +58,6 @@ export type AnyMessage = ActivateMessage | HostStopMessage | SetInventoryMessage | CreateActorMessage - | CustomPacketMessage2 | DestroyActorMessage | SetRaceMenuOpenMessage | SpSnippetMessage diff --git a/skymp5-client/src/services/messages/changeValues.ts b/skymp5-client/src/services/messages/changeValuesMessage.ts similarity index 100% rename from skymp5-client/src/services/messages/changeValues.ts rename to skymp5-client/src/services/messages/changeValuesMessage.ts diff --git a/skymp5-client/src/services/messages/consoleCommandMessage.ts b/skymp5-client/src/services/messages/consoleCommandMessage.ts index 491596e8f6..5acd10214d 100644 --- a/skymp5-client/src/services/messages/consoleCommandMessage.ts +++ b/skymp5-client/src/services/messages/consoleCommandMessage.ts @@ -7,5 +7,5 @@ export interface ConsoleCommandMessage { interface ConsoleCommandMessageData { commandName: string; - args: unknown[]; + args: Array; } diff --git a/skymp5-client/src/services/messages/createActorMessage.ts b/skymp5-client/src/services/messages/createActorMessage.ts index f0a463da3c..c5bc7df996 100644 --- a/skymp5-client/src/services/messages/createActorMessage.ts +++ b/skymp5-client/src/services/messages/createActorMessage.ts @@ -3,14 +3,31 @@ import { Equipment } from "../../sync/equipment"; import { Inventory } from "../../sync/inventory"; import { Transform } from "../../sync/movement"; import { Animation } from "../../sync/animation"; +import { MsgType } from "src/messages"; + +export interface SetNodeTextureSetEntry { + nodeName: string; + textureSetId: number; +}; + +export interface SetNodeScaleEntry { + nodeName: string; + scale: number; +}; + +export interface CustomPropsEntry { + propName: string; + propValueJsonDump: string; +}; export interface CreateActorMessage extends CreateActorMessageMainProps { - type: "createActor"; + t: MsgType.CreateActor, idx: number; - baseRecordType: "DOOR" | undefined; // see PartOne.cpp + baseRecordType?: "DOOR"; // see PartOne.cpp transform: Transform; isMe: boolean; props?: CreateActorMessageAdditionalProps; + customPropsJsonDumps: CustomPropsEntry[]; } export interface CreateActorMessageMainProps { @@ -26,8 +43,8 @@ export interface CreateActorMessageMainProps { export interface CreateActorMessageAdditionalProps { isOpen?: boolean; isHarvested?: boolean; - setNodeTextureSet?: Record; - setNodeScale?: Record; + setNodeTextureSet?: SetNodeTextureSetEntry[]; + setNodeScale?: SetNodeScaleEntry[]; disabled?: boolean; lastAnimation?: string; displayName?: string; diff --git a/skymp5-client/src/services/messages/customEventMessage.ts b/skymp5-client/src/services/messages/customEventMessage.ts index b150edd3d6..1596a1d1f3 100644 --- a/skymp5-client/src/services/messages/customEventMessage.ts +++ b/skymp5-client/src/services/messages/customEventMessage.ts @@ -2,6 +2,6 @@ import { MsgType } from "../../messages"; export interface CustomEventMessage { t: MsgType.CustomEvent, - args: unknown[], + argsJsonDumps: string[], eventName: string } diff --git a/skymp5-client/src/services/messages/customPacketMessage.ts b/skymp5-client/src/services/messages/customPacketMessage.ts index eeef66b48e..ac7cefa164 100644 --- a/skymp5-client/src/services/messages/customPacketMessage.ts +++ b/skymp5-client/src/services/messages/customPacketMessage.ts @@ -2,10 +2,5 @@ import { MsgType } from "../../messages"; export interface CustomPacketMessage { t: MsgType.CustomPacket, - content: CustomPacketMessageContent -} - -interface CustomPacketMessageContent { - customPacketType: string, - gameData: Record + contentJsonDump: string } diff --git a/skymp5-client/src/services/messages/customPacketMessage2.ts b/skymp5-client/src/services/messages/customPacketMessage2.ts deleted file mode 100644 index 1d6ed02df7..0000000000 --- a/skymp5-client/src/services/messages/customPacketMessage2.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface CustomPacketMessage2 { - type: "customPacket"; - content: Record; -} diff --git a/skymp5-client/src/services/messages/deathStateContainerMessage.ts b/skymp5-client/src/services/messages/deathStateContainerMessage.ts index b909ff74e1..d90c986492 100644 --- a/skymp5-client/src/services/messages/deathStateContainerMessage.ts +++ b/skymp5-client/src/services/messages/deathStateContainerMessage.ts @@ -1,5 +1,5 @@ import { MsgType } from "../../messages"; -import { ChangeValuesMessage } from "./changeValues"; +import { ChangeValuesMessage } from "./changeValuesMessage"; import { TeleportMessage } from "./teleportMessage"; import { UpdatePropertyMessage } from "./updatePropertyMessage"; diff --git a/skymp5-client/src/services/messages/destroyActorMessage.ts b/skymp5-client/src/services/messages/destroyActorMessage.ts index 118aebdcd4..dae0e4a94f 100644 --- a/skymp5-client/src/services/messages/destroyActorMessage.ts +++ b/skymp5-client/src/services/messages/destroyActorMessage.ts @@ -1,4 +1,6 @@ +import { MsgType } from "../../messages"; + export interface DestroyActorMessage { - type: "destroyActor"; + t: MsgType.DestroyActor, idx: number; } diff --git a/skymp5-client/src/services/messages/finishSpSnippetMessage.ts b/skymp5-client/src/services/messages/finishSpSnippetMessage.ts index 42a0109ed7..7801f66996 100644 --- a/skymp5-client/src/services/messages/finishSpSnippetMessage.ts +++ b/skymp5-client/src/services/messages/finishSpSnippetMessage.ts @@ -2,6 +2,6 @@ import { MsgType } from "../../messages" export interface FinishSpSnippetMessage { t: MsgType.FinishSpSnippet, - returnValue: unknown, // TODO: improve type: there should union of possible Papyrus values + returnValue?: boolean | number | string; snippetIdx: number, } diff --git a/skymp5-client/src/services/messages/hostStartMessage.ts b/skymp5-client/src/services/messages/hostStartMessage.ts index 214cd7bf28..117538885a 100644 --- a/skymp5-client/src/services/messages/hostStartMessage.ts +++ b/skymp5-client/src/services/messages/hostStartMessage.ts @@ -1,4 +1,6 @@ +import { MsgType } from "../../messages"; + export interface HostStartMessage { - type: "hostStart"; + t: MsgType.HostStart; target: number; } diff --git a/skymp5-client/src/services/messages/hostStopMessage.ts b/skymp5-client/src/services/messages/hostStopMessage.ts index 58b7e83e6f..afcdcf26c0 100644 --- a/skymp5-client/src/services/messages/hostStopMessage.ts +++ b/skymp5-client/src/services/messages/hostStopMessage.ts @@ -1,4 +1,6 @@ +import { MsgType } from "../../messages"; + export interface HostStopMessage { - type: "hostStop"; + t: MsgType.HostStop; target: number; } diff --git a/skymp5-client/src/services/messages/putItemMessage.ts b/skymp5-client/src/services/messages/putItemMessage.ts index fc5f91f96c..2321ee040a 100644 --- a/skymp5-client/src/services/messages/putItemMessage.ts +++ b/skymp5-client/src/services/messages/putItemMessage.ts @@ -3,6 +3,7 @@ import { MsgType } from "../../messages"; export interface PutItemMessage extends Extra { t: MsgType.PutItem, + baseId: number; count: number; target: number; }; diff --git a/skymp5-client/src/services/messages/setInventoryMessage.ts b/skymp5-client/src/services/messages/setInventoryMessage.ts index 850e123fdd..3ec06e75f6 100644 --- a/skymp5-client/src/services/messages/setInventoryMessage.ts +++ b/skymp5-client/src/services/messages/setInventoryMessage.ts @@ -1,6 +1,7 @@ import { Inventory } from "skyrimPlatform"; +import { MsgType } from "../../messages"; export interface SetInventoryMessage { - type: "setInventory"; + t: MsgType.SetInventory; inventory: Inventory; } diff --git a/skymp5-client/src/services/messages/setRaceMenuOpenMessage.ts b/skymp5-client/src/services/messages/setRaceMenuOpenMessage.ts index 55b08929fe..77ccf41d34 100644 --- a/skymp5-client/src/services/messages/setRaceMenuOpenMessage.ts +++ b/skymp5-client/src/services/messages/setRaceMenuOpenMessage.ts @@ -1,4 +1,6 @@ +import { MsgType } from "../../messages"; + export interface SetRaceMenuOpenMessage { - type: "setRaceMenuOpen"; + t: MsgType.SetRaceMenuOpen; open: boolean; } diff --git a/skymp5-client/src/services/messages/spSnippetMessage.ts b/skymp5-client/src/services/messages/spSnippetMessage.ts index f973d2e00d..9b88602edb 100644 --- a/skymp5-client/src/services/messages/spSnippetMessage.ts +++ b/skymp5-client/src/services/messages/spSnippetMessage.ts @@ -1,5 +1,7 @@ +import { MsgType } from "../../messages"; + export interface SpSnippetMessage { - type: "spSnippet"; + t: MsgType.SpSnippet; class: string; function: string; arguments: any[]; diff --git a/skymp5-client/src/services/messages/spellCastMessage.ts b/skymp5-client/src/services/messages/spellCastMessage.ts index 589d9c6eec..005a090968 100644 --- a/skymp5-client/src/services/messages/spellCastMessage.ts +++ b/skymp5-client/src/services/messages/spellCastMessage.ts @@ -1,6 +1,4 @@ import { MsgType } from "../../messages"; -// @ts-expect-error (TODO: Remove in 2.10.0) -import { ActorAnimationVariables } from 'skyrimPlatform'; export interface SpellCastMsgData { caster: number @@ -11,7 +9,11 @@ export interface SpellCastMsgData { castingSource: number aimAngle: number, aimHeading: number, - actorAnimationVariables: ActorAnimationVariables + actorAnimationVariables: { + booleans: number[] + floats: number[] + integers: number[] + } } export interface SpellCastMessage { diff --git a/skymp5-client/src/services/messages/takeItemMessage.ts b/skymp5-client/src/services/messages/takeItemMessage.ts index 8eeab60196..d5780a87d8 100644 --- a/skymp5-client/src/services/messages/takeItemMessage.ts +++ b/skymp5-client/src/services/messages/takeItemMessage.ts @@ -3,6 +3,7 @@ import { MsgType } from "../../messages"; export interface TakeItemMessage extends Extra { t: MsgType.TakeItem, + baseId: number; count: number; target: number; }; diff --git a/skymp5-client/src/services/messages/teleportMessage2.ts b/skymp5-client/src/services/messages/teleportMessage2.ts index a34a3eee6d..db76e6309f 100644 --- a/skymp5-client/src/services/messages/teleportMessage2.ts +++ b/skymp5-client/src/services/messages/teleportMessage2.ts @@ -1,5 +1,7 @@ +import { MsgType } from "../../messages"; + export interface TeleportMessage2 { - type: "teleport"; + t: MsgType.Teleport2; pos: number[]; rot: number[]; worldOrCell: number; diff --git a/skymp5-client/src/services/messages/updateAnimVariablesMessage.ts b/skymp5-client/src/services/messages/updateAnimVariablesMessage.ts index 98db94f410..b4d253323c 100644 --- a/skymp5-client/src/services/messages/updateAnimVariablesMessage.ts +++ b/skymp5-client/src/services/messages/updateAnimVariablesMessage.ts @@ -1,10 +1,12 @@ import { MsgType } from "../../messages"; -// @ts-expect-error (TODO: Remove in 2.10.0) -import { ActorAnimationVariables } from 'skyrimPlatform'; export interface UpdateAnimVariablesMessageMsgData { actorRemoteId: number - actorAnimationVariables: ActorAnimationVariables + actorAnimationVariables: { + booleans: number[] + floats: number[] + integers: number[] + } } export interface UpdateAnimVariablesMessage { diff --git a/skymp5-client/src/services/messages/updateGameModeDataMessage.ts b/skymp5-client/src/services/messages/updateGameModeDataMessage.ts index 8c7138debe..25a9bbeaba 100644 --- a/skymp5-client/src/services/messages/updateGameModeDataMessage.ts +++ b/skymp5-client/src/services/messages/updateGameModeDataMessage.ts @@ -1,6 +1,13 @@ +import { MsgType } from "../../messages"; + +export interface GamemodeValuePair { + name: string; + content: string; +} + export interface UpdateGamemodeDataMessage { - type: "updateGamemodeData"; - eventSources: Record; - updateOwnerFunctions: Record; - updateNeighborFunctions: Record; + t: MsgType.UpdateGamemodeData; + eventSources: GamemodeValuePair[]; + updateOwnerFunctions: GamemodeValuePair[]; + updateNeighborFunctions: GamemodeValuePair[]; } diff --git a/skymp5-client/src/services/services/authService.ts b/skymp5-client/src/services/services/authService.ts index 198a72d58e..d45561283d 100644 --- a/skymp5-client/src/services/services/authService.ts +++ b/skymp5-client/src/services/services/authService.ts @@ -11,7 +11,6 @@ import { ConnectionMessage } from "../events/connectionMessage"; import { CreateActorMessage } from "../messages/createActorMessage"; import { CustomPacketMessage } from "../messages/customPacketMessage"; import { NetworkingService } from "./networkingService"; -import { CustomPacketMessage2 } from "../messages/customPacketMessage2"; import { MsgType } from "../../messages"; import { ConnectionDenied } from "../events/connectionDenied"; @@ -46,7 +45,7 @@ export class AuthService extends ClientListener { this.controller.emitter.on("createActorMessage", (e) => this.onCreateActorMessage(e)); this.controller.emitter.on("connectionAccepted", () => this.handleConnectionAccepted()); this.controller.emitter.on("connectionDenied", (e) => this.handleConnectionDenied(e)); - this.controller.emitter.on("customPacketMessage2", (e) => this.onCustomPacketMessage2(e)); + this.controller.emitter.on("customPacketMessage", (e) => this.onCustomPacketMessage(e)); this.controller.on("browserMessage", (e) => this.onBrowserMessage(e)); this.controller.on("tick", () => this.onTick()); this.controller.once("update", () => this.onceUpdate()); @@ -96,10 +95,24 @@ export class AuthService extends ClientListener { this.authAttemptProgressIndicator = false; } - private onCustomPacketMessage2(event: ConnectionMessage): void { + private onCustomPacketMessage(event: ConnectionMessage): void { const msg = event.message; - switch (msg.content["customPacketType"]) { + let msgContent: Record = {}; + + try { + msgContent = JSON.parse(msg.contentJsonDump); + } catch (e) { + if (e instanceof SyntaxError) { + logError(this, "onCustomPacketMessage failed to parse JSON", e.message, "json:", msg.contentJsonDump); + return; + } + else { + throw e; + } + } + + switch (msgContent["customPacketType"]) { // case 'loginRequired': // logTrace(this, 'loginRequired received'); // this.loginWithSkympIoCredentials(); @@ -541,12 +554,12 @@ export class AuthService extends ClientListener { ); const message: CustomPacketMessage = { t: MsgType.CustomPacket, - content: { + contentJsonDump: JSON.stringify({ customPacketType: 'loginWithSkympIo', gameData: { profileId: authData.local.profileId, }, - }, + }), }; this.controller.emitter.emit("sendMessage", { message: message, @@ -559,12 +572,12 @@ export class AuthService extends ClientListener { logTrace(this, 'Logging in as a master API user'); const message: CustomPacketMessage = { t: MsgType.CustomPacket, - content: { + contentJsonDump: JSON.stringify({ customPacketType: 'loginWithSkympIo', gameData: { session: authData.remote.session, }, - }, + }), }; this.controller.emitter.emit("sendMessage", { message: message, diff --git a/skymp5-client/src/services/services/consoleCommandsService.ts b/skymp5-client/src/services/services/consoleCommandsService.ts index d06ead9fd9..001a52dd6c 100644 --- a/skymp5-client/src/services/services/consoleCommandsService.ts +++ b/skymp5-client/src/services/services/consoleCommandsService.ts @@ -83,12 +83,19 @@ export class ConsoleCommandsService extends ClientListener { } } + for (let i = 0; i < args.length; ++i) { + if (typeof args[i] !== "string" && typeof args[i] !== "number") { + logError(this, `Bad argument type in command`, commandName, `argument index`, i); + return false; + } + } + this.controller.emitter.emit("sendMessage", { message: { t: MsgType.ConsoleCommand, data: { commandName, - args + args: args as (string | number)[] } }, reliability: "reliable" diff --git a/skymp5-client/src/services/services/gamemodeEventSourceService.ts b/skymp5-client/src/services/services/gamemodeEventSourceService.ts index 3434762090..e7eed7f6d4 100644 --- a/skymp5-client/src/services/services/gamemodeEventSourceService.ts +++ b/skymp5-client/src/services/services/gamemodeEventSourceService.ts @@ -37,7 +37,10 @@ export class GamemodeEventSourceService extends ClientListener { }); } - let eventNames = Object.keys(event.message.eventSources); + let eventSourcesRecord: Record = {}; + event.message.eventSources.forEach(pair => eventSourcesRecord[pair.name] = pair.content); + + let eventNames = Object.keys(eventSourcesRecord); let blockedEventSources = this.sp.settings["skymp5-client"]["blockedEventSources"]; @@ -50,7 +53,7 @@ export class GamemodeEventSourceService extends ClientListener { eventNames.forEach((eventName) => { try { - const fn = new Function('ctx', event.message.eventSources[eventName]); + const fn = new Function('ctx', eventSourcesRecord[eventName]!); const ctx: GamemodeApiEventSourceCtx = { refr: undefined, value: undefined, @@ -68,7 +71,7 @@ export class GamemodeEventSourceService extends ClientListener { sendEvent: (...args: unknown[]) => { const message: CustomEventMessage = { t: MsgType.CustomEvent, - args, + argsJsonDumps: args.map(arg => JSON.stringify(arg)), eventName }; this.controller.emitter.emit("sendMessage", { diff --git a/skymp5-client/src/services/services/gamemodeUpdateService.ts b/skymp5-client/src/services/services/gamemodeUpdateService.ts index f1ac14ef6f..734fd68577 100644 --- a/skymp5-client/src/services/services/gamemodeUpdateService.ts +++ b/skymp5-client/src/services/services/gamemodeUpdateService.ts @@ -1,7 +1,7 @@ import { FormModel } from "../../view/model"; import { ConnectionMessage } from "../events/connectionMessage"; import { CreateActorMessage } from "../messages/createActorMessage"; -import { UpdateGamemodeDataMessage } from "../messages/updateGameModeDataMessage"; +import { GamemodeValuePair, UpdateGamemodeDataMessage } from "../messages/updateGameModeDataMessage"; import { ClientListener, CombinedController, Sp } from "./clientListener"; import { RemoteServer } from "./remoteServer"; import { ObjectReference } from "skyrimPlatform"; @@ -109,11 +109,11 @@ export class GamemodeUpdateService extends ClientListener { this.updateGamemodeUpdateFunctions( 'updateNeighborFunctions', - event.message.updateNeighborFunctions || {}, + event.message.updateNeighborFunctions, ); this.updateGamemodeUpdateFunctions( 'updateOwnerFunctions', - event.message.updateOwnerFunctions || {}, + event.message.updateOwnerFunctions, ); } @@ -196,16 +196,20 @@ export class GamemodeUpdateService extends ClientListener { private updateGamemodeUpdateFunctions( storageVar: string, - functionSources: Record, + functionSources: GamemodeValuePair[], ) { - this.sp.storage[storageVar] = JSON.parse(JSON.stringify(functionSources)); - for (const propName of Object.keys(functionSources)) { + let functionSourcesRecord: Record = {}; + functionSources.forEach(pair => functionSourcesRecord[pair.name] = pair.content); + + this.sp.storage[storageVar] = functionSourcesRecord; + + for (const propName of Object.keys(functionSourcesRecord)) { try { (this.sp.storage[storageVar] as any)[propName] = new Function( 'ctx', (this.sp.storage[storageVar] as any)[propName], ); - const emptyFunction = functionSources[propName] === ''; + const emptyFunction = functionSourcesRecord[propName] === ''; if (emptyFunction) { delete (this.sp.storage[storageVar] as any)[propName]; logTrace(this, storageVar, propName, 'Added empty'); diff --git a/skymp5-client/src/services/services/magicSyncService.ts b/skymp5-client/src/services/services/magicSyncService.ts index 85ffb19a16..16a60ca575 100644 --- a/skymp5-client/src/services/services/magicSyncService.ts +++ b/skymp5-client/src/services/services/magicSyncService.ts @@ -45,7 +45,7 @@ export class MagicSyncService extends ClientListener { return; } - const animVariables = getAnimationVariablesFromActor(ac.getFormID()); + const animVariables = this.getAnimationVariablesFromActorConverted(ac.getFormID()); this.controller.emitter.emit("sendMessage", { message: { t: MsgType.UpdateAnimVariables, data: this.getUpdateAnimVariablesEventData(ac, animVariables) }, @@ -81,7 +81,7 @@ export class MagicSyncService extends ClientListener { let msg: SpellCastMsgData = this.lastSpellCastEventMsg; msg.interruptCast = true; - msg.actorAnimationVariables = getAnimationVariablesFromActor(remoteIdToLocalId(this.lastSpellCastEventMsg.caster)); + msg.actorAnimationVariables = this.getAnimationVariablesFromActorConverted(remoteIdToLocalId(this.lastSpellCastEventMsg.caster)); this.controller.emitter.emit("sendMessage", { message: { t: MsgType.SpellCast, data: msg }, @@ -106,11 +106,23 @@ export class MagicSyncService extends ClientListener { aimAngle: e.aimAngle, // @ts-expect-error (TODO: Remove in 2.10.0) aimHeading: e.aimHeading, - actorAnimationVariables: getAnimationVariablesFromActor(e.caster.getFormID()), + actorAnimationVariables: this.getAnimationVariablesFromActorConverted(e.caster.getFormID()), } return spellCastData; } + private getAnimationVariablesFromActorConverted(actorId: number) { + const animVars = getAnimationVariablesFromActor(actorId); + const booleans: ArrayBuffer = animVars.booleans; + const floats: ArrayBuffer = animVars.floats; + const integers: ArrayBuffer = animVars.integers; + return { + booleans: Array.from(new Uint8Array(booleans)), + floats: Array.from(new Uint8Array(floats)), + integers: Array.from(new Uint8Array(integers)), + } + } + private getUpdateAnimVariablesEventData(ac: Actor, animVariables: ActorAnimationVariables): UpdateAnimVariablesMessageMsgData { const animVarsData: UpdateAnimVariablesMessageMsgData = { actorRemoteId: localIdToRemoteId(ac.getFormID(), true), diff --git a/skymp5-client/src/services/services/networkingService.ts b/skymp5-client/src/services/services/networkingService.ts index 254ea846a2..a18a8649e3 100644 --- a/skymp5-client/src/services/services/networkingService.ts +++ b/skymp5-client/src/services/services/networkingService.ts @@ -79,118 +79,114 @@ export class NetworkingService extends ClientListener { // TODO: in theory can be empty jsonContent and non-empty error const msgAny: AnyMessage = JSON.parse(jsonContent); - if ("type" in msgAny) { - if (msgAny.type === "createActor") { - const event = { message: msgAny }; - this.controller.emitter.emit("createActorMessage", event); - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.type === "customPacket") { - const event = { message: msgAny }; - this.controller.emitter.emit("customPacketMessage2", event); - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.type === "destroyActor") { - const event = { message: msgAny }; - this.controller.emitter.emit("destroyActorMessage", event); - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.type === "hostStart") { - const event = { message: msgAny }; - this.controller.emitter.emit("hostStartMessage", event); - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.type === "hostStop") { - const event = { message: msgAny }; - this.controller.emitter.emit("hostStopMessage", event); - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.type === "setInventory") { - const event = { message: msgAny }; - this.controller.emitter.emit("setInventoryMessage", event); - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.type === "setRaceMenuOpen") { - const event = { message: msgAny }; - this.controller.emitter.emit("setRaceMenuOpenMessage", event); - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.type === "spSnippet") { - const event = { message: msgAny }; - this.controller.emitter.emit("spSnippetMessage", event); - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.type === "updateGamemodeData") { - const event = { message: msgAny }; - this.controller.emitter.emit("updateGamemodeDataMessage", event); - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.type === "teleport") { - const event = { message: msgAny }; - this.controller.emitter.emit("teleportMessage2", event); - this.controller.emitter.emit("anyMessage", event); - } - else { - throw new NeverError(msgAny); - } + if (msgAny.t === MsgType.OpenContainer) { + const event = { message: msgAny }; + this.controller.emitter.emit("openContainerMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.UpdateMovement) { + const event = { message: msgAny }; + this.controller.emitter.emit("updateMovementMessage", event) + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.UpdateAnimation) { + const event = { message: msgAny }; + this.controller.emitter.emit("updateAnimationMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.UpdateEquipment) { + const event = { message: msgAny }; + this.controller.emitter.emit("updateEquipmentMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.ChangeValues) { + const event = { message: msgAny }; + this.controller.emitter.emit("changeValuesMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.SpellCast) { + const event = { message: msgAny }; + this.controller.emitter.emit("spellCastMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.UpdateAnimVariables) { + const event = { message: msgAny }; + this.controller.emitter.emit("updateAnimVariablesMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.UpdateAppearance) { + const event = { message: msgAny }; + this.controller.emitter.emit("updateAppearanceMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.Teleport) { + const event = { message: msgAny }; + this.controller.emitter.emit("teleportMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.UpdateProperty) { + const event = { message: msgAny }; + this.controller.emitter.emit("updatePropertyMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.DeathStateContainer) { + const event = { message: msgAny }; + this.controller.emitter.emit("deathStateContainerMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.CreateActor) { + const event = { message: msgAny }; + this.controller.emitter.emit("createActorMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.CustomPacket) { + const event = { message: msgAny }; + this.controller.emitter.emit("customPacketMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.DestroyActor) { + const event = { message: msgAny }; + this.controller.emitter.emit("destroyActorMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.HostStart) { + const event = { message: msgAny }; + this.controller.emitter.emit("hostStartMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.HostStop) { + const event = { message: msgAny }; + this.controller.emitter.emit("hostStopMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.SetInventory) { + const event = { message: msgAny }; + this.controller.emitter.emit("setInventoryMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.SetRaceMenuOpen) { + const event = { message: msgAny }; + this.controller.emitter.emit("setRaceMenuOpenMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.SpSnippet) { + const event = { message: msgAny }; + this.controller.emitter.emit("spSnippetMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.UpdateGamemodeData) { + const event = { message: msgAny }; + this.controller.emitter.emit("updateGamemodeDataMessage", event); + this.controller.emitter.emit("anyMessage", event); + } + else if (msgAny.t === MsgType.Teleport2) { + const event = { message: msgAny }; + this.controller.emitter.emit("teleportMessage2", event); + this.controller.emitter.emit("anyMessage", event); } else { - if (msgAny.t === MsgType.OpenContainer) { - const event = { message: msgAny }; - this.controller.emitter.emit("openContainerMessage", event); - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.t === MsgType.UpdateMovement) { - const event = { message: msgAny }; - this.controller.emitter.emit("updateMovementMessage", event) - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.t === MsgType.UpdateAnimation) { - const event = { message: msgAny }; - this.controller.emitter.emit("updateAnimationMessage", event); - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.t === MsgType.UpdateEquipment) { - const event = { message: msgAny }; - this.controller.emitter.emit("updateEquipmentMessage", event); - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.t === MsgType.ChangeValues) { - const event = { message: msgAny }; - this.controller.emitter.emit("changeValuesMessage", event); - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.t === MsgType.SpellCast) { - const event = { message: msgAny }; - this.controller.emitter.emit("spellCastMessage", event); - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.t === MsgType.UpdateAnimVariables) { - const event = { message: msgAny }; - this.controller.emitter.emit("updateAnimVariablesMessage", event); - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.t === MsgType.UpdateAppearance) { - const event = { message: msgAny }; - this.controller.emitter.emit("updateAppearanceMessage", event); - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.t === MsgType.Teleport) { - const event = { message: msgAny }; - this.controller.emitter.emit("teleportMessage", event); - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.t === MsgType.UpdateProperty) { - const event = { message: msgAny }; - this.controller.emitter.emit("updatePropertyMessage", event); - this.controller.emitter.emit("anyMessage", event); - } - else if (msgAny.t === MsgType.DeathStateContainer) { - const event = { message: msgAny }; - this.controller.emitter.emit("deathStateContainerMessage", event); - this.controller.emitter.emit("anyMessage", event); - } - // todo: never error + // throw new NeverError(msgAny); + throw new Error("Unhandled MsgType " + msgAny.t); } break; } diff --git a/skymp5-client/src/services/services/remoteServer.ts b/skymp5-client/src/services/services/remoteServer.ts index 30ccf59e41..09204d6d37 100644 --- a/skymp5-client/src/services/services/remoteServer.ts +++ b/skymp5-client/src/services/services/remoteServer.ts @@ -30,7 +30,7 @@ import { ModelApplyUtils } from '../../view/modelApplyUtils'; import { FormModel, WorldModel } from '../../view/model'; import { LoadGameService } from './loadGameService'; import { UpdateMovementMessage } from '../messages/updateMovementMessage'; -import { ChangeValuesMessage } from '../messages/changeValues'; +import { ChangeValuesMessage } from '../messages/changeValuesMessage'; import { UpdateAnimationMessage } from '../messages/updateAnimationMessage'; import { UpdateEquipmentMessage } from '../messages/updateEquipmentMessage'; import { RagdollService } from './ragdollService'; @@ -62,6 +62,7 @@ import { logTrace, logError } from '../../logging'; import { SpellCastMessage } from '../messages/spellCastMessage'; import { UpdateAnimVariablesMessage } from '../messages/updateAnimVariablesMessage'; +import { MsgType } from '../../messages'; export const getPcInventory = (): Inventory | undefined => { const res = storage['pcInv']; @@ -385,11 +386,26 @@ export class RemoteServer extends ClientListener { if (msg.props) { for (const propName in msg.props) { - const i = this.getIdManager().getId(msg.idx); (form as Record)[propName] = msg.props[propName as keyof CreateActorMessageAdditionalProps]; } } + msg.customPropsJsonDumps.forEach(element => { + let parsed: unknown; + try { + parsed = JSON.parse(element.propValueJsonDump); + } + catch (e) { + if (e instanceof SyntaxError) { + logError(this, "createActor", msg.refrId?.toString(16), "failed to parse custom prop", element.propName, element.propValueJsonDump, e.message); + } + else { + throw e; + } + } + (form as Record)[element.propName] = parsed; + }); + if (msg.isMe) { this.worldModel.playerCharacterFormIdx = i; this.worldModel.playerCharacterRefrId = msg.refrId || 0; @@ -400,8 +416,9 @@ export class RemoteServer extends ClientListener { if (msg.props && !msg.props.isHostedByOther) { } - if (msg.props && msg.props.isRaceMenuOpen && msg.isMe) - this.onSetRaceMenuOpenMessage({ message: { type: 'setRaceMenuOpen', open: true } }); + if (msg.props && msg.props.isRaceMenuOpen && msg.isMe) { + this.onSetRaceMenuOpenMessage({ message: { t: MsgType.SetRaceMenuOpen, open: true } }); + } const numSetInventory = this.numSetInventory; @@ -418,7 +435,7 @@ export class RemoteServer extends ClientListener { if (msg.props && msg.props.inventory) { this.onSetInventoryMessage({ message: { - type: 'setInventory', + t: MsgType.SetInventory, inventory: msg.props.inventory } }); @@ -903,9 +920,9 @@ export class RemoteServer extends ClientListener { if (!ac) return; const actorAnimationVariables: ActorAnimationVariables = { - booleans: new Uint8Array(Object.values(msg.data.actorAnimationVariables.booleans)), - floats: new Uint8Array(Object.values(msg.data.actorAnimationVariables.floats)), - integers: new Uint8Array(Object.values(msg.data.actorAnimationVariables.integers)) + booleans: new Uint8Array(msg.data.actorAnimationVariables.booleans), + floats: new Uint8Array(msg.data.actorAnimationVariables.floats), + integers: new Uint8Array(msg.data.actorAnimationVariables.integers) }; if (msg.data.interruptCast) { @@ -929,9 +946,9 @@ export class RemoteServer extends ClientListener { if (!ac) return; const actorAnimationVariables: ActorAnimationVariables = { - booleans: new Uint8Array(Object.values(msg.data.actorAnimationVariables.booleans)), - floats: new Uint8Array(Object.values(msg.data.actorAnimationVariables.floats)), - integers: new Uint8Array(Object.values(msg.data.actorAnimationVariables.integers)) + booleans: new Uint8Array(msg.data.actorAnimationVariables.booleans), + floats: new Uint8Array(msg.data.actorAnimationVariables.floats), + integers: new Uint8Array(msg.data.actorAnimationVariables.integers) }; const isApplyed = applyAnimationVariablesToActor(ac.getFormID(), actorAnimationVariables); diff --git a/skymp5-client/src/services/services/sendInputsService.ts b/skymp5-client/src/services/services/sendInputsService.ts index 6d1ba97049..6302b7b54e 100644 --- a/skymp5-client/src/services/services/sendInputsService.ts +++ b/skymp5-client/src/services/services/sendInputsService.ts @@ -16,7 +16,7 @@ import { nextHostAttempt } from "../../view/hostAttempts"; import { SkympClient } from "./skympClient"; import { MessageWithRefrId } from "../events/sendMessageWithRefrIdEvent"; import { UpdateMovementMessage } from "../messages/updateMovementMessage"; -import { ChangeValuesMessage } from "../messages/changeValues"; +import { ChangeValuesMessage } from "../messages/changeValuesMessage"; import { UpdateAnimationMessage } from "../messages/updateAnimationMessage"; import { UpdateEquipmentMessage } from "../messages/updateEquipmentMessage"; import { UpdateAppearanceMessage } from "../messages/updateAppearanceMessage"; diff --git a/skymp5-client/src/services/services/spSnippetService.ts b/skymp5-client/src/services/services/spSnippetService.ts index 614b83132d..beb8ddd764 100644 --- a/skymp5-client/src/services/services/spSnippetService.ts +++ b/skymp5-client/src/services/services/spSnippetService.ts @@ -32,11 +32,23 @@ export class SpSnippetService extends ClientListener { res = null; } - const message: FinishSpSnippetMessage = { + if (res !== null + && typeof res !== "number" + && typeof res !== "string" + && typeof res !== "boolean") { + logError(this, `Unsupported SpSnippet result type '${typeof res}'`) + return; + } + + const message: FinishSpSnippetMessage = res === null ? { t: MsgType.FinishSpSnippet, - returnValue: res, - snippetIdx: msg.snippetIdx, + snippetIdx: msg.snippetIdx } + : { + t: MsgType.FinishSpSnippet, + returnValue: res, + snippetIdx: msg.snippetIdx, + } this.controller.emitter.emit("sendMessage", { message: message, @@ -49,7 +61,7 @@ export class SpSnippetService extends ClientListener { }); } - private async run(snippet: SpSnippetMessage): Promise { + private async run(snippet: SpSnippetMessage): Promise { const functionLowerCase = snippet.function.toLowerCase(); const classLowerCase = snippet.class.toLowerCase(); @@ -164,7 +176,7 @@ export class SpSnippetService extends ClientListener { return arg; }; - private async runMethod(snippet: SpSnippetMessage): Promise { + private async runMethod(snippet: SpSnippetMessage): Promise { const selfId = remoteIdToLocalId(snippet.selfId); const self = this.sp.Game.getFormEx(selfId); if (!self) @@ -196,7 +208,7 @@ export class SpSnippetService extends ClientListener { ); }; - private async runStatic(snippet: SpSnippetMessage): Promise { + private async runStatic(snippet: SpSnippetMessage): Promise { const papyrusClass = this.spAny[snippet.class]; return await papyrusClass[snippet.function]( ...snippet.arguments.map((arg) => this.deserializeArg(arg)) diff --git a/skymp5-client/src/view/modelApplyUtils.ts b/skymp5-client/src/view/modelApplyUtils.ts index 1e65d91662..d8d6832920 100644 --- a/skymp5-client/src/view/modelApplyUtils.ts +++ b/skymp5-client/src/view/modelApplyUtils.ts @@ -1,6 +1,7 @@ import { ObjectReference, Actor, Game, FormType, TextureSet, NetImmerse } from "skyrimPlatform"; import { Inventory, applyInventory } from "../sync/inventory"; import { logError, logTrace } from "../logging"; +import { SetNodeScaleEntry, SetNodeTextureSetEntry } from "src/services/messages/createActorMessage"; // For 0xff000000+ used from FormView // For objects from master files used directly from remoteServer.ts @@ -96,31 +97,29 @@ export class ModelApplyUtils { } } - static applyModelNodeTextureSet(refr: ObjectReference, setNodeTextureSet?: Record) { + static applyModelNodeTextureSet(refr: ObjectReference, setNodeTextureSet?: SetNodeTextureSetEntry[]) { if (setNodeTextureSet) { - for (const key in setNodeTextureSet) { - const textureSetId = setNodeTextureSet[key]; + setNodeTextureSet.forEach(element => { const firstPerson = false; - const textureSet = TextureSet.from(Game.getFormEx(textureSetId)); + const textureSet = TextureSet.from(Game.getFormEx(element.textureSetId)); if (textureSet !== null) { - NetImmerse.setNodeTextureSet(refr, key, textureSet, firstPerson); - logTrace("ModelApplyUtils", refr.getFormID().toString(16), `Applied texture set`, textureSetId.toString(16), `to`, key); + NetImmerse.setNodeTextureSet(refr, element.nodeName, textureSet, firstPerson); + logTrace("ModelApplyUtils", refr.getFormID().toString(16), `Applied texture set`, element.textureSetId.toString(16), `to`, element.nodeName); } else { - logError("ModelApplyUtils", refr.getFormID().toString(16), `Failed to apply texture set`, textureSetId.toString(16), `to`, key); + logError("ModelApplyUtils", refr.getFormID().toString(16), `Failed to apply texture set`, element.textureSetId.toString(16), `to`, element.nodeName); } - } + }); } } - static applyModelNodeScale(refr: ObjectReference, setNodeScale?: Record) { + static applyModelNodeScale(refr: ObjectReference, setNodeScale?: SetNodeScaleEntry[]) { if (setNodeScale) { - for (const key in setNodeScale) { - const scale = setNodeScale[key]; + setNodeScale.forEach(element => { const firstPerson = false; - NetImmerse.setNodeScale(refr, key, scale, firstPerson); - logTrace("ModelApplyUtils", refr.getFormID().toString(16), `Applied node scale`, scale, `to`, key); - } + NetImmerse.setNodeScale(refr, element.nodeName, element.scale, firstPerson); + logTrace("ModelApplyUtils", refr.getFormID().toString(16), `Applied node scale`, element.scale, `to`, element.nodeName); + }); } } } diff --git a/skymp5-server/cpp/addon/property_bindings/AppearanceBinding.cpp b/skymp5-server/cpp/addon/property_bindings/AppearanceBinding.cpp index a54c78466e..bc14b93dd0 100644 --- a/skymp5-server/cpp/addon/property_bindings/AppearanceBinding.cpp +++ b/skymp5-server/cpp/addon/property_bindings/AppearanceBinding.cpp @@ -1,5 +1,6 @@ #include "AppearanceBinding.h" #include "NapiHelper.h" +#include "UpdateAppearanceMessage.h" Napi::Value AppearanceBinding::Get(Napi::Env env, ScampServer& scampServer, uint32_t formId) @@ -34,19 +35,14 @@ void AppearanceBinding::Set(Napi::Env env, ScampServer& scampServer, auto appearance = actor.GetAppearance(); - std::string msg; - msg += Networking::MinPacketId; - msg += nlohmann::json{ - { "data", - appearance ? nlohmann::json::parse(appearance->ToJson()) - : nlohmann::json{} }, - { "idx", actor.GetIdx() }, - { "t", MsgType::UpdateAppearance } - }.dump(); + UpdateAppearanceMessage message; + message.data = + appearance ? std::optional(*appearance) : std::nullopt; + message.idx = actor.GetIdx(); for (auto listener : actor.GetActorListeners()) { - // TODO: change to SendToUser - listener->SendToUserDeferred(msg.data(), msg.size(), true, - kChannelAppearance, false); + // TODO: change to SendToUser, probably was deferred only for ability to + // send text packets + listener->SendToUserDeferred(message, true, kChannelAppearance, false); } } diff --git a/skymp5-server/cpp/messages/ActivateMessage.h b/skymp5-server/cpp/messages/ActivateMessage.h new file mode 100644 index 0000000000..a20a2b14be --- /dev/null +++ b/skymp5-server/cpp/messages/ActivateMessage.h @@ -0,0 +1,35 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include +#include + +struct ActivateMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::Activate)>{}; + + struct Data + { + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType) + .Serialize("caster", caster) + .Serialize("target", target) + .Serialize("isSecondActivation", isSecondActivation); + } + + uint64_t caster = 0; + uint64_t target = 0; + bool isSecondActivation = false; + }; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType).Serialize("data", data); + } + + Data data; +}; diff --git a/skymp5-server/cpp/messages/ConsoleCommandMessage.h b/skymp5-server/cpp/messages/ConsoleCommandMessage.h new file mode 100644 index 0000000000..1bb914737c --- /dev/null +++ b/skymp5-server/cpp/messages/ConsoleCommandMessage.h @@ -0,0 +1,33 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include +#include + +struct ConsoleCommandMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::ConsoleCommand)>{}; + + struct Data + { + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType) + .Serialize("commandName", commandName) + .Serialize("args", args); + } + + std::string commandName; + std::vector> args; + }; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType).Serialize("data", data); + } + + Data data; +}; diff --git a/skymp5-server/cpp/messages/CraftItemMessage.h b/skymp5-server/cpp/messages/CraftItemMessage.h new file mode 100644 index 0000000000..1b6ffdecb8 --- /dev/null +++ b/skymp5-server/cpp/messages/CraftItemMessage.h @@ -0,0 +1,35 @@ +#pragma once +#include "../server_guest_lib/Inventory.h" +#include "MessageBase.h" +#include "MsgType.h" +#include + +struct CraftItemMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::CraftItem)>{}; + + struct Data + { + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType) + .Serialize("workbench", workbench) + .Serialize("craftInputObjects", craftInputObjects) + .Serialize("resultObjectId", resultObjectId); + } + + uint32_t workbench = 0; + Inventory craftInputObjects; + uint32_t resultObjectId = 0; + }; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType).Serialize("data", data); + } + + Data data; +}; diff --git a/skymp5-server/cpp/messages/CreateActorMessage.h b/skymp5-server/cpp/messages/CreateActorMessage.h new file mode 100644 index 0000000000..050cdac745 --- /dev/null +++ b/skymp5-server/cpp/messages/CreateActorMessage.h @@ -0,0 +1,173 @@ +#pragma once +#include "../server_guest_lib/AnimationData.h" +#include "../server_guest_lib/Appearance.h" +#include "../server_guest_lib/Equipment.h" +#include "../server_guest_lib/Inventory.h" +#include "MessageBase.h" +#include "MsgType.h" +#include +#include + +struct Transform +{ + template + void Serialize(Archive& archive) + { + archive.Serialize("worldOrCell", worldOrCell) + .Serialize("pos", pos) + .Serialize("rot", rot); + } + + uint32_t worldOrCell = 0; + std::array pos = { 0.f, 0.f, 0.f }; + std::array rot = { 0.f, 0.f, 0.f }; +}; + +struct SetNodeTextureSetEntry +{ + template + void Serialize(Archive& archive) + { + archive.Serialize("nodeName", nodeName) + .Serialize("textureSetId", textureSetId); + } + + std::string nodeName; + uint32_t textureSetId = 0; +}; + +struct SetNodeScaleEntry +{ + template + void Serialize(Archive& archive) + { + archive.Serialize("nodeName", nodeName).Serialize("scale", scale); + } + + std::string nodeName; + float scale = 0.f; +}; + +struct CustomPropsEntry +{ + template + void Serialize(Archive& archive) + { + archive.Serialize("propName", propName) + .Serialize("propValueJsonDump", propValueJsonDump); + } + + std::string propName; + std::string propValueJsonDump; +}; + +struct CreateActorMessageAdditionalProps +{ + template + void Serialize(Archive& archive) + { + archive.Serialize("isOpen", isOpen) + .Serialize("isHarvested", isHarvested) + .Serialize("setNodeTextureSet", setNodeTextureSet) + .Serialize("setNodeScale", setNodeScale) + .Serialize("isDisabled", isDisabled) + .Serialize("lastAnimation", lastAnimation) + .Serialize("displayName", displayName) + .Serialize("isHostedByOther", isHostedByOther) + .Serialize("isRaceMenuOpen", isRaceMenuOpen) + .Serialize("learnedSpells", learnedSpells) + .Serialize("healRate", healRate) + .Serialize("healRateMult", healRateMult) + .Serialize("health", health) + .Serialize("magickaRate", magickaRate) + .Serialize("magickaRateMult", magickaRateMult) + .Serialize("magicka", magicka) + .Serialize("staminaRate", staminaRate) + .Serialize("staminaRateMult", staminaRateMult) + .Serialize("stamina", stamina) + .Serialize("healthPercentage", healthPercentage) + .Serialize("staminaPercentage", staminaPercentage) + .Serialize("magickaPercentage", magickaPercentage) + .Serialize("templateChain", templateChain) + .Serialize("inventory", inventory) + .Serialize("isDead", isDead); + } + + std::optional isOpen; + std::optional isHarvested; + std::optional> setNodeTextureSet; + std::optional> setNodeScale; + std::optional isDisabled; + std::optional lastAnimation; + std::optional displayName; + std::optional isHostedByOther; + std::optional isRaceMenuOpen; + std::optional> learnedSpells; + std::optional healRate; + std::optional healRateMult; + std::optional health; + std::optional magickaRate; + std::optional magickaRateMult; + std::optional magicka; + std::optional staminaRate; + std::optional staminaRateMult; + std::optional stamina; + std::optional healthPercentage; + std::optional staminaPercentage; + std::optional magickaPercentage; + std::optional> templateChain; + std::optional inventory; + + // TODO: take a look why doubles CreateActorMessageMainProps + std::optional isDead; +}; + +struct CreateActorMessageMainProps +{ + template + void Serialize(Archive& archive) + { + archive.Serialize("refrId", refrId) + .Serialize("baseId", baseId) + .Serialize("appearance", appearance) + .Serialize("equipment", equipment) + .Serialize("animation", animation) + .Serialize("isDead", isDead); + } + + std::optional refrId; + std::optional baseId = 0; + std::optional appearance; + std::optional equipment; + std::optional animation; + std::optional isDead; +}; + +struct CreateActorMessage + : public MessageBase + , public CreateActorMessageMainProps +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::CreateActor)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType) + .Serialize("idx", idx) + .Serialize("baseRecordType", baseRecordType) + .Serialize("transform", transform) + .Serialize("isMe", isMe) + .Serialize("props", props) + .Serialize("customPropsJsonDumps", customPropsJsonDumps); + + CreateActorMessageMainProps::Serialize(archive); + } + + uint32_t idx = 0; + std::optional baseRecordType; + Transform transform; + bool isMe = false; + CreateActorMessageAdditionalProps props; + std::vector customPropsJsonDumps; +}; diff --git a/skymp5-server/cpp/messages/CustomEventMessage.h b/skymp5-server/cpp/messages/CustomEventMessage.h new file mode 100644 index 0000000000..7cc2c0e8df --- /dev/null +++ b/skymp5-server/cpp/messages/CustomEventMessage.h @@ -0,0 +1,21 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include + +struct CustomEventMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::CustomEvent)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType) + .Serialize("argsJsonDumps", argsJsonDumps) + .Serialize("eventName", eventName); + } + + std::vector argsJsonDumps; + std::string eventName; +}; diff --git a/skymp5-server/cpp/messages/CustomPacketMessage.h b/skymp5-server/cpp/messages/CustomPacketMessage.h new file mode 100644 index 0000000000..0299e1b56d --- /dev/null +++ b/skymp5-server/cpp/messages/CustomPacketMessage.h @@ -0,0 +1,19 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include + +struct CustomPacketMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::CustomPacket)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType) + .Serialize("contentJsonDump", contentJsonDump); + } + + std::string contentJsonDump; +}; diff --git a/skymp5-server/cpp/messages/DestroyActorMessage.h b/skymp5-server/cpp/messages/DestroyActorMessage.h new file mode 100644 index 0000000000..34ca8cfd32 --- /dev/null +++ b/skymp5-server/cpp/messages/DestroyActorMessage.h @@ -0,0 +1,18 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include + +struct DestroyActorMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::DestroyActor)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType).Serialize("idx", idx); + } + + uint32_t idx = 0; +}; diff --git a/skymp5-server/cpp/messages/DropItemMessage.h b/skymp5-server/cpp/messages/DropItemMessage.h new file mode 100644 index 0000000000..7e8129deb7 --- /dev/null +++ b/skymp5-server/cpp/messages/DropItemMessage.h @@ -0,0 +1,21 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include + +struct DropItemMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::DropItem)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType) + .Serialize("baseId", baseId) + .Serialize("count", count); + } + + uint64_t baseId = 0; + uint32_t count = 0; +}; diff --git a/skymp5-server/cpp/messages/FinishSpSnippetMessage.h b/skymp5-server/cpp/messages/FinishSpSnippetMessage.h new file mode 100644 index 0000000000..876ef0c8cc --- /dev/null +++ b/skymp5-server/cpp/messages/FinishSpSnippetMessage.h @@ -0,0 +1,22 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include + +struct FinishSpSnippetMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::FinishSpSnippet)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType) + .Serialize("returnValue", returnValue) + .Serialize("snippetIdx", snippetIdx); + } + + std::optional> returnValue; + int64_t snippetIdx = 0; +}; diff --git a/skymp5-server/cpp/messages/HitMessage.h b/skymp5-server/cpp/messages/HitMessage.h new file mode 100644 index 0000000000..b7a5b69f99 --- /dev/null +++ b/skymp5-server/cpp/messages/HitMessage.h @@ -0,0 +1,43 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include + +struct HitMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::OnHit)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType).Serialize("data", data); + } + + struct Data + { + template + void Serialize(Archive& archive) + { + archive.Serialize("taggressor", aggressor) + .Serialize("isBashAttack", isBashAttack) + .Serialize("isHitBlocked", isHitBlocked) + .Serialize("isPowerAttack", isPowerAttack) + .Serialize("isSneakAttack", isSneakAttack) + .Serialize("projectile", projectile) + .Serialize("source", source) + .Serialize("target", target); + } + + uint32_t aggressor = 0; + bool isBashAttack = false; + bool isHitBlocked = false; + bool isPowerAttack = false; + bool isSneakAttack = false; + uint32_t projectile = 0; + uint32_t source = 0; + uint32_t target = 0; + }; + + Data data; +}; diff --git a/skymp5-server/cpp/messages/HostMessage.h b/skymp5-server/cpp/messages/HostMessage.h new file mode 100644 index 0000000000..eed1325398 --- /dev/null +++ b/skymp5-server/cpp/messages/HostMessage.h @@ -0,0 +1,18 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include + +struct HostMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::Host)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType).Serialize("remoteId", remoteId); + } + + uint64_t remoteId = 0; +}; diff --git a/skymp5-server/cpp/messages/HostStartMessage.h b/skymp5-server/cpp/messages/HostStartMessage.h new file mode 100644 index 0000000000..9ac859d96c --- /dev/null +++ b/skymp5-server/cpp/messages/HostStartMessage.h @@ -0,0 +1,18 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include + +struct HostStartMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::HostStart)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType).Serialize("target", target); + } + + uint64_t target = 0; +}; diff --git a/skymp5-server/cpp/messages/HostStopMessage.h b/skymp5-server/cpp/messages/HostStopMessage.h new file mode 100644 index 0000000000..8c552877bd --- /dev/null +++ b/skymp5-server/cpp/messages/HostStopMessage.h @@ -0,0 +1,18 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include + +struct HostStopMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::HostStop)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType).Serialize("target", target); + } + + uint64_t target = 0; +}; diff --git a/skymp5-server/cpp/messages/Messages.h b/skymp5-server/cpp/messages/Messages.h index 87162a17a0..d85db1ab78 100644 --- a/skymp5-server/cpp/messages/Messages.h +++ b/skymp5-server/cpp/messages/Messages.h @@ -1,14 +1,48 @@ #pragma once +#include "ActivateMessage.h" #include "ChangeValuesMessage.h" +#include "ConsoleCommandMessage.h" +#include "CraftItemMessage.h" +#include "CreateActorMessage.h" +#include "CustomEventMessage.h" +#include "CustomPacketMessage.h" #include "DeathStateContainerMessage.h" +#include "DestroyActorMessage.h" +#include "DropItemMessage.h" +#include "FinishSpSnippetMessage.h" +#include "HitMessage.h" +#include "HostMessage.h" +#include "HostStartMessage.h" +#include "HostStopMessage.h" +#include "OnEquipMessage.h" #include "OpenContainerMessage.h" +#include "PlayerBowShotMessage.h" +#include "PutItemMessage.h" +#include "SetInventoryMessage.h" +#include "SetRaceMenuOpenMessage.h" +#include "SpSnippetMessage.h" +#include "SpellCastMessage.h" +#include "TakeItemMessage.h" #include "TeleportMessage.h" +#include "TeleportMessage2.h" +#include "UpdateAnimVariablesMessage.h" #include "UpdateAnimationMessage.h" +#include "UpdateAppearanceMessage.h" #include "UpdateEquipmentMessage.h" +#include "UpdateGameModeDataMessage.h" #include "UpdateMovementMessage.h" #include "UpdatePropertyMessage.h" #define REGISTER_MESSAGES \ + REGISTER_MESSAGE(ActivateMessage) \ + REGISTER_MESSAGE(ConsoleCommandMessage) \ + REGISTER_MESSAGE(CraftItemMessage) \ + REGISTER_MESSAGE(CustomEventMessage) \ + REGISTER_MESSAGE(DestroyActorMessage) \ + REGISTER_MESSAGE(DropItemMessage) \ + REGISTER_MESSAGE(FinishSpSnippetMessage) \ + REGISTER_MESSAGE(HitMessage) \ + REGISTER_MESSAGE(HostMessage) \ REGISTER_MESSAGE(UpdateMovementMessage) \ REGISTER_MESSAGE(UpdateAnimationMessage) \ REGISTER_MESSAGE(DeathStateContainerMessage) \ @@ -16,4 +50,20 @@ REGISTER_MESSAGE(TeleportMessage) \ REGISTER_MESSAGE(UpdatePropertyMessage) \ REGISTER_MESSAGE(OpenContainerMessage) \ - REGISTER_MESSAGE(UpdateEquipmentMessage) + REGISTER_MESSAGE(UpdateEquipmentMessage) \ + REGISTER_MESSAGE(CustomPacketMessage) \ + REGISTER_MESSAGE(HostStartMessage) \ + REGISTER_MESSAGE(HostStopMessage) \ + REGISTER_MESSAGE(OnEquipMessage) \ + REGISTER_MESSAGE(PlayerBowShotMessage) \ + REGISTER_MESSAGE(PutItemMessage) \ + REGISTER_MESSAGE(SetInventoryMessage) \ + REGISTER_MESSAGE(SetRaceMenuOpenMessage) \ + REGISTER_MESSAGE(SpellCastMessage) \ + REGISTER_MESSAGE(SpSnippetMessage) \ + REGISTER_MESSAGE(TakeItemMessage) \ + REGISTER_MESSAGE(TeleportMessage2) \ + REGISTER_MESSAGE(UpdateAnimVariablesMessage) \ + REGISTER_MESSAGE(UpdateAppearanceMessage) \ + REGISTER_MESSAGE(UpdateGameModeDataMessage) \ + REGISTER_MESSAGE(CreateActorMessage) diff --git a/skymp5-server/cpp/messages/MinPacketId.h b/skymp5-server/cpp/messages/MinPacketId.h index f808a154ee..6cf5fe8b0d 100644 --- a/skymp5-server/cpp/messages/MinPacketId.h +++ b/skymp5-server/cpp/messages/MinPacketId.h @@ -1,3 +1,4 @@ +#pragma once namespace Networking { constexpr unsigned char MinPacketId = 134; } diff --git a/skymp5-server/cpp/messages/MsgType.h b/skymp5-server/cpp/messages/MsgType.h index 5595019db3..0679f08ee8 100644 --- a/skymp5-server/cpp/messages/MsgType.h +++ b/skymp5-server/cpp/messages/MsgType.h @@ -29,5 +29,16 @@ enum class MsgType : uint8_t SpellCast = 23, UpdateAnimVariables = 24, + // ex-strings + DestroyActor = 25, + HostStart = 26, + HostStop = 27, + SetInventory = 28, + SetRaceMenuOpen = 29, + SpSnippet = 30, + Teleport2 = 31, + UpdateGamemodeData = 32, + CreateActor = 33, + Max }; diff --git a/skymp5-server/cpp/messages/OnEquipMessage.h b/skymp5-server/cpp/messages/OnEquipMessage.h new file mode 100644 index 0000000000..8d2fd985ae --- /dev/null +++ b/skymp5-server/cpp/messages/OnEquipMessage.h @@ -0,0 +1,18 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include + +struct OnEquipMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::OnEquip)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType).Serialize("baseId", baseId); + } + + uint32_t baseId = 0; +}; diff --git a/skymp5-server/cpp/messages/OpenContainerMessage.h b/skymp5-server/cpp/messages/OpenContainerMessage.h index 3c738e07f5..fb8bedd570 100644 --- a/skymp5-server/cpp/messages/OpenContainerMessage.h +++ b/skymp5-server/cpp/messages/OpenContainerMessage.h @@ -1,7 +1,6 @@ #pragma once #include "MessageBase.h" #include "MsgType.h" -#include #include struct OpenContainerMessage : public MessageBase diff --git a/skymp5-server/cpp/messages/PlayerBowShotMessage.h b/skymp5-server/cpp/messages/PlayerBowShotMessage.h new file mode 100644 index 0000000000..c48a987442 --- /dev/null +++ b/skymp5-server/cpp/messages/PlayerBowShotMessage.h @@ -0,0 +1,25 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include + +struct PlayerBowShotMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::PlayerBowShot)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType) + .Serialize("weaponId", weaponId) + .Serialize("ammoId", ammoId) + .Serialize("power", power) + .Serialize("isSunGazing", isSunGazing); + } + + uint32_t weaponId = 0; + uint32_t ammoId = 0; + float power = 0.f; + bool isSunGazing = false; +}; diff --git a/skymp5-server/cpp/messages/PutItemMessage.h b/skymp5-server/cpp/messages/PutItemMessage.h new file mode 100644 index 0000000000..d33eca6e88 --- /dev/null +++ b/skymp5-server/cpp/messages/PutItemMessage.h @@ -0,0 +1,28 @@ +#pragma once +#include "../server_guest_lib/Inventory.h" +#include "MessageBase.h" +#include "MsgType.h" +#include + +struct PutItemMessage + : public MessageBase + , public Inventory::ExtraData +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::PutItem)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType) + .Serialize("baseId", baseId) + .Serialize("count", count) + .Serialize("target", target); + + ExtraData::Serialize(archive); + } + + uint32_t baseId = 0; + uint32_t count = 0; + uint32_t target = 0; +}; diff --git a/skymp5-server/cpp/messages/SetInventoryMessage.h b/skymp5-server/cpp/messages/SetInventoryMessage.h new file mode 100644 index 0000000000..7ee26e8254 --- /dev/null +++ b/skymp5-server/cpp/messages/SetInventoryMessage.h @@ -0,0 +1,19 @@ +#pragma once +#include "../server_guest_lib/Inventory.h" +#include "MessageBase.h" +#include "MsgType.h" +#include + +struct SetInventoryMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::SetInventory)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType).Serialize("inventory", inventory); + } + + Inventory inventory; +}; diff --git a/skymp5-server/cpp/messages/SetRaceMenuOpenMessage.h b/skymp5-server/cpp/messages/SetRaceMenuOpenMessage.h new file mode 100644 index 0000000000..bff8c258c9 --- /dev/null +++ b/skymp5-server/cpp/messages/SetRaceMenuOpenMessage.h @@ -0,0 +1,19 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include + +struct SetRaceMenuOpenMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::SetRaceMenuOpen)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType).Serialize("open", open); + } + + bool open = false; +}; diff --git a/skymp5-server/cpp/messages/SpSnippetMessage.h b/skymp5-server/cpp/messages/SpSnippetMessage.h new file mode 100644 index 0000000000..83549b032e --- /dev/null +++ b/skymp5-server/cpp/messages/SpSnippetMessage.h @@ -0,0 +1,41 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include + +struct SpSnippetObjectArgument +{ + template + void Serialize(Archive& archive) + { + archive.Serialize("formId", formId).Serialize("type", type); + } + + uint32_t formId = 0; + std::string type; +}; + +struct SpSnippetMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::SpSnippet)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType) + .Serialize("class", class_) + .Serialize("function", function) + .Serialize("arguments", arguments) + .Serialize("selfId", selfId) + .Serialize("snippetIdx", snippetIdx); + } + + std::string class_; + std::string function; + std::vector>> + arguments; + uint32_t selfId = 0; + int64_t snippetIdx = 0; +}; diff --git a/skymp5-server/cpp/messages/SpellCastMessage.h b/skymp5-server/cpp/messages/SpellCastMessage.h new file mode 100644 index 0000000000..d0c208e692 --- /dev/null +++ b/skymp5-server/cpp/messages/SpellCastMessage.h @@ -0,0 +1,61 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include + +struct ActorAnimationVariables +{ + std::vector booleans; + std::vector floats; + std::vector integers; + + template + void Serialize(Archive& archive) + { + archive.Serialize("booleans", booleans) + .Serialize("floats", floats) + .Serialize("integers", integers); + } +}; + +struct SpellCastMsgData +{ + template + void Serialize(Archive& archive) + { + archive.Serialize("caster", caster) + .Serialize("target", target) + .Serialize("spell", spell) + .Serialize("isDualCasting", isDualCasting) + .Serialize("interruptCast", interruptCast) + .Serialize("castingSource", castingSource) + .Serialize("aimAngle", aimAngle) + .Serialize("aimHeading", aimHeading) + .Serialize("actorAnimationVariables", actorAnimationVariables); + } + + uint32_t caster = 0; + uint32_t target = 0; + uint32_t spell = 0; + bool isDualCasting = false; + bool interruptCast = false; + int32_t castingSource = 0; + float aimAngle = 0.f; + float aimHeading = 0.f; + + ActorAnimationVariables actorAnimationVariables; +}; + +struct SpellCastMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::SpellCast)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType).Serialize("data", data); + } + + SpellCastMsgData data; +}; diff --git a/skymp5-server/cpp/messages/TakeItemMessage.h b/skymp5-server/cpp/messages/TakeItemMessage.h new file mode 100644 index 0000000000..34012a980b --- /dev/null +++ b/skymp5-server/cpp/messages/TakeItemMessage.h @@ -0,0 +1,28 @@ +#pragma once +#include "../server_guest_lib/Inventory.h" +#include "MessageBase.h" +#include "MsgType.h" +#include + +struct TakeItemMessage + : public MessageBase + , public Inventory::ExtraData +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::TakeItem)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType) + .Serialize("baseId", baseId) + .Serialize("count", count) + .Serialize("target", target); + + ExtraData::Serialize(archive); + } + + uint32_t baseId = 0; + uint32_t count = 0; + uint32_t target = 0; +}; diff --git a/skymp5-server/cpp/messages/TeleportMessage2.h b/skymp5-server/cpp/messages/TeleportMessage2.h new file mode 100644 index 0000000000..94ff83a120 --- /dev/null +++ b/skymp5-server/cpp/messages/TeleportMessage2.h @@ -0,0 +1,23 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include +#include + +struct TeleportMessage2 : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::Teleport2)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType) + .Serialize("pos", pos) + .Serialize("rot", rot) + .Serialize("worldOrCell", worldOrCell); + } + + std::array pos, rot; + uint32_t worldOrCell = 0; +}; diff --git a/skymp5-server/cpp/messages/UpdateAnimVariablesMessage.h b/skymp5-server/cpp/messages/UpdateAnimVariablesMessage.h new file mode 100644 index 0000000000..2bd49653b9 --- /dev/null +++ b/skymp5-server/cpp/messages/UpdateAnimVariablesMessage.h @@ -0,0 +1,34 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include "UpdateAnimVariablesMessage.h" // ActorAnimationVariables +#include + +struct UpdateAnimVariablesMessageMsgData +{ + template + void Serialize(Archive& archive) + { + archive.Serialize("actorRemoteId", actorRemoteId) + .Serialize("actorAnimationVariables", actorAnimationVariables); + } + + uint32_t actorRemoteId = 0; + ActorAnimationVariables actorAnimationVariables; +}; + +struct UpdateAnimVariablesMessage + : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::UpdateAnimVariables)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType).Serialize("data", data); + } + + UpdateAnimVariablesMessageMsgData data; +}; diff --git a/skymp5-server/cpp/messages/UpdateAnimationMessage.h b/skymp5-server/cpp/messages/UpdateAnimationMessage.h index de85d4b1a3..e400209bdd 100644 --- a/skymp5-server/cpp/messages/UpdateAnimationMessage.h +++ b/skymp5-server/cpp/messages/UpdateAnimationMessage.h @@ -1,4 +1,5 @@ #pragma once +#include "../server_guest_lib/AnimationData.h" #include "MessageBase.h" #include "MsgType.h" #include @@ -11,19 +12,6 @@ struct UpdateAnimationMessage : public MessageBase std::integral_constant(MsgType::UpdateAnimation)>{}; - struct Data - { - template - void Serialize(Archive& archive) - { - archive.Serialize("numChanges", numChanges) - .Serialize("animEventName", animEventName); - } - - uint32_t numChanges = 0; - std::string animEventName; - }; - template void Serialize(Archive& archive) { @@ -33,5 +21,5 @@ struct UpdateAnimationMessage : public MessageBase } uint32_t idx = 0; - Data data; + AnimationData data; }; diff --git a/skymp5-server/cpp/messages/UpdateAppearanceMessage.h b/skymp5-server/cpp/messages/UpdateAppearanceMessage.h new file mode 100644 index 0000000000..0930cea072 --- /dev/null +++ b/skymp5-server/cpp/messages/UpdateAppearanceMessage.h @@ -0,0 +1,26 @@ +#pragma once + +#include "../server_guest_lib/Appearance.h" +#include "MessageBase.h" +#include "MsgType.h" +#include +#include +#include + +struct UpdateAppearanceMessage : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::UpdateAppearance)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType) + .Serialize("idx", idx) + .Serialize("data", data); + } + + uint32_t idx = 0; + std::optional data; +}; diff --git a/skymp5-server/cpp/messages/UpdateGameModeDataMessage.h b/skymp5-server/cpp/messages/UpdateGameModeDataMessage.h new file mode 100644 index 0000000000..1d373c0cd7 --- /dev/null +++ b/skymp5-server/cpp/messages/UpdateGameModeDataMessage.h @@ -0,0 +1,38 @@ +#pragma once +#include "MessageBase.h" +#include "MsgType.h" +#include +#include + +struct GamemodeValuePair +{ + template + void Serialize(Archive& archive) + { + archive.Serialize("name", name).Serialize("content", content); + } + + std::string name; + std::string content; +}; + +struct UpdateGameModeDataMessage + : public MessageBase +{ + static constexpr auto kMsgType = + std::integral_constant(MsgType::UpdateGamemodeData)>{}; + + template + void Serialize(Archive& archive) + { + archive.Serialize("t", kMsgType) + .Serialize("eventSources", eventSources) + .Serialize("updateOwnerFunctions", updateOwnerFunctions) + .Serialize("updateNeighborFunctions", updateNeighborFunctions); + } + + std::vector eventSources; + std::vector updateOwnerFunctions; + std::vector updateNeighborFunctions; +}; diff --git a/skymp5-server/cpp/mp_common/Config.h b/skymp5-server/cpp/mp_common/Config.h index 4df95fd331..43121f0b6b 100644 --- a/skymp5-server/cpp/mp_common/Config.h +++ b/skymp5-server/cpp/mp_common/Config.h @@ -5,7 +5,7 @@ constexpr auto kMaxPlayers = MAX_PLAYERS; // This value should be increased each time messaging protocol changes // regardless of is it a major or minor change. Every change is considered // incompatible to keep protocol versions system maintainable. -constexpr auto kMessagingProtocolVersion = "4_"; +constexpr auto kMessagingProtocolVersion = "5_"; // Users with kMessagingProtocolVersion different to server's one must not be // able to connect. So we use this value as SLikeNet password by default. diff --git a/skymp5-server/cpp/mp_common/NetworkingInterface.h b/skymp5-server/cpp/mp_common/NetworkingInterface.h index bc2dd4488e..e40c1c88e6 100644 --- a/skymp5-server/cpp/mp_common/NetworkingInterface.h +++ b/skymp5-server/cpp/mp_common/NetworkingInterface.h @@ -60,28 +60,4 @@ class IServer : public ISendTarget virtual std::string GetIp(UserId userId) const = 0; }; - -template -inline void Format(const FormatCallback& cb, const char* format, Ts... args) -{ - auto textSize = (size_t)snprintf(nullptr, 0, format, args...); - - const auto n = textSize + sizeof('\0') + sizeof(MinPacketId); - std::vector buf(n); - - buf[0] = MinPacketId; - auto len = (size_t)snprintf(buf.data() + 1, n - 1, format, args...); - - cb(reinterpret_cast(buf.data()), len + 1); -} - -template -inline void SendFormatted(Networking::ISendTarget* sendTarget, - Networking::UserId userId, const char* format, - Ts... args) -{ - Format([&](Networking::PacketData data, - size_t length) { sendTarget->Send(userId, data, length, true); }, - format, args...); -} } diff --git a/skymp5-server/cpp/server_guest_lib/ActionListener.cpp b/skymp5-server/cpp/server_guest_lib/ActionListener.cpp index 0c0434e7dd..094cb1aa8a 100644 --- a/skymp5-server/cpp/server_guest_lib/ActionListener.cpp +++ b/skymp5-server/cpp/server_guest_lib/ActionListener.cpp @@ -11,6 +11,7 @@ #include "MovementValidation.h" #include "MpObjectReference.h" #include "MsgType.h" +#include "Overloaded.h" #include "UserMessageOutput.h" #include "WorldState.h" #include "gamemode_events/CraftEvent.h" @@ -22,12 +23,14 @@ #include #include +#include "HostStartMessage.h" +#include "HostStopMessage.h" #include "UpdateEquipmentMessage.h" -MpActor* ActionListener::SendToNeighbours( - uint32_t idx, const simdjson::dom::element& jMessage, - Networking::UserId userId, Networking::PacketData data, size_t length, - bool reliable) +MpActor* ActionListener::SendToNeighbours(uint32_t idx, + Networking::UserId userId, + Networking::PacketData data, + size_t length, bool reliable) { MpActor* myActor = partOne.serverState.ActorByUser(userId); // The old behavior is doing nothing in that case. This is covered by tests @@ -87,9 +90,8 @@ MpActor* ActionListener::SendToNeighbours(uint32_t idx, const RawMessageData& rawMsgData, bool reliable) { - return SendToNeighbours(idx, rawMsgData.parsed, rawMsgData.userId, - rawMsgData.unparsed, rawMsgData.unparsedLength, - reliable); + return SendToNeighbours(idx, rawMsgData.userId, rawMsgData.unparsed, + rawMsgData.unparsedLength, reliable); } void ActionListener::OnCustomPacket(const RawMessageData& rawMsgData, @@ -418,37 +420,40 @@ void ActionListener::OnPlayerBowShot(const RawMessageData& rawMsgData, namespace { -VarValue VarValueFromJson(const simdjson::dom::element& parentMsg, - const simdjson::dom::element& element) +VarValue VarValueFromSpSnippetReturnValue( + const std::optional>& returnValue) { static const auto kKey = JsonPointer("returnValue"); - // TODO: DOUBLE, STRING ... - switch (element.type()) { - case simdjson::dom::element_type::INT64: - case simdjson::dom::element_type::UINT64: { - int32_t v; - ReadEx(parentMsg, kKey, &v); - return VarValue(v); - } - case simdjson::dom::element_type::BOOL: { - bool v; - ReadEx(parentMsg, kKey, &v); - return VarValue(v); - } - case simdjson::dom::element_type::NULL_VALUE: - return VarValue::None(); - default: - break; - } - throw std::runtime_error("VarValueFromJson - Unsupported json type " + - std::to_string(static_cast(element.type()))); + if (!returnValue) { + return VarValue::None(); + } + + return std::visit( + Viet::Overloaded{ + [&](bool v) { return VarValue(v); }, + [&](double v) { + // TODO: consider removing std::floor and logs after careful test + // because Papyrus VM should support mixed arithmetics, so we can + // always pass double + auto rounded = static_cast(std::floor(v)); + if (std::abs(std::floor(v) - v) > + std::numeric_limits::epsilon()) { + spdlog::error( + "VarValueFromSpSnippetReturnValue - Floating point values are not " + "yet supported, rounding down ({} -> {})", + v, rounded); + } + return VarValue(rounded); + }, + [&](const std::string& v) { return VarValue(v); } }, + *returnValue); } } -void ActionListener::OnFinishSpSnippet(const RawMessageData& rawMsgData, - uint32_t snippetIdx, - simdjson::dom::element& returnValue) +void ActionListener::OnFinishSpSnippet( + const RawMessageData& rawMsgData, uint32_t snippetIdx, + const std::optional>& returnValue) { MpActor* actor = partOne.serverState.ActorByUser(rawMsgData.userId); if (!actor) @@ -457,7 +462,7 @@ void ActionListener::OnFinishSpSnippet(const RawMessageData& rawMsgData, std::to_string(rawMsgData.userId)); actor->ResolveSnippet(snippetIdx, - VarValueFromJson(rawMsgData.parsed, returnValue)); + VarValueFromSpSnippetReturnValue(returnValue)); } void ActionListener::OnEquip(const RawMessageData& rawMsgData, uint32_t baseId) @@ -610,9 +615,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 @@ -638,17 +643,17 @@ 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); } } } } -void ActionListener::OnCustomEvent(const RawMessageData& rawMsgData, - const char* eventName, - simdjson::dom::element& args) +void ActionListener::OnCustomEvent( + const RawMessageData& rawMsgData, const char* eventName, + const std::vector& argsJsonDumps) { auto ac = partOne.serverState.ActorByUser(rawMsgData.userId); if (!ac) { @@ -659,9 +664,16 @@ void ActionListener::OnCustomEvent(const RawMessageData& rawMsgData, return; } + nlohmann::json jsonArray = nlohmann::json::array(); + + for (auto& arg : argsJsonDumps) { + jsonArray.push_back(nlohmann::json::parse(arg)); + } + + const std::string jsonArrayDump = jsonArray.dump(); + for (auto& listener : partOne.GetListeners()) { - CustomEvent customEvent(ac->GetFormId(), eventName, - simdjson::minify(args)); + CustomEvent customEvent(ac->GetFormId(), eventName, jsonArrayDump); listener->OnMpApiEvent(customEvent); } } @@ -1144,7 +1156,7 @@ void ActionListener::OnSpellCast(const RawMessageData& rawMsgData, SendToNeighbours(myActor->idx, rawMsgData); - if (spellCastData.isInterruptCast) { + if (spellCastData.interruptCast) { return; } @@ -1190,9 +1202,3 @@ void ActionListener::OnSpellCast(const RawMessageData& rawMsgData, spellCastData.target, spellCastData.spell, damage, spellCastData.caster, static_cast(spellCastData.castingSource)); } - -void ActionListener::OnUnknown(const RawMessageData& rawMsgData) -{ - spdlog::error("Got unhandled message: {}", - simdjson::minify(rawMsgData.parsed)); -} diff --git a/skymp5-server/cpp/server_guest_lib/ActionListener.h b/skymp5-server/cpp/server_guest_lib/ActionListener.h index b024dc4e8a..c34b76db06 100644 --- a/skymp5-server/cpp/server_guest_lib/ActionListener.h +++ b/skymp5-server/cpp/server_guest_lib/ActionListener.h @@ -19,7 +19,6 @@ class ActionListener { Networking::PacketData unparsed = nullptr; size_t unparsedLength = 0; - simdjson::dom::element parsed; Networking::UserId userId = Networking::InvalidUserId; }; @@ -66,9 +65,9 @@ class ActionListener uint32_t weaponId, uint32_t ammoId, float power, bool isSunGazing); - virtual void OnFinishSpSnippet(const RawMessageData& rawMsgData, - uint32_t snippetIdx, - simdjson::dom::element& returnValue); + virtual void OnFinishSpSnippet( + const RawMessageData& rawMsgData, uint32_t snippetIdx, + const std::optional>& returnValue); virtual void OnEquip(const RawMessageData& rawMsgData, uint32_t baseId); @@ -84,7 +83,8 @@ class ActionListener uint32_t remoteId); virtual void OnCustomEvent(const RawMessageData& rawMsgData, - const char* eventName, simdjson::dom::element& e); + const char* eventName, + const std::vector& argsJsonDumps); virtual void OnChangeValues(const RawMessageData& rawMsgData, const ActorValues& actorValues); @@ -96,13 +96,9 @@ class ActionListener virtual void OnSpellCast(const RawMessageData& rawMsgData, const SpellCastData& spellCastData); - virtual void OnUnknown(const RawMessageData& rawMsgData); - private: // Returns user's actor if there is attached one - MpActor* SendToNeighbours(uint32_t idx, - const simdjson::dom::element& jMessage, - Networking::UserId userId, + MpActor* SendToNeighbours(uint32_t idx, Networking::UserId userId, Networking::PacketData data, size_t length, bool reliable); diff --git a/skymp5-server/cpp/server_guest_lib/AnimationData.cpp b/skymp5-server/cpp/server_guest_lib/AnimationData.cpp index b492d062e7..8c760ad5ac 100644 --- a/skymp5-server/cpp/server_guest_lib/AnimationData.cpp +++ b/skymp5-server/cpp/server_guest_lib/AnimationData.cpp @@ -1,21 +1,11 @@ #include "AnimationData.h" -AnimationData::AnimationData() - : animEventName("") - , numChanges(0) -{ -} +#include "archives/SimdJsonInputArchive.h" -AnimationData AnimationData::FromJson(const simdjson::dom::element& data) +AnimationData AnimationData::FromJson(const simdjson::dom::element& element) { - JsonPointer animEventName("animEventName"), numChanges("numChanges"); - - const char* animEventNameStr = ""; - - AnimationData result; - ReadEx(data, animEventName, &animEventNameStr); - ReadEx(data, numChanges, &result.numChanges); - - result.animEventName = animEventNameStr; - return result; + SimdJsonInputArchive ar(element); + AnimationData res; + res.Serialize(ar); + return res; } diff --git a/skymp5-server/cpp/server_guest_lib/AnimationData.h b/skymp5-server/cpp/server_guest_lib/AnimationData.h index 84e5f89dd3..d3e6724c05 100644 --- a/skymp5-server/cpp/server_guest_lib/AnimationData.h +++ b/skymp5-server/cpp/server_guest_lib/AnimationData.h @@ -1,13 +1,18 @@ #pragma once -#include "JsonUtils.h" #include struct AnimationData { - AnimationData(); + // TODO: get rid of FromJson method in favor of archives + static AnimationData FromJson(const simdjson::dom::element& element); - std::string animEventName; - uint32_t numChanges; + template + void Serialize(Archive& archive) + { + archive.Serialize("animEventName", animEventName) + .Serialize("numChanges", numChanges); + } - static AnimationData FromJson(const simdjson::dom::element& data); + std::string animEventName; + uint32_t numChanges = 0; }; diff --git a/skymp5-server/cpp/server_guest_lib/Appearance.cpp b/skymp5-server/cpp/server_guest_lib/Appearance.cpp index b4fa061d6c..f8bb49c705 100644 --- a/skymp5-server/cpp/server_guest_lib/Appearance.cpp +++ b/skymp5-server/cpp/server_guest_lib/Appearance.cpp @@ -1,84 +1,36 @@ #include "Appearance.h" #include "JsonUtils.h" +#include "archives/JsonInputArchive.h" +#include "archives/JsonOutputArchive.h" +#include "archives/SimdJsonInputArchive.h" -Tint Tint::FromJson(simdjson::dom::element& j) +Tint Tint::FromJson(simdjson::dom::element& element) { - static const JsonPointer argb("argb"), type("type"), - texturePath("texturePath"); - + SimdJsonInputArchive ar(element); Tint res; - ReadEx(j, argb, &res.argb); - ReadEx(j, type, &res.type); - - const char* texturePathCstr = ""; - ReadEx(j, texturePath, &texturePathCstr); - res.texturePath = texturePathCstr; - + res.Serialize(ar); return res; } Appearance Appearance::FromJson(const nlohmann::json& j) { - simdjson::dom::parser p; - simdjson::dom::element parsed = p.parse(j.dump()); - return FromJson(parsed); + JsonInputArchive ar(j); + Appearance res; + res.Serialize(ar); + return res; } Appearance Appearance::FromJson(simdjson::dom::element& j) { - static const JsonPointer isFemale("isFemale"), raceId("raceId"), - weight("weight"), skinColor("skinColor"), hairColor("hairColor"), - headpartIds("headpartIds"), headTextureSetId("headTextureSetId"), - options("options"), presets("presets"), name("name"), tints("tints"); - + SimdJsonInputArchive ar(j); Appearance res; - ReadEx(j, isFemale, &res.isFemale); - ReadEx(j, raceId, &res.raceId); - ReadEx(j, weight, &res.weight); - ReadEx(j, skinColor, &res.skinColor); - ReadEx(j, hairColor, &res.hairColor); - ReadVector(j, headpartIds, &res.headpartIds); - ReadEx(j, headTextureSetId, &res.headTextureSetId); - ReadVector(j, options, &res.faceMorphs); - ReadVector(j, presets, &res.facePresets); - - const char* myName; - try { - Read(j, name, &myName); - } catch (std::exception&) { - myName = ""; - } - res.name = myName; - - simdjson::dom::element jTints; - ReadEx(j, tints, &jTints); - - res.tints.reserve(30); - auto jTintsArr = jTints.operator simdjson::dom::array(); - for (simdjson::dom::element el : jTintsArr) { - res.tints.push_back(Tint::FromJson(el)); - } - + res.Serialize(ar); return res; } std::string Appearance::ToJson() const { - auto j = nlohmann::json{ { "isFemale", isFemale }, - { "raceId", raceId }, - { "weight", weight }, - { "skinColor", skinColor }, - { "hairColor", hairColor }, - { "headpartIds", headpartIds }, - { "headTextureSetId", headTextureSetId }, - { "options", faceMorphs }, - { "presets", facePresets }, - { "name", name } }; - j["tints"] = nlohmann::json::array(); - for (auto& tint : tints) { - j["tints"].push_back(nlohmann::json{ { "texturePath", tint.texturePath }, - { "argb", tint.argb }, - { "type", tint.type } }); - } - return j.dump(); + JsonOutputArchive ar; + const_cast(this)->Serialize(ar); + return ar.j.dump(); } diff --git a/skymp5-server/cpp/server_guest_lib/Appearance.h b/skymp5-server/cpp/server_guest_lib/Appearance.h index fa43cbd061..722c162be2 100644 --- a/skymp5-server/cpp/server_guest_lib/Appearance.h +++ b/skymp5-server/cpp/server_guest_lib/Appearance.h @@ -6,7 +6,16 @@ struct Tint { - static Tint FromJson(simdjson::dom::element& j); + // TODO: get rid of these methods + static Tint FromJson(simdjson::dom::element& element); + + template + void Serialize(Archive& archive) + { + archive.Serialize("texturePath", texturePath) + .Serialize("argb", argb) + .Serialize("type", type); + } std::string texturePath; int32_t argb = 0; @@ -15,11 +24,27 @@ struct Tint struct Appearance { - // TODO: port to archives + // TODO: get rid of these methods static Appearance FromJson(const nlohmann::json& j); static Appearance FromJson(simdjson::dom::element& j); std::string ToJson() const; + template + void Serialize(Archive& archive) + { + archive.Serialize("isFemale", isFemale) + .Serialize("raceId", raceId) + .Serialize("weight", weight) + .Serialize("skinColor", skinColor) + .Serialize("hairColor", hairColor) + .Serialize("headpartIds", headpartIds) + .Serialize("headTextureSetId", headTextureSetId) + .Serialize("options", options) + .Serialize("presets", presets) + .Serialize("tints", tints) + .Serialize("name", name); + } + bool isFemale = false; uint32_t raceId = 0; float weight = 0.f; @@ -27,8 +52,8 @@ struct Appearance int32_t hairColor = 0; std::vector headpartIds; uint32_t headTextureSetId = 0; - std::vector faceMorphs; - std::vector facePresets; + std::vector options; // faceMorphs + std::vector presets; // facePresets std::vector tints; std::string name; }; diff --git a/skymp5-server/cpp/server_guest_lib/ConsoleCommands.cpp b/skymp5-server/cpp/server_guest_lib/ConsoleCommands.cpp index e9db027700..d52bdc1387 100644 --- a/skymp5-server/cpp/server_guest_lib/ConsoleCommands.cpp +++ b/skymp5-server/cpp/server_guest_lib/ConsoleCommands.cpp @@ -21,6 +21,12 @@ ConsoleCommands::Argument::Argument(const std::string& str) data = str; } +ConsoleCommands::Argument::Argument( + const std::variant& data_) +{ + data = data_; +} + bool ConsoleCommands::Argument::IsInteger() const noexcept { return data.index() == 0; diff --git a/skymp5-server/cpp/server_guest_lib/ConsoleCommands.h b/skymp5-server/cpp/server_guest_lib/ConsoleCommands.h index 61a7aecec5..bde0d38efe 100644 --- a/skymp5-server/cpp/server_guest_lib/ConsoleCommands.h +++ b/skymp5-server/cpp/server_guest_lib/ConsoleCommands.h @@ -12,8 +12,9 @@ class Argument { public: Argument(); - Argument(int64_t integer); - Argument(const std::string& str); + explicit Argument(int64_t integer); + explicit Argument(const std::string& str); + explicit Argument(const std::variant& data_); bool IsInteger() const noexcept; bool IsString() const noexcept; diff --git a/skymp5-server/cpp/server_guest_lib/FormCallbacks.h b/skymp5-server/cpp/server_guest_lib/FormCallbacks.h index 1fcb241197..56473e770f 100644 --- a/skymp5-server/cpp/server_guest_lib/FormCallbacks.h +++ b/skymp5-server/cpp/server_guest_lib/FormCallbacks.h @@ -13,9 +13,8 @@ class FormCallbacks using SendToUserFn = std::function; - // TODO: use MessageBase instead of raw data using SendToUserDeferredFn = std::function; SubscribeCallback subscribe, unsubscribe; @@ -25,6 +24,6 @@ class FormCallbacks static FormCallbacks DoNothing() { return { [](auto, auto) {}, [](auto, auto) {}, [](auto, auto&, auto) {}, - [](auto, auto, auto, auto, auto, auto) {} }; + [](auto, auto&, auto, auto, auto) {} }; } }; diff --git a/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.cpp b/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.cpp index 73d0932cfa..3ee3f0bf15 100644 --- a/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.cpp +++ b/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.cpp @@ -3,28 +3,22 @@ #include "WorldState.h" #include -void BaseActorValues::VisitBaseActorValues(BaseActorValues& baseActorValues, - MpChangeForm& changeForm, - const PropertiesVisitor& visitor) +void BaseActorValues::VisitBaseActorValuesAndPercentages( + BaseActorValues& baseActorValues, MpChangeForm& changeForm, + CreateActorMessage& message) { - visitor("health", std::to_string(baseActorValues.health).c_str()); - visitor("stamina", std::to_string(baseActorValues.stamina).c_str()); - visitor("magicka", std::to_string(baseActorValues.magicka).c_str()); - visitor("healRate", std::to_string(baseActorValues.healRate).c_str()); - visitor("staminaRate", std::to_string(baseActorValues.staminaRate).c_str()); - visitor("magickaRate", std::to_string(baseActorValues.magickaRate).c_str()); - visitor("healRateMult", - std::to_string(baseActorValues.healRateMult).c_str()); - visitor("staminaRateMult", - std::to_string(baseActorValues.staminaRateMult).c_str()); - visitor("magickaRateMult", - std::to_string(baseActorValues.magickaRateMult).c_str()); - visitor("healthPercentage", - std::to_string(changeForm.actorValues.healthPercentage).c_str()); - visitor("staminaPercentage", - std::to_string(changeForm.actorValues.staminaPercentage).c_str()); - visitor("magickaPercentage", - std::to_string(changeForm.actorValues.magickaPercentage).c_str()); + message.props.health = baseActorValues.health; + message.props.stamina = baseActorValues.stamina; + message.props.magicka = baseActorValues.magicka; + message.props.healRate = baseActorValues.healRate; + message.props.staminaRate = baseActorValues.staminaRate; + message.props.magickaRate = baseActorValues.magickaRate; + message.props.healRateMult = baseActorValues.healRateMult; + message.props.staminaRateMult = baseActorValues.staminaRateMult; + message.props.magickaRateMult = baseActorValues.magickaRateMult; + message.props.healthPercentage = changeForm.actorValues.healthPercentage; + message.props.staminaPercentage = changeForm.actorValues.staminaPercentage; + message.props.magickaPercentage = changeForm.actorValues.magickaPercentage; } // TODO: implement auto-calc flag diff --git a/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.h b/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.h index 8cf96c48b4..76dff18347 100644 --- a/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.h +++ b/skymp5-server/cpp/server_guest_lib/GetBaseActorValues.h @@ -6,14 +6,13 @@ #include #include +#include "CreateActorMessage.h" + struct BaseActorValues : public ActorValues { - using PropertiesVisitor = - std::function; - - void VisitBaseActorValues(BaseActorValues& baseActorValues, - MpChangeForm& changeForm, - const PropertiesVisitor& visitor); + void VisitBaseActorValuesAndPercentages(BaseActorValues& baseActorValues, + MpChangeForm& changeForm, + CreateActorMessage& message); }; BaseActorValues GetBaseActorValues(WorldState* worldState, uint32_t baseId, diff --git a/skymp5-server/cpp/server_guest_lib/HitData.h b/skymp5-server/cpp/server_guest_lib/HitData.h index e5518c974f..79abb477c9 100644 --- a/skymp5-server/cpp/server_guest_lib/HitData.h +++ b/skymp5-server/cpp/server_guest_lib/HitData.h @@ -1,37 +1,5 @@ #pragma once -#include "JsonUtils.h" -#include -struct HitData -{ - // Aggressor player's form id - uint32_t aggressor = 0; - bool isBashAttack = false; - bool isHitBlocked = false; - bool isPowerAttack = false; - bool isSneakAttack = false; - uint32_t projectile = 0; - // Weapon's form id - uint32_t source = 0; - // Target player's form id - uint32_t target = 0; +#include "HitMessage.h" - static HitData FromJson(const simdjson::dom::element& data) - { - JsonPointer aggressor("aggressor"), target("target"), - isBashAttack("isBashAttack"), isHitBlocked("isHitBlocked"), - isPowerAttack("isPowerAttack"), isSneakAttack("isSneakAttack"), - projectile("projectile"), source("source"); - - HitData result; - ReadEx(data, aggressor, &result.aggressor); - ReadEx(data, target, &result.target); - ReadEx(data, isBashAttack, &result.isBashAttack); - ReadEx(data, isHitBlocked, &result.isHitBlocked); - ReadEx(data, isPowerAttack, &result.isPowerAttack); - ReadEx(data, isSneakAttack, &result.isSneakAttack); - ReadEx(data, projectile, &result.projectile); - ReadEx(data, source, &result.source); - return result; - } -}; +using HitData = HitMessage::Data; diff --git a/skymp5-server/cpp/server_guest_lib/Inventory.h b/skymp5-server/cpp/server_guest_lib/Inventory.h index 046e811d5a..ec09e05554 100644 --- a/skymp5-server/cpp/server_guest_lib/Inventory.h +++ b/skymp5-server/cpp/server_guest_lib/Inventory.h @@ -28,15 +28,13 @@ class Inventory static Inventory FromJson(const simdjson::dom::element& element); static Inventory FromJson(const nlohmann::json& j); - class Entry + class ExtraData { public: template void Serialize(Archive& archive) { - archive.Serialize("baseId", baseId) - .Serialize("count", count) - .Serialize("health", health) + archive.Serialize("health", health) .Serialize("enchantmentId", enchantmentId) .Serialize("maxCharge", maxCharge) .Serialize("removeEnchantmentOnUnequip", removeEnchantmentOnUnequip) @@ -49,13 +47,6 @@ class Inventory .Serialize("wornLeft", wornLeft); } - // TODO: get rid of this in favor of Serialize - // nlohmann::json ToJson() const; - - uint32_t baseId = 0; - uint32_t count = 0; - - // extras std::optional health; std::optional enchantmentId; std::optional maxCharge; @@ -67,6 +58,21 @@ class Inventory std::optional poisonCount; std::optional worn_; std::optional wornLeft; + }; + + class Entry : public ExtraData + { + public: + template + void Serialize(Archive& archive) + { + archive.Serialize("baseId", baseId).Serialize("count", count); + + ExtraData::Serialize(archive); + } + + uint32_t baseId = 0; + uint32_t count = 0; // TODO: get rid of this in favor of Serialize static Entry FromJson(const simdjson::dom::element& e); diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.cpp b/skymp5-server/cpp/server_guest_lib/MpActor.cpp index efee1f380f..8f3a96f481 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.cpp +++ b/skymp5-server/cpp/server_guest_lib/MpActor.cpp @@ -323,7 +323,7 @@ void MpActor::RemoveFromFaction(FormDesc factionForm, bool lazyLoad) }); } -void MpActor::VisitProperties(const PropertiesVisitor& visitor, +void MpActor::VisitProperties(CreateActorMessage& message, VisitPropertiesMode mode) { const auto baseId = GetBaseId(); @@ -340,31 +340,28 @@ void MpActor::VisitProperties(const PropertiesVisitor& visitor, MpChangeForm changeForm = GetChangeForm(); - MpObjectReference::VisitProperties(visitor, mode); + MpObjectReference::VisitProperties(message, mode); if (mode == VisitPropertiesMode::All && IsRaceMenuOpen()) { - visitor("isRaceMenuOpen", "true"); + message.props.isRaceMenuOpen = true; } if (mode == VisitPropertiesMode::All) { - baseActorValues.VisitBaseActorValues(baseActorValues, changeForm, visitor); + baseActorValues.VisitBaseActorValuesAndPercentages(baseActorValues, + changeForm, message); } - visitor("learnedSpells", - nlohmann::json(changeForm.learnedSpells.GetLearnedSpells()) - .dump() - .c_str()); + message.props.learnedSpells = changeForm.learnedSpells.GetLearnedSpells(); if (!changeForm.templateChain.empty()) { - // should be faster than nlohmann::json - std::string jsonDump = "["; + std::vector templateChain; + templateChain.reserve(changeForm.templateChain.size()); + for (auto& element : changeForm.templateChain) { - jsonDump += std::to_string(element.ToFormId(GetParent()->espmFiles)); - jsonDump += ','; + templateChain.push_back(element.ToFormId(GetParent()->espmFiles)); } - jsonDump.pop_back(); // comma - jsonDump += "]"; - visitor("templateChain", jsonDump.data()); + + message.props.templateChain = std::move(templateChain); } } @@ -400,13 +397,12 @@ void MpActor::SendToUser(const IMessageBase& message, bool reliable) } } -void MpActor::SendToUserDeferred(const void* data, size_t size, bool reliable, +void MpActor::SendToUserDeferred(const IMessageBase& message, bool reliable, int deferredChannelId, bool overwritePreviousChannelMessages) { if (callbacks->sendToUserDeferred) { - callbacks->sendToUserDeferred(this, data, size, reliable, - deferredChannelId, + callbacks->sendToUserDeferred(this, message, reliable, deferredChannelId, overwritePreviousChannelMessages); } else { throw std::runtime_error("sendToUserDeferred is nullptr"); @@ -459,17 +455,21 @@ bool MpActor::OnEquip(uint32_t baseId) if (isIngredient || isPotion) { EatItem(baseId, recordType); - nlohmann::json j = nlohmann::json::array(); - j.push_back( - nlohmann::json({ { "formId", baseId }, - { "type", isIngredient ? "Ingredient" : "Potion" } })); - j.push_back(false); - j.push_back(false); + std::vector>> + spSnippetArgs; + + SpSnippetObjectArgument spSnippetObjectArgument; + spSnippetObjectArgument.formId = baseId; + spSnippetObjectArgument.type = isIngredient ? "Ingredient" : "Potion"; + + spSnippetArgs.push_back(spSnippetObjectArgument); + spSnippetArgs.push_back(false); + spSnippetArgs.push_back(false); - std::string serializedArgs = j.dump(); for (auto listener : GetActorListeners()) { if (listener != this) { - SpSnippet("Actor", "EquipItem", serializedArgs.data(), GetFormId()) + SpSnippet("Actor", "EquipItem", spSnippetArgs, GetFormId()) .Execute(listener, SpSnippetMode::kNoReturnResult); } } diff --git a/skymp5-server/cpp/server_guest_lib/MpActor.h b/skymp5-server/cpp/server_guest_lib/MpActor.h index 775f0a9468..34999ebe6c 100644 --- a/skymp5-server/cpp/server_guest_lib/MpActor.h +++ b/skymp5-server/cpp/server_guest_lib/MpActor.h @@ -63,12 +63,12 @@ class MpActor : public MpObjectReference bool lazyLoad = true); void RemoveFromFaction(FormDesc factionForm, bool lazyLoad = true); - void VisitProperties(const PropertiesVisitor& visitor, + void VisitProperties(CreateActorMessage& message, VisitPropertiesMode mode) override; void Disable() override; void SendToUser(const IMessageBase& message, bool reliable); - void SendToUserDeferred(const void* data, size_t size, bool reliable, + void SendToUserDeferred(const IMessageBase& message, bool reliable, int deferredChannelId, bool overwritePreviousChannelMessages); diff --git a/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/MpObjectReference.cpp index 2b906f6aa4..1262aa50dc 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 @@ -322,74 +323,72 @@ bool MpObjectReference::GetTeleportFlag() const return pImpl->teleportFlag; } -void MpObjectReference::VisitProperties(const PropertiesVisitor& visitor, +void MpObjectReference::VisitProperties(CreateActorMessage& message, VisitPropertiesMode mode) { if (IsHarvested()) { - visitor("isHarvested", "true"); + message.props.isHarvested = true; } if (IsOpen()) { - visitor("isOpen", "true"); + message.props.isOpen = true; } if (auto actor = AsActor(); actor && actor->IsDead()) { - visitor("isDead", "true"); + message.props.isDead = true; } if (mode == VisitPropertiesMode::All && !GetInventory().IsEmpty()) { - auto inventoryDump = GetInventory().ToJson().dump(); - visitor("inventory", inventoryDump.data()); + message.props.inventory = GetInventory(); } if (IsEspmForm() && IsDisabled()) { - visitor("disabled", "true"); + message.props.isDisabled = true; } if (ChangeForm().lastAnimation.has_value()) { - std::string raw = *ChangeForm().lastAnimation; - nlohmann::json j = raw; - std::string lastAnimationAsJson = j.dump(); - visitor("lastAnimation", lastAnimationAsJson.data()); + message.props.lastAnimation = *ChangeForm().lastAnimation; } if (ChangeForm().setNodeScale.has_value()) { - // worse performance than building json string manually but proper escaping - // TODO: consider switching to a faster JSON builder - nlohmann::json setNodeScaleAsJson; - for (auto& [key, value] : *ChangeForm().setNodeScale) { - setNodeScaleAsJson[key] = value; + std::vector setNodeScale; + for (auto& [nodeName, scale] : *ChangeForm().setNodeScale) { + SetNodeScaleEntry setNodeScaleEntry; + setNodeScaleEntry.nodeName = nodeName; + setNodeScaleEntry.scale = scale; + setNodeScale.push_back(setNodeScaleEntry); } - visitor("setNodeScale", setNodeScaleAsJson.dump().data()); + message.props.setNodeScale = std::move(setNodeScale); } if (ChangeForm().setNodeTextureSet.has_value()) { - // worse performance than building json string manually but proper escaping - // TODO: consider switching to a faster JSON builder - nlohmann::json setNodeTextureSetAsJson; - for (auto& [key, value] : *ChangeForm().setNodeTextureSet) { - setNodeTextureSetAsJson[key] = - FormDesc::FromString(value).ToFormId(GetParent()->espmFiles); + std::vector setNodeTextureSet; + for (auto& [nodeName, textureSetId] : *ChangeForm().setNodeTextureSet) { + SetNodeTextureSetEntry setNodeTextureSetEntry; + setNodeTextureSetEntry.nodeName = nodeName; + setNodeTextureSetEntry.textureSetId = + FormDesc::FromString(textureSetId).ToFormId(GetParent()->espmFiles); + setNodeTextureSet.push_back(setNodeTextureSetEntry); } - visitor("setNodeTextureSet", setNodeTextureSetAsJson.dump().data()); + message.props.setNodeTextureSet = std::move(setNodeTextureSet); } if (ChangeForm().displayName.has_value()) { const std::string& raw = *ChangeForm().displayName; if (raw != PapyrusObjectReference::kOriginalNameExpression) { - nlohmann::json j = raw; - std::string displayNameAsJson = j.dump(); - visitor("displayName", displayNameAsJson.data()); + message.props.displayName = raw; } } - // Property flags (isVisibleByOwner, isVisibleByNeighbor) should be checked - // by a visitor - auto& dynamicFields = ChangeForm().dynamicFields.GetAsJson(); - for (auto it = dynamicFields.begin(); it != dynamicFields.end(); ++it) { - std::string dump = it.value().dump(); - visitor(it.key().data(), dump.data()); - } + // Property flags (isVisibleByOwner, isVisibleByNeighbor) are expected to be + // checked by a caller (PartOne.cpp in this case) + ChangeForm().dynamicFields.ForEach( + [&](const std::string& propName, const nlohmann::json& propValue) { + CustomPropsEntry customPropsEntry; + customPropsEntry.propName = propName; + customPropsEntry.propValueJsonDump = propValue.dump(); + message.customPropsJsonDumps.push_back(customPropsEntry); + }); } void MpObjectReference::Activate(MpObjectReference& activationSource, @@ -1761,14 +1760,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/MpObjectReference.h b/skymp5-server/cpp/server_guest_lib/MpObjectReference.h index e539528d78..5ebcf4645a 100644 --- a/skymp5-server/cpp/server_guest_lib/MpObjectReference.h +++ b/skymp5-server/cpp/server_guest_lib/MpObjectReference.h @@ -20,6 +20,7 @@ #include #include +#include "CreateActorMessage.h" #include "UpdatePropertyMessage.h" struct GridPosInfo @@ -108,10 +109,7 @@ class MpObjectReference bool IsActivationBlocked() const; bool GetTeleportFlag() const; - using PropertiesVisitor = - std::function; - - virtual void VisitProperties(const PropertiesVisitor& visitor, + virtual void VisitProperties(CreateActorMessage& message, VisitPropertiesMode mode); virtual void Activate(MpObjectReference& activationSource, bool defaultProcessingOnly = false, diff --git a/skymp5-server/cpp/server_guest_lib/PacketParser.cpp b/skymp5-server/cpp/server_guest_lib/PacketParser.cpp index 68cf244f1b..c3e74c22d9 100644 --- a/skymp5-server/cpp/server_guest_lib/PacketParser.cpp +++ b/skymp5-server/cpp/server_guest_lib/PacketParser.cpp @@ -59,7 +59,6 @@ void PacketParser::TransformPacketIntoAction(Networking::UserId userId, ActionListener::RawMessageData rawMsgData{ data, length, - /*parsed (json)*/ {}, userId, }; @@ -73,6 +72,70 @@ void PacketParser::TransformPacketIntoAction(Networking::UserId userId, }); } switch (result->msgType) { + case MsgType::Invalid: { + return; + } + case MsgType::Activate: { + auto message = + reinterpret_cast(result->message.get()); + actionListener.OnActivate( + rawMsgData, FormIdCasts::LongToNormal(message->data.caster), + FormIdCasts::LongToNormal(message->data.target), + message->data.isSecondActivation); + return; + } + case MsgType::ConsoleCommand: { + auto message = + reinterpret_cast(result->message.get()); + + std::vector consoleArgs; + consoleArgs.resize(message->data.args.size()); + for (size_t i = 0; i < message->data.args.size(); i++) { + consoleArgs[i] = ConsoleCommands::Argument(message->data.args[i]); + } + + actionListener.OnConsoleCommand(rawMsgData, message->data.commandName, + consoleArgs); + return; + } + case MsgType::CraftItem: { + auto message = + reinterpret_cast(result->message.get()); + actionListener.OnCraftItem(rawMsgData, message->data.craftInputObjects, + message->data.workbench, + message->data.resultObjectId); + return; + } + case MsgType::CustomEvent: { + auto message = + reinterpret_cast(result->message.get()); + actionListener.OnCustomEvent(rawMsgData, message->eventName.data(), + message->argsJsonDumps); + return; + } + case MsgType::DropItem: { + auto message = + reinterpret_cast(result->message.get()); + + Inventory::Entry entry; + entry.baseId = FormIdCasts::LongToNormal(message->baseId); + entry.count = message->count; + + actionListener.OnDropItem( + rawMsgData, FormIdCasts::LongToNormal(message->baseId), entry); + return; + } + case MsgType::OnHit: { + auto message = reinterpret_cast(result->message.get()); + actionListener.OnHit(rawMsgData, message->data); + return; + } + case MsgType::Host: { + auto message = reinterpret_cast(result->message.get()); + actionListener.OnHostAttempt( + rawMsgData, FormIdCasts::LongToNormal(message->remoteId)); + return; + } case MsgType::UpdateMovement: { auto message = reinterpret_cast(result->message.get()); @@ -121,181 +184,100 @@ void PacketParser::TransformPacketIntoAction(Networking::UserId userId, actionListener.OnChangeValues(rawMsgData, actorValues); return; } - default: { - // likel a binary packet, can't just fall back to simdjson parsing - spdlog::error("PacketParser.cpp doesn't implement MsgType {}", - static_cast(result->msgType)); + case MsgType::CustomPacket: { + auto message = + reinterpret_cast(result->message.get()); + auto parsedContent = pImpl->simdjsonParser + .parse(message->contentJsonDump.data(), + message->contentJsonDump.size()) + .value(); + actionListener.OnCustomPacket(rawMsgData, parsedContent); return; } - } - } - - rawMsgData.parsed = - pImpl->simdjsonParser.parse(data + 1, length - 1).value(); - - const auto& jMessage = rawMsgData.parsed; - - int64_t type = static_cast(MsgType::Invalid); - Read(jMessage, JsonPointers::t, &type); - - switch (static_cast(type)) { - case MsgType::Invalid: - break; - case MsgType::CustomPacket: { - simdjson::dom::element content; - Read(jMessage, JsonPointers::content, &content); - actionListener.OnCustomPacket(rawMsgData, content); - } break; - case MsgType::UpdateAppearance: { - uint32_t idx; - ReadEx(jMessage, JsonPointers::idx, &idx); - simdjson::dom::element jData; - Read(jMessage, JsonPointers::data, &jData); - - actionListener.OnUpdateAppearance(rawMsgData, idx, - Appearance::FromJson(jData)); - } break; - case MsgType::Activate: { - simdjson::dom::element data_; - ReadEx(jMessage, JsonPointers::data, &data_); - uint64_t caster, target; - ReadEx(data_, JsonPointers::caster, &caster); - ReadEx(data_, JsonPointers::target, &target); - bool isSecondActivation; - ReadEx(data_, JsonPointers::isSecondActivation, &isSecondActivation); - actionListener.OnActivate(rawMsgData, FormIdCasts::LongToNormal(caster), - FormIdCasts::LongToNormal(target), - isSecondActivation); - } break; - case MsgType::UpdateProperty: - break; - case MsgType::PutItem: - case MsgType::TakeItem: { - uint32_t target; - ReadEx(jMessage, JsonPointers::target, &target); - auto e = Inventory::Entry::FromJson(jMessage); - if (static_cast(type) == MsgType::PutItem) { - e.SetWorn(Inventory::Worn::None); - actionListener.OnPutItem(rawMsgData, target, e); - } else { - actionListener.OnTakeItem(rawMsgData, target, e); + case MsgType::UpdateAppearance: { + auto message = + reinterpret_cast(result->message.get()); + const uint32_t idx = message->idx; + const Appearance& data = *message->data; + actionListener.OnUpdateAppearance(rawMsgData, idx, data); + return; + } + case MsgType::UpdateProperty: { + return; } - } break; - case MsgType::FinishSpSnippet: { - uint32_t snippetIdx; - ReadEx(jMessage, JsonPointers::snippetIdx, &snippetIdx); + case MsgType::PutItem: { + auto message = + reinterpret_cast(result->message.get()); + const auto& extra = static_cast(*message); + const uint32_t baseId = message->baseId; + const uint32_t count = message->count; + const uint32_t target = message->target; - simdjson::dom::element returnValue; - ReadEx(jMessage, JsonPointers::returnValue, &returnValue); + Inventory::Entry entry; + entry.baseId = baseId; + entry.count = count; + static_cast(entry) = extra; - actionListener.OnFinishSpSnippet(rawMsgData, snippetIdx, returnValue); + entry.SetWorn(Inventory::Worn::None); - break; - } - case MsgType::OnEquip: { - uint32_t baseId; - ReadEx(jMessage, JsonPointers::baseId, &baseId); - actionListener.OnEquip(rawMsgData, baseId); - break; - } - case MsgType::ConsoleCommand: { - simdjson::dom::element data_; - ReadEx(jMessage, JsonPointers::data, &data_); - const char* commandName; - ReadEx(data_, JsonPointers::commandName, &commandName); - simdjson::dom::element args; - ReadEx(data_, JsonPointers::args, &args); + actionListener.OnPutItem(rawMsgData, target, entry); + return; + } + case MsgType::TakeItem: { + auto message = + reinterpret_cast(result->message.get()); + const auto& extra = static_cast(*message); + const uint32_t baseId = message->baseId; + const uint32_t count = message->count; + const uint32_t target = message->target; - auto arr = args.get_array().value(); + Inventory::Entry entry; + entry.baseId = baseId; + entry.count = count; + static_cast(entry) = extra; - std::vector consoleArgs; - consoleArgs.resize(arr.size()); - for (size_t i = 0; i < arr.size(); ++i) { - simdjson::dom::element el; - ReadEx(args, i, &el); + actionListener.OnTakeItem(rawMsgData, target, entry); + return; + } + case MsgType::FinishSpSnippet: { + auto message = + reinterpret_cast(result->message.get()); + const std::optional>& + returnValue = message->returnValue; + const uint32_t snippetIdx = static_cast(message->snippetIdx); - std::string s = simdjson::minify(el); - if (!s.empty() && s[0] == '"') { - const char* s; - ReadEx(args, i, &s); - consoleArgs[i] = std::string(s); - } else { - int64_t ingeter; - ReadEx(args, i, &ingeter); - consoleArgs[i] = ingeter; - } + actionListener.OnFinishSpSnippet(rawMsgData, snippetIdx, returnValue); + return; + } + case MsgType::OnEquip: { + auto message = + reinterpret_cast(result->message.get()); + actionListener.OnEquip(rawMsgData, message->baseId); + return; + } + case MsgType::SpellCast: { + auto message = + reinterpret_cast(result->message.get()); + actionListener.OnSpellCast(rawMsgData, message->data); + return; + } + case MsgType::UpdateAnimVariables: { + actionListener.OnUpdateAnimVariables(rawMsgData); + return; + } + case MsgType::PlayerBowShot: { + auto message = + reinterpret_cast(result->message.get()); + actionListener.OnPlayerBowShot(rawMsgData, message->weaponId, + message->ammoId, message->power, + message->isSunGazing); + break; + } + default: { + spdlog::error("PacketParser.cpp doesn't implement MsgType {}", + static_cast(result->msgType)); + return; } - actionListener.OnConsoleCommand(rawMsgData, commandName, consoleArgs); - break; - } - case MsgType::CraftItem: { - simdjson::dom::element data_; - ReadEx(jMessage, JsonPointers::data, &data_); - uint32_t workbench; - ReadEx(data_, JsonPointers::workbench, &workbench); - uint32_t resultObjectId; - ReadEx(data_, JsonPointers::resultObjectId, &resultObjectId); - simdjson::dom::element craftInputObjects; - ReadEx(data_, JsonPointers::craftInputObjects, &craftInputObjects); - actionListener.OnCraftItem(rawMsgData, - Inventory::FromJson(craftInputObjects), - workbench, resultObjectId); - break; - } - case MsgType::Host: { - uint64_t remoteId; - ReadEx(jMessage, JsonPointers::remoteId, &remoteId); - actionListener.OnHostAttempt(rawMsgData, - FormIdCasts::LongToNormal(remoteId)); - break; - } - case MsgType::CustomEvent: { - simdjson::dom::element args; - ReadEx(jMessage, JsonPointers::args, &args); - const char* eventName; - ReadEx(jMessage, JsonPointers::eventName, &eventName); - actionListener.OnCustomEvent(rawMsgData, eventName, args); - break; - } - case MsgType::OnHit: { - simdjson::dom::element data_; - ReadEx(jMessage, JsonPointers::data, &data_); - actionListener.OnHit(rawMsgData, HitData::FromJson(data_)); - break; - } - case MsgType::SpellCast: { - simdjson::dom::element data_; - ReadEx(jMessage, JsonPointers::data, &data_); - actionListener.OnSpellCast(rawMsgData, SpellCastData::FromJson(data_)); - break; - } - case MsgType::UpdateAnimVariables: { - actionListener.OnUpdateAnimVariables(rawMsgData); - break; - } - case MsgType::DropItem: { - uint64_t baseId; - ReadEx(jMessage, JsonPointers::baseId, &baseId); - auto entry = Inventory::Entry::FromJson(jMessage); - actionListener.OnDropItem(rawMsgData, FormIdCasts::LongToNormal(baseId), - entry); - break; - } - case MsgType::PlayerBowShot: { - uint32_t weaponId; - ReadEx(jMessage, JsonPointers::weaponId, &weaponId); - uint32_t ammoId; - ReadEx(jMessage, JsonPointers::ammoId, &ammoId); - float power; - ReadEx(jMessage, JsonPointers::power, &power); - bool isSunGazing; - ReadEx(jMessage, JsonPointers::isSunGazing, &isSunGazing); - actionListener.OnPlayerBowShot(rawMsgData, weaponId, ammoId, power, - isSunGazing); - break; } - default: - actionListener.OnUnknown(rawMsgData); - break; } } diff --git a/skymp5-server/cpp/server_guest_lib/PartOne.cpp b/skymp5-server/cpp/server_guest_lib/PartOne.cpp index e3ad404423..dba4414803 100644 --- a/skymp5-server/cpp/server_guest_lib/PartOne.cpp +++ b/skymp5-server/cpp/server_guest_lib/PartOne.cpp @@ -13,15 +13,54 @@ #include #include +#include "CreateActorMessage.h" +#include "CustomPacketMessage.h" +#include "DestroyActorMessage.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); @@ -41,7 +80,7 @@ struct PartOne::Impl simdjson::dom::parser parser; espm::Loader* espm = nullptr; - std::function onSubscribe, onUnsubscribe; @@ -52,7 +91,7 @@ struct PartOne::Impl std::shared_ptr logger; - Networking::ISendTarget* sendTarget = nullptr; + std::unique_ptr sendTarget; std::unique_ptr damageFormula{}; FakeSendTarget fakeSendTarget; @@ -83,7 +122,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 +261,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 +461,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,37 +578,36 @@ 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 subscribe = [this](MpObjectReference* emitter, MpObjectReference* listener) { - return pImpl->onSubscribe(pImpl->sendTarget, emitter, listener); + return pImpl->onSubscribe(pImpl->sendTarget.get(), emitter, listener); }, unsubscribe = [this](MpObjectReference* emitter, MpObjectReference* listener) { - return pImpl->onUnsubscribe(pImpl->sendTarget, emitter, listener); + return pImpl->onUnsubscribe(pImpl->sendTarget.get(), emitter, listener); }; 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()) { @@ -584,8 +629,11 @@ FormCallbacks PartOne::CreateFormCallbacks() }; FormCallbacks::SendToUserDeferredFn sendToUserDeferred = - [this, st](MpActor* actor, const void* data, size_t size, bool reliable, + [this, st](MpActor* actor, const IMessageBase& message, bool reliable, int deferredChannelId, bool overwritePreviousChannelMessages) { + SLNet::BitStream stream; + GetMessageSerializerInstance().Serialize(message, stream); + if (deferredChannelId < 0 || deferredChannelId >= 100) { return spdlog::error( "sendToUserDeferred - invalid deferredChannelId {}", @@ -606,9 +654,11 @@ FormCallbacks PartOne::CreateFormCallbacks() } DeferredMessage deferredMessage; - deferredMessage.packetData = { static_cast(data), - static_cast(data) + - size }; + deferredMessage.packetData = { + reinterpret_cast(stream.GetData()), + reinterpret_cast(stream.GetData()) + + stream.GetNumberOfBytesUsed() + }; deferredMessage.packetReliable = reliable; deferredMessage.actorIdExpected = actor->GetFormId(); @@ -649,7 +699,7 @@ void PartOne::Init() pImpl.reset(new Impl); pImpl->logger.reset(new spdlog::logger{ "empty logger" }); - pImpl->onSubscribe = [this](Networking::ISendTarget* sendTarget, + pImpl->onSubscribe = [this](PartOneSendTargetWrapper* sendTarget, MpObjectReference* emitter, MpObjectReference* listener) { if (!emitter) { @@ -673,97 +723,70 @@ 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.isDead = true; } const bool isOwner = emitter == listener; - std::string props; - auto mode = VisitPropertiesMode::OnlyPublic; if (isOwner) { mode = VisitPropertiesMode::All; } - const char *propsPrefix = "", *propsPostfix = ""; - auto visitor = [&](const char* propName, const char* jsonValue) { - auto it = pImpl->gamemodeApiState.createdProperties.find(propName); + emitter->VisitProperties(message, mode); + + auto isFilteredOut = [&](const CustomPropsEntry& customPropsEntry) { + auto it = pImpl->gamemodeApiState.createdProperties.find( + customPropsEntry.propName); if (it != pImpl->gamemodeApiState.createdProperties.end()) { if (!it->second.isVisibleByOwner) { // From docs: isVisibleByNeighbors is considered to be always false // for properties with `isVisibleByOwner == false`, in that case, // actual flag value is ignored. - return; + return true; } - if (!it->second.isVisibleByNeighbors && !isOwner) { - return; + return true; } } - - propsPrefix = R"(, "props": { )"; - propsPostfix = R"( })"; - - if (props.size() > 0) - props += R"(, ")"; - else - props += '"'; - props += propName; - props += R"(": )"; - props += jsonValue; + return false; }; - emitter->VisitProperties(visitor, mode); + + message.customPropsJsonDumps.erase( + std::remove_if(message.customPropsJsonDumps.begin(), + message.customPropsJsonDumps.end(), isFilteredOut), + message.customPropsJsonDumps.end()); const bool hasUser = emitterAsActor && serverState.UserByActor(emitterAsActor) != Networking::InvalidUserId; @@ -773,37 +796,29 @@ void PartOne::Init() (hosterIterator != worldState.hosters.end() && hosterIterator->second != 0 && hosterIterator->second != listener->GetFormId())) { - visitor("isHostedByOther", "true"); + message.props.isHostedByOther = true; } - const char* method = "createActor"; - uint32_t worldOrCell = emitter->GetCellOrWorld().ToFormId(worldState.espmFiles); // See 'perf: improve game framerate #1186' // Client needs to know if it is DOOR or not - const char* baseRecordTypePrefix = ""; - std::string baseRecordType; if (const std::string& baseType = emitter->GetBaseType(); baseType == "DOOR") { - baseRecordTypePrefix = R"(, "baseRecordType": )"; - baseRecordType = '"' + baseType + '"'; - } - - Networking::SendFormatted( - sendTarget, listenerUserId, - R"({"type": "%s", "idx": %u, "isMe": %s, "transform": {"pos": - [%f,%f,%f], "rot": [%f,%f,%f], "worldOrCell": %u}%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s})", - method, emitter->GetIdx(), isMe ? "true" : "false", emitterPos.x, - emitterPos.y, emitterPos.z, emitterRot.x, emitterRot.y, emitterRot.z, - worldOrCell, baseRecordTypePrefix, baseRecordType.data(), - appearancePrefix, appearance, equipmentPrefix, equipment, - animationPrefix, animation, refrIdPrefix, refrId, baseIdPrefix, baseId, - isDeadPrefix, isDead, propsPrefix, props.data(), propsPostfix); + message.baseRecordType = "DOOR"; + } + + message.idx = emitter->GetIdx(); + message.isMe = isMe; + message.transform.pos = { emitterPos.x, emitterPos.y, emitterPos.z }; + message.transform.rot = { emitterRot.x, emitterRot.y, emitterRot.z }; + message.transform.worldOrCell = worldOrCell; + + sendTarget->Send(listenerUserId, message, true); }; - pImpl->onUnsubscribe = [this](Networking::ISendTarget* sendTarget, + pImpl->onUnsubscribe = [this](PartOneSendTargetWrapper* sendTarget, MpObjectReference* emitter, MpObjectReference* listener) { MpActor* listenerAsActor = listener->AsActor(); @@ -813,10 +828,11 @@ void PartOne::Init() auto listenerUserId = serverState.UserByActor(listenerAsActor); if (listenerUserId != Networking::InvalidUserId && - listenerUserId != serverState.disconnectingUserId) - Networking::SendFormatted(sendTarget, listenerUserId, - R"({"type": "destroyActor", "idx": %u})", - emitter->GetIdx()); + listenerUserId != serverState.disconnectingUserId) { + DestroyActorMessage message; + message.idx = emitter->GetIdx(); + sendTarget->Send(listenerUserId, message, true); + } }; } diff --git a/skymp5-server/cpp/server_guest_lib/PartOne.h b/skymp5-server/cpp/server_guest_lib/PartOne.h index d13ca55027..7478afba3b 100644 --- a/skymp5-server/cpp/server_guest_lib/PartOne.h +++ b/skymp5-server/cpp/server_guest_lib/PartOne.h @@ -1,6 +1,7 @@ #pragma once #include "AnimationSystem.h" #include "GamemodeApi.h" +#include "HitData.h" #include "MpActor.h" #include "Networking.h" #include "NiPoint3.h" @@ -21,7 +22,21 @@ using ProfileId = int32_t; 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 { @@ -82,7 +97,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..ced4734f08 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.arguments = 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..309533e6a4 100644 --- a/skymp5-server/cpp/server_guest_lib/SpSnippet.h +++ b/skymp5-server/cpp/server_guest_lib/SpSnippet.h @@ -1,8 +1,11 @@ #pragma once +#include "SpSnippetMessage.h" // SpSnippetObjectArgument #include "papyrus-vm/Structures.h" #include #include #include +#include +#include class MpActor; @@ -15,12 +18,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); \ diff --git a/skymp5-server/cpp/server_guest_lib/SpellCastData.h b/skymp5-server/cpp/server_guest_lib/SpellCastData.h index ed160e452b..a0382a79f4 100644 --- a/skymp5-server/cpp/server_guest_lib/SpellCastData.h +++ b/skymp5-server/cpp/server_guest_lib/SpellCastData.h @@ -1,8 +1,8 @@ #pragma once -#include "JsonUtils.h" -#include +#include "SpellCastMessage.h" -enum class SpellType +// TODO: rename to CastingSource +enum SpellType { Left, Right, @@ -10,34 +10,4 @@ enum class SpellType Instant, }; -struct SpellCastData -{ - uint32_t caster = 0; - uint32_t target = 0; - uint32_t spell = 0; - - bool isDualCasting = false; - bool isInterruptCast = false; - - SpellType castingSource = SpellType::Left; - - static SpellCastData FromJson(const simdjson::dom::element& data) - { - const JsonPointer caster("caster"), target("target"), spell("spell"), - interruptCast("interruptCast"), isDualCasting("isDualCasting"), - castingSource("castingSource"); - - SpellCastData result; - ReadEx(data, caster, &result.caster); - ReadEx(data, target, &result.target); - ReadEx(data, spell, &result.spell); - ReadEx(data, interruptCast, &result.isInterruptCast); - ReadEx(data, isDualCasting, &result.isDualCasting); - - uint32_t cSource = 0; - ReadEx(data, castingSource, &cSource); - result.castingSource = static_cast(cSource); - - return result; - } -}; +using SpellCastData = SpellCastMsgData; diff --git a/skymp5-server/cpp/server_guest_lib/SweetPieScript.cpp b/skymp5-server/cpp/server_guest_lib/SweetPieScript.cpp index 70cf7b1290..f453e9d6ff 100644 --- a/skymp5-server/cpp/server_guest_lib/SweetPieScript.cpp +++ b/skymp5-server/cpp/server_guest_lib/SweetPieScript.cpp @@ -131,7 +131,6 @@ void SweetPieScript::Notify(MpActor& actor, const WorldState& worldState, uint32_t formId, uint32_t count, bool silent) { std::string type; - std::stringstream ss; auto lookupRes = worldState.GetEspm().GetBrowser().LookupById(formId); if (!lookupRes.rec) { @@ -159,13 +158,18 @@ void SweetPieScript::Notify(MpActor& actor, const WorldState& worldState, formId); } - ss << "["; - ss << nlohmann::json({ { "formId", formId }, { "type", type } }).dump(); - ss << "," << static_cast(count) << "," - << (static_cast(silent) ? "true" : "false"); - ss << "]"; - std::string args = ss.str(); - (void)SpSnippet("SkympHacks", "AddItem", args.data()) + SpSnippetObjectArgument itemToAdd; + itemToAdd.formId = formId; + itemToAdd.type = type; + + std::vector>> + addItemArgs; + addItemArgs.push_back(itemToAdd); + addItemArgs.push_back(static_cast(count)); + addItemArgs.push_back(silent); + + (void)SpSnippet("SkympHacks", "AddItem", addItemArgs) .Execute(&actor, SpSnippetMode::kNoReturnResult); } @@ -237,23 +241,37 @@ void SweetPieScript::EquipItem(MpActor& actor, uint32_t baseId, { bool isShield = baseId == 0x7F47DC9; bool isArrow = baseId == 0x010B0A7; - std::string type = "weapon"; - if (isShield) { - type = "armor"; - } - if (isArrow) { - type = "ammo"; - } - std::stringstream ss; - ss << "[" << nlohmann::json{ { "formId", baseId }, { "type", type } }.dump() - << ", " << (preventRemoval ? "true" : "false") << ", " - << (silent ? "true" : "false") << "]"; - std::string args = ss.str(); - spdlog::info("Equipping item: {}", args); - SpSnippet("Actor", "EquipItem", args.data(), actor.GetFormId()) + + const std::string type = ([&] { + if (isShield) { + return "armor"; + } else if (isArrow) { + return "ammo"; + } else { + return "weapon"; + } + })(); + + SpSnippetObjectArgument itemToEquip; + itemToEquip.formId = baseId; + itemToEquip.type = type; + + std::vector>> + equipItemArgs; + equipItemArgs.push_back(itemToEquip); + equipItemArgs.push_back(preventRemoval); + equipItemArgs.push_back(silent); + + SpSnippet("Actor", "EquipItem", equipItemArgs, actor.GetFormId()) .Execute(&actor, SpSnippetMode::kNoReturnResult); + if (!isShield && !isArrow) { - SpSnippet("Actor", "DrawWeapon", "[]", actor.GetFormId()) + static const std::vector>> + kEmptyArgs; + + SpSnippet("Actor", "DrawWeapon", kEmptyArgs, actor.GetFormId()) .Execute(&actor, SpSnippetMode::kNoReturnResult); } } diff --git a/skymp5-server/cpp/server_guest_lib/formulas/IDamageFormula.h b/skymp5-server/cpp/server_guest_lib/formulas/IDamageFormula.h index b71a5a0d8d..408e16e07b 100644 --- a/skymp5-server/cpp/server_guest_lib/formulas/IDamageFormula.h +++ b/skymp5-server/cpp/server_guest_lib/formulas/IDamageFormula.h @@ -1,8 +1,8 @@ #pragma once +#include "HitData.h" +#include "SpellCastData.h" class MpActor; -struct HitData; -struct SpellCastData; class IDamageFormula { diff --git a/skymp5-server/cpp/server_guest_lib/gamemode_events/ReadBookEvent.cpp b/skymp5-server/cpp/server_guest_lib/gamemode_events/ReadBookEvent.cpp index 5546581322..9884189b9e 100644 --- a/skymp5-server/cpp/server_guest_lib/gamemode_events/ReadBookEvent.cpp +++ b/skymp5-server/cpp/server_guest_lib/gamemode_events/ReadBookEvent.cpp @@ -90,7 +90,7 @@ void ReadBookEvent::OnFireBlocked(WorldState* worldState) std::vector arguments = { aSpell }; SpSnippet("Actor", "RemoveSpell", - SpSnippetFunctionGen::SerializeArguments(arguments).data(), + SpSnippetFunctionGen::SerializeArguments(arguments), actor->GetFormId()) .Execute(actor, SpSnippetMode::kNoReturnResult); } 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 607c7828fc..aee1d91a82 100644 --- a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusActor.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusActor.cpp @@ -63,8 +63,7 @@ VarValue PapyrusActor::SetActorValue(VarValue self, // spsnippet don't support auto sending to host. so determining current // hoster explicitly - SpSnippet(GetName(), "SetActorValue", serializedArgs.data(), - actor->GetFormId()) + SpSnippet(GetName(), "SetActorValue", serializedArgs, actor->GetFormId()) .Execute(it == actor->GetParent()->hosters.end() ? actor : &actor->GetParent()->GetFormAt(it->second), @@ -168,8 +167,7 @@ VarValue PapyrusActor::SetAlpha(VarValue self, auto funcName = "SetAlpha"; auto serializedArgs = SpSnippetFunctionGen::SerializeArguments(arguments); for (auto listener : selfRefr->GetActorListeners()) { - SpSnippet(GetName(), funcName, serializedArgs.data(), - selfRefr->GetFormId()) + SpSnippet(GetName(), funcName, serializedArgs, selfRefr->GetFormId()) .Execute(listener, SpSnippetMode::kNoReturnResult); } } @@ -215,7 +213,7 @@ VarValue PapyrusActor::EquipItem(VarValue self, } SpSnippet(GetName(), "EquipItem", - SpSnippetFunctionGen::SerializeArguments(arguments).data(), + SpSnippetFunctionGen::SerializeArguments(arguments), actor->GetFormId()) .Execute(actor, SpSnippetMode::kNoReturnResult); } @@ -249,7 +247,7 @@ VarValue PapyrusActor::EquipSpell(VarValue self, } SpSnippet(GetName(), "EquipSpell", - SpSnippetFunctionGen::SerializeArguments(arguments).data(), + SpSnippetFunctionGen::SerializeArguments(arguments), actor->GetFormId()) .Execute(actor, SpSnippetMode::kNoReturnResult); } @@ -267,7 +265,7 @@ VarValue PapyrusActor::UnequipItem(VarValue self, if (auto actor = GetFormPtr(self)) { SpSnippet(GetName(), "UnequipItem", - SpSnippetFunctionGen::SerializeArguments(arguments).data(), + SpSnippetFunctionGen::SerializeArguments(arguments), actor->GetFormId()) .Execute(actor, SpSnippetMode::kNoReturnResult); } @@ -282,7 +280,7 @@ VarValue PapyrusActor::SetDontMove(VarValue self, throw std::runtime_error("SetDontMove requires at least one argument"); } SpSnippet(GetName(), "SetDontMove", - SpSnippetFunctionGen::SerializeArguments(arguments).data(), + SpSnippetFunctionGen::SerializeArguments(arguments), actor->GetFormId()) .Execute(actor, SpSnippetMode::kNoReturnResult); } @@ -484,7 +482,7 @@ VarValue PapyrusActor::AddSpell(VarValue self, actor->AddSpell(spellId); SpSnippet(GetName(), "AddSpell", - SpSnippetFunctionGen::SerializeArguments(arguments).data(), + SpSnippetFunctionGen::SerializeArguments(arguments), actor->GetFormId()) .Execute(actor, SpSnippetMode::kNoReturnResult); @@ -528,7 +526,7 @@ VarValue PapyrusActor::RemoveSpell(VarValue self, actor->RemoveSpell(spellId); SpSnippet(GetName(), "RemoveSpell", - SpSnippetFunctionGen::SerializeArguments(arguments).data(), + SpSnippetFunctionGen::SerializeArguments(arguments), actor->GetFormId()) .Execute(actor, SpSnippetMode::kNoReturnResult); diff --git a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusDebug.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusDebug.cpp index 74afca3a81..0ad4f8e388 100644 --- a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusDebug.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusDebug.cpp @@ -10,7 +10,7 @@ VarValue PapyrusDebug::SendAnimationEvent( if (targetActor) { auto funcName = "SendAnimationEvent"; auto s = SpSnippetFunctionGen::SerializeArguments(arguments, targetActor); - SpSnippet(GetName(), funcName, s.data()) + SpSnippet(GetName(), funcName, s) .Execute(targetActor, SpSnippetMode::kNoReturnResult); } return VarValue::None(); diff --git a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusEffectShader.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusEffectShader.cpp index 1afdea5b35..abf5fc9a1a 100644 --- a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusEffectShader.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusEffectShader.cpp @@ -32,13 +32,13 @@ void PapyrusEffectShader::Helper(VarValue& self, const char* funcName, for (auto listener : actorForm->GetActorListeners()) { SpSnippet( GetName(), funcName, - SpSnippetFunctionGen::SerializeArguments(arguments, listener).data(), + SpSnippetFunctionGen::SerializeArguments(arguments, listener), selfRec.ToGlobalId(selfRec.rec->GetId())) .Execute(listener, SpSnippetMode::kNoReturnResult); // Workaround to use this function on player clone if (actorForm->GetFormId() == listener->GetFormId()) { SpSnippet(GetName(), funcName, - SpSnippetFunctionGen::SerializeArguments(arguments).data(), + SpSnippetFunctionGen::SerializeArguments(arguments), selfRec.ToGlobalId(selfRec.rec->GetId())) .Execute(listener, SpSnippetMode::kNoReturnResult); } diff --git a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusGame.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusGame.cpp index 36a5c08b7e..ac5766d391 100644 --- a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusGame.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusGame.cpp @@ -142,7 +142,7 @@ VarValue PapyrusGame::GetCameraState(VarValue self, if (auto actor = compatibilityPolicy->GetDefaultActor( GetName(), "GetCameraState", self.GetMetaStackId())) { Viet::Promise promise = - SpSnippet(GetName(), "GetCameraState", serializedArgs.data()) + SpSnippet(GetName(), "GetCameraState", serializedArgs) .Execute(actor, SpSnippetMode::kReturnResult); return VarValue(Viet::Promise(promise)); } @@ -156,7 +156,7 @@ void PapyrusGame::RaceMenuHelper(VarValue& self, const char* funcName, if (auto actor = compatibilityPolicy->GetDefaultActor( GetName(), funcName, self.GetMetaStackId())) { actor->SetRaceMenuOpen(true); - SpSnippet(GetName(), funcName, serializedArgs.data()) + SpSnippet(GetName(), funcName, serializedArgs) .Execute(actor, SpSnippetMode::kNoReturnResult); } } @@ -209,7 +209,7 @@ VarValue PapyrusGame::ShakeController(VarValue self, auto serializedArgs = SpSnippetFunctionGen::SerializeArguments(arguments); if (auto actor = compatibilityPolicy->GetDefaultActor( GetName(), funcName, self.GetMetaStackId())) { - SpSnippet(GetName(), funcName, serializedArgs.data()) + SpSnippet(GetName(), funcName, serializedArgs) .Execute(actor, SpSnippetMode::kNoReturnResult); } diff --git a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusNetImmerse.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusNetImmerse.cpp index b529b1b384..f35040e8f8 100644 --- a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusNetImmerse.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusNetImmerse.cpp @@ -32,7 +32,7 @@ VarValue PapyrusNetImmerse::SetNodeTextureSet( auto funcName = "SetNodeTextureSet"; auto serializedArgs = SpSnippetFunctionGen::SerializeArguments(arguments); for (auto listener : ref->GetActorListeners()) { - SpSnippet(GetName(), funcName, serializedArgs.data()) + SpSnippet(GetName(), funcName, serializedArgs) .Execute(listener, SpSnippetMode::kNoReturnResult); } @@ -64,7 +64,7 @@ VarValue PapyrusNetImmerse::SetNodeScale( auto funcName = "SetNodeScale"; auto serializedArgs = SpSnippetFunctionGen::SerializeArguments(arguments); for (auto listener : ref->GetActorListeners()) { - SpSnippet(GetName(), funcName, serializedArgs.data()) + SpSnippet(GetName(), funcName, serializedArgs) .Execute(listener, SpSnippetMode::kNoReturnResult); } diff --git a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusObjectReference.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusObjectReference.cpp index 94abb4c989..5aa4617fbf 100644 --- a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusObjectReference.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusObjectReference.cpp @@ -168,7 +168,7 @@ VarValue PapyrusObjectReference::AddItem( if (!silent && count > 0) { if (auto actor = selfRefr->AsActor()) { auto args = SpSnippetFunctionGen::SerializeArguments(arguments); - (void)SpSnippet("SkympHacks", "AddItem", args.data()) + (void)SpSnippet("SkympHacks", "AddItem", args) .Execute(actor, SpSnippetMode::kNoReturnResult); } } @@ -263,7 +263,7 @@ VarValue PapyrusObjectReference::RemoveItem( if (!silent && count > 0) { if (auto actor = selfRefr->AsActor()) { auto args = SpSnippetFunctionGen::SerializeArguments(arguments); - (void)SpSnippet("SkympHacks", "RemoveItem", args.data()) + (void)SpSnippet("SkympHacks", "RemoveItem", args) .Execute(actor, SpSnippetMode::kNoReturnResult); } } @@ -352,8 +352,7 @@ void PlaceAtMeSpSnippet(MpObjectReference* self, auto funcName = "PlaceAtMe"; auto serializedArgs = SpSnippetFunctionGen::SerializeArguments(arguments); for (auto listener : self->GetActorListeners()) { - SpSnippet("ObjectReference", funcName, serializedArgs.data(), - self->GetFormId()) + SpSnippet("ObjectReference", funcName, serializedArgs, self->GetFormId()) .Execute(listener, SpSnippetMode::kNoReturnResult); } } @@ -450,8 +449,7 @@ VarValue PapyrusObjectReference::Enable(VarValue self, auto funcName = "Enable"; auto serializedArgs = SpSnippetFunctionGen::SerializeArguments(arguments); for (auto listener : selfRefr->GetActorListeners()) { - SpSnippet(GetName(), funcName, serializedArgs.data(), - selfRefr->GetFormId()) + SpSnippet(GetName(), funcName, serializedArgs, selfRefr->GetFormId()) .Execute(listener, SpSnippetMode::kNoReturnResult); } } @@ -471,8 +469,7 @@ VarValue PapyrusObjectReference::Disable( auto funcName = "Disable"; auto serializedArgs = SpSnippetFunctionGen::SerializeArguments(arguments); for (auto listener : selfRefr->GetActorListeners()) { - SpSnippet(GetName(), funcName, serializedArgs.data(), - selfRefr->GetFormId()) + SpSnippet(GetName(), funcName, serializedArgs, selfRefr->GetFormId()) .Execute(listener, SpSnippetMode::kNoReturnResult); } } @@ -581,8 +578,7 @@ VarValue PapyrusObjectReference::SetPosition( auto funcName = "SetPosition"; auto serializedArgs = SpSnippetFunctionGen::SerializeArguments(arguments); for (auto listener : selfRefr->GetActorListeners()) { - SpSnippet(GetName(), funcName, serializedArgs.data(), - selfRefr->GetFormId()) + SpSnippet(GetName(), funcName, serializedArgs, selfRefr->GetFormId()) .Execute(listener, SpSnippetMode::kNoReturnResult); } } @@ -620,8 +616,7 @@ VarValue PapyrusObjectReference::PlayAnimation( auto funcName = "PlayAnimation"; auto serializedArgs = SpSnippetFunctionGen::SerializeArguments(arguments); for (auto listener : selfRefr->GetActorListeners()) { - SpSnippet(GetName(), funcName, serializedArgs.data(), - selfRefr->GetFormId()) + SpSnippet(GetName(), funcName, serializedArgs, selfRefr->GetFormId()) .Execute(listener, SpSnippetMode::kNoReturnResult); } } @@ -645,9 +640,9 @@ VarValue PapyrusObjectReference::PlayAnimationAndWait( std::vector> promises; for (auto listener : selfRefr->GetActorListeners()) { - auto promise = SpSnippet(GetName(), funcName, serializedArgs.data(), - selfRefr->GetFormId()) - .Execute(listener, SpSnippetMode::kReturnResult); + auto promise = + SpSnippet(GetName(), funcName, serializedArgs, selfRefr->GetFormId()) + .Execute(listener, SpSnippetMode::kReturnResult); promises.push_back(promise); } @@ -684,8 +679,7 @@ VarValue PapyrusObjectReference::PlayGamebryoAnimation( auto funcName = "PlayGamebryoAnimation"; auto serializedArgs = SpSnippetFunctionGen::SerializeArguments(arguments); for (auto listener : selfRefr->GetActorListeners()) { - SpSnippet(GetName(), funcName, serializedArgs.data(), - selfRefr->GetFormId()) + SpSnippet(GetName(), funcName, serializedArgs, selfRefr->GetFormId()) .Execute(listener, SpSnippetMode::kNoReturnResult); } } @@ -886,8 +880,7 @@ VarValue PapyrusObjectReference::SetDisplayName( auto funcName = "SetDisplayName"; auto serializedArgs = SpSnippetFunctionGen::SerializeArguments(arguments); for (auto listener : selfRefr->GetActorListeners()) { - SpSnippet(GetName(), funcName, serializedArgs.data(), - selfRefr->GetFormId()) + SpSnippet(GetName(), funcName, serializedArgs, selfRefr->GetFormId()) .Execute(listener, SpSnippetMode::kNoReturnResult); } } diff --git a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusSound.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusSound.cpp index 59975b15b6..2f8fc6fbce 100644 --- a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusSound.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusSound.cpp @@ -22,7 +22,7 @@ VarValue PapyrusSound::Play(VarValue self, auto funcName = "Play"; auto serializedArgs = SpSnippetFunctionGen::SerializeArguments(arguments); for (auto listener : refr->GetActorListeners()) { - SpSnippet(GetName(), funcName, serializedArgs.data(), selfId) + SpSnippet(GetName(), funcName, serializedArgs, selfId) .Execute(listener, SpSnippetMode::kNoReturnResult); } } diff --git a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusVisualEffect.cpp b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusVisualEffect.cpp index a2929193c9..13037ac255 100644 --- a/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusVisualEffect.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_classes/PapyrusVisualEffect.cpp @@ -32,13 +32,13 @@ void PapyrusVisualEffect::Helper(VarValue& self, const char* funcName, for (auto listener : actorForm->GetActorListeners()) { SpSnippet( GetName(), funcName, - SpSnippetFunctionGen::SerializeArguments(arguments, listener).data(), + SpSnippetFunctionGen::SerializeArguments(arguments, listener), selfRec.ToGlobalId(selfRec.rec->GetId())) .Execute(listener, SpSnippetMode::kNoReturnResult); // Workaround to use this function on player clone if (actorForm->GetFormId() == listener->GetFormId()) { SpSnippet(GetName(), funcName, - SpSnippetFunctionGen::SerializeArguments(arguments).data(), + SpSnippetFunctionGen::SerializeArguments(arguments), selfRec.ToGlobalId(selfRec.rec->GetId())) .Execute(listener, SpSnippetMode::kNoReturnResult); } diff --git a/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageUtils.cpp b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageUtils.cpp index 951e190a60..c27f099de6 100644 --- a/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageUtils.cpp +++ b/skymp5-server/cpp/server_guest_lib/script_storages/ScriptStorageUtils.cpp @@ -1,5 +1,6 @@ #include "ScriptStorageUtils.h" +#include #include #include @@ -7,10 +8,12 @@ std::string ScriptStorageUtils::GetFileName(const std::string& path) { std::string s = path; while (s.find('/') != s.npos || s.find('\\') != s.npos) { - while (s.find('/') != s.npos) + while (s.find('/') != s.npos) { s = { s.begin() + s.find('/') + 1, s.end() }; - while (s.find('\\') != s.npos) + } + while (s.find('\\') != s.npos) { s = { s.begin() + s.find('\\') + 1, s.end() }; + } } return s; } diff --git a/skyrim-platform/src/platform_se/CMakeLists.txt b/skyrim-platform/src/platform_se/CMakeLists.txt index 77ccd21872..cae4cb3df8 100644 --- a/skyrim-platform/src/platform_se/CMakeLists.txt +++ b/skyrim-platform/src/platform_se/CMakeLists.txt @@ -158,11 +158,11 @@ if(NOT "${SKIP_SKYRIM_PLATFORM_BUILDING}") "${CONVERT_FILES_DIR}/Definitions.txt" "${CMAKE_CURRENT_BINARY_DIR}/_codegen/skyrimPlatform.ts" ) - add_custom_target(codegen ALL COMMAND $ ${CODEGEN_ARGS}) - add_dependencies(codegen TSConverter) + add_custom_target(skyrim_platform_codegen ALL COMMAND $ ${CODEGEN_ARGS}) + add_dependencies(skyrim_platform_codegen TSConverter) - # force "skyrim-platform" custom target to be built strictly after codegen - list(APPEND DEPENDENCIES_FOR_CUSTOM_TARGETS codegen) + # force "skyrim-platform" custom target to be built strictly after skyrim_platform_codegen + list(APPEND DEPENDENCIES_FOR_CUSTOM_TARGETS skyrim_platform_codegen) endif() # this target was originally called pack, now it is called skyrim-platform diff --git a/skyrim-platform/src/platform_se/skyrim_platform/CallNative.cpp b/skyrim-platform/src/platform_se/skyrim_platform/CallNative.cpp index 036fea7705..c61af39e9e 100644 --- a/skyrim-platform/src/platform_se/skyrim_platform/CallNative.cpp +++ b/skyrim-platform/src/platform_se/skyrim_platform/CallNative.cpp @@ -2,12 +2,12 @@ #include "CallNativeApi.h" #include "GetNativeFunctionAddr.h" #include "NullPointerException.h" -#include "Overloaded.h" #include "SendAnimationEvent.h" #include "SkyrimPlatform.h" #include "StringHolder.h" #include "VmCall.h" #include "VmCallback.h" +#include "viet/Overloaded.h" extern CallNativeApi::NativeCallRequirements g_nativeCallRequirements; @@ -20,7 +20,7 @@ Variable CallNative::AnySafeToVariable(const CallNative::AnySafe& v, return res; } return std::visit( - overloaded{ + Viet::Overloaded{ [&](double f) { Variable res; treatNumberAsInt ? res.SetSInt((int)floor(f)) : res.SetFloat((float)f); diff --git a/unit/ArchiveTestBinary.cpp b/unit/ArchiveTestBinary.cpp new file mode 100644 index 0000000000..2759a7eadc --- /dev/null +++ b/unit/ArchiveTestBinary.cpp @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "archives/BitStreamInputArchive.h" +#include "archives/BitStreamOutputArchive.h" + +namespace { +struct VariantStruct +{ + template + void Serialize(Archive& archive) + { + archive.Serialize("args", args); + } + + std::vector> args; +}; +} + +TEST_CASE("BitStreamArchive variant", "[Archives] [Serialization]") +{ + VariantStruct variantStruct; + variantStruct.args.push_back(255); + variantStruct.args.push_back("a"); + variantStruct.args.push_back(2); + variantStruct.args.push_back("b"); + variantStruct.args.push_back(3); + variantStruct.args.push_back("c"); + + SLNet::BitStream stream; + + BitStreamOutputArchive archive(stream); + variantStruct.Serialize(archive); + + std::vector data = { static_cast(stream.GetData()), + static_cast(stream.GetData()) + + stream.GetNumberOfBytesUsed() }; + + REQUIRE(data == + std::vector{ + 0, 0, 0, 6, // vector size + 0, 0, 0, 0, // variant index 0 (int) + 0, 0, 0, 255, // 255 + 0, 0, 0, 1, // variant index 1 (string) + 0, 0, 0, 1, // string length 1 + 'a', // character 'a' + 0, 0, 0, 0, // variant index 0 (int) + 0, 0, 0, 2, // 2 + 0, 0, 0, 1, // variant index 1 (string) + 0, 0, 0, 1, // string length 1 + 'b', // character 'b' + 0, 0, 0, 0, // variant index 0 (int) + 0, 0, 0, 3, // 3 + 0, 0, 0, 1, // variant index 1 (string) + 0, 0, 0, 1, // string length 1 + 'c' // character 'c' + }); + + SLNet::BitStream streamRead; + + VariantStruct variantStructRead; + + BitStreamInputArchive archiveRead(stream); + variantStructRead.Serialize(archiveRead); + + REQUIRE(variantStructRead.args == variantStruct.args); +} diff --git a/unit/ArchiveTest.cpp b/unit/ArchiveTestSimdJson.cpp similarity index 100% rename from unit/ArchiveTest.cpp rename to unit/ArchiveTestSimdJson.cpp diff --git a/unit/ConsoleCommandTest.cpp b/unit/ConsoleCommandTest.cpp index 426ff70f49..1cc251050b 100644 --- a/unit/ConsoleCommandTest.cpp +++ b/unit/ConsoleCommandTest.cpp @@ -46,8 +46,11 @@ TEST_CASE("ConsoleCommand packet is parsed", "[ConsoleCommand]") 122, reinterpret_cast(msg.data()), msg.size(), listener); - REQUIRE(listener.args == - std::vector({ 0x14, 0x12eb7, 0x1 })); + REQUIRE( + listener.args == + std::vector( + { ConsoleCommands::Argument(0x14), ConsoleCommands::Argument(0x12eb7), + ConsoleCommands::Argument(0x1) })); REQUIRE(listener.consoleCommandName == "additem"); REQUIRE(listener.rawMsgData.userId == 122); } @@ -66,8 +69,10 @@ TEST_CASE("AddItem doesn't execute for non-privilleged users", msgData.userId = 0; REQUIRE_THROWS_WITH( - p.GetActionListener().OnConsoleCommand(msgData, "additem", - { 0x14, 0x12eb7, 0x108 }), + p.GetActionListener().OnConsoleCommand( + msgData, "additem", + { ConsoleCommands::Argument(0x14), ConsoleCommands::Argument(0x12eb7), + ConsoleCommands::Argument(0x108) }), ContainsSubstring("Not enough permissions to use this command")); p.DestroyActor(0xff000000); @@ -90,7 +95,9 @@ TEST_CASE("AddItem executes", "[ConsoleCommand][espm]") p.Messages().clear(); p.GetActionListener().OnConsoleCommand(msgData, "additem", - { 0x14, 0x12eb7, 0x108 }); + { ConsoleCommands::Argument(0x14), + ConsoleCommands::Argument(0x12eb7), + ConsoleCommands::Argument(0x108) }); p.Tick(); // send deferred messages @@ -130,8 +137,10 @@ TEST_CASE("PlaceAtMe executes", "[ConsoleCommand][espm]") msgData.userId = 0; p.Messages().clear(); - p.GetActionListener().OnConsoleCommand(msgData, "placeatme", - { 0x14, EncGiant01 }); + p.GetActionListener().OnConsoleCommand( + msgData, "placeatme", + { ConsoleCommands::Argument(0x14), + ConsoleCommands::Argument(EncGiant01) }); auto& refr = p.worldState.GetFormAt(0xff000001); REQUIRE(refr.GetBaseId() == EncGiant01); diff --git a/unit/PapyrusObjectReferenceTest.cpp b/unit/PapyrusObjectReferenceTest.cpp index 682ab8f7d9..c9fb4a6e9d 100644 --- a/unit/PapyrusObjectReferenceTest.cpp +++ b/unit/PapyrusObjectReferenceTest.cpp @@ -51,13 +51,11 @@ TestReference& CreateMpObjectReference(PartOne& partOne, uint32_t id) auto GetDummyMessageData() { - static simdjson::dom::parser parser; static uint8_t unparsed[] = { Networking::MinPacketId, '{', '}' }; ActionListener::RawMessageData data; data.userId = 1; data.unparsed = unparsed; - data.parsed = parser.parse(std::string("{}")).value(); data.unparsedLength = std::size(unparsed); return data; }; diff --git a/unit/PartOneTest.cpp b/unit/PartOneTest.cpp index f30cb39853..934031c8a3 100644 --- a/unit/PartOneTest.cpp +++ b/unit/PartOneTest.cpp @@ -44,8 +44,9 @@ TEST_CASE("OnCustomPacket", "[PartOne]") DoConnect(partOne, 0); DoMessage(partOne, 0, - nlohmann::json{ { "t", MsgType::CustomPacket }, - { "content", { { "x", "y" } } } }); + nlohmann::json{ + { "t", MsgType::CustomPacket }, + { "contentJsonDump", nlohmann::json{ { "x", "y" } }.dump() } }); REQUIRE_THAT(lst->str(), ContainsSubstring("OnCustomPacket(0, {\"x\":\"y\"})")); @@ -61,7 +62,8 @@ TEST_CASE("Messages for non-existent users", "[PartOne]") REQUIRE_THROWS_WITH( DoMessage(partOne, 0, nlohmann::json{ { "t", MsgType::CustomPacket }, - { "content", { { "x", "y" } } } }), + { "contentJsonDump", + nlohmann::json{ { "x", "y" } }.dump() } }), ContainsSubstring("User with id 0 doesn't exist")); DoConnect(partOne, 0); @@ -69,14 +71,16 @@ TEST_CASE("Messages for non-existent users", "[PartOne]") REQUIRE_NOTHROW( DoMessage(partOne, 0, nlohmann::json{ { "t", MsgType::CustomPacket }, - { "content", { { "x", "y" } } } })); + { "contentJsonDump", + nlohmann::json{ { "x", "y" } }.dump() } })); DoDisconnect(partOne, 0); REQUIRE_THROWS_WITH( DoMessage(partOne, 0, nlohmann::json{ { "t", MsgType::CustomPacket }, - { "content", { { "x", "y" } } } }), + { "contentJsonDump", + nlohmann::json{ { "x", "y" } }.dump() } }), ContainsSubstring("User with id 0 doesn't exist")); } @@ -118,8 +122,9 @@ TEST_CASE("Server custom packet", "[PartOne]") partOne.SendCustomPacket(1, nlohmann::json({ { "x", "y" } }).dump()); REQUIRE(partOne.Messages().size() == 1); REQUIRE(partOne.Messages()[0].j.dump() == - nlohmann::json{ { "type", "customPacket" }, - { "content", { { "x", "y" } } } } + nlohmann::json{ + { "t", static_cast(MsgType::CustomPacket) }, + { "contentJsonDump", nlohmann::json{ { "x", "y" } }.dump() } } .dump()); REQUIRE(partOne.Messages()[0].userId == 1); REQUIRE(partOne.Messages()[0].reliable); diff --git a/unit/PartOne_ActivateTest.cpp b/unit/PartOne_ActivateTest.cpp index 7cdac93449..0d898d62c4 100644 --- a/unit/PartOne_ActivateTest.cpp +++ b/unit/PartOne_ActivateTest.cpp @@ -154,7 +154,8 @@ TEST_CASE("See harvested PurpleMountainFlower in Whiterun", "[PartOne][espm]") auto it = std::find_if( partOne.Messages().begin(), partOne.Messages().end(), [&](auto m) { - return m.reliable && m.userId == 0 && m.j["type"] == "createActor" && + return m.reliable && m.userId == 0 && + m.j["t"] == static_cast(MsgType::CreateActor) && m.j["refrId"] == refrId && m.j["props"] == nlohmann::json{ { "isHarvested", true } }; }); @@ -182,7 +183,8 @@ TEST_CASE("See open DisplayCaseSmFlat01 in Whiterun", "[PartOne][espm]") auto it = std::find_if( partOne.Messages().begin(), partOne.Messages().end(), [&](auto m) { - return m.reliable && m.userId == 0 && m.j["type"] == "createActor" && + return m.reliable && m.userId == 0 && + m.j["t"] == static_cast(MsgType::CreateActor) && m.j["refrId"] == refrId && m.j["props"] == nlohmann::json{ { "isOpen", true } }; }); @@ -210,7 +212,8 @@ TEST_CASE("Activate DisplayCaseSmFlat01 in Whiterun", "[PartOne][espm]") auto it = std::find_if( partOne.Messages().begin(), partOne.Messages().end(), [&](auto m) { - return m.reliable && m.userId == 0 && m.j["type"] == "createActor" && + return m.reliable && m.userId == 0 && + m.j["t"] == static_cast(MsgType::CreateActor) && m.j["refrId"] == refrId && m.j["props"] == nullptr; }); REQUIRE(it != partOne.Messages().end()); @@ -316,7 +319,8 @@ TEST_CASE("Activate PurpleMountainFlower in Whiterun", "[PartOne][espm]") auto it = std::find_if( partOne.Messages().begin(), partOne.Messages().end(), [&](auto m) { - return m.reliable && m.userId == 0 && m.j["type"] == "createActor" && + return m.reliable && m.userId == 0 && + m.j["t"] == static_cast(MsgType::CreateActor) && m.j["refrId"] == refrId && m.j["props"] == nullptr; }); REQUIRE(it != partOne.Messages().end()); @@ -490,11 +494,11 @@ TEST_CASE("Server creates and destroys an object for user correcly", partOne.SetUserActor(0, 0xff000ABC); auto refId = 0x01000f69; - REQUIRE(std::find_if(partOne.Messages().begin(), partOne.Messages().end(), - [&](auto m) { - return m.j["type"] == "createActor" && m.reliable && - m.userId == 0 && m.j["refrId"] == 0x01000f69; - }) != partOne.Messages().end()); + REQUIRE(std::find_if( + partOne.Messages().begin(), partOne.Messages().end(), [&](auto m) { + return m.j["t"] == static_cast(MsgType::CreateActor) && + m.reliable && m.userId == 0 && m.j["refrId"] == 0x01000f69; + }) != partOne.Messages().end()); auto& ac = partOne.worldState.GetFormAt(0xff000ABC); ac.SetPos({ 0, 0, 0 }); @@ -570,7 +574,8 @@ TEST_CASE("Activate torch", "[espm][PartOne]") auto it = std::find_if( partOne.Messages().begin(), partOne.Messages().end(), [&](auto m) { - return m.reliable && m.userId == 0 && m.j["type"] == "createActor" && + return m.reliable && m.userId == 0 && + m.j["t"] == static_cast(MsgType::CreateActor) && m.j["refrId"] == refrId && m.j["props"] == nullptr; }); REQUIRE(it != partOne.Messages().end()); diff --git a/skyrim-platform/src/platform_se/skyrim_platform/Overloaded.h b/viet/include/Overloaded.h similarity index 64% rename from skyrim-platform/src/platform_se/skyrim_platform/Overloaded.h rename to viet/include/Overloaded.h index 90fd1bd748..7032122719 100644 --- a/skyrim-platform/src/platform_se/skyrim_platform/Overloaded.h +++ b/viet/include/Overloaded.h @@ -1,9 +1,12 @@ #pragma once // See https://en.cppreference.com/w/cpp/utility/variant/visit + +namespace Viet { template -struct overloaded : Ts... +struct Overloaded : Ts... { using Ts::operator()...; }; template -overloaded(Ts...) -> overloaded; +Overloaded(Ts...) -> Overloaded; +}