iw4x-client/src/Components/Modules/GSC/ScriptExtension.cpp

346 lines
10 KiB
C++
Raw Normal View History

2022-02-27 08:44:31 -05:00
#include <STDInclude.hpp>
#include "ScriptExtension.hpp"
#include "Script.hpp"
namespace Components
{
2022-04-28 08:03:22 -04:00
std::unordered_map<std::uint16_t, Game::ent_field_t> ScriptExtension::CustomEntityFields;
std::unordered_map<std::uint16_t, Game::client_fields_s> ScriptExtension::CustomClientFields;
void ScriptExtension::AddEntityField(const char* name, Game::fieldtype_t type,
const Game::ScriptCallbackEnt& setter, const Game::ScriptCallbackEnt& getter)
{
static std::uint16_t fieldOffsetStart = 15; // fields count
assert((fieldOffsetStart & Game::ENTFIELD_MASK) == Game::ENTFIELD_ENTITY);
CustomEntityFields[fieldOffsetStart] = {name, fieldOffsetStart, type, setter, getter};
2022-04-28 08:03:22 -04:00
++fieldOffsetStart;
}
void ScriptExtension::AddClientField(const char* name, Game::fieldtype_t type,
const Game::ScriptCallbackClient& setter, const Game::ScriptCallbackClient& getter)
{
static std::uint16_t fieldOffsetStart = 21; // fields count
assert((fieldOffsetStart & Game::ENTFIELD_MASK) == Game::ENTFIELD_ENTITY);
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
CustomClientFields[fieldOffsetStart] = {name, offset, type, setter, getter};
2022-04-28 08:03:22 -04:00
++fieldOffsetStart;
}
void ScriptExtension::GScr_AddFieldsForEntityStub()
{
for (const auto& [offset, field] : CustomEntityFields)
2022-04-28 08:03:22 -04:00
{
Game::Scr_AddClassField(Game::ClassNum::CLASS_NUM_ENTITY, field.name, field.ofs);
}
Utils::Hook::Call<void()>(0x4A7CF0)(); // GScr_AddFieldsForClient
for (const auto& [offset, field] : CustomClientFields)
2022-04-28 08:03:22 -04:00
{
Game::Scr_AddClassField(Game::ClassNum::CLASS_NUM_ENTITY, field.name, field.ofs);
}
}
// Because some functions are inlined we have to hook this function instead of Scr_SetEntityField
int ScriptExtension::Scr_SetObjectFieldStub(unsigned int classnum, int entnum, int offset)
{
if (classnum == Game::ClassNum::CLASS_NUM_ENTITY)
{
const auto entity_offset = static_cast<std::uint16_t>(offset);
2022-10-14 17:56:09 -04:00
if (const auto itr = CustomEntityFields.find(entity_offset); itr != CustomEntityFields.end())
2022-04-28 08:03:22 -04:00
{
2022-10-14 17:56:09 -04:00
itr->second.setter(&Game::g_entities[entnum], offset);
2022-04-28 08:03:22 -04:00
return 1;
}
}
// No custom generic field was found, let the game handle it
return Game::Scr_SetObjectField(classnum, entnum, offset);
}
2022-04-28 09:25:53 -04:00
// Offset was already converted to array 'index' following binop offset & ~Game::ENTFIELD_MASK
2022-04-28 08:03:22 -04:00
void ScriptExtension::Scr_SetClientFieldStub(Game::gclient_s* client, int offset)
{
const auto client_offset = static_cast<std::uint16_t>(offset);
2022-10-14 17:56:09 -04:00
if (const auto itr = CustomClientFields.find(client_offset); itr != CustomClientFields.end())
2022-04-28 08:03:22 -04:00
{
2022-10-14 17:56:09 -04:00
itr->second.setter(client, &itr->second);
2022-04-28 08:03:22 -04:00
return;
}
// No custom field client was found, let the game handle it
Game::Scr_SetClientField(client, offset);
}
void ScriptExtension::Scr_GetEntityFieldStub(int entnum, int offset)
{
if ((offset & Game::ENTFIELD_MASK) == Game::ENTFIELD_CLIENT)
{
// If we have a ENTFIELD_CLIENT offset we need to check g_entity is actually a fully connected client
if (Game::g_entities[entnum].client != nullptr)
{
2022-10-14 17:56:09 -04:00
const auto client_offset = static_cast<std::uint16_t>(offset & ~Game::ENTFIELD_MASK);
if (const auto itr = CustomClientFields.find(client_offset); itr != CustomClientFields.end())
2022-04-28 08:03:22 -04:00
{
// Game functions probably don't ever need to use the reference to client_fields_s...
2022-10-14 17:56:09 -04:00
itr->second.getter(Game::g_entities[entnum].client, &itr->second);
2022-04-28 08:03:22 -04:00
return;
}
}
}
// Regular entity offsets can be searched directly in our custom handler
const auto entity_offset = static_cast<std::uint16_t>(offset);
2022-10-14 17:56:09 -04:00
if (const auto itr = CustomEntityFields.find(entity_offset); itr != CustomEntityFields.end())
2022-04-28 08:03:22 -04:00
{
2022-10-14 17:56:09 -04:00
itr->second.getter(&Game::g_entities[entnum], offset);
2022-04-28 08:03:22 -04:00
return;
}
// No custom generic field was found, let the game handle it
Game::Scr_GetEntityField(entnum, offset);
}
2022-01-23 14:32:20 -05:00
void ScriptExtension::AddFunctions()
{
2022-04-09 12:27:18 -04:00
// Misc functions
2022-07-23 17:22:58 -04:00
Script::AddFunction("ToUpper", [] // gsc: ToUpper(<string>)
2022-04-09 12:27:18 -04:00
{
const auto scriptValue = Game::Scr_GetConstString(0);
const auto* string = Game::SL_ConvertToString(scriptValue);
char out[1024] = {0}; // 1024 is the max for a string in this SL system
bool changed = false;
size_t i = 0;
2022-04-09 12:27:18 -04:00
while (i < sizeof(out))
{
const auto value = *string;
2022-04-09 14:49:35 -04:00
const auto result = static_cast<char>(std::toupper(static_cast<unsigned char>(value)));
out[i] = result;
2022-04-09 12:27:18 -04:00
if (value != result)
changed = true;
if (result == '\0') // Finished converting string
break;
++string;
++i;
}
// Null terminating character was overwritten
if (i >= sizeof(out))
{
Game::Scr_Error("string too long");
return;
}
if (changed)
{
Game::Scr_AddString(out);
}
else
{
Game::SL_AddRefToString(scriptValue);
Game::Scr_AddConstString(scriptValue);
Game::SL_RemoveRefToString(scriptValue);
}
});
2022-04-10 08:00:55 -04:00
// Func present on IW5
2022-07-23 17:22:58 -04:00
Script::AddFunction("StrICmp", [] // gsc: StrICmp(<string>, <string>)
2022-04-10 08:00:55 -04:00
{
const auto value1 = Game::Scr_GetConstString(0);
const auto value2 = Game::Scr_GetConstString(1);
const auto result = _stricmp(Game::SL_ConvertToString(value1),
2022-04-10 08:00:55 -04:00
Game::SL_ConvertToString(value2));
Game::Scr_AddInt(result);
});
// Func present on IW5
2022-07-23 17:22:58 -04:00
Script::AddFunction("IsEndStr", [] // gsc: IsEndStr(<string>, <string>)
2022-04-10 08:00:55 -04:00
{
2022-08-08 08:37:24 -04:00
const auto* str = Game::Scr_GetString(0);
const auto* suffix = Game::Scr_GetString(1);
2022-04-10 08:00:55 -04:00
2022-08-08 08:37:24 -04:00
if (str == nullptr || suffix == nullptr)
2022-04-10 08:00:55 -04:00
{
Game::Scr_Error("^1IsEndStr: Illegal parameters!\n");
return;
}
2022-08-08 08:37:24 -04:00
Game::Scr_AddBool(Utils::String::EndsWith(str, suffix));
2022-04-10 08:00:55 -04:00
});
2022-05-04 07:44:45 -04:00
2022-07-23 17:22:58 -04:00
Script::AddFunction("IsArray", [] // gsc: IsArray(<object>)
2022-05-04 07:44:45 -04:00
{
2022-07-21 12:56:16 -04:00
auto type = Game::Scr_GetType(0);
2022-05-04 07:44:45 -04:00
bool result;
if (type == Game::VAR_POINTER)
2022-05-04 07:44:45 -04:00
{
2022-07-21 12:56:16 -04:00
type = Game::Scr_GetPointerType(0);
assert(type >= Game::FIRST_OBJECT);
result = (type == Game::VAR_ARRAY);
2022-05-04 07:44:45 -04:00
}
else
{
assert(type < Game::FIRST_OBJECT);
result = false;
}
Game::Scr_AddBool(result);
});
2022-08-08 08:37:24 -04:00
// Func present on IW5
Script::AddFunction("CastFloat", [] // gsc: CastFloat()
2022-08-08 08:37:24 -04:00
{
switch (Game::Scr_GetType(0))
{
case Game::VAR_STRING:
Game::Scr_AddFloat(static_cast<float>(std::atof(Game::Scr_GetString(0))));
break;
case Game::VAR_FLOAT:
Game::Scr_AddFloat(Game::Scr_GetFloat(0));
break;
case Game::VAR_INTEGER:
Game::Scr_AddFloat(static_cast<float>(Game::Scr_GetInt(0)));
break;
default:
Game::Scr_ParamError(0, Utils::String::VA("cannot cast %s to float", Game::Scr_GetTypeName(0)));
break;
}
});
Script::AddFunction("Strtol", [] // gsc: Strtol(<string>, <int>)
{
const auto* input = Game::Scr_GetString(0);
const auto base = Game::Scr_GetInt(1);
char* end;
const auto result = std::strtol(input, &end, base);
if (input == end)
{
Game::Scr_ParamError(0, "cannot cast string to int");
}
Game::Scr_AddInt(result);
});
}
2022-01-23 14:32:20 -05:00
void ScriptExtension::AddMethods()
{
2022-01-23 14:32:20 -05:00
// ScriptExtension methods
Script::AddMethod("GetIp", [](const Game::scr_entref_t entref) // gsc: self GetIp()
{
const auto* ent = Game::GetPlayerEntity(entref);
const auto* client = Script::GetClient(ent);
2022-08-20 06:09:41 -04:00
std::string ip = Game::NET_AdrToString(client->header.netchan.remoteAddress);
if (const auto pos = ip.find_first_of(":"); pos != std::string::npos)
2022-01-23 14:32:20 -05:00
ip.erase(ip.begin() + pos, ip.end()); // Erase port
2022-01-07 16:00:44 -05:00
Game::Scr_AddString(ip.data());
});
Script::AddMethod("GetPing", [](const Game::scr_entref_t entref) // gsc: self GetPing()
{
const auto* ent = Game::GetPlayerEntity(entref);
const auto* client = Script::GetClient(ent);
2022-01-07 16:00:44 -05:00
Game::Scr_AddInt(client->ping);
});
Script::AddMethod("SetPing", [](const 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);
2022-08-20 06:30:34 -04:00
client->ping = ping;
});
}
2022-03-21 14:55:35 -04:00
void ScriptExtension::Scr_TableLookupIStringByRow()
{
if (Game::Scr_GetNumParam() < 3)
{
Game::Scr_Error("USAGE: tableLookupIStringByRow( filename, rowNum, returnValueColumnNum )\n");
return;
}
const auto* fileName = Game::Scr_GetString(0);
const auto rowNum = Game::Scr_GetInt(1);
const auto returnValueColumnNum = Game::Scr_GetInt(2);
const auto* table = Game::DB_FindXAssetHeader(Game::ASSET_TYPE_STRINGTABLE, fileName).stringTable;
if (table == nullptr)
{
Game::Scr_ParamError(0, Utils::String::VA("%s does not exist\n", fileName));
return;
}
const auto* value = Game::StringTable_GetColumnValueForRow(table, rowNum, returnValueColumnNum);
Game::Scr_AddIString(value);
}
2022-04-28 08:03:22 -04:00
void ScriptExtension::AddEntityFields()
{
AddEntityField("entityflags", Game::F_INT,
2022-04-28 08:03:22 -04:00
[](Game::gentity_s* ent, [[maybe_unused]] int offset)
{
ent->flags = Game::Scr_GetInt(0);
},
[](Game::gentity_s* ent, [[maybe_unused]] int offset)
{
Game::Scr_AddInt(ent->flags);
});
}
void ScriptExtension::AddClientFields()
{
AddClientField("clientflags", Game::F_INT,
2022-04-28 08:03:22 -04:00
[](Game::gclient_s* pSelf, [[maybe_unused]] const Game::client_fields_s* pField)
{
pSelf->flags = Game::Scr_GetInt(0);
},
[](Game::gclient_s* pSelf, [[maybe_unused]] const Game::client_fields_s* pField)
{
Game::Scr_AddInt(pSelf->flags);
});
}
2022-01-23 14:32:20 -05:00
ScriptExtension::ScriptExtension()
{
AddFunctions();
AddMethods();
AddEntityFields();
AddClientFields();
2022-04-28 08:03:22 -04:00
2022-03-21 14:55:35 -04:00
// Correct builtin function pointer
Utils::Hook::Set<Game::BuiltinFunction>(0x79A90C, Scr_TableLookupIStringByRow);
2022-04-28 08:03:22 -04:00
Utils::Hook(0x4EC721, GScr_AddFieldsForEntityStub, HOOK_CALL).install()->quick(); // GScr_AddFieldsForEntity
2022-04-28 08:03:22 -04:00
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
2022-05-27 07:23:41 -04:00
// Fix format string in Scr_RandomFloatRange
Utils::Hook::Set<const char*>(0x5F10C6, "Scr_RandomFloatRange parms: %f %f ");
}
2022-01-07 16:00:44 -05:00
}