NorthstarLauncher/primedev/scripts/scriptdatatables.cpp

875 lines
27 KiB
C++

#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;
// 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>->template 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 ((*g_pFilesystem)->m_vtable2->FileExists(&(*g_pFilesystem)->m_vtable2, sDiskAssetPath.c_str(), "GAME"))
{
std::string sTableCSV = 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>->template 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, (SQInteger)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 = (Vector3*)(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", 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)
{
NOTE_UNUSED(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).RCast<Datatable* (*)(HSquirrelVM*)>();
}
ON_DLL_LOAD_RELIESON("client.dll", ClientScriptDatatables, ClientSquirrel, (CModule module))
{
SQ_GetDatatableInternal<ScriptContext::CLIENT> = module.Offset(0x1C9070).RCast<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() && 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);
}