[Script] Re-work stuff (no hidden changes)

*maybe* (Based on a true story and events)
This commit is contained in:
Diavolo 2022-07-06 17:48:40 +02:00
parent 22d57a675e
commit 60f7c7c254
No known key found for this signature in database
GPG Key ID: FA77F074E98D98A5
23 changed files with 385 additions and 313 deletions

View File

@ -33,7 +33,6 @@
| `-stdout` | Redirect all logging output to the terminal iw4x is started from, or if there is none, creates a new terminal window to write log information in. |
| `-console` | Allow the game to display its own separate interactive console window. |
| `-dedicated` | Starts the game as a headless dedicated server. |
| `-scriptablehttp` | Enable HTTP related gsc functions. |
| `-bigminidumps` | Include all code sections from loaded modules in the dump. |
| `-reallybigminidumps` | Include data sections from all loaded modules in the dump. |
| `-dump` | Write info of loaded assets to the raw folder as they are being loaded. |

View File

@ -49,7 +49,6 @@ namespace Components
Loader::Register(new Zones());
Loader::Register(new D3D9Ex());
Loader::Register(new Logger());
Loader::Register(new Script());
Loader::Register(new Weapon());
Loader::Register(new Window());
Loader::Register(new Command());
@ -105,7 +104,6 @@ namespace Components
Loader::Register(new Elevators());
Loader::Register(new ClientCommand());
Loader::Register(new VisionFile());
Loader::Register(new ScriptExtension());
Loader::Register(new Branding());
Loader::Register(new Debug());
Loader::Register(new RawMouse());
@ -115,6 +113,8 @@ namespace Components
Loader::Register(new UserInfo());
Loader::Register(new Events());
Loader::Register(new GSC());
Loader::Pregame = false;
// Make sure preDestroy is called when the game shuts down

View File

