mirror of
https://github.com/fedddddd/iw5-gsc-utils.git
synced 2026-08-01 04:40:34 +00:00
354 lines
8.4 KiB
C++
354 lines
8.4 KiB
C++
#include <stdinc.hpp>
|
|
#include "loader/component_loader.hpp"
|
|
#include "scheduler.hpp"
|
|
#include "scripting.hpp"
|
|
#include "command.hpp"
|
|
|
|
#include "game/scripting/event.hpp"
|
|
#include "game/scripting/execution.hpp"
|
|
#include "game/scripting/array.hpp"
|
|
#include "game/scripting/function.hpp"
|
|
|
|
#include "gsc.hpp"
|
|
|
|
namespace gsc
|
|
{
|
|
namespace
|
|
{
|
|
void return_value(const scripting::script_value& value)
|
|
{
|
|
if (game::scr_VmPub->outparamcount)
|
|
{
|
|
game::Scr_ClearOutParams();
|
|
}
|
|
|
|
scripting::push_value(value);
|
|
}
|
|
|
|
auto field_offset_start = 0xA000u;
|
|
|
|
std::unordered_map<unsigned int, entity_field_t> custom_fields[class_id_t::class_count];
|
|
|
|
utils::hook::detour scr_get_object_field_hook;
|
|
utils::hook::detour scr_set_object_field_hook;
|
|
utils::hook::detour scr_post_load_scripts_hook;
|
|
|
|
const char* get_script_loc()
|
|
{
|
|
return "unknown location";
|
|
}
|
|
|
|
void print_error(const char* fmt, ...)
|
|
{
|
|
static char buffer[0x1000];
|
|
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
_vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer), fmt, ap);
|
|
va_end(ap);
|
|
|
|
printf("\n"
|
|
"******* script runtime error *******\n"
|
|
"%s\n"
|
|
"\tat %s\n"
|
|
"************************************",
|
|
buffer, get_script_loc());
|
|
}
|
|
|
|
const gsc::entity_field_t* find_field(unsigned int classnum, unsigned int offset)
|
|
{
|
|
const auto& class_map = custom_fields[classnum];
|
|
const auto field_iter = class_map.find(offset);
|
|
if (field_iter == class_map.end())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
return &field_iter->second;
|
|
}
|
|
|
|
void scr_get_object_field_stub(unsigned int classnum, int entnum, unsigned int offset)
|
|
{
|
|
const auto field = find_field(classnum, offset);
|
|
if (field == nullptr)
|
|
{
|
|
return scr_get_object_field_hook.invoke<void>(classnum, entnum, offset);
|
|
}
|
|
|
|
try
|
|
{
|
|
const auto result = field->getter(entnum);
|
|
return_value(result);
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
print_error("while getting builtin field \"%s\"\n%s", field->name.data(), e.what());
|
|
}
|
|
}
|
|
|
|
void scr_set_object_field_stub(unsigned int classnum, int entnum, unsigned int offset)
|
|
{
|
|
const auto field = find_field(classnum, offset);
|
|
if (field == nullptr)
|
|
{
|
|
return scr_set_object_field_hook.invoke<void>(classnum, entnum, offset);
|
|
}
|
|
|
|
function_args args;
|
|
|
|
try
|
|
{
|
|
field->setter(entnum, args[0]);
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
print_error("while setting builtin field \"%s\"\n%s", field->name.data(), e.what());
|
|
}
|
|
}
|
|
|
|
void scr_post_load_scripts_stub()
|
|
{
|
|
for (auto i = 0; i < class_id_t::class_count; i++)
|
|
{
|
|
const auto& class_map = custom_fields[i];
|
|
for (const auto& [offset, field] : class_map)
|
|
{
|
|
const auto str_id = game::SL_GetString(field.name.data(), 0);
|
|
const auto canon_str = game::SL_GetCanonicalString(field.name.data());
|
|
game::Scr_AddClassField(i, str_id, canon_str, offset);
|
|
}
|
|
}
|
|
|
|
return scr_post_load_scripts_hook.invoke<void>();
|
|
}
|
|
}
|
|
|
|
void call_function(const script_function& function, const std::string& name)
|
|
{
|
|
try
|
|
{
|
|
function_args args;
|
|
const auto result = function.operator()(args);
|
|
const auto type = result.get_raw().type;
|
|
|
|
if (type)
|
|
{
|
|
return_value(result);
|
|
}
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
print_error("in call to builtin function \"%s\"\n%s", name.data(), e.what());
|
|
}
|
|
}
|
|
|
|
void call_method(const script_method& method, const std::string& name, game::scr_entref_t ent)
|
|
{
|
|
try
|
|
{
|
|
function_args args;
|
|
const auto result = method.operator()(ent, args);
|
|
const auto type = result.get_raw().type;
|
|
|
|
if (type)
|
|
{
|
|
return_value(result);
|
|
}
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
print_error("in call to builtin method \"%s\"\n%s", name.data(), e.what());
|
|
}
|
|
}
|
|
|
|
namespace field
|
|
{
|
|
void add(const class_id_t classnum, const std::string& name, const field_getter_t getter, const field_setter_t& setter)
|
|
{
|
|
const auto offset = field_offset_start++;
|
|
auto& class_map = custom_fields[classnum];
|
|
|
|
entity_field_t field{};
|
|
field.name = name;
|
|
field.getter = getter;
|
|
field.setter = setter;
|
|
class_map.insert(std::make_pair(offset, field));
|
|
}
|
|
}
|
|
|
|
function_args::function_args()
|
|
{
|
|
for (auto i = 0u; i < game::scr_VmPub->outparamcount; i++)
|
|
{
|
|
const auto value = game::scr_VmPub->top[-i];
|
|
this->values_.emplace_back(value);
|
|
}
|
|
}
|
|
|
|
function_args::function_args(const std::vector<scripting::script_value>& values)
|
|
: values_(values)
|
|
{
|
|
}
|
|
|
|
std::uint32_t function_args::size() const
|
|
{
|
|
return this->values_.size();
|
|
}
|
|
|
|
const std::vector<scripting::script_value>& function_args::get_raw() const
|
|
{
|
|
return this->values_;
|
|
}
|
|
|
|
scripting::value_wrap function_args::get(const std::uint32_t index) const
|
|
{
|
|
if (index >= this->values_.size())
|
|
{
|
|
throw std::runtime_error(utils::string::va("parameter %d does not exist", index));
|
|
}
|
|
|
|
return {this->values_[index], index};
|
|
}
|
|
|
|
class component final : public component_interface
|
|
{
|
|
public:
|
|
void on_startup([[maybe_unused]] plugin::plugin* plugin) override
|
|
{
|
|
scr_get_object_field_hook.create(0x52BDB0, scr_get_object_field_stub);
|
|
scr_set_object_field_hook.create(0x52BCC0, scr_set_object_field_stub);
|
|
scr_post_load_scripts_hook.create(0x628B50, scr_post_load_scripts_stub);
|
|
|
|
field::add(class_id_t::class_entity, "entityflags",
|
|
[](unsigned int entnum) -> scripting::script_value
|
|
{
|
|
const auto entity = &game::g_entities[entnum];
|
|
return entity->flags;
|
|
},
|
|
[](unsigned int entnum, const scripting::script_value& value)
|
|
{
|
|
const auto entity = &game::g_entities[entnum];
|
|
entity->flags = value.as<int>();
|
|
}
|
|
);
|
|
|
|
field::add(class_id_t::class_entity, "clientflags",
|
|
[](unsigned int entnum) -> scripting::script_value
|
|
{
|
|
const auto entity = &game::g_entities[entnum];
|
|
return entity->client->flags;
|
|
},
|
|
[](unsigned int entnum, const scripting::script_value& value)
|
|
{
|
|
const auto entity = &game::g_entities[entnum];
|
|
entity->client->flags = value.as<int>();
|
|
}
|
|
);
|
|
|
|
function::add("executecommand", [](const function_args& args)
|
|
-> scripting::script_value
|
|
{
|
|
game::Cbuf_AddText(0, args[0].as<const char*>());
|
|
return {};
|
|
});
|
|
|
|
function::add("addcommand", [](const function_args& args)
|
|
-> scripting::script_value
|
|
{
|
|
const auto name = args[0].as<std::string>();
|
|
const auto function = args[1].as<scripting::function>();
|
|
command::add_script_command(name, [function](const command::params& params)
|
|
{
|
|
scripting::array array;
|
|
for (auto i = 0; i < params.size(); i++)
|
|
{
|
|
array.push(params[i]);
|
|
}
|
|
|
|
function({array.get_raw()});
|
|
});
|
|
|
|
return {};
|
|
});
|
|
|
|
function::add("dropallbots", [](const function_args&)
|
|
-> scripting::script_value
|
|
{
|
|
for (auto i = 0; i < *game::svs_clientCount; i++)
|
|
{
|
|
if (game::svs_clients[i].header.state != game::CS_FREE
|
|
&& game::svs_clients[i].header.netchan.remoteAddress.type == game::NA_BOT)
|
|
{
|
|
game::SV_GameDropClient(i, "GAME_GET_TO_COVER");
|
|
}
|
|
}
|
|
|
|
return {};
|
|
});
|
|
|
|
method::add("specialtymarathon", [](const game::scr_entref_t ent, const function_args& args)
|
|
-> scripting::script_value
|
|
{
|
|
if (ent.classnum != 0)
|
|
{
|
|
throw std::runtime_error("invalid entity");
|
|
}
|
|
|
|
const auto client = ent.entnum;
|
|
|
|
if (game::g_entities[client].client == nullptr)
|
|
{
|
|
throw std::runtime_error("not a player entity");
|
|
}
|
|
|
|
const auto toggle = args[0].as<int>();
|
|
auto flags = game::g_entities[client].client->ps.perks[0];
|
|
|
|
game::g_entities[client].client->ps.perks[0] = toggle
|
|
? flags | 0x4000u
|
|
: flags & ~0x4000u;
|
|
|
|
return {};
|
|
});
|
|
|
|
method::add("isbot", [](const game::scr_entref_t ent, const function_args&)
|
|
-> scripting::script_value
|
|
{
|
|
if (ent.classnum != 0)
|
|
{
|
|
throw std::runtime_error("invalid entity");
|
|
}
|
|
|
|
const auto client = ent.entnum;
|
|
|
|
if (game::g_entities[client].client == nullptr)
|
|
{
|
|
throw std::runtime_error("not a player entity");
|
|
}
|
|
|
|
return game::svs_clients[client].bIsTestClient;
|
|
});
|
|
|
|
method::add("arecontrolsfrozen", [](const game::scr_entref_t ent, const function_args&)
|
|
-> scripting::script_value
|
|
{
|
|
if (ent.classnum != 0)
|
|
{
|
|
throw std::runtime_error("invalid entity");
|
|
}
|
|
|
|
const auto client = ent.entnum;
|
|
|
|
if (game::g_entities[client].client == nullptr)
|
|
{
|
|
throw std::runtime_error("not a player entity");
|
|
}
|
|
|
|
return (game::g_entities[client].client->flags & 4) != 0;
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
REGISTER_COMPONENT(gsc::component)
|