diff options
Diffstat (limited to 'NorthstarDLL/scripts')
-rw-r--r-- | NorthstarDLL/scripts/client/clientchathooks.cpp | 70 | ||||
-rw-r--r-- | NorthstarDLL/scripts/client/scriptbrowserhooks.cpp | 25 | ||||
-rw-r--r-- | NorthstarDLL/scripts/client/scriptmainmenupromos.cpp | 124 | ||||
-rw-r--r-- | NorthstarDLL/scripts/client/scriptmodmenu.cpp | 166 | ||||
-rw-r--r-- | NorthstarDLL/scripts/client/scriptserverbrowser.cpp | 410 | ||||
-rw-r--r-- | NorthstarDLL/scripts/client/scriptservertoclientstringcommand.cpp | 19 | ||||
-rw-r--r-- | NorthstarDLL/scripts/scriptdatatables.cpp | 910 | ||||
-rw-r--r-- | NorthstarDLL/scripts/scriptjson.cpp | 249 | ||||
-rw-r--r-- | NorthstarDLL/scripts/scriptutility.cpp | 14 | ||||
-rw-r--r-- | NorthstarDLL/scripts/server/miscserverfixes.cpp | 7 | ||||
-rw-r--r-- | NorthstarDLL/scripts/server/miscserverscript.cpp | 60 | ||||
-rw-r--r-- | NorthstarDLL/scripts/server/scriptuserinfo.cpp | 105 |
12 files changed, 2159 insertions, 0 deletions
diff --git a/NorthstarDLL/scripts/client/clientchathooks.cpp b/NorthstarDLL/scripts/client/clientchathooks.cpp new file mode 100644 index 00000000..0fc68302 --- /dev/null +++ b/NorthstarDLL/scripts/client/clientchathooks.cpp @@ -0,0 +1,70 @@ +#include "pch.h" +#include "squirrel/squirrel.h" + +#include "server/serverchathooks.h" +#include "client/localchatwriter.h" + +#include <rapidjson/document.h> + +AUTOHOOK_INIT() + +// clang-format off +AUTOHOOK(CHudChat__AddGameLine, client.dll + 0x22E580, +void, __fastcall, (void* self, const char* message, int inboxId, bool isTeam, bool isDead)) +// clang-format on +{ + // This hook is called for each HUD, but we only want our logic to run once. + if (self != *CHudChat::allHuds) + return; + + int senderId = inboxId & CUSTOM_MESSAGE_INDEX_MASK; + bool isAnonymous = senderId == 0; + bool isCustom = isAnonymous || (inboxId & CUSTOM_MESSAGE_INDEX_BIT); + + // Type is set to 0 for non-custom messages, custom messages have a type encoded as the first byte + int type = 0; + const char* payload = message; + if (isCustom) + { + type = message[0]; + payload = message + 1; + } + + SQRESULT result = g_pSquirrel<ScriptContext::CLIENT>->Call( + "CHudChat_ProcessMessageStartThread", static_cast<int>(senderId) - 1, payload, isTeam, isDead, type); + if (result == SQRESULT_ERROR) + for (CHudChat* hud = *CHudChat::allHuds; hud != NULL; hud = hud->next) + CHudChat__AddGameLine(hud, message, inboxId, isTeam, isDead); +} + +ADD_SQFUNC("void", NSChatWrite, "int context, string text", "", ScriptContext::CLIENT) +{ + int chatContext = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1); + const char* str = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2); + + LocalChatWriter((LocalChatWriter::Context)chatContext).Write(str); + return SQRESULT_NULL; +} + +ADD_SQFUNC("void", NSChatWriteRaw, "int context, string text", "", ScriptContext::CLIENT) +{ + int chatContext = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1); + const char* str = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2); + + LocalChatWriter((LocalChatWriter::Context)chatContext).InsertText(str); + return SQRESULT_NULL; +} + +ADD_SQFUNC("void", NSChatWriteLine, "int context, string text", "", ScriptContext::CLIENT) +{ + int chatContext = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1); + const char* str = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2); + + LocalChatWriter((LocalChatWriter::Context)chatContext).WriteLine(str); + return SQRESULT_NULL; +} + +ON_DLL_LOAD_CLIENT("client.dll", ClientChatHooks, (CModule module)) +{ + AUTOHOOK_DISPATCH() +} diff --git a/NorthstarDLL/scripts/client/scriptbrowserhooks.cpp b/NorthstarDLL/scripts/client/scriptbrowserhooks.cpp new file mode 100644 index 00000000..df4014de --- /dev/null +++ b/NorthstarDLL/scripts/client/scriptbrowserhooks.cpp @@ -0,0 +1,25 @@ +#include "pch.h" + +AUTOHOOK_INIT() + +bool* bIsOriginOverlayEnabled; + +// clang-format off +AUTOHOOK(OpenExternalWebBrowser, engine.dll + 0x184E40, +void, __fastcall, (char* pUrl, char flags)) +// clang-format on +{ + bool bIsOriginOverlayEnabledOriginal = *bIsOriginOverlayEnabled; + if (flags & 2 && !strncmp(pUrl, "http", 4)) // custom force external browser flag + *bIsOriginOverlayEnabled = false; // if this bool is false, game will use an external browser rather than the origin overlay one + + OpenExternalWebBrowser(pUrl, flags); + *bIsOriginOverlayEnabled = bIsOriginOverlayEnabledOriginal; +} + +ON_DLL_LOAD_CLIENT("engine.dll", ScriptExternalBrowserHooks, (CModule module)) +{ + AUTOHOOK_DISPATCH() + + bIsOriginOverlayEnabled = module.Offset(0x13978255).As<bool*>(); +} diff --git a/NorthstarDLL/scripts/client/scriptmainmenupromos.cpp b/NorthstarDLL/scripts/client/scriptmainmenupromos.cpp new file mode 100644 index 00000000..0ea167f8 --- /dev/null +++ b/NorthstarDLL/scripts/client/scriptmainmenupromos.cpp @@ -0,0 +1,124 @@ +#include "pch.h" +#include "squirrel/squirrel.h" +#include "masterserver/masterserver.h" + +// mirror this in script +enum eMainMenuPromoDataProperty +{ + newInfoTitle1, + newInfoTitle2, + newInfoTitle3, + + largeButtonTitle, + largeButtonText, + largeButtonUrl, + largeButtonImageIndex, + + smallButton1Title, + smallButton1Url, + smallButton1ImageIndex, + + smallButton2Title, + smallButton2Url, + smallButton2ImageIndex +}; +ADD_SQFUNC("void", NSRequestCustomMainMenuPromos, "", "", ScriptContext::UI) +{ + g_pMasterServerManager->RequestMainMenuPromos(); + return SQRESULT_NULL; +} + +ADD_SQFUNC("bool", NSHasCustomMainMenuPromoData, "", "", ScriptContext::UI) +{ + g_pSquirrel<ScriptContext::UI>->pushbool(sqvm, g_pMasterServerManager->m_bHasMainMenuPromoData); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("var", NSGetCustomMainMenuPromoData, "int promoDataKey", "", ScriptContext::UI) +{ + if (!g_pMasterServerManager->m_bHasMainMenuPromoData) + return SQRESULT_NULL; + + switch (g_pSquirrel<ScriptContext::UI>->getinteger(sqvm, 1)) + { + case eMainMenuPromoDataProperty::newInfoTitle1: + { + g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.newInfoTitle1.c_str()); + break; + } + + case eMainMenuPromoDataProperty::newInfoTitle2: + { + g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.newInfoTitle2.c_str()); + break; + } + + case eMainMenuPromoDataProperty::newInfoTitle3: + { + g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.newInfoTitle3.c_str()); + break; + } + + case eMainMenuPromoDataProperty::largeButtonTitle: + { + g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.largeButtonTitle.c_str()); + break; + } + + case eMainMenuPromoDataProperty::largeButtonText: + { + g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.largeButtonText.c_str()); + break; + } + + case eMainMenuPromoDataProperty::largeButtonUrl: + { + g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.largeButtonUrl.c_str()); + break; + } + + case eMainMenuPromoDataProperty::largeButtonImageIndex: + { + g_pSquirrel<ScriptContext::UI>->pushinteger(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.largeButtonImageIndex); + break; + } + + case eMainMenuPromoDataProperty::smallButton1Title: + { + g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton1Title.c_str()); + break; + } + + case eMainMenuPromoDataProperty::smallButton1Url: + { + g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton1Url.c_str()); + break; + } + + case eMainMenuPromoDataProperty::smallButton1ImageIndex: + { + g_pSquirrel<ScriptContext::UI>->pushinteger(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton1ImageIndex); + break; + } + + case eMainMenuPromoDataProperty::smallButton2Title: + { + g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton2Title.c_str()); + break; + } + + case eMainMenuPromoDataProperty::smallButton2Url: + { + g_pSquirrel<ScriptContext::UI>->pushstring(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton2Url.c_str()); + break; + } + + case eMainMenuPromoDataProperty::smallButton2ImageIndex: + { + g_pSquirrel<ScriptContext::UI>->pushinteger(sqvm, g_pMasterServerManager->m_sMainMenuPromoData.smallButton2ImageIndex); + break; + } + } + + return SQRESULT_NOTNULL; +} diff --git a/NorthstarDLL/scripts/client/scriptmodmenu.cpp b/NorthstarDLL/scripts/client/scriptmodmenu.cpp new file mode 100644 index 00000000..75d05acc --- /dev/null +++ b/NorthstarDLL/scripts/client/scriptmodmenu.cpp @@ -0,0 +1,166 @@ +#include "pch.h" +#include "mods/modmanager.h" +#include "squirrel/squirrel.h" + +ADD_SQFUNC("array<string>", NSGetModNames, "", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) +{ + g_pSquirrel<context>->newarray(sqvm, 0); + + for (Mod& mod : g_pModManager->m_LoadedMods) + { + g_pSquirrel<context>->pushstring(sqvm, mod.Name.c_str()); + g_pSquirrel<context>->arrayappend(sqvm, -2); + } + + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("bool", NSIsModEnabled, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) +{ + const SQChar* modName = g_pSquirrel<context>->getstring(sqvm, 1); + + // manual lookup, not super performant but eh not a big deal + for (Mod& mod : g_pModManager->m_LoadedMods) + { + if (!mod.Name.compare(modName)) + { + g_pSquirrel<context>->pushbool(sqvm, mod.m_bEnabled); + return SQRESULT_NOTNULL; + } + } + + return SQRESULT_NULL; +} + +ADD_SQFUNC("void", NSSetModEnabled, "string modName, bool enabled", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) +{ + const SQChar* modName = g_pSquirrel<context>->getstring(sqvm, 1); + const SQBool enabled = g_pSquirrel<context>->getbool(sqvm, 2); + + // manual lookup, not super performant but eh not a big deal + for (Mod& mod : g_pModManager->m_LoadedMods) + { + if (!mod.Name.compare(modName)) + { + mod.m_bEnabled = enabled; + return SQRESULT_NULL; + } + } + + return SQRESULT_NULL; +} + +ADD_SQFUNC("string", NSGetModDescriptionByModName, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) +{ + const SQChar* modName = g_pSquirrel<context>->getstring(sqvm, 1); + + // manual lookup, not super performant but eh not a big deal + for (Mod& mod : g_pModManager->m_LoadedMods) + { + if (!mod.Name.compare(modName)) + { + g_pSquirrel<context>->pushstring(sqvm, mod.Description.c_str()); + return SQRESULT_NOTNULL; + } + } + + return SQRESULT_NULL; +} + +ADD_SQFUNC("string", NSGetModVersionByModName, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) +{ + const SQChar* modName = g_pSquirrel<context>->getstring(sqvm, 1); + + // manual lookup, not super performant but eh not a big deal + for (Mod& mod : g_pModManager->m_LoadedMods) + { + if (!mod.Name.compare(modName)) + { + g_pSquirrel<context>->pushstring(sqvm, mod.Version.c_str()); + return SQRESULT_NOTNULL; + } + } + + return SQRESULT_NULL; +} + +ADD_SQFUNC("string", NSGetModDownloadLinkByModName, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) +{ + const SQChar* modName = g_pSquirrel<context>->getstring(sqvm, 1); + + // manual lookup, not super performant but eh not a big deal + for (Mod& mod : g_pModManager->m_LoadedMods) + { + if (!mod.Name.compare(modName)) + { + g_pSquirrel<context>->pushstring(sqvm, mod.DownloadLink.c_str()); + return SQRESULT_NOTNULL; + } + } + + return SQRESULT_NULL; +} + +ADD_SQFUNC("int", NSGetModLoadPriority, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) +{ + const SQChar* modName = g_pSquirrel<context>->getstring(sqvm, 1); + + // manual lookup, not super performant but eh not a big deal + for (Mod& mod : g_pModManager->m_LoadedMods) + { + if (!mod.Name.compare(modName)) + { + g_pSquirrel<context>->pushinteger(sqvm, mod.LoadPriority); + return SQRESULT_NOTNULL; + } + } + + return SQRESULT_NULL; +} + +ADD_SQFUNC("bool", NSIsModRequiredOnClient, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) +{ + const SQChar* modName = g_pSquirrel<context>->getstring(sqvm, 1); + + // manual lookup, not super performant but eh not a big deal + for (Mod& mod : g_pModManager->m_LoadedMods) + { + if (!mod.Name.compare(modName)) + { + g_pSquirrel<context>->pushbool(sqvm, mod.RequiredOnClient); + return SQRESULT_NOTNULL; + } + } + + return SQRESULT_NULL; +} + +ADD_SQFUNC( + "array<string>", NSGetModConvarsByModName, "string modName", "", ScriptContext::SERVER | ScriptContext::CLIENT | ScriptContext::UI) +{ + const SQChar* modName = g_pSquirrel<context>->getstring(sqvm, 1); + g_pSquirrel<context>->newarray(sqvm, 0); + + // manual lookup, not super performant but eh not a big deal + for (Mod& mod : g_pModManager->m_LoadedMods) + { + if (!mod.Name.compare(modName)) + { + for (ModConVar* cvar : mod.ConVars) + { + g_pSquirrel<context>->pushstring(sqvm, cvar->Name.c_str()); + g_pSquirrel<context>->arrayappend(sqvm, -2); + } + + return SQRESULT_NOTNULL; + } + } + + return SQRESULT_NOTNULL; // return empty array +} + +ADD_SQFUNC("void", NSReloadMods, "", "", ScriptContext::UI) +{ + g_pModManager->LoadMods(); + return SQRESULT_NULL; +} diff --git a/NorthstarDLL/scripts/client/scriptserverbrowser.cpp b/NorthstarDLL/scripts/client/scriptserverbrowser.cpp new file mode 100644 index 00000000..5f1287ad --- /dev/null +++ b/NorthstarDLL/scripts/client/scriptserverbrowser.cpp @@ -0,0 +1,410 @@ +#include "pch.h" +#include "squirrel/squirrel.h" +#include "masterserver/masterserver.h" +#include "server/auth/serverauthentication.h" +#include "engine/r2engine.h" +#include "client/r2client.h" + +// functions for viewing server browser + +ADD_SQFUNC("bool", NSIsMasterServerAuthenticated, "", "", ScriptContext::UI) +{ + g_pSquirrel<context>->pushbool(sqvm, g_pMasterServerManager->m_bOriginAuthWithMasterServerDone); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("void", NSRequestServerList, "", "", ScriptContext::UI) +{ + g_pMasterServerManager->RequestServerList(); + return SQRESULT_NULL; +} + +ADD_SQFUNC("bool", NSIsRequestingServerList, "", "", ScriptContext::UI) +{ + g_pSquirrel<context>->pushbool(sqvm, g_pMasterServerManager->m_bScriptRequestingServerList); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("bool", NSMasterServerConnectionSuccessful, "", "", ScriptContext::UI) +{ + g_pSquirrel<context>->pushbool(sqvm, g_pMasterServerManager->m_bSuccessfullyConnected); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("int", NSGetServerCount, "", "", ScriptContext::UI) +{ + g_pSquirrel<context>->pushinteger(sqvm, g_pMasterServerManager->m_vRemoteServers.size()); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("string", NSGetServerName, "int serverIndex", "", ScriptContext::UI) +{ + SQInteger serverIndex = g_pSquirrel<context>->getinteger(sqvm, 1); + + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "Tried to get name of server index {} when only {} servers are available", + serverIndex, + g_pMasterServerManager->m_vRemoteServers.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel<context>->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].name); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("string", NSGetServerDescription, "int serverIndex", "", ScriptContext::UI) +{ + SQInteger serverIndex = g_pSquirrel<context>->getinteger(sqvm, 1); + + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "Tried to get description of server index {} when only {} servers are available", + serverIndex, + g_pMasterServerManager->m_vRemoteServers.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel<context>->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].description.c_str()); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("string", NSGetServerMap, "int serverIndex", "", ScriptContext::UI) +{ + SQInteger serverIndex = g_pSquirrel<context>->getinteger(sqvm, 1); + + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "Tried to get map of server index {} when only {} servers are available", + serverIndex, + g_pMasterServerManager->m_vRemoteServers.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel<context>->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].map); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("string", NSGetServerPlaylist, "int serverIndex", "", ScriptContext::UI) +{ + SQInteger serverIndex = g_pSquirrel<context>->getinteger(sqvm, 1); + + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "Tried to get playlist of server index {} when only {} servers are available", + serverIndex, + g_pMasterServerManager->m_vRemoteServers.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel<context>->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].playlist); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("int", NSGetServerPlayerCount, "int serverIndex", "", ScriptContext::UI) +{ + SQInteger serverIndex = g_pSquirrel<context>->getinteger(sqvm, 1); + + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "Tried to get playercount of server index {} when only {} servers are available", + serverIndex, + g_pMasterServerManager->m_vRemoteServers.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel<context>->pushinteger(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].playerCount); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("int", NSGetServerMaxPlayerCount, "int serverIndex", "", ScriptContext::UI) +{ + SQInteger serverIndex = g_pSquirrel<context>->getinteger(sqvm, 1); + + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "Tried to get max playercount of server index {} when only {} servers are available", + serverIndex, + g_pMasterServerManager->m_vRemoteServers.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel<context>->pushinteger(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].maxPlayers); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("string", NSGetServerID, "int serverIndex", "", ScriptContext::UI) +{ + SQInteger serverIndex = g_pSquirrel<context>->getinteger(sqvm, 1); + + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "Tried to get id of server index {} when only {} servers are available", + serverIndex, + g_pMasterServerManager->m_vRemoteServers.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel<context>->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].id); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("bool", NSServerRequiresPassword, "int serverIndex", "", ScriptContext::UI) +{ + SQInteger serverIndex = g_pSquirrel<context>->getinteger(sqvm, 1); + + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "Tried to get hasPassword of server index {} when only {} servers are available", + serverIndex, + g_pMasterServerManager->m_vRemoteServers.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel<context>->pushbool(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].requiresPassword); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("int", NSGetServerRequiredModsCount, "int serverIndex", "", ScriptContext::UI) +{ + SQInteger serverIndex = g_pSquirrel<context>->getinteger(sqvm, 1); + + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "Tried to get required mods count of server index {} when only {} servers are available", + serverIndex, + g_pMasterServerManager->m_vRemoteServers.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel<context>->pushinteger(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].requiredMods.size()); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("string", NSGetServerRegion, "int serverIndex", "", ScriptContext::UI) +{ + SQInteger serverIndex = g_pSquirrel<context>->getinteger(sqvm, 1); + + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "Tried to get region of server index {} when only {} servers are available", + serverIndex, + g_pMasterServerManager->m_vRemoteServers.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel<context>->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].region, -1); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("string", NSGetServerRequiredModName, "int serverIndex, int modIndex", "", ScriptContext::UI) +{ + SQInteger serverIndex = g_pSquirrel<context>->getinteger(sqvm, 1); + SQInteger modIndex = g_pSquirrel<context>->getinteger(sqvm, 2); + + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "Tried to get hasPassword of server index {} when only {} servers are available", + serverIndex, + g_pMasterServerManager->m_vRemoteServers.size()) + .c_str()); + return SQRESULT_ERROR; + } + + if (modIndex >= g_pMasterServerManager->m_vRemoteServers[serverIndex].requiredMods.size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "Tried to get required mod name of mod index {} when only {} mod are available", + modIndex, + g_pMasterServerManager->m_vRemoteServers[serverIndex].requiredMods.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel<context>->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].requiredMods[modIndex].Name.c_str()); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("string", NSGetServerRequiredModVersion, "int serverIndex, int modIndex", "", ScriptContext::UI) +{ + SQInteger serverIndex = g_pSquirrel<context>->getinteger(sqvm, 1); + SQInteger modIndex = g_pSquirrel<context>->getinteger(sqvm, 2); + + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "Tried to get required mod version of server index {} when only {} servers are available", + serverIndex, + g_pMasterServerManager->m_vRemoteServers.size()) + .c_str()); + return SQRESULT_ERROR; + } + + if (modIndex >= g_pMasterServerManager->m_vRemoteServers[serverIndex].requiredMods.size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "Tried to get required mod version of mod index {} when only {} mod are available", + modIndex, + g_pMasterServerManager->m_vRemoteServers[serverIndex].requiredMods.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel<context>->pushstring(sqvm, g_pMasterServerManager->m_vRemoteServers[serverIndex].requiredMods[modIndex].Version.c_str()); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("void", NSClearRecievedServerList, "", "", ScriptContext::UI) +{ + g_pMasterServerManager->ClearServerList(); + return SQRESULT_NULL; +} + +// functions for authenticating with servers + +ADD_SQFUNC("void", NSTryAuthWithServer, "int serverIndex, string password = ''", "", ScriptContext::UI) +{ + SQInteger serverIndex = g_pSquirrel<context>->getinteger(sqvm, 1); + const SQChar* password = g_pSquirrel<context>->getstring(sqvm, 2); + + if (serverIndex >= g_pMasterServerManager->m_vRemoteServers.size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "Tried to auth with server index {} when only {} servers are available", + serverIndex, + g_pMasterServerManager->m_vRemoteServers.size()) + .c_str()); + return SQRESULT_ERROR; + } + + // send off persistent data first, don't worry about server/client stuff, since m_additionalPlayerData should only have entries when + // we're a local server note: this seems like it could create a race condition, test later + for (auto& pair : g_pServerAuthentication->m_PlayerAuthenticationData) + g_pServerAuthentication->WritePersistentData(pair.first); + + // do auth + g_pMasterServerManager->AuthenticateWithServer( + R2::g_pLocalPlayerUserID, + g_pMasterServerManager->m_sOwnClientAuthToken, + g_pMasterServerManager->m_vRemoteServers[serverIndex].id, + (char*)password); + + return SQRESULT_NULL; +} + +ADD_SQFUNC("bool", NSIsAuthenticatingWithServer, "", "", ScriptContext::UI) +{ + g_pSquirrel<context>->pushbool(sqvm, g_pMasterServerManager->m_bScriptAuthenticatingWithGameServer); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("bool", NSWasAuthSuccessful, "", "", ScriptContext::UI) +{ + g_pSquirrel<context>->pushbool(sqvm, g_pMasterServerManager->m_bSuccessfullyAuthenticatedWithGameServer); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("void", NSConnectToAuthedServer, "", "", ScriptContext::UI) +{ + if (!g_pMasterServerManager->m_bHasPendingConnectionInfo) + { + g_pSquirrel<context>->raiseerror( + sqvm, fmt::format("Tried to connect to authed server before any pending connection info was available").c_str()); + return SQRESULT_ERROR; + } + + RemoteServerConnectionInfo& info = g_pMasterServerManager->m_pendingConnectionInfo; + + // set auth token, then try to connect + // i'm honestly not entirely sure how silentconnect works regarding ports and encryption so using connect for now + R2::g_pCVar->FindVar("serverfilter")->SetValue(info.authToken); + R2::Cbuf_AddText( + R2::Cbuf_GetCurrentPlayer(), + fmt::format( + "connect {}.{}.{}.{}:{}", + info.ip.S_un.S_un_b.s_b1, + info.ip.S_un.S_un_b.s_b2, + info.ip.S_un.S_un_b.s_b3, + info.ip.S_un.S_un_b.s_b4, + info.port) + .c_str(), + R2::cmd_source_t::kCommandSrcCode); + + g_pMasterServerManager->m_bHasPendingConnectionInfo = false; + return SQRESULT_NULL; +} + +ADD_SQFUNC("void", NSTryAuthWithLocalServer, "", "", ScriptContext::UI) +{ + // do auth request + g_pMasterServerManager->AuthenticateWithOwnServer(R2::g_pLocalPlayerUserID, g_pMasterServerManager->m_sOwnClientAuthToken); + + return SQRESULT_NULL; +} + +ADD_SQFUNC("void", NSCompleteAuthWithLocalServer, "", "", ScriptContext::UI) +{ + // literally just set serverfilter + // note: this assumes we have no authdata other than our own + if (g_pServerAuthentication->m_RemoteAuthenticationData.size()) + R2::g_pCVar->FindVar("serverfilter")->SetValue(g_pServerAuthentication->m_RemoteAuthenticationData.begin()->first.c_str()); + + return SQRESULT_NULL; +} + +ADD_SQFUNC("string", NSGetAuthFailReason, "", "", ScriptContext::UI) +{ + g_pSquirrel<context>->pushstring(sqvm, g_pMasterServerManager->m_sAuthFailureReason.c_str(), -1); + return SQRESULT_NOTNULL; +} diff --git a/NorthstarDLL/scripts/client/scriptservertoclientstringcommand.cpp b/NorthstarDLL/scripts/client/scriptservertoclientstringcommand.cpp new file mode 100644 index 00000000..f3cb2f18 --- /dev/null +++ b/NorthstarDLL/scripts/client/scriptservertoclientstringcommand.cpp @@ -0,0 +1,19 @@ +#include "pch.h" +#include "squirrel/squirrel.h" +#include "core/convar/convar.h" +#include "core/convar/concommand.h" + +void ConCommand_ns_script_servertoclientstringcommand(const CCommand& arg) +{ + if (g_pSquirrel<ScriptContext::CLIENT>->m_pSQVM) + g_pSquirrel<ScriptContext::CLIENT>->Call("NSClientCodeCallback_RecievedServerToClientStringCommand", arg.ArgS()); +} + +ON_DLL_LOAD_CLIENT_RELIESON("client.dll", ScriptServerToClientStringCommand, ClientSquirrel, (CModule module)) +{ + RegisterConCommand( + "ns_script_servertoclientstringcommand", + ConCommand_ns_script_servertoclientstringcommand, + "", + FCVAR_CLIENTDLL | FCVAR_SERVER_CAN_EXECUTE); +} diff --git a/NorthstarDLL/scripts/scriptdatatables.cpp b/NorthstarDLL/scripts/scriptdatatables.cpp new file mode 100644 index 00000000..915d4df0 --- /dev/null +++ b/NorthstarDLL/scripts/scriptdatatables.cpp @@ -0,0 +1,910 @@ +#include "pch.h" +#include "squirrel/squirrel.h" +#include "core/filesystem/rpakfilesystem.h" +#include "core/convar/convar.h" +#include "dedicated/dedicated.h" +#include "core/filesystem/filesystem.h" +#include "core/math/vector.h" +#include "core/tier0.h" +#include "engine/r2engine.h" +#include <iostream> +#include <sstream> +#include <map> +#include <fstream> +#include <filesystem> + +const uint64_t USERDATA_TYPE_DATATABLE = 0xFFF7FFF700000004; +const uint64_t USERDATA_TYPE_DATATABLE_CUSTOM = 0xFFFCFFFC12345678; + +enum class DatatableType : int +{ + BOOL = 0, + INT, + FLOAT, + VECTOR, + STRING, + ASSET, + UNK_STRING // unknown but deffo a string type +}; + +struct ColumnInfo +{ + char* name; + DatatableType type; + int offset; +}; + +struct Datatable +{ + int numColumns; + int numRows; + ColumnInfo* columnInfo; + char* data; // actually data pointer + int rowInfo; +}; + +ConVar* Cvar_ns_prefer_datatable_from_disk; + +template <ScriptContext context> Datatable* (*SQ_GetDatatableInternal)(HSquirrelVM* sqvm); + +struct CSVData +{ + std::string m_sAssetName; + std::string m_sCSVString; + char* m_pDataBuf; + size_t m_nDataBufSize; + + std::vector<char*> columns; + std::vector<std::vector<char*>> dataPointers; +}; + +std::unordered_map<std::string, CSVData> CSVCache; + +Vector3 StringToVector(char* pString) +{ + Vector3 vRet; + + int length = 0; + while (pString[length]) + { + if ((pString[length] == '<') || (pString[length] == '>')) + pString[length] = '\0'; + length++; + } + + int startOfFloat = 1; + int currentIndex = 1; + + while (pString[currentIndex] && (pString[currentIndex] != ',')) + currentIndex++; + pString[currentIndex] = '\0'; + vRet.x = std::stof(&pString[startOfFloat]); + startOfFloat = ++currentIndex; + + while (pString[currentIndex] && (pString[currentIndex] != ',')) + currentIndex++; + pString[currentIndex] = '\0'; + vRet.y = std::stof(&pString[startOfFloat]); + startOfFloat = ++currentIndex; + + while (pString[currentIndex] && (pString[currentIndex] != ',')) + currentIndex++; + pString[currentIndex] = '\0'; + vRet.z = std::stof(&pString[startOfFloat]); + startOfFloat = ++currentIndex; + + return vRet; +} + +// var function GetDataTable( asset path ) +REPLACE_SQFUNC(GetDataTable, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) +{ + const char* pAssetName; + g_pSquirrel<context>->getasset(sqvm, 2, &pAssetName); + + if (strncmp(pAssetName, "datatable/", 10)) + { + g_pSquirrel<context>->raiseerror(sqvm, fmt::format("Asset \"{}\" doesn't start with \"datatable/\"", pAssetName).c_str()); + return SQRESULT_ERROR; + } + else if (!Cvar_ns_prefer_datatable_from_disk->GetBool() && g_pPakLoadManager->LoadFile(pAssetName)) + return g_pSquirrel<context>->m_funcOriginals["GetDataTable"](sqvm); + // either we prefer disk datatables, or we're loading a datatable that wasn't found in rpak + else + { + std::string sAssetPath(fmt::format("scripts/{}", pAssetName)); + + // first, check the cache + if (CSVCache.find(pAssetName) != CSVCache.end()) + { + CSVData** pUserdata = g_pSquirrel<context>->createuserdata<CSVData*>(sqvm, sizeof(CSVData*)); + g_pSquirrel<context>->setuserdatatypeid(sqvm, -1, USERDATA_TYPE_DATATABLE_CUSTOM); + *pUserdata = &CSVCache[pAssetName]; + + return SQRESULT_NOTNULL; + } + + // check files on disk + // we don't use .rpak as the extension for on-disk datatables, so we need to replace .rpak with .csv in the filename we're reading + fs::path diskAssetPath("scripts"); + if (fs::path(pAssetName).extension() == ".rpak") + diskAssetPath /= fs::path(pAssetName).remove_filename() / (fs::path(pAssetName).stem().string() + ".csv"); + else + diskAssetPath /= fs::path(pAssetName); + + std::string sDiskAssetPath(diskAssetPath.string()); + if ((*R2::g_pFilesystem)->m_vtable2->FileExists(&(*R2::g_pFilesystem)->m_vtable2, sDiskAssetPath.c_str(), "GAME")) + { + std::string sTableCSV = R2::ReadVPKFile(sDiskAssetPath.c_str()); + if (!sTableCSV.size()) + { + g_pSquirrel<context>->raiseerror(sqvm, fmt::format("Datatable \"{}\" is empty", pAssetName).c_str()); + return SQRESULT_ERROR; + } + + // somewhat shit, but ensure we end with a newline to make parsing easier + if (sTableCSV[sTableCSV.length() - 1] != '\n') + sTableCSV += '\n'; + + CSVData csv; + csv.m_sAssetName = pAssetName; + csv.m_sCSVString = sTableCSV; + csv.m_nDataBufSize = sTableCSV.size(); + csv.m_pDataBuf = new char[csv.m_nDataBufSize]; + memcpy(csv.m_pDataBuf, &sTableCSV[0], csv.m_nDataBufSize); + + // parse the csv + // csvs are essentially comma and newline-deliniated sets of strings for parsing, only thing we need to worry about is quoted + // entries when we parse an element of the csv, rather than allocating an entry for it, we just convert that element to a + // null-terminated string i.e., store the ptr to the first char of it, then make the comma that delinates it a nullchar + + bool bHasColumns = false; + bool bInQuotes = false; + + std::vector<char*> vCurrentRow; + char* pElemStart = csv.m_pDataBuf; + char* pElemEnd = nullptr; + + for (int i = 0; i < csv.m_nDataBufSize; i++) + { + if (csv.m_pDataBuf[i] == '\r' && csv.m_pDataBuf[i + 1] == '\n') + { + if (!pElemEnd) + pElemEnd = csv.m_pDataBuf + i; + + continue; // next iteration can handle the \n + } + + // newline, end of a row + if (csv.m_pDataBuf[i] == '\n') + { + // shouldn't have newline in string + if (bInQuotes) + { + g_pSquirrel<context>->raiseerror(sqvm, "Unexpected \\n in string"); + return SQRESULT_ERROR; + } + + // push last entry to current row + if (pElemEnd) + *pElemEnd = '\0'; + else + csv.m_pDataBuf[i] = '\0'; + + vCurrentRow.push_back(pElemStart); + + // newline, push last line to csv data and go from there + if (!bHasColumns) + { + bHasColumns = true; + csv.columns = vCurrentRow; + } + else + csv.dataPointers.push_back(vCurrentRow); + + vCurrentRow.clear(); + // put start of current element at char after newline + pElemStart = csv.m_pDataBuf + i + 1; + pElemEnd = nullptr; + } + // we're starting or ending a quoted string + else if (csv.m_pDataBuf[i] == '"') + { + // start quoted string + if (!bInQuotes) + { + // shouldn't have quoted strings in column names + if (!bHasColumns) + { + g_pSquirrel<context>->raiseerror(sqvm, "Unexpected \" in column name"); + return SQRESULT_ERROR; + } + + bInQuotes = true; + // put start of current element at char after string begin + pElemStart = csv.m_pDataBuf + i + 1; + } + // end quoted string + else + { + pElemEnd = csv.m_pDataBuf + i; + bInQuotes = false; + } + } + // don't parse commas in quotes + else if (bInQuotes) + { + continue; + } + // comma, push new entry to current row + else if (csv.m_pDataBuf[i] == ',') + { + if (pElemEnd) + *pElemEnd = '\0'; + else + csv.m_pDataBuf[i] = '\0'; + + vCurrentRow.push_back(pElemStart); + // put start of next element at char after comma + pElemStart = csv.m_pDataBuf + i + 1; + pElemEnd = nullptr; + } + } + + // add to cache and return + CSVData** pUserdata = g_pSquirrel<context>->createuserdata<CSVData*>(sqvm, sizeof(CSVData*)); + g_pSquirrel<context>->setuserdatatypeid(sqvm, -1, USERDATA_TYPE_DATATABLE_CUSTOM); + CSVCache[pAssetName] = csv; + *pUserdata = &CSVCache[pAssetName]; + + return SQRESULT_NOTNULL; + } + // the file doesn't exist on disk, check rpak if we haven't already + else if (Cvar_ns_prefer_datatable_from_disk->GetBool() && g_pPakLoadManager->LoadFile(pAssetName)) + return g_pSquirrel<context>->m_funcOriginals["GetDataTable"](sqvm); + // the file doesn't exist at all, error + else + { + g_pSquirrel<context>->raiseerror(sqvm, fmt::format("Datatable {} not found", pAssetName).c_str()); + return SQRESULT_ERROR; + } + } +} + +// int function GetDataTableColumnByName( var datatable, string columnName ) +REPLACE_SQFUNC(GetDataTableColumnByName, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel<context>->m_funcOriginals["GetDataTableColumnByName"](sqvm); + + CSVData* csv = *pData; + const char* pColumnName = g_pSquirrel<context>->getstring(sqvm, 2); + + for (int i = 0; i < csv->columns.size(); i++) + { + if (!strcmp(csv->columns[i], pColumnName)) + { + g_pSquirrel<context>->pushinteger(sqvm, i); + return SQRESULT_NOTNULL; + } + } + + // column not found + g_pSquirrel<context>->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowCount( var datatable ) +REPLACE_SQFUNC(GetDataTableRowCount, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel<context>->m_funcOriginals["GetDatatableRowCount"](sqvm); + + CSVData* csv = *pData; + g_pSquirrel<context>->pushinteger(sqvm, csv->dataPointers.size()); + return SQRESULT_NOTNULL; +} + +// string function GetDataTableString( var datatable, int row, int col ) +REPLACE_SQFUNC(GetDataTableString, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel<context>->m_funcOriginals["GetDataTableString"](sqvm); + + CSVData* csv = *pData; + const int nRow = g_pSquirrel<context>->getinteger(sqvm, 2); + const int nCol = g_pSquirrel<context>->getinteger(sqvm, 3); + if (nRow >= csv->dataPointers.size() || nCol >= csv->dataPointers[nRow].size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "row {} and col {} are outside of range row {} and col {}", nRow, nCol, csv->dataPointers.size(), csv->columns.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel<context>->pushstring(sqvm, csv->dataPointers[nRow][nCol], -1); + return SQRESULT_NOTNULL; +} + +// asset function GetDataTableAsset( var datatable, int row, int col ) +REPLACE_SQFUNC(GetDataTableAsset, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel<context>->m_funcOriginals["GetDataTableAsset"](sqvm); + + CSVData* csv = *pData; + const int nRow = g_pSquirrel<context>->getinteger(sqvm, 2); + const int nCol = g_pSquirrel<context>->getinteger(sqvm, 3); + if (nRow >= csv->dataPointers.size() || nCol >= csv->dataPointers[nRow].size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "row {} and col {} are outside of range row {} and col {}", nRow, nCol, csv->dataPointers.size(), csv->columns.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel<context>->pushasset(sqvm, csv->dataPointers[nRow][nCol], -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableInt( var datatable, int row, int col ) +REPLACE_SQFUNC(GetDataTableInt, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel<context>->m_funcOriginals["GetDataTableInt"](sqvm); + + CSVData* csv = *pData; + const int nRow = g_pSquirrel<context>->getinteger(sqvm, 2); + const int nCol = g_pSquirrel<context>->getinteger(sqvm, 3); + if (nRow >= csv->dataPointers.size() || nCol >= csv->dataPointers[nRow].size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "row {} and col {} are outside of range row {} and col {}", nRow, nCol, csv->dataPointers.size(), csv->columns.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel<context>->pushinteger(sqvm, std::stoi(csv->dataPointers[nRow][nCol])); + return SQRESULT_NOTNULL; +} + +// float function GetDataTableFloat( var datatable, int row, int col ) +REPLACE_SQFUNC(GetDataTableFloat, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel<context>->m_funcOriginals["GetDataTableFloat"](sqvm); + + CSVData* csv = *pData; + const int nRow = g_pSquirrel<context>->getinteger(sqvm, 2); + const int nCol = g_pSquirrel<context>->getinteger(sqvm, 3); + if (nRow >= csv->dataPointers.size() || nCol >= csv->dataPointers[nRow].size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "row {} and col {} are outside of range row {} and col {}", nRow, nCol, csv->dataPointers.size(), csv->columns.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel<context>->pushfloat(sqvm, std::stof(csv->dataPointers[nRow][nCol])); + return SQRESULT_NOTNULL; +} + +// bool function GetDataTableBool( var datatable, int row, int col ) +REPLACE_SQFUNC(GetDataTableBool, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel<context>->m_funcOriginals["GetDataTableBool"](sqvm); + + CSVData* csv = *pData; + const int nRow = g_pSquirrel<context>->getinteger(sqvm, 2); + const int nCol = g_pSquirrel<context>->getinteger(sqvm, 3); + if (nRow >= csv->dataPointers.size() || nCol >= csv->dataPointers[nRow].size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "row {} and col {} are outside of range row {} and col {}", nRow, nCol, csv->dataPointers.size(), csv->columns.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel<context>->pushbool(sqvm, std::stoi(csv->dataPointers[nRow][nCol])); + return SQRESULT_NOTNULL; +} + +// vector function GetDataTableVector( var datatable, int row, int col ) +REPLACE_SQFUNC(GetDataTableVector, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel<context>->m_funcOriginals["GetDataTableVector"](sqvm); + + CSVData* csv = *pData; + const int nRow = g_pSquirrel<context>->getinteger(sqvm, 2); + const int nCol = g_pSquirrel<context>->getinteger(sqvm, 3); + if (nRow >= csv->dataPointers.size() || nCol >= csv->dataPointers[nRow].size()) + { + g_pSquirrel<context>->raiseerror( + sqvm, + fmt::format( + "row {} and col {} are outside of range row {} and col {}", nRow, nCol, csv->dataPointers.size(), csv->columns.size()) + .c_str()); + return SQRESULT_ERROR; + } + + g_pSquirrel<context>->pushvector(sqvm, StringToVector(csv->dataPointers[nRow][nCol])); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowMatchingStringValue( var datatable, int col, string value ) +REPLACE_SQFUNC(GetDataTableRowMatchingStringValue, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel<context>->m_funcOriginals["GetDataTableRowMatchingStringValue"](sqvm); + + CSVData* csv = *pData; + int nCol = g_pSquirrel<context>->getinteger(sqvm, 2); + const char* pStringVal = g_pSquirrel<context>->getstring(sqvm, 3); + for (int i = 0; i < csv->dataPointers.size(); i++) + { + if (!strcmp(csv->dataPointers[i][nCol], pStringVal)) + { + g_pSquirrel<context>->pushinteger(sqvm, i); + return SQRESULT_NOTNULL; + } + } + + g_pSquirrel<context>->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowMatchingAssetValue( var datatable, int col, asset value ) +REPLACE_SQFUNC(GetDataTableMatchingAssetValue, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel<context>->m_funcOriginals["GetDataTableRowMatchingAssetValue"](sqvm); + + CSVData* csv = *pData; + int nCol = g_pSquirrel<context>->getinteger(sqvm, 2); + const char* pStringVal; + g_pSquirrel<context>->getasset(sqvm, 3, &pStringVal); + for (int i = 0; i < csv->dataPointers.size(); i++) + { + if (!strcmp(csv->dataPointers[i][nCol], pStringVal)) + { + g_pSquirrel<context>->pushinteger(sqvm, i); + return SQRESULT_NOTNULL; + } + } + + g_pSquirrel<context>->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowMatchingFloatValue( var datatable, int col, float value ) +REPLACE_SQFUNC(GetDataTableRowMatchingFloatValue, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel<context>->m_funcOriginals["GetDataTableRowMatchingFloatValue"](sqvm); + + CSVData* csv = *pData; + int nCol = g_pSquirrel<context>->getinteger(sqvm, 2); + const float flFloatVal = g_pSquirrel<context>->getfloat(sqvm, 3); + for (int i = 0; i < csv->dataPointers.size(); i++) + { + if (flFloatVal == std::stof(csv->dataPointers[i][nCol])) + { + g_pSquirrel<context>->pushinteger(sqvm, i); + return SQRESULT_NOTNULL; + } + } + + g_pSquirrel<context>->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowMatchingIntValue( var datatable, int col, int value ) +REPLACE_SQFUNC(GetDataTableRowMatchingIntValue, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel<context>->m_funcOriginals["GetDataTableRowMatchingIntValue"](sqvm); + + CSVData* csv = *pData; + int nCol = g_pSquirrel<context>->getinteger(sqvm, 2); + const int nIntVal = g_pSquirrel<context>->getinteger(sqvm, 3); + for (int i = 0; i < csv->dataPointers.size(); i++) + { + if (nIntVal == std::stoi(csv->dataPointers[i][nCol])) + { + g_pSquirrel<context>->pushinteger(sqvm, i); + return SQRESULT_NOTNULL; + } + } + + g_pSquirrel<context>->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowMatchingVectorValue( var datatable, int col, vector value ) +REPLACE_SQFUNC(GetDataTableRowMatchingVectorValue, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel<context>->m_funcOriginals["GetDataTableRowMatchingVectorValue"](sqvm); + + CSVData* csv = *pData; + int nCol = g_pSquirrel<context>->getinteger(sqvm, 2); + const Vector3 vVectorVal = g_pSquirrel<context>->getvector(sqvm, 3); + + for (int i = 0; i < csv->dataPointers.size(); i++) + { + if (vVectorVal == StringToVector(csv->dataPointers[i][nCol])) + { + g_pSquirrel<context>->pushinteger(sqvm, i); + return SQRESULT_NOTNULL; + } + } + + g_pSquirrel<context>->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowGreaterThanOrEqualToIntValue( var datatable, int col, int value ) +REPLACE_SQFUNC(GetDataTableRowGreaterThanOrEqualToIntValue, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel<context>->m_funcOriginals["GetDataTableRowGreaterThanOrEqualToIntValue"](sqvm); + + CSVData* csv = *pData; + int nCol = g_pSquirrel<context>->getinteger(sqvm, 2); + const int nIntVal = g_pSquirrel<context>->getinteger(sqvm, 3); + for (int i = 0; i < csv->dataPointers.size(); i++) + { + if (nIntVal >= std::stoi(csv->dataPointers[i][nCol])) + { + spdlog::info("datatable not loaded"); + g_pSquirrel<context>->pushinteger(sqvm, 1); + return SQRESULT_NOTNULL; + } + } + + g_pSquirrel<context>->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowLessThanOrEqualToIntValue( var datatable, int col, int value ) +REPLACE_SQFUNC(GetDataTableRowLessThanOrEqualToIntValue, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel<context>->m_funcOriginals["GetDataTableRowLessThanOrEqualToIntValue"](sqvm); + + CSVData* csv = *pData; + int nCol = g_pSquirrel<context>->getinteger(sqvm, 2); + const int nIntVal = g_pSquirrel<context>->getinteger(sqvm, 3); + for (int i = 0; i < csv->dataPointers.size(); i++) + { + if (nIntVal <= std::stoi(csv->dataPointers[i][nCol])) + { + g_pSquirrel<context>->pushinteger(sqvm, i); + return SQRESULT_NOTNULL; + } + } + + g_pSquirrel<context>->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowGreaterThanOrEqualToFloatValue( var datatable, int col, float value ) +REPLACE_SQFUNC(GetDataTableRowGreaterThanOrEqualToFloatValue, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel<context>->m_funcOriginals["GetDataTableRowGreaterThanOrEqualToFloatValue"](sqvm); + + CSVData* csv = *pData; + int nCol = g_pSquirrel<context>->getinteger(sqvm, 2); + const float flFloatVal = g_pSquirrel<context>->getfloat(sqvm, 3); + for (int i = 0; i < csv->dataPointers.size(); i++) + { + if (flFloatVal >= std::stof(csv->dataPointers[i][nCol])) + { + g_pSquirrel<context>->pushinteger(sqvm, i); + return SQRESULT_NOTNULL; + } + } + + g_pSquirrel<context>->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +// int function GetDataTableRowLessThanOrEqualToFloatValue( var datatable, int col, float value ) +REPLACE_SQFUNC(GetDataTableRowLessThanOrEqualToFloatValue, (ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER)) +{ + CSVData** pData; + uint64_t typeId; + g_pSquirrel<context>->getuserdata(sqvm, 2, &pData, &typeId); + + if (typeId != USERDATA_TYPE_DATATABLE_CUSTOM) + return g_pSquirrel<context>->m_funcOriginals["GetDataTableRowLessThanOrEqualToFloatValue"](sqvm); + + CSVData* csv = *pData; + int nCol = g_pSquirrel<context>->getinteger(sqvm, 2); + const float flFloatVal = g_pSquirrel<context>->getfloat(sqvm, 3); + for (int i = 0; i < csv->dataPointers.size(); i++) + { + if (flFloatVal <= std::stof(csv->dataPointers[i][nCol])) + { + g_pSquirrel<context>->pushinteger(sqvm, i); + return SQRESULT_NOTNULL; + } + } + + g_pSquirrel<context>->pushinteger(sqvm, -1); + return SQRESULT_NOTNULL; +} + +std::string DataTableToString(Datatable* datatable) +{ + std::string sCSVString; + + // write columns + bool bShouldComma = false; + for (int i = 0; i < datatable->numColumns; i++) + { + if (bShouldComma) + sCSVString += ','; + else + bShouldComma = true; + + sCSVString += datatable->columnInfo[i].name; + } + + // write rows + for (int row = 0; row < datatable->numRows; row++) + { + sCSVString += '\n'; + + bool bShouldComma = false; + for (int col = 0; col < datatable->numColumns; col++) + { + if (bShouldComma) + sCSVString += ','; + else + bShouldComma = true; + + // output typed data + ColumnInfo column = datatable->columnInfo[col]; + const void* pUntypedVal = datatable->data + column.offset + row * datatable->rowInfo; + switch (column.type) + { + case DatatableType::BOOL: + { + sCSVString += *(bool*)pUntypedVal ? '1' : '0'; + break; + } + + case DatatableType::INT: + { + sCSVString += std::to_string(*(int*)pUntypedVal); + break; + } + + case DatatableType::FLOAT: + { + sCSVString += std::to_string(*(float*)pUntypedVal); + break; + } + + case DatatableType::VECTOR: + { + Vector3 pVector((float*)pUntypedVal); + sCSVString += fmt::format("<{},{},{}>", pVector.x, pVector.y, pVector.z); + break; + } + + case DatatableType::STRING: + case DatatableType::ASSET: + case DatatableType::UNK_STRING: + { + sCSVString += fmt::format("\"{}\"", *(char**)pUntypedVal); + break; + } + } + } + } + + return sCSVString; +} + +void DumpDatatable(const char* pDatatablePath) +{ + Datatable* pDatatable = (Datatable*)g_pPakLoadManager->LoadFile(pDatatablePath); + if (!pDatatable) + { + spdlog::error("couldn't load datatable {} (rpak containing it may not be loaded?)", pDatatablePath); + return; + } + + std::string sOutputPath(fmt::format("{}/scripts/datatable/{}.csv", R2::g_pModName, fs::path(pDatatablePath).stem().string())); + std::string sDatatableContents(DataTableToString(pDatatable)); + + fs::create_directories(fs::path(sOutputPath).remove_filename()); + std::ofstream outputStream(sOutputPath); + outputStream.write(sDatatableContents.c_str(), sDatatableContents.size()); + outputStream.close(); + + spdlog::info("dumped datatable {} {} to {}", pDatatablePath, (void*)pDatatable, sOutputPath); +} + +void ConCommand_dump_datatable(const CCommand& args) +{ + if (args.ArgC() < 2) + { + spdlog::info("usage: dump_datatable datatable/tablename.rpak"); + return; + } + + DumpDatatable(args.Arg(1)); +} + +void ConCommand_dump_datatables(const CCommand& args) +{ + // likely not a comprehensive list, might be missing a couple? + static const std::vector<const char*> VANILLA_DATATABLE_PATHS = { + "datatable/burn_meter_rewards.rpak", + "datatable/burn_meter_store.rpak", + "datatable/calling_cards.rpak", + "datatable/callsign_icons.rpak", + "datatable/camo_skins.rpak", + "datatable/default_pilot_loadouts.rpak", + "datatable/default_titan_loadouts.rpak", + "datatable/faction_leaders.rpak", + "datatable/fd_awards.rpak", + "datatable/features_mp.rpak", + "datatable/non_loadout_weapons.rpak", + "datatable/pilot_abilities.rpak", + "datatable/pilot_executions.rpak", + "datatable/pilot_passives.rpak", + "datatable/pilot_properties.rpak", + "datatable/pilot_weapons.rpak", + "datatable/pilot_weapon_features.rpak", + "datatable/pilot_weapon_mods.rpak", + "datatable/pilot_weapon_mods_common.rpak", + "datatable/playlist_items.rpak", + "datatable/titans_mp.rpak", + "datatable/titan_abilities.rpak", + "datatable/titan_executions.rpak", + "datatable/titan_fd_upgrades.rpak", + "datatable/titan_nose_art.rpak", + "datatable/titan_passives.rpak", + "datatable/titan_primary_mods.rpak", + "datatable/titan_primary_mods_common.rpak", + "datatable/titan_primary_weapons.rpak", + "datatable/titan_properties.rpak", + "datatable/titan_skins.rpak", + "datatable/titan_voices.rpak", + "datatable/unlocks_faction_level.rpak", + "datatable/unlocks_fd_titan_level.rpak", + "datatable/unlocks_player_level.rpak", + "datatable/unlocks_random.rpak", + "datatable/unlocks_titan_level.rpak", + "datatable/unlocks_weapon_level_pilot.rpak", + "datatable/weapon_skins.rpak", + "datatable/xp_per_faction_level.rpak", + "datatable/xp_per_fd_titan_level.rpak", + "datatable/xp_per_player_level.rpak", + "datatable/xp_per_titan_level.rpak", + "datatable/xp_per_weapon_level.rpak", + "datatable/faction_leaders_dropship_anims.rpak", + "datatable/score_events.rpak", + "datatable/startpoints.rpak", + "datatable/sp_levels.rpak", + "datatable/community_entries.rpak", + "datatable/spotlight_images.rpak", + "datatable/death_hints_mp.rpak", + "datatable/flightpath_assets.rpak", + "datatable/earn_meter_mp.rpak", + "datatable/battle_chatter_voices.rpak", + "datatable/battle_chatter.rpak", + "datatable/titan_os_conversations.rpak", + "datatable/faction_dialogue.rpak", + "datatable/grunt_chatter_mp.rpak", + "datatable/spectre_chatter_mp.rpak", + "datatable/pain_death_sounds.rpak", + "datatable/caller_ids_mp.rpak"}; + + for (const char* datatable : VANILLA_DATATABLE_PATHS) + DumpDatatable(datatable); +} + +ON_DLL_LOAD_RELIESON("server.dll", ServerScriptDatatables, ServerSquirrel, (CModule module)) +{ + SQ_GetDatatableInternal<ScriptContext::SERVER> = module.Offset(0x1250f0).As<Datatable* (*)(HSquirrelVM*)>(); +} + +ON_DLL_LOAD_RELIESON("client.dll", ClientScriptDatatables, ClientSquirrel, (CModule module)) +{ + SQ_GetDatatableInternal<ScriptContext::CLIENT> = module.Offset(0x1C9070).As<Datatable* (*)(HSquirrelVM*)>(); + SQ_GetDatatableInternal<ScriptContext::UI> = SQ_GetDatatableInternal<ScriptContext::CLIENT>; +} + +ON_DLL_LOAD_RELIESON("engine.dll", SharedScriptDataTables, ConVar, (CModule module)) +{ + Cvar_ns_prefer_datatable_from_disk = new ConVar( + "ns_prefer_datatable_from_disk", + IsDedicatedServer() && Tier0::CommandLine()->CheckParm("-nopakdedi") ? "1" : "0", + FCVAR_NONE, + "whether to prefer loading datatables from disk, rather than rpak"); + + RegisterConCommand("dump_datatables", ConCommand_dump_datatables, "dumps all datatables from a hardcoded list", FCVAR_NONE); + RegisterConCommand("dump_datatable", ConCommand_dump_datatable, "dump a datatable", FCVAR_NONE); +} diff --git a/NorthstarDLL/scripts/scriptjson.cpp b/NorthstarDLL/scripts/scriptjson.cpp new file mode 100644 index 00000000..f41b0457 --- /dev/null +++ b/NorthstarDLL/scripts/scriptjson.cpp @@ -0,0 +1,249 @@ +#include "pch.h" +#include "squirrel/squirrel.h" + +#include "rapidjson/error/en.h" +#include "rapidjson/document.h" +#include "rapidjson/writer.h" +#include "rapidjson/stringbuffer.h" + +#ifdef _MSC_VER +#undef GetObject // fuck microsoft developers +#endif + +template <ScriptContext context> void +DecodeJsonArray(HSquirrelVM* sqvm, rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>>* arr) +{ + g_pSquirrel<context>->newarray(sqvm, 0); + + for (auto& itr : arr->GetArray()) + { + switch (itr.GetType()) + { + case rapidjson::kObjectType: + DecodeJsonTable<context>(sqvm, &itr); + g_pSquirrel<context>->arrayappend(sqvm, -2); + break; + case rapidjson::kArrayType: + DecodeJsonArray<context>(sqvm, &itr); + g_pSquirrel<context>->arrayappend(sqvm, -2); + break; + case rapidjson::kStringType: + g_pSquirrel<context>->pushstring(sqvm, itr.GetString(), -1); + g_pSquirrel<context>->arrayappend(sqvm, -2); + break; + case rapidjson::kTrueType: + case rapidjson::kFalseType: + g_pSquirrel<context>->pushbool(sqvm, itr.GetBool()); + g_pSquirrel<context>->arrayappend(sqvm, -2); + break; + case rapidjson::kNumberType: + if (itr.IsDouble() || itr.IsFloat()) + g_pSquirrel<context>->pushfloat(sqvm, itr.GetFloat()); + else + g_pSquirrel<context>->pushinteger(sqvm, itr.GetInt()); + g_pSquirrel<context>->arrayappend(sqvm, -2); + break; + } + } +} + +template <ScriptContext context> void +DecodeJsonTable(HSquirrelVM* sqvm, rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>>* obj) +{ + g_pSquirrel<context>->newtable(sqvm); + + for (auto itr = obj->MemberBegin(); itr != obj->MemberEnd(); itr++) + { + switch (itr->value.GetType()) + { + case rapidjson::kObjectType: + g_pSquirrel<context>->pushstring(sqvm, itr->name.GetString(), -1); + DecodeJsonTable<context>( + sqvm, (rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>>*)&itr->value); + g_pSquirrel<context>->newslot(sqvm, -3, false); + break; + case rapidjson::kArrayType: + g_pSquirrel<context>->pushstring(sqvm, itr->name.GetString(), -1); + DecodeJsonArray<context>( + sqvm, (rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>>*)&itr->value); + g_pSquirrel<context>->newslot(sqvm, -3, false); + break; + case rapidjson::kStringType: + g_pSquirrel<context>->pushstring(sqvm, itr->name.GetString(), -1); + g_pSquirrel<context>->pushstring(sqvm, itr->value.GetString(), -1); + + g_pSquirrel<context>->newslot(sqvm, -3, false); + break; + case rapidjson::kTrueType: + case rapidjson::kFalseType: + g_pSquirrel<context>->pushstring(sqvm, itr->name.GetString(), -1); + g_pSquirrel<context>->pushbool(sqvm, itr->value.GetBool()); + g_pSquirrel<context>->newslot(sqvm, -3, false); + break; + case rapidjson::kNumberType: + if (itr->value.IsDouble() || itr->value.IsFloat()) + { + g_pSquirrel<context>->pushstring(sqvm, itr->name.GetString(), -1); + g_pSquirrel<context>->pushfloat(sqvm, itr->value.GetFloat()); + } + else + { + g_pSquirrel<context>->pushstring(sqvm, itr->name.GetString(), -1); + g_pSquirrel<context>->pushinteger(sqvm, itr->value.GetInt()); + } + g_pSquirrel<context>->newslot(sqvm, -3, false); + break; + } + } +} + +template <ScriptContext context> void EncodeJSONTable( + SQTable* table, + rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>>* obj, + rapidjson::MemoryPoolAllocator<SourceAllocator>& allocator) +{ + for (int i = 0; i < table->_numOfNodes; i++) + { + tableNode* node = &table->_nodes[i]; + if (node->key._Type == OT_STRING) + { + rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>> newObj(rapidjson::kObjectType); + rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>> newArray(rapidjson::kArrayType); + + switch (node->val._Type) + { + case OT_STRING: + obj->AddMember( + rapidjson::StringRef(node->key._VAL.asString->_val), rapidjson::StringRef(node->val._VAL.asString->_val), allocator); + break; + case OT_INTEGER: + obj->AddMember(rapidjson::StringRef(node->key._VAL.asString->_val), node->val._VAL.asInteger, allocator); + break; + case OT_FLOAT: + obj->AddMember(rapidjson::StringRef(node->key._VAL.asString->_val), node->val._VAL.asFloat, allocator); + break; + case OT_BOOL: + if (node->val._VAL.asInteger) + { + obj->AddMember(rapidjson::StringRef(node->key._VAL.asString->_val), true, allocator); + } + else + { + obj->AddMember(rapidjson::StringRef(node->key._VAL.asString->_val), false, allocator); + } + break; + case OT_TABLE: + EncodeJSONTable<context>(node->val._VAL.asTable, &newObj, allocator); + obj->AddMember(rapidjson::StringRef(node->key._VAL.asString->_val), newObj, allocator); + break; + case OT_ARRAY: + EncodeJSONArray<context>(node->val._VAL.asArray, &newArray, allocator); + obj->AddMember(rapidjson::StringRef(node->key._VAL.asString->_val), newArray, allocator); + break; + default: + spdlog::warn("SQ_EncodeJSON: squirrel type {} not supported", SQTypeNameFromID(node->val._Type)); + break; + } + } + } +} + +template <ScriptContext context> void EncodeJSONArray( + SQArray* arr, + rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>>* obj, + rapidjson::MemoryPoolAllocator<SourceAllocator>& allocator) +{ + for (int i = 0; i < arr->_usedSlots; i++) + { + SQObject* node = &arr->_values[i]; + + rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>> newObj(rapidjson::kObjectType); + rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>> newArray(rapidjson::kArrayType); + + switch (node->_Type) + { + case OT_STRING: + obj->PushBack(rapidjson::StringRef(node->_VAL.asString->_val), allocator); + break; + case OT_INTEGER: + obj->PushBack(node->_VAL.asInteger, allocator); + break; + case OT_FLOAT: + obj->PushBack(node->_VAL.asFloat, allocator); + break; + case OT_BOOL: + if (node->_VAL.asInteger) + obj->PushBack(rapidjson::StringRef("true"), allocator); + else + obj->PushBack(rapidjson::StringRef("false"), allocator); + break; + case OT_TABLE: + EncodeJSONTable<context>(node->_VAL.asTable, &newObj, allocator); + obj->PushBack(newObj, allocator); + break; + case OT_ARRAY: + EncodeJSONArray<context>(node->_VAL.asArray, &newArray, allocator); + obj->PushBack(newArray, allocator); + break; + default: + spdlog::info("SQ encode Json type {} not supported", SQTypeNameFromID(node->_Type)); + } + } +} + +ADD_SQFUNC( + "table", + DecodeJSON, + "string json, bool fatalParseErrors = false", + "converts a json string to a squirrel table", + ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER) +{ + const char* pJson = g_pSquirrel<context>->getstring(sqvm, 1); + const bool bFatalParseErrors = g_pSquirrel<context>->getbool(sqvm, 2); + + rapidjson_document doc; + doc.Parse(pJson); + if (doc.HasParseError()) + { + g_pSquirrel<context>->newtable(sqvm); + + std::string sErrorString = fmt::format( + "Failed parsing json file: encountered parse error \"{}\" at offset {}", + GetParseError_En(doc.GetParseError()), + doc.GetErrorOffset()); + + if (bFatalParseErrors) + g_pSquirrel<context>->raiseerror(sqvm, sErrorString.c_str()); + else + spdlog::warn(sErrorString); + + return SQRESULT_NOTNULL; + } + + DecodeJsonTable<context>(sqvm, (rapidjson::GenericValue<rapidjson::UTF8<char>, rapidjson::MemoryPoolAllocator<SourceAllocator>>*)&doc); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC( + "string", + EncodeJSON, + "table data", + "converts a squirrel table to a json string", + ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER) +{ + rapidjson_document doc; + doc.SetObject(); + + // temp until this is just the func parameter type + HSquirrelVM* vm = (HSquirrelVM*)sqvm; + SQTable* table = vm->_stackOfCurrentFunction[1]._VAL.asTable; + EncodeJSONTable<context>(table, &doc, doc.GetAllocator()); + + rapidjson::StringBuffer buffer; + rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); + doc.Accept(writer); + const char* pJsonString = buffer.GetString(); + + g_pSquirrel<context>->pushstring(sqvm, pJsonString, -1); + return SQRESULT_NOTNULL; +} diff --git a/NorthstarDLL/scripts/scriptutility.cpp b/NorthstarDLL/scripts/scriptutility.cpp new file mode 100644 index 00000000..8dae49cf --- /dev/null +++ b/NorthstarDLL/scripts/scriptutility.cpp @@ -0,0 +1,14 @@ +#include "pch.h" +#include "squirrel/squirrel.h" + +// asset function StringToAsset( string assetName ) +ADD_SQFUNC( + "asset", + StringToAsset, + "string assetName", + "converts a given string to an asset", + ScriptContext::UI | ScriptContext::CLIENT | ScriptContext::SERVER) +{ + g_pSquirrel<context>->pushasset(sqvm, g_pSquirrel<context>->getstring(sqvm, 1), -1); + return SQRESULT_NOTNULL; +} diff --git a/NorthstarDLL/scripts/server/miscserverfixes.cpp b/NorthstarDLL/scripts/server/miscserverfixes.cpp new file mode 100644 index 00000000..4feca505 --- /dev/null +++ b/NorthstarDLL/scripts/server/miscserverfixes.cpp @@ -0,0 +1,7 @@ +#include "pch.h" + +ON_DLL_LOAD("server.dll", MiscServerFixes, (CModule module)) +{ + // nop out call to VGUI shutdown since it crashes the game when quitting from the console + module.Offset(0x154A96).NOP(5); +} diff --git a/NorthstarDLL/scripts/server/miscserverscript.cpp b/NorthstarDLL/scripts/server/miscserverscript.cpp new file mode 100644 index 00000000..0d865388 --- /dev/null +++ b/NorthstarDLL/scripts/server/miscserverscript.cpp @@ -0,0 +1,60 @@ +#include "pch.h" +#include "squirrel/squirrel.h" +#include "masterserver/masterserver.h" +#include "server/auth/serverauthentication.h" +#include "dedicated/dedicated.h" +#include "client/r2client.h" +#include "server/r2server.h" + +#include <filesystem> + +ADD_SQFUNC("void", NSEarlyWritePlayerPersistenceForLeave, "entity player", "", ScriptContext::SERVER) +{ + const R2::CBasePlayer* pPlayer = g_pSquirrel<context>->getentity<R2::CBasePlayer>(sqvm, 1); + if (!pPlayer) + { + spdlog::warn("NSEarlyWritePlayerPersistenceForLeave got null player"); + + g_pSquirrel<context>->pushbool(sqvm, false); + return SQRESULT_NOTNULL; + } + + R2::CBaseClient* pClient = &R2::g_pClientArray[pPlayer->m_nPlayerIndex - 1]; + if (g_pServerAuthentication->m_PlayerAuthenticationData.find(pClient) == g_pServerAuthentication->m_PlayerAuthenticationData.end()) + { + g_pSquirrel<context>->pushbool(sqvm, false); + return SQRESULT_NOTNULL; + } + + g_pServerAuthentication->m_PlayerAuthenticationData[pClient].needPersistenceWriteOnLeave = false; + g_pServerAuthentication->WritePersistentData(pClient); + return SQRESULT_NULL; +} + +ADD_SQFUNC("bool", NSIsWritingPlayerPersistence, "", "", ScriptContext::SERVER) +{ + g_pSquirrel<context>->pushbool(sqvm, g_pMasterServerManager->m_bSavingPersistentData); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("bool", NSIsPlayerLocalPlayer, "entity player", "", ScriptContext::SERVER) +{ + const R2::CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->getentity<R2::CBasePlayer>(sqvm, 1); + if (!pPlayer) + { + spdlog::warn("NSIsPlayerLocalPlayer got null player"); + + g_pSquirrel<context>->pushbool(sqvm, false); + return SQRESULT_NOTNULL; + } + + R2::CBaseClient* pClient = &R2::g_pClientArray[pPlayer->m_nPlayerIndex - 1]; + g_pSquirrel<context>->pushbool(sqvm, !strcmp(R2::g_pLocalPlayerUserID, pClient->m_UID)); + return SQRESULT_NOTNULL; +} + +ADD_SQFUNC("bool", NSIsDedicated, "", "", ScriptContext::SERVER) +{ + g_pSquirrel<context>->pushbool(sqvm, IsDedicatedServer()); + return SQRESULT_NOTNULL; +} diff --git a/NorthstarDLL/scripts/server/scriptuserinfo.cpp b/NorthstarDLL/scripts/server/scriptuserinfo.cpp new file mode 100644 index 00000000..68baac0e --- /dev/null +++ b/NorthstarDLL/scripts/server/scriptuserinfo.cpp @@ -0,0 +1,105 @@ +#include "pch.h" +#include "squirrel/squirrel.h" +#include "engine/r2engine.h" +#include "server/r2server.h" + +// clang-format off +ADD_SQFUNC("string", GetUserInfoKVString_Internal, "entity player, string key, string defaultValue = \"\"", + "Gets the string value of a given player's userinfo convar by name", ScriptContext::SERVER) +// clang-format on +{ + const R2::CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->getentity<R2::CBasePlayer>(sqvm, 1); + if (!pPlayer) + { + g_pSquirrel<ScriptContext::SERVER>->raiseerror(sqvm, "player is null"); + return SQRESULT_ERROR; + } + + const char* pKey = g_pSquirrel<ScriptContext::SERVER>->getstring(sqvm, 2); + const char* pDefaultValue = g_pSquirrel<ScriptContext::SERVER>->getstring(sqvm, 3); + + const char* pResult = R2::g_pClientArray[pPlayer->m_nPlayerIndex - 1].m_ConVars->GetString(pKey, pDefaultValue); + g_pSquirrel<ScriptContext::SERVER>->pushstring(sqvm, pResult); + return SQRESULT_NOTNULL; +} + +// clang-format off +ADD_SQFUNC("asset", GetUserInfoKVAsset_Internal, "entity player, string key, asset defaultValue = $\"\"", + "Gets the asset value of a given player's userinfo convar by name", ScriptContext::SERVER) +// clang-format on +{ + const R2::CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->getentity<R2::CBasePlayer>(sqvm, 1); + if (!pPlayer) + { + g_pSquirrel<ScriptContext::SERVER>->raiseerror(sqvm, "player is null"); + return SQRESULT_ERROR; + } + + const char* pKey = g_pSquirrel<ScriptContext::SERVER>->getstring(sqvm, 2); + const char* pDefaultValue; + g_pSquirrel<ScriptContext::SERVER>->getasset(sqvm, 3, &pDefaultValue); + + const char* pResult = R2::g_pClientArray[pPlayer->m_nPlayerIndex - 1].m_ConVars->GetString(pKey, pDefaultValue); + g_pSquirrel<ScriptContext::SERVER>->pushasset(sqvm, pResult); + return SQRESULT_NOTNULL; +} + +// clang-format off +ADD_SQFUNC("int", GetUserInfoKVInt_Internal, "entity player, string key, int defaultValue = 0", + "Gets the int value of a given player's userinfo convar by name", ScriptContext::SERVER) +// clang-format on +{ + const R2::CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->getentity<R2::CBasePlayer>(sqvm, 1); + if (!pPlayer) + { + g_pSquirrel<ScriptContext::SERVER>->raiseerror(sqvm, "player is null"); + return SQRESULT_ERROR; + } + + const char* pKey = g_pSquirrel<ScriptContext::SERVER>->getstring(sqvm, 2); + const int iDefaultValue = g_pSquirrel<ScriptContext::SERVER>->getinteger(sqvm, 3); + + const int iResult = R2::g_pClientArray[pPlayer->m_nPlayerIndex - 1].m_ConVars->GetInt(pKey, iDefaultValue); + g_pSquirrel<ScriptContext::SERVER>->pushinteger(sqvm, iResult); + return SQRESULT_NOTNULL; +} + +// clang-format off +ADD_SQFUNC("float", GetUserInfoKVFloat_Internal, "entity player, string key, float defaultValue = 0", + "Gets the float value of a given player's userinfo convar by name", ScriptContext::SERVER) +// clang-format on +{ + const R2::CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->getentity<R2::CBasePlayer>(sqvm, 1); + if (!pPlayer) + { + g_pSquirrel<ScriptContext::SERVER>->raiseerror(sqvm, "player is null"); + return SQRESULT_ERROR; + } + + const char* pKey = g_pSquirrel<ScriptContext::SERVER>->getstring(sqvm, 2); + const float flDefaultValue = g_pSquirrel<ScriptContext::SERVER>->getfloat(sqvm, 3); + + const float flResult = R2::g_pClientArray[pPlayer->m_nPlayerIndex - 1].m_ConVars->GetFloat(pKey, flDefaultValue); + g_pSquirrel<ScriptContext::SERVER>->pushfloat(sqvm, flResult); + return SQRESULT_NOTNULL; +} + +// clang-format off +ADD_SQFUNC("bool", GetUserInfoKVBool_Internal, "entity player, string key, bool defaultValue = false", + "Gets the bool value of a given player's userinfo convar by name", ScriptContext::SERVER) +// clang-format on +{ + const R2::CBasePlayer* pPlayer = g_pSquirrel<ScriptContext::SERVER>->getentity<R2::CBasePlayer>(sqvm, 1); + if (!pPlayer) + { + g_pSquirrel<ScriptContext::SERVER>->raiseerror(sqvm, "player is null"); + return SQRESULT_ERROR; + } + + const char* pKey = g_pSquirrel<ScriptContext::SERVER>->getstring(sqvm, 2); + const bool bDefaultValue = g_pSquirrel<ScriptContext::SERVER>->getbool(sqvm, 3); + + const bool bResult = R2::g_pClientArray[pPlayer->m_nPlayerIndex - 1].m_ConVars->GetInt(pKey, bDefaultValue); + g_pSquirrel<ScriptContext::SERVER>->pushbool(sqvm, bResult); + return SQRESULT_NOTNULL; +} |