@ -74,7 +74,6 @@ namespace Components
#include "Modules/Toast.hpp"
#include "Modules/Zones.hpp"
#include "Modules/D3D9Ex.hpp"
#include "Modules/Script.hpp"
#include "Modules/Weapon.hpp"
#include "Modules/Window.hpp"
#include "Modules/Command.hpp"
@ -136,7 +135,6 @@ namespace Components
#include "Modules/ClientCommand.hpp"
#include "Modules/VisionFile.hpp"
#include "Modules/Gamepad.hpp"
#include "Modules/ScriptExtension.hpp"
#include "Modules/Branding.hpp"
#include "Modules/Debug.hpp"
#include "Modules/RawMouse.hpp"
@ -145,3 +143,5 @@ namespace Components
#include "Modules/Ceg.hpp"
#include "Modules/UserInfo.hpp"
#include "Modules/Events.hpp"
#include "Modules/GSC/GSC.hpp"

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp>
#include "GSC/Script.hpp"
namespace Components
{
@ -121,24 +122,6 @@ namespace Components
Script::AddMethod("IsBot", Bots::GScr_isTestClient); // Usage: self IsBot();
Script::AddMethod("IsTestClient", Bots::GScr_isTestClient); // Usage: self IsTestClient();
Script::AddMethod("SetPing", [](Game::scr_entref_t entref) // gsc: self SetPing(<int>)
{
auto ping = Game::Scr_GetInt(0);
ping = std::clamp(ping, 0, 999);
const auto* ent = Game::GetPlayerEntity(entref);
auto* client = Script::GetClient(ent);
if (Game::SV_IsTestClient(ent->s.number) == 0)
{
Game::Scr_Error("^1SetPing: Can only call on a bot!\n");
return;
}
client->ping = static_cast<int16_t>(ping);
});
Script::AddMethod("BotStop", [](Game::scr_entref_t entref) // Usage: <bot> BotStop();
{
const auto* ent = Game::GetPlayerEntity(entref);
@ -201,7 +184,7 @@ namespace Components
return;
}
for (auto i = 0u; i < std::extent_v<decltype(BotActions)>; ++i)
for (std::size_t i = 0; i < std::extent_v<decltype(BotActions)>; ++i)
{
if (Utils::String::ToLower(&action[1]) != BotActions[i].action)
continue;
@ -331,9 +314,9 @@ namespace Components
});
// Zero the bot command array
for (auto i = 0u; i < std::extent_v<decltype(g_botai)>; i++)
for (std::size_t i = 0; i < std::extent_v<decltype(g_botai)>; ++i)
{
g_botai[i] = {0};
std::memset(&g_botai[i], 0, sizeof(BotMovementInfo));
g_botai[i].weapon = 1; // Prevent the bots from defaulting to the 'none' weapon
}

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp>
#include "GSC/Script.hpp"
namespace Components
{

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp>
#include "GSC/Script.hpp"
namespace Components
{

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp>
#include "GSC/Script.hpp"
namespace Components
{
@ -968,9 +969,6 @@ namespace Components
Script::AddFunction("HttpGet", []
{
if (!Flags::HasFlag("scriptablehttp"))
return;
const auto* url = Game::Scr_GetString(0);
if (url == nullptr)
@ -989,9 +987,6 @@ namespace Components
Script::AddFunction("HttpCancel", []
{
if (!Flags::HasFlag("scriptablehttp"))
return;
const auto object = Game::Scr_GetObject(0);
for (const auto& download : Download::ScriptDownloads)
{

View File

@ -0,0 +1,17 @@
#include <STDInclude.hpp>
#include "IO.hpp"
#include "Script.hpp"
#include "ScriptExtension.hpp"
#include "ScriptStorage.hpp"
namespace Components
{
GSC::GSC()
{
Loader::Register(new IO());
Loader::Register(new Script());
Loader::Register(new ScriptExtension());
Loader::Register(new ScriptStorage());
}
}

View File

@ -0,0 +1,10 @@
#pragma once
namespace Components
{
class GSC : public Component
{
public:
GSC();
};
}

View File

@ -0,0 +1,115 @@
#include <STDInclude.hpp>
#include "IO.hpp"
#include "Script.hpp"
namespace Components
{
const char* IO::QueryStrings[] = { R"(..)", R"(../)", R"(..\)" };
void IO::AddScriptFunctions()
{
Script::AddFunction("FileWrite", [] // gsc: FileWrite(<filepath>, <string>, <mode>)
{
const auto* path = Game::Scr_GetString(0);
auto* text = Game::Scr_GetString(1);
auto* mode = Game::Scr_GetString(2);
if (path == nullptr)
{
Game::Scr_ParamError(0, "^1FileWrite: filepath is not defined!\n");
return;
}
if (text == nullptr || mode == nullptr)
{
Game::Scr_Error("^1FileWrite: Illegal parameters!\n");
return;
}
for (std::size_t i = 0; i < ARRAYSIZE(QueryStrings); ++i)
{
if (std::strstr(path, QueryStrings[i]) != nullptr)
{
Logger::PrintError(Game::CON_CHANNEL_ERROR, "FileWrite: directory traversal is not allowed!\n");
return;
}
}
if (mode != "append"s && mode != "write"s)
{
Logger::Warning(Game::CON_CHANNEL_SCRIPT, "FileWrite: mode not defined or was wrong, defaulting to 'write'\n");
mode = "write";
}
const auto* scriptData = Utils::String::VA("%s/%s", "scriptdata", path);
if (mode == "write"s)
{
FileSystem::FileWriter(scriptData).write(text);
}
else if (mode == "append"s)
{
FileSystem::FileWriter(scriptData, true).write(text);
}
});
Script::AddFunction("FileRead", [] // gsc: FileRead(<filepath>)
{
const auto* path = Game::Scr_GetString(0);
if (path == nullptr)
{
Game::Scr_ParamError(0, "^1FileRead: filepath is not defined!\n");
return;
}
for (std::size_t i = 0; i < ARRAYSIZE(QueryStrings); ++i)
{
if (std::strstr(path, QueryStrings[i]) != nullptr)
{
Logger::PrintError(Game::CON_CHANNEL_ERROR, "FileRead: directory traversal is not allowed!\n");
return;
}
}
const auto* scriptData = Utils::String::VA("%s/%s", "scriptdata", path);
if (!FileSystem::FileReader(scriptData).exists())
{
Logger::PrintError(Game::CON_CHANNEL_ERROR, "FileRead: file '{}' not found!\n", scriptData);
return;
}
Game::Scr_AddString(FileSystem::FileReader(scriptData).getBuffer().data());
});
Script::AddFunction("FileExists", [] // gsc: FileExists(<filepath>)
{
const auto* path = Game::Scr_GetString(0);
if (path == nullptr)
{
Game::Scr_ParamError(0, "^1FileExists: filepath is not defined!\n");
return;
}
for (std::size_t i = 0; i < ARRAYSIZE(QueryStrings); ++i)
{
if (std::strstr(path, QueryStrings[i]) != nullptr)
{
Logger::PrintError(Game::CON_CHANNEL_ERROR, "FileExists: directory traversal is not allowed!\n");
return;
}
}
const auto* scriptData = Utils::String::VA("%s/%s", "scriptdata", path);
Game::Scr_AddBool(FileSystem::FileReader(scriptData).exists());
});
}
IO::IO()
{
AddScriptFunctions();
}
}

View File

@ -0,0 +1,15 @@
#pragma once
namespace Components
{
class IO : public Component
{
public:
IO();
private:
static const char* QueryStrings[];
static void AddScriptFunctions();
};
}

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp>
#include "Script.hpp"
namespace Components
{
@ -7,7 +8,6 @@ namespace Components
std::unordered_map<std::string, Game::BuiltinMethodDef> Script::CustomScrMethods;
std::vector<std::string> Script::ScriptNameStack;
unsigned short Script::FunctionName;
std::unordered_map<std::string, std::string> Script::ScriptStorage;
std::unordered_map<int, std::string> Script::ScriptBaseProgramNum;
int Script::LastFrameTime = -1;
@ -416,11 +416,11 @@ namespace Components
unsigned int Script::SetExpFogStub()
{
if (Game::Scr_GetNumParam() == 6u)
if (Game::Scr_GetNumParam() == 6)
{
std::memmove(&Game::scrVmPub->top[-4], &Game::scrVmPub->top[-5], sizeof(Game::VariableValue) * 6);
Game::scrVmPub->top += 1;
Game::scrVmPub->top[-6].type = Game::scrParamType_t::VAR_FLOAT;
Game::scrVmPub->top[-6].type = Game::VAR_FLOAT;
Game::scrVmPub->top[-6].u.floatValue = 0.0f;
++Game::scrVmPub->outparamcount;
@ -540,7 +540,7 @@ namespace Components
{
Script::AddFunction("ReplaceFunc", [] // gsc: ReplaceFunc(<function>, <function>)
{
if (Game::Scr_GetNumParam() != 2u)
if (Game::Scr_GetNumParam() != 2)
{
Game::Scr_Error("^1ReplaceFunc: Needs two parameters!\n");
return;
@ -592,78 +592,6 @@ namespace Components
}
});
// Script Storage Functions
Script::AddFunction("StorageSet", [] // gsc: StorageSet(<str key>, <str data>);
{
const auto* key = Game::Scr_GetString(0);
const auto* value = Game::Scr_GetString(1);
if (key == nullptr || value == nullptr)
{
Game::Scr_Error("^1StorageSet: Illegal parameters!\n");
return;
}
Script::ScriptStorage.insert_or_assign(key, value);
});
Script::AddFunction("StorageRemove", [] // gsc: StorageRemove(<str key>);
{
const auto* key = Game::Scr_GetString(0);
if (key == nullptr)
{
Game::Scr_ParamError(0, "^1StorageRemove: Illegal parameter!\n");
return;
}
if (!Script::ScriptStorage.contains(key))
{
Game::Scr_Error(Utils::String::VA("^1StorageRemove: Store does not have key '%s'!\n", key));
return;
}
Script::ScriptStorage.erase(key);
});
Script::AddFunction("StorageGet", [] // gsc: StorageGet(<str key>);
{
const auto* key = Game::Scr_GetString(0);
if (key == nullptr)
{
Game::Scr_ParamError(0, "^1StorageGet: Illegal parameter!\n");
return;
}
if (!Script::ScriptStorage.contains(key))
{
Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key));
return;
}
const auto& data = Script::ScriptStorage.at(key);
Game::Scr_AddString(data.data());
});
Script::AddFunction("StorageHas", [] // gsc: StorageHas(<str key>);
{
const auto* key = Game::Scr_GetString(0);
if (key == nullptr)
{
Game::Scr_ParamError(0, "^1StorageHas: Illegal parameter!\n");
return;
}
Game::Scr_AddBool(Script::ScriptStorage.contains(key));
});
Script::AddFunction("StorageClear", [] // gsc: StorageClear();
{
Script::ScriptStorage.clear();
});
// PlayerCmd_AreControlsFrozen GSC function from Black Ops 2
Script::AddMethod("AreControlsFrozen", [](Game::scr_entref_t entref) // Usage: self AreControlsFrozen();
{

View File

@ -1,5 +1,4 @@
#pragma once
#include <Game/Structs.hpp>
namespace Components
{
@ -21,7 +20,6 @@ namespace Components
static std::unordered_map<std::string, Game::BuiltinMethodDef> CustomScrMethods;
static std::vector<std::string> ScriptNameStack;
static unsigned short FunctionName;
static std::unordered_map<std::string, std::string> ScriptStorage;
static std::unordered_map<int, std::string> ScriptBaseProgramNum;
static int LastFrameTime;

View File

@ -1,9 +1,9 @@
#include <STDInclude.hpp>
#include "ScriptExtension.hpp"
#include "Script.hpp"
namespace Components
{
const char* ScriptExtension::QueryStrings[] = { R"(..)", R"(../)", R"(..\)" };
std::unordered_map<std::uint16_t, Game::ent_field_t> ScriptExtension::CustomEntityFields;
std::unordered_map<std::uint16_t, Game::client_fields_s> ScriptExtension::CustomClientFields;
@ -13,7 +13,7 @@ namespace Components
static std::uint16_t fieldOffsetStart = 15; // fields count
assert((fieldOffsetStart & Game::ENTFIELD_MASK) == Game::ENTFIELD_ENTITY);
ScriptExtension::CustomEntityFields[fieldOffsetStart] = {name, fieldOffsetStart, type, setter, getter};
CustomEntityFields[fieldOffsetStart] = {name, fieldOffsetStart, type, setter, getter};
++fieldOffsetStart;
}
@ -26,20 +26,20 @@ namespace Components
const auto offset = fieldOffsetStart | Game::ENTFIELD_CLIENT; // This is how client field's offset is calculated
// Use 'index' in 'array' as map key. It will be used later in Scr_SetObjectFieldStub
ScriptExtension::CustomClientFields[fieldOffsetStart] = {name, offset, type, setter, getter};
CustomClientFields[fieldOffsetStart] = {name, offset, type, setter, getter};
++fieldOffsetStart;
}
void ScriptExtension::GScr_AddFieldsForEntityStub()
{
for (const auto& [offset, field] : ScriptExtension::CustomEntityFields)
for (const auto& [offset, field] : CustomEntityFields)
{
Game::Scr_AddClassField(Game::ClassNum::CLASS_NUM_ENTITY, field.name, field.ofs);
}
Utils::Hook::Call<void()>(0x4A7CF0)(); // GScr_AddFieldsForClient
for (const auto& [offset, field] : ScriptExtension::CustomClientFields)
for (const auto& [offset, field] : CustomClientFields)
{
Game::Scr_AddClassField(Game::ClassNum::CLASS_NUM_ENTITY, field.name, field.ofs);
}
@ -52,8 +52,8 @@ namespace Components
{
const auto entity_offset = static_cast<std::uint16_t>(offset);
const auto got = ScriptExtension::CustomEntityFields.find(entity_offset);
if (got != ScriptExtension::CustomEntityFields.end())
const auto got =CustomEntityFields.find(entity_offset);
if (got != CustomEntityFields.end())
{
got->second.setter(&Game::g_entities[entnum], offset);
return 1;
@ -69,8 +69,8 @@ namespace Components
{
const auto client_offset = static_cast<std::uint16_t>(offset);
const auto got = ScriptExtension::CustomClientFields.find(client_offset);
if (got != ScriptExtension::CustomClientFields.end())
const auto got = CustomClientFields.find(client_offset);
if (got != CustomClientFields.end())
{
got->second.setter(client, &got->second);
return;
@ -89,8 +89,8 @@ namespace Components
{
const auto client_offset = static_cast<std::uint16_t>(offset & ~Game::ENTFIELD_MASK);
const auto got = ScriptExtension::CustomClientFields.find(client_offset);
if (got != ScriptExtension::CustomClientFields.end())
const auto got =CustomClientFields.find(client_offset);
if (got != CustomClientFields.end())
{
// Game functions probably don't ever need to use the reference to client_fields_s...
got->second.getter(Game::g_entities[entnum].client, &got->second);
@ -102,8 +102,8 @@ namespace Components
// Regular entity offsets can be searched directly in our custom handler
const auto entity_offset = static_cast<std::uint16_t>(offset);
const auto got = ScriptExtension::CustomEntityFields.find(entity_offset);
if (got != ScriptExtension::CustomEntityFields.end())
const auto got = CustomEntityFields.find(entity_offset);
if (got != CustomEntityFields.end())
{
got->second.getter(&Game::g_entities[entnum], offset);
return;
@ -115,125 +115,6 @@ namespace Components
void ScriptExtension::AddFunctions()
{
// File functions
Script::AddFunction("FileWrite", [] // gsc: FileWrite(<filepath>, <string>, <mode>)
{
const auto* path = Game::Scr_GetString(0);
auto* text = Game::Scr_GetString(1);
auto* mode = Game::Scr_GetString(2);
if (path == nullptr)
{
Game::Scr_ParamError(0, "^1FileWrite: filepath is not defined!\n");
return;
}
if (text == nullptr || mode == nullptr)
{
Game::Scr_Error("^1FileWrite: Illegal parameters!\n");
return;
}
for (auto i = 0u; i < ARRAYSIZE(ScriptExtension::QueryStrings); ++i)
{
if (std::strstr(path, ScriptExtension::QueryStrings[i]) != nullptr)
{
Logger::Print("^1FileWrite: directory traversal is not allowed!\n");
return;
}
}
if (mode != "append"s && mode != "write"s)
{
Logger::Print("^3FileWrite: mode not defined or was wrong, defaulting to 'write'\n");
mode = "write";
}
if (mode == "write"s)
{
FileSystem::FileWriter(path).write(text);
}
else if (mode == "append"s)
{
FileSystem::FileWriter(path, true).write(text);
}
});
Script::AddFunction("FileRead", [] // gsc: FileRead(<filepath>)
{
const auto* path = Game::Scr_GetString(0);
if (path == nullptr)
{
Game::Scr_ParamError(0, "^1FileRead: filepath is not defined!\n");
return;
}
for (auto i = 0u; i < ARRAYSIZE(ScriptExtension::QueryStrings); ++i)
{
if (std::strstr(path, ScriptExtension::QueryStrings[i]) != nullptr)
{
Logger::Print("^1FileRead: directory traversal is not allowed!\n");
return;
}
}
if (!FileSystem::FileReader(path).exists())
{
Logger::Print("^1FileRead: file not found!\n");
return;
}
Game::Scr_AddString(FileSystem::FileReader(path).getBuffer().data());
});
Script::AddFunction("FileExists", [] // gsc: FileExists(<filepath>)
{
const auto* path = Game::Scr_GetString(0);
if (path == nullptr)
{
Game::Scr_ParamError(0, "^1FileExists: filepath is not defined!\n");
return;
}
for (auto i = 0u; i < ARRAYSIZE(ScriptExtension::QueryStrings); ++i)
{
if (std::strstr(path, ScriptExtension::QueryStrings[i]) != nullptr)
{
Logger::Print("^1FileExists: directory traversal is not allowed!\n");
return;
}
}
Game::Scr_AddInt(FileSystem::FileReader(path).exists());
});
Script::AddFunction("FileRemove", [] // gsc: FileRemove(<filepath>)
{
const auto* path = Game::Scr_GetString(0);
if (path == nullptr)
{
Game::Scr_ParamError(0, "^1FileRemove: filepath is not defined!\n");
return;
}
for (auto i = 0u; i < ARRAYSIZE(ScriptExtension::QueryStrings); ++i)
{
if (std::strstr(path, ScriptExtension::QueryStrings[i]) != nullptr)
{
Logger::Print("^1FileRemove: directory traversal is not allowed!\n");
return;
}
}
const auto p = std::filesystem::path(path);
const auto& folder = p.parent_path().string();
const auto& file = p.filename().string();
Game::Scr_AddInt(FileSystem::DeleteFile(folder, file));
});
// Misc functions
Script::AddFunction("ToUpper", [] // gsc: ToUpper(<string>)
{
@ -311,11 +192,11 @@ namespace Components
const auto type = Game::Scr_GetType(0);
bool result;
if (type == Game::scrParamType_t::VAR_POINTER)
if (type == Game::VAR_POINTER)
{
const auto ptr_type = Game::Scr_GetPointerType(0);
assert(ptr_type >= Game::FIRST_OBJECT);
result = (ptr_type == Game::scrParamType_t::VAR_ARRAY);
result = (ptr_type == Game::VAR_ARRAY);
}
else
{
@ -350,6 +231,18 @@ namespace Components
Game::Scr_AddInt(client->ping);
});
Script::AddMethod("SetPing", [](Game::scr_entref_t entref) // gsc: self SetPing(<int>)
{
auto ping = Game::Scr_GetInt(0);
ping = std::clamp(ping, 0, 999);
const auto* ent = Game::GetPlayerEntity(entref);
auto* client = Script::GetClient(ent);
client->ping = static_cast<int16_t>(ping);
});
}
void ScriptExtension::Scr_TableLookupIStringByRow()
@ -378,7 +271,7 @@ namespace Components
void ScriptExtension::AddEntityFields()
{
ScriptExtension::AddEntityField("entityflags", Game::fieldtype_t::F_INT,
AddEntityField("entityflags", Game::fieldtype_t::F_INT,
[](Game::gentity_s* ent, [[maybe_unused]] int offset)
{
ent->flags = Game::Scr_GetInt(0);
@ -391,7 +284,7 @@ namespace Components
void ScriptExtension::AddClientFields()
{
ScriptExtension::AddClientField("clientflags", Game::fieldtype_t::F_INT,
AddClientField("clientflags", Game::fieldtype_t::F_INT,
[](Game::gclient_s* pSelf, [[maybe_unused]] const Game::client_fields_s* pField)
{
pSelf->flags = Game::Scr_GetInt(0);
@ -404,19 +297,19 @@ namespace Components
ScriptExtension::ScriptExtension()
{
ScriptExtension::AddFunctions();
ScriptExtension::AddMethods();
ScriptExtension::AddEntityFields();
ScriptExtension::AddClientFields();
AddFunctions();
AddMethods();
AddEntityFields();
AddClientFields();
// Correct builtin function pointer
Utils::Hook::Set<void(*)()>(0x79A90C, ScriptExtension::Scr_TableLookupIStringByRow);
Utils::Hook::Set<Game::BuiltinFunction>(0x79A90C, Scr_TableLookupIStringByRow);
Utils::Hook(0x4EC721, ScriptExtension::GScr_AddFieldsForEntityStub, HOOK_CALL).install()->quick(); // GScr_AddFieldsForEntity
Utils::Hook(0x4EC721, GScr_AddFieldsForEntityStub, HOOK_CALL).install()->quick(); // GScr_AddFieldsForEntity
Utils::Hook(0x41BED2, ScriptExtension::Scr_SetObjectFieldStub, HOOK_CALL).install()->quick(); // SetEntityFieldValue
Utils::Hook(0x5FBF01, ScriptExtension::Scr_SetClientFieldStub, HOOK_CALL).install()->quick(); // Scr_SetObjectField
Utils::Hook(0x4FF413, ScriptExtension::Scr_GetEntityFieldStub, HOOK_CALL).install()->quick(); // Scr_GetObjectField
Utils::Hook(0x41BED2, Scr_SetObjectFieldStub, HOOK_CALL).install()->quick(); // SetEntityFieldValue
Utils::Hook(0x5FBF01, Scr_SetClientFieldStub, HOOK_CALL).install()->quick(); // Scr_SetObjectField
Utils::Hook(0x4FF413, Scr_GetEntityFieldStub, HOOK_CALL).install()->quick(); // Scr_GetObjectField
// Fix format string in Scr_RandomFloatRange
Utils::Hook::Set<const char*>(0x5F10C6, "Scr_RandomFloatRange parms: %f %f ");

View File

@ -11,8 +11,6 @@ namespace Components
static void AddClientField(const char* name, Game::fieldtype_t type, const Game::ScriptCallbackClient& setter, const Game::ScriptCallbackClient& getter);
private:
static const char* QueryStrings[];
static std::unordered_map<std::uint16_t, Game::ent_field_t> CustomEntityFields;
static std::unordered_map<std::uint16_t, Game::client_fields_s> CustomClientFields;

View File

@ -0,0 +1,101 @@
#include <STDInclude.hpp>
#include "ScriptStorage.hpp"
#include "Script.hpp"
namespace Components
{
std::unordered_map<std::string, std::string> ScriptStorage::Data;
void ScriptStorage::AddScriptFunctions()
{
Script::AddFunction("StorageSet", [] // gsc: StorageSet(<str key>, <str data>);
{
const auto* key = Game::Scr_GetString(0);
const auto* value = Game::Scr_GetString(1);
if (key == nullptr || value == nullptr)
{
Game::Scr_Error("^1StorageSet: Illegal parameters!\n");
return;
}
Data.insert_or_assign(key, value);
});
Script::AddFunction("StorageRemove", [] // gsc: StorageRemove(<str key>);
{
const auto* key = Game::Scr_GetString(0);
if (key == nullptr)
{
Game::Scr_ParamError(0, "^1StorageRemove: Illegal parameter!\n");
return;
}
if (!Data.contains(key))
{
Game::Scr_Error(Utils::String::VA("^1StorageRemove: Store does not have key '%s'!\n", key));
return;
}
Data.erase(key);
});
Script::AddFunction("StorageGet", [] // gsc: StorageGet(<str key>);
{
const auto* key = Game::Scr_GetString(0);
if (key == nullptr)
{
Game::Scr_ParamError(0, "^1StorageGet: Illegal parameter!\n");
return;
}
if (!Data.contains(key))
{
Game::Scr_Error(Utils::String::VA("^1StorageGet: Store does not have key '%s'!\n", key));
return;
}
const auto& data = Data.at(key);
Game::Scr_AddString(data.data());
});
Script::AddFunction("StorageHas", [] // gsc: StorageHas(<str key>);
{
const auto* key = Game::Scr_GetString(0);
if (key == nullptr)
{
Game::Scr_ParamError(0, "^1StorageHas: Illegal parameter!\n");
return;
}
Game::Scr_AddBool(Data.contains(key));
});
Script::AddFunction("StorageDump", [] // gsc: StorageDump();
{
if (Data.empty())
{
Game::Scr_Error("^1StorageDump: ScriptStorage is empty!\n");
return;
}
const json11::Json json = Data;
FileSystem::FileWriter("scriptdata/scriptstorage.json").write(json.dump());
});
Script::AddFunction("StorageClear", [] // gsc: StorageClear();
{
Data.clear();
});
}
ScriptStorage::ScriptStorage()
{
AddScriptFunctions();
}
}

View File

@ -0,0 +1,15 @@
#pragma once
namespace Components
{
class ScriptStorage : public Component
{
public:
ScriptStorage();
private:
static std::unordered_map<std::string, std::string> Data;
static void AddScriptFunctions();
};
}

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp>
#include "GSC/Script.hpp"
namespace Components
{

View File

@ -1,4 +1,5 @@
#include <STDInclude.hpp>
#include "GSC/Script.hpp"
namespace Components
{

View File

@ -13,7 +13,6 @@ namespace Components
Dvar::Var ZoneBuilder::PreferDiskAssetsDvar;
ZoneBuilder::Zone::Zone(const std::string& name) : indexStart(0), externalSize(0),
// Reserve 100MB by default.
// That's totally fine, as the dedi doesn't load images and therefore doesn't need much memory.
// That way we can be sure it won't need to reallocate memory.
@ -21,11 +20,8 @@ namespace Components
// Well, decompressed maps can get way larger than 100MB, so let's increase that.
buffer(0xC800000),
zoneName(name), dataMap("zone_source/" + name + ".csv"), branding{ nullptr }, assetDepth(0)
{}
ZoneBuilder::Zone::Zone() : indexStart(0), externalSize(0), buffer(0xC800000), zoneName("null_zone"),
dataMap(), branding{ nullptr }, assetDepth(0)
{}
{
}
ZoneBuilder::Zone::~Zone()
{
@ -124,7 +120,7 @@ namespace Components
{
Logger::Print("Loading required FastFiles...\n");
for (int i = 0; i < this->dataMap.getRows(); ++i)
for (std::size_t i = 0; i < this->dataMap.getRows(); ++i)
{
if (this->dataMap.getElementAt(i, 0) == "require")
{
@ -149,7 +145,7 @@ namespace Components
bool ZoneBuilder::Zone::loadAssets()
{
for (int i = 0; i < this->dataMap.getRows(); ++i)
for (std::size_t i = 0; i < this->dataMap.getRows(); ++i)
{
if (this->dataMap.getElementAt(i, 0) != "require")
{

View File

@ -32,7 +32,6 @@ namespace Components
};
Zone(const std::string& zoneName);
Zone();
~Zone();
void build();

View File

@ -2,36 +2,21 @@
namespace Utils
{
CSV::CSV(const std::string& file, bool isFile, bool allowComments)
CSV::CSV(const std::string& file, const bool isFile, const bool allowComments)
{
this->parse(file, isFile, allowComments);
}
CSV::~CSV()
std::size_t CSV::getRows() const
{
this->dataMap.clear();
return this->dataMap_.size();
}
int CSV::getRows()
std::size_t CSV::getColumns() const
{
return this->dataMap.size();
}
std::size_t count = 0;
int CSV::getColumns(size_t row)
{
if (this->dataMap.size() > row)
{
return this->dataMap[row].size();
}
return 0;
}
int CSV::getColumns()
{
int count = 0;
for (int i = 0; i < this->getRows(); ++i)
for (std::size_t i = 0; i < this->getRows(); ++i)
{
count = std::max(this->getColumns(i), count);
}
@ -39,11 +24,21 @@ namespace Utils
return count;
}
std::string CSV::getElementAt(size_t row, size_t column)
std::size_t CSV::getColumns(const std::size_t row) const
{
if (this->dataMap.size() > row)
if (this->dataMap_.size() > row)
{
auto _row = this->dataMap[row];
return this->dataMap_[row].size();
}
return 0;
}
std::string CSV::getElementAt(const std::size_t row, const std::size_t column) const
{
if (this->dataMap_.size() > row)
{
auto& _row = this->dataMap_[row];
if (_row.size() > column)
{
@ -51,18 +46,27 @@ namespace Utils
}
}
return "";
return {};
}
void CSV::parse(const std::string& file, bool isFile, bool allowComments)
bool CSV::isValid() const
{
return this->valid_;
}
void CSV::parse(const std::string& file, const bool isFile, const bool allowComments)
{
std::string buffer;
if (isFile)
{
if (!Utils::IO::FileExists(file)) return;
buffer = Utils::IO::ReadFile(file);
this->valid = true;
if (!IO::FileExists(file))
{
return;
}
buffer = IO::ReadFile(file);
this->valid_ = true;
}
else
{
@ -71,7 +75,7 @@ namespace Utils
if (!buffer.empty())
{
auto rows = Utils::String::Split(buffer, '\n');
const auto rows = String::Split(buffer, '\n');
for (auto& row : rows)
{
@ -80,14 +84,14 @@ namespace Utils
}
}
void CSV::parseRow(const std::string& row, bool allowComments)
void CSV::parseRow(const std::string& row, const bool allowComments)
{
bool isString = false;
std::string element;
std::vector<std::string> _row;
char tempStr = 0;
for (unsigned int i = 0; i < row.size(); ++i)
for (std::size_t i = 0; i < row.size(); ++i)
{
if (row[i] == ',' && !isString) // Flush entry
{
@ -95,25 +99,29 @@ namespace Utils
element.clear();
continue;
}
else if (row[i] == '"') // Start/Terminate string
if (row[i] == '"') // Start/Terminate string
{
isString = !isString;
continue;
}
else if (i < (row.size() - 1) && row[i] == '\\' &&row[i + 1] == '"' && isString) // Handle quotes in strings as \"
if (i < (row.size() - 1) && row[i] == '\\' &&row[i + 1] == '"' && isString) // Handle quotes in strings as \"
{
tempStr = '"';
++i;
}
else if (!isString && (row[i] == '\n' || row[i] == '\x0D' || row[i] == '\x0A' || row[i] == '\t'))
{
//++i;
continue;
}
else if (!isString && (row[i] == '#' || (row[i] == '/' && (i + 1) < row.size() && row[i + 1] == '/') ) && allowComments) // Skip comments. I know CSVs usually don't have comments, but in this case it's useful
{
return;
}
else
{
tempStr = row[i];
@ -125,11 +133,11 @@ namespace Utils
// Push last element
_row.push_back(element);
if (_row.size() == 0 || (_row.size() == 1 && !_row[0].size())) // Skip empty rows
if (_row.empty() || (_row.size() == 1 && _row[0].empty())) // Skip empty rows
{
return;
}
this->dataMap.push_back(_row);
this->dataMap_.push_back(_row);
}
}

View File

@ -5,21 +5,19 @@ namespace Utils
class CSV
{
public:
CSV() { }
CSV(const std::string& file, bool isFile = true, bool allowComments = true);
~CSV();
int getRows();
int getColumns();
int getColumns(size_t row);
[[nodiscard]] std::size_t getRows() const;
[[nodiscard]] std::size_t getColumns() const;
[[nodiscard]] std::size_t getColumns(std::size_t row) const;
std::string getElementAt(size_t row, size_t column);
[[nodiscard]] std::string getElementAt(std::size_t row, std::size_t column) const;
bool isValid() { return this->valid; }
[[nodiscard]] bool isValid() const;
private:
bool valid = false;
std::vector<std::vector<std::string>> dataMap;
bool valid_ = false;
std::vector<std::vector<std::string>> dataMap_;
void parse(const std::string& file, bool isFile = true, bool allowComments = true);
void parseRow(const std::string& row, bool allowComments = true);