mirror of
https://github.com/XLabsProject/s1x-client.git
synced 2023-08-02 15:02:12 +02:00
Merge pull request #70 from XLabsProject/feature/scripting
Feature/scripting
This commit is contained in:
commit
c4c7d52d60
6
.gitmodules
vendored
6
.gitmodules
vendored
@ -35,3 +35,9 @@
|
||||
[submodule "deps/WinToast"]
|
||||
path = deps/WinToast
|
||||
url = https://github.com/mohabouje/WinToast.git
|
||||
[submodule "deps/sol2"]
|
||||
path = deps/sol2
|
||||
url = https://github.com/ThePhD/sol2.git
|
||||
[submodule "deps/lua"]
|
||||
path = deps/lua
|
||||
url = https://github.com/lua/lua.git
|
||||
|
1
deps/lua
vendored
Submodule
1
deps/lua
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit e7803f7dbcdc966ab1f9db143424ee811ab1a398
|
37
deps/premake/l_u_a.lua
vendored
Normal file
37
deps/premake/l_u_a.lua
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
-- Scripts or variables named lua fuck with premake ._.
|
||||
l_u_a = {
|
||||
source = path.join(dependencies.basePath, "lua"),
|
||||
}
|
||||
|
||||
function l_u_a.import()
|
||||
links { "lua" }
|
||||
l_u_a.includes()
|
||||
end
|
||||
|
||||
function l_u_a.includes()
|
||||
includedirs {
|
||||
l_u_a.source
|
||||
}
|
||||
|
||||
end
|
||||
|
||||
function l_u_a.project()
|
||||
project "lua"
|
||||
language "C"
|
||||
|
||||
l_u_a.includes()
|
||||
|
||||
files {
|
||||
path.join(l_u_a.source, "*.h"),
|
||||
path.join(l_u_a.source, "*.c"),
|
||||
}
|
||||
|
||||
removefiles {
|
||||
path.join(l_u_a.source, "onelua.c"),
|
||||
}
|
||||
|
||||
warnings "Off"
|
||||
kind "StaticLib"
|
||||
end
|
||||
|
||||
table.insert(dependencies, l_u_a)
|
20
deps/premake/sol2.lua
vendored
Normal file
20
deps/premake/sol2.lua
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
sol2 = {
|
||||
source = path.join(dependencies.basePath, "sol2"),
|
||||
}
|
||||
|
||||
function sol2.import()
|
||||
sol2.includes()
|
||||
l_u_a.import()
|
||||
end
|
||||
|
||||
function sol2.includes()
|
||||
includedirs {
|
||||
path.join(sol2.source, "include")
|
||||
}
|
||||
end
|
||||
|
||||
function sol2.project()
|
||||
|
||||
end
|
||||
|
||||
table.insert(dependencies, sol2)
|
1
deps/sol2
vendored
Submodule
1
deps/sol2
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit bc04471c11b32956e307371605710226b3957a44
|
222
src/client/component/logfile.cpp
Normal file
222
src/client/component/logfile.cpp
Normal file
@ -0,0 +1,222 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
#include "scheduler.hpp"
|
||||
|
||||
#include "game/scripting/entity.hpp"
|
||||
#include "game/scripting/execution.hpp"
|
||||
#include "game/scripting/lua/value_conversion.hpp"
|
||||
#include "game/scripting/lua/error.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
#include "logfile.hpp"
|
||||
|
||||
namespace logfile
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour scr_player_killed_hook;
|
||||
utils::hook::detour scr_player_damage_hook;
|
||||
|
||||
std::vector<sol::protected_function> player_killed_callbacks;
|
||||
std::vector<sol::protected_function> player_damage_callbacks;
|
||||
|
||||
sol::lua_value convert_entity(lua_State* state, const game::mp::gentity_s* ent)
|
||||
{
|
||||
if (!ent)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto player = scripting::call("getEntByNum", {ent->s.entityNum});
|
||||
|
||||
return scripting::lua::convert(state, player);
|
||||
}
|
||||
|
||||
std::string get_weapon_name(unsigned int weapon, bool isAlternate)
|
||||
{
|
||||
char output[1024] = { 0 };
|
||||
game::BG_GetWeaponNameComplete(weapon, isAlternate, output, 1024);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
sol::lua_value convert_vector(lua_State* state, const float* vec)
|
||||
{
|
||||
if (!vec)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto _vec = scripting::vector(vec);
|
||||
|
||||
return scripting::lua::convert(state, _vec);
|
||||
}
|
||||
|
||||
std::string convert_mod(const int meansOfDeath)
|
||||
{
|
||||
const auto value = reinterpret_cast<game::scr_string_t**>(0x1409B5360)[meansOfDeath];
|
||||
const auto string = game::SL_ConvertToString(*value);
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
void scr_player_killed_stub(game::mp::gentity_s* self, const game::mp::gentity_s* inflictor, game::mp::gentity_s* attacker, int damage,
|
||||
int meansOfDeath, const unsigned int weapon, bool isAlternate, const float* vDir, const unsigned int hitLoc, int psTimeOffset, int deathAnimDuration)
|
||||
{
|
||||
const std::string _hitLoc = reinterpret_cast<const char**>(0x1409B5400)[hitLoc];
|
||||
const auto _mod = convert_mod(meansOfDeath);
|
||||
|
||||
const auto _weapon = get_weapon_name(weapon, isAlternate);
|
||||
|
||||
for (const auto& callback : player_killed_callbacks)
|
||||
{
|
||||
const auto state = callback.lua_state();
|
||||
|
||||
const auto _self = convert_entity(state, self);
|
||||
const auto _inflictor = convert_entity(state, inflictor);
|
||||
const auto _attacker = convert_entity(state, attacker);
|
||||
|
||||
const auto _vDir = convert_vector(state, vDir);
|
||||
|
||||
const auto result = callback(_self, _inflictor, _attacker, damage, _mod, _weapon, _vDir, _hitLoc, psTimeOffset, deathAnimDuration);
|
||||
|
||||
scripting::lua::handle_error(result);
|
||||
|
||||
if (result.valid() && result.get_type() == sol::type::number)
|
||||
{
|
||||
damage = result.get<int>();
|
||||
}
|
||||
}
|
||||
|
||||
if (damage == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scr_player_killed_hook.invoke<void>(self, inflictor, attacker, damage, meansOfDeath, weapon, isAlternate, vDir, hitLoc, psTimeOffset, deathAnimDuration);
|
||||
}
|
||||
|
||||
void scr_player_damage_stub(game::mp::gentity_s* self, const game::mp::gentity_s* inflictor, game::mp::gentity_s* attacker, int damage, int dflags,
|
||||
int meansOfDeath, const unsigned int weapon, bool isAlternate, const float* vPoint, const float* vDir, const unsigned int hitLoc, int timeOffset)
|
||||
{
|
||||
const std::string _hitLoc = reinterpret_cast<const char**>(0x1409B5400)[hitLoc];
|
||||
const auto _mod = convert_mod(meansOfDeath);
|
||||
|
||||
const auto _weapon = get_weapon_name(weapon, isAlternate);
|
||||
|
||||
for (const auto& callback : player_damage_callbacks)
|
||||
{
|
||||
const auto state = callback.lua_state();
|
||||
|
||||
const auto _self = convert_entity(state, self);
|
||||
const auto _inflictor = convert_entity(state, inflictor);
|
||||
const auto _attacker = convert_entity(state, attacker);
|
||||
|
||||
const auto _vPoint = convert_vector(state, vPoint);
|
||||
const auto _vDir = convert_vector(state, vDir);
|
||||
|
||||
const auto result = callback(_self, _inflictor, _attacker, damage, dflags, _mod, _weapon, _vPoint, _vDir, _hitLoc);
|
||||
|
||||
scripting::lua::handle_error(result);
|
||||
|
||||
if (result.valid() && result.get_type() == sol::type::number)
|
||||
{
|
||||
damage = result.get<int>();
|
||||
}
|
||||
}
|
||||
|
||||
if (damage == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
scr_player_damage_hook.invoke<void>(self, inflictor, attacker, damage, dflags, meansOfDeath, weapon, isAlternate, vPoint, vDir, hitLoc, timeOffset);
|
||||
}
|
||||
|
||||
bool evaluate_say(char* text, game::mp::gentity_s* ent)
|
||||
{
|
||||
auto hidden = false;
|
||||
|
||||
++text;
|
||||
|
||||
if (text[0] == '/')
|
||||
{
|
||||
hidden = true;
|
||||
++text;
|
||||
}
|
||||
|
||||
const std::string message = text;
|
||||
const auto client = ent->s.entityNum;
|
||||
|
||||
scheduler::once([message, client]()
|
||||
{
|
||||
const scripting::entity level{*game::levelEntityId};
|
||||
const auto player = scripting::call("getEntByNum", {client}).as<scripting::entity>();
|
||||
|
||||
scripting::notify(level, "say", {player, message});
|
||||
scripting::notify(player, "say", {message});
|
||||
}, scheduler::pipeline::server);
|
||||
|
||||
return hidden;
|
||||
}
|
||||
}
|
||||
|
||||
void add_player_damage_callback(const sol::protected_function& callback)
|
||||
{
|
||||
player_damage_callbacks.push_back(callback);
|
||||
}
|
||||
|
||||
void add_player_killed_callback(const sol::protected_function& callback)
|
||||
{
|
||||
player_killed_callbacks.push_back(callback);
|
||||
}
|
||||
|
||||
void clear_callbacks()
|
||||
{
|
||||
player_damage_callbacks.clear();
|
||||
player_killed_callbacks.clear();
|
||||
}
|
||||
|
||||
const auto say_stub = utils::hook::assemble([](utils::hook::assembler& a)
|
||||
{
|
||||
const auto hidden = a.newLabel();
|
||||
|
||||
a.pushad64();
|
||||
a.mov(rcx, rbx);
|
||||
a.mov(rdx, rdi);
|
||||
|
||||
a.call_aligned(evaluate_say);
|
||||
|
||||
a.cmp(al, 0);
|
||||
a.jne(hidden);
|
||||
|
||||
a.popad64();
|
||||
a.lea(rcx, dword_ptr(rsp, 0x80));
|
||||
a.mov(r8d, 0x96);
|
||||
a.jmp(0x1402E99DA);
|
||||
|
||||
a.bind(hidden);
|
||||
a.popad64();
|
||||
a.jmp(0x1402E9A44);
|
||||
});
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
utils::hook::jump(0x1402E99CC, say_stub, true);
|
||||
|
||||
scr_player_damage_hook.create(0x140332150, scr_player_damage_stub);
|
||||
scr_player_killed_hook.create(0x1403323D0, scr_player_killed_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(logfile::component)
|
8
src/client/component/logfile.hpp
Normal file
8
src/client/component/logfile.hpp
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
namespace logfile
|
||||
{
|
||||
void add_player_damage_callback(const sol::protected_function& callback);
|
||||
void add_player_killed_callback(const sol::protected_function& callback);
|
||||
void clear_callbacks();
|
||||
}
|
89
src/client/component/scripting.cpp
Normal file
89
src/client/component/scripting.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
#include "game/scripting/entity.hpp"
|
||||
#include "game/scripting/event.hpp"
|
||||
#include "game/scripting/lua/engine.hpp"
|
||||
#include "game/scripting/execution.hpp"
|
||||
|
||||
#include "scheduler.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour vm_notify_hook;
|
||||
utils::hook::detour scr_load_level_hook;
|
||||
utils::hook::detour g_shutdown_game_hook;
|
||||
|
||||
void vm_notify_stub(const unsigned int notify_list_owner_id, const game::scr_string_t string_value,
|
||||
game::VariableValue* top)
|
||||
{
|
||||
if (!game::VirtualLobby_Loaded())
|
||||
{
|
||||
const auto* string = game::SL_ConvertToString(string_value);
|
||||
if (string)
|
||||
{
|
||||
event e;
|
||||
e.name = string;
|
||||
e.entity = notify_list_owner_id;
|
||||
|
||||
for (auto* value = top; value->type != game::SCRIPT_END; --value)
|
||||
{
|
||||
e.arguments.emplace_back(*value);
|
||||
}
|
||||
|
||||
if (e.name == "connected")
|
||||
{
|
||||
scripting::clear_entity_fields(e.entity);
|
||||
}
|
||||
|
||||
lua::engine::notify(e);
|
||||
}
|
||||
}
|
||||
|
||||
vm_notify_hook.invoke<void>(notify_list_owner_id, string_value, top);
|
||||
}
|
||||
|
||||
void scr_load_level_stub()
|
||||
{
|
||||
|
||||
scr_load_level_hook.invoke<void>();
|
||||
if (!game::VirtualLobby_Loaded())
|
||||
{
|
||||
lua::engine::start();
|
||||
}
|
||||
}
|
||||
|
||||
void g_shutdown_game_stub(const int free_scripts)
|
||||
{
|
||||
if (!game::VirtualLobby_Loaded())
|
||||
{
|
||||
lua::engine::stop();
|
||||
}
|
||||
return g_shutdown_game_hook.invoke<void>(free_scripts);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
vm_notify_hook.create(SELECT_VALUE(0x140320E50, 0x1403FD5B0), vm_notify_stub);
|
||||
// SP address is wrong, but should be ok
|
||||
scr_load_level_hook.create(SELECT_VALUE(0x140005260, 0x140325B90), scr_load_level_stub);
|
||||
g_shutdown_game_hook.create(SELECT_VALUE(0x140228BA0, 0x1402F8C10), g_shutdown_game_stub);
|
||||
|
||||
scheduler::loop([]()
|
||||
{
|
||||
lua::engine::run_frame();
|
||||
}, scheduler::pipeline::server);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(scripting::component)
|
@ -25,7 +25,7 @@ namespace game
|
||||
|
||||
bool VirtualLobby_Loaded()
|
||||
{
|
||||
return *mp::virtualLobby_loaded == 1;
|
||||
return !game::environment::is_sp() && *mp::virtualLobby_loaded == 1;
|
||||
}
|
||||
|
||||
namespace environment
|
||||
|
115
src/client/game/scripting/entity.cpp
Normal file
115
src/client/game/scripting/entity.cpp
Normal file
@ -0,0 +1,115 @@
|
||||
#include <std_include.hpp>
|
||||
#include "entity.hpp"
|
||||
#include "script_value.hpp"
|
||||
#include "execution.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
entity::entity()
|
||||
: entity(0)
|
||||
{
|
||||
}
|
||||
|
||||
entity::entity(const entity& other) : entity(other.entity_id_)
|
||||
{
|
||||
}
|
||||
|
||||
entity::entity(entity&& other) noexcept
|
||||
{
|
||||
this->entity_id_ = other.entity_id_;
|
||||
other.entity_id_ = 0;
|
||||
}
|
||||
|
||||
entity::entity(const unsigned int entity_id)
|
||||
: entity_id_(entity_id)
|
||||
{
|
||||
this->add();
|
||||
}
|
||||
|
||||
entity::~entity()
|
||||
{
|
||||
this->release();
|
||||
}
|
||||
|
||||
entity& entity::operator=(const entity& other)
|
||||
{
|
||||
if (&other != this)
|
||||
{
|
||||
this->release();
|
||||
this->entity_id_ = other.entity_id_;
|
||||
this->add();
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
entity& entity::operator=(entity&& other) noexcept
|
||||
{
|
||||
if (&other != this)
|
||||
{
|
||||
this->release();
|
||||
this->entity_id_ = other.entity_id_;
|
||||
other.entity_id_ = 0;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
unsigned int entity::get_entity_id() const
|
||||
{
|
||||
return this->entity_id_;
|
||||
}
|
||||
|
||||
game::scr_entref_t entity::get_entity_reference() const
|
||||
{
|
||||
if (!this->entity_id_)
|
||||
{
|
||||
const auto not_null = static_cast<uint16_t>(~0ui16);
|
||||
return game::scr_entref_t{not_null, not_null};
|
||||
}
|
||||
|
||||
return game::Scr_GetEntityIdRef(this->get_entity_id());
|
||||
}
|
||||
|
||||
bool entity::operator==(const entity& other) const noexcept
|
||||
{
|
||||
return this->get_entity_id() == other.get_entity_id();
|
||||
}
|
||||
|
||||
bool entity::operator!=(const entity& other) const noexcept
|
||||
{
|
||||
return !this->operator==(other);
|
||||
}
|
||||
|
||||
void entity::add() const
|
||||
{
|
||||
if (this->entity_id_)
|
||||
{
|
||||
game::AddRefToValue(game::SCRIPT_OBJECT, {static_cast<int>(this->entity_id_)});
|
||||
}
|
||||
}
|
||||
|
||||
void entity::release() const
|
||||
{
|
||||
if (this->entity_id_)
|
||||
{
|
||||
game::RemoveRefToValue(game::SCRIPT_OBJECT, {static_cast<int>(this->entity_id_)});
|
||||
}
|
||||
}
|
||||
|
||||
void entity::set(const std::string& field, const script_value& value) const
|
||||
{
|
||||
set_entity_field(*this, field, value);
|
||||
}
|
||||
|
||||
template <>
|
||||
script_value entity::get<script_value>(const std::string& field) const
|
||||
{
|
||||
return get_entity_field(*this, field);
|
||||
}
|
||||
|
||||
script_value entity::call(const std::string& name, const std::vector<script_value>& arguments) const
|
||||
{
|
||||
return call_function(name, *this, arguments);
|
||||
}
|
||||
}
|
49
src/client/game/scripting/entity.hpp
Normal file
49
src/client/game/scripting/entity.hpp
Normal file
@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
#include "script_value.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class entity final
|
||||
{
|
||||
public:
|
||||
entity();
|
||||
entity(unsigned int entity_id);
|
||||
|
||||
entity(const entity& other);
|
||||
entity(entity&& other) noexcept;
|
||||
|
||||
~entity();
|
||||
|
||||
entity& operator=(const entity& other);
|
||||
entity& operator=(entity&& other) noexcept;
|
||||
|
||||
void set(const std::string& field, const script_value& value) const;
|
||||
|
||||
template <typename T = script_value>
|
||||
T get(const std::string& field) const;
|
||||
|
||||
script_value call(const std::string& name, const std::vector<script_value>& arguments = {}) const;
|
||||
|
||||
unsigned int get_entity_id() const;
|
||||
game::scr_entref_t get_entity_reference() const;
|
||||
|
||||
bool operator ==(const entity& other) const noexcept;
|
||||
bool operator !=(const entity& other) const noexcept;
|
||||
|
||||
private:
|
||||
unsigned int entity_id_;
|
||||
|
||||
void add() const;
|
||||
void release() const;
|
||||
};
|
||||
|
||||
template <>
|
||||
script_value entity::get(const std::string& field) const;
|
||||
|
||||
template <typename T>
|
||||
T entity::get(const std::string& field) const
|
||||
{
|
||||
return this->get<script_value>(field).as<T>();
|
||||
}
|
||||
}
|
13
src/client/game/scripting/event.hpp
Normal file
13
src/client/game/scripting/event.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
#include "script_value.hpp"
|
||||
#include "entity.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
struct event
|
||||
{
|
||||
std::string name;
|
||||
entity entity{};
|
||||
std::vector<script_value> arguments;
|
||||
};
|
||||
}
|
209
src/client/game/scripting/execution.cpp
Normal file
209
src/client/game/scripting/execution.cpp
Normal file
@ -0,0 +1,209 @@
|
||||
#include <std_include.hpp>
|
||||
#include "execution.hpp"
|
||||
#include "safe_execution.hpp"
|
||||
#include "stack_isolation.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
namespace
|
||||
{
|
||||
game::VariableValue* allocate_argument()
|
||||
{
|
||||
game::VariableValue* value_ptr = ++game::scr_VmPub->top;
|
||||
++game::scr_VmPub->inparamcount;
|
||||
return value_ptr;
|
||||
}
|
||||
|
||||
void push_value(const script_value& value)
|
||||
{
|
||||
auto* value_ptr = allocate_argument();
|
||||
*value_ptr = value.get_raw();
|
||||
|
||||
game::AddRefToValue(value_ptr->type, value_ptr->u);
|
||||
}
|
||||
|
||||
int get_field_id(const int classnum, const std::string& field)
|
||||
{
|
||||
const auto class_id = game::g_classMap[classnum].id;
|
||||
const auto field_str = game::SL_GetString(field.data(), 0);
|
||||
const auto _ = gsl::finally([field_str]()
|
||||
{
|
||||
game::RemoveRefToValue(game::SCRIPT_STRING, {static_cast<int>(field_str)});
|
||||
});
|
||||
|
||||
const auto offset = game::FindVariable(class_id, field_str);
|
||||
if (offset)
|
||||
{
|
||||
const auto index = 3 * (offset + 0xFA00 * (class_id & 3));
|
||||
const auto id = reinterpret_cast<PINT64>(SELECT_VALUE(0x149BB5680, 0x14821DF80))[index];
|
||||
return static_cast<int>(id);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
script_value get_return_value()
|
||||
{
|
||||
if (game::scr_VmPub->inparamcount == 0)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
game::Scr_ClearOutParams();
|
||||
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
|
||||
game::scr_VmPub->inparamcount = 0;
|
||||
|
||||
return script_value(game::scr_VmPub->top[1 - game::scr_VmPub->outparamcount]);
|
||||
}
|
||||
}
|
||||
|
||||
void notify(const entity& entity, const std::string& event, const std::vector<script_value>& arguments)
|
||||
{
|
||||
stack_isolation _;
|
||||
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
|
||||
{
|
||||
push_value(*i);
|
||||
}
|
||||
|
||||
const auto event_id = game::SL_GetString(event.data(), 0);
|
||||
game::Scr_NotifyId(entity.get_entity_id(), event_id, game::scr_VmPub->inparamcount);
|
||||
}
|
||||
|
||||
script_value call_function(const std::string& name, const entity& entity,
|
||||
const std::vector<script_value>& arguments)
|
||||
{
|
||||
const auto entref = entity.get_entity_reference();
|
||||
|
||||
const auto is_method_call = *reinterpret_cast<const int*>(&entref) != -1;
|
||||
const auto function = find_function(name, !is_method_call);
|
||||
if (function == nullptr)
|
||||
{
|
||||
throw std::runtime_error("Unknown "s + (is_method_call ? "method" : "function") + " '" + name + "'");
|
||||
}
|
||||
|
||||
stack_isolation _;
|
||||
|
||||
for (auto i = arguments.rbegin(); i != arguments.rend(); ++i)
|
||||
{
|
||||
push_value(*i);
|
||||
}
|
||||
|
||||
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
|
||||
game::scr_VmPub->inparamcount = 0;
|
||||
|
||||
if (!safe_execution::call(function, entref))
|
||||
{
|
||||
throw std::runtime_error("Error executing "s + (is_method_call ? "method" : "function") +" '" + name + "'");
|
||||
}
|
||||
|
||||
return get_return_value();
|
||||
}
|
||||
|
||||
script_value call_function(const std::string& name, const std::vector<script_value>& arguments)
|
||||
{
|
||||
return call_function(name, entity(), arguments);
|
||||
}
|
||||
|
||||
template <>
|
||||
script_value call(const std::string& name, const std::vector<script_value>& arguments)
|
||||
{
|
||||
return call_function(name, arguments);
|
||||
}
|
||||
|
||||
static std::unordered_map<unsigned int, std::unordered_map<std::string, script_value>> custom_fields;
|
||||
|
||||
script_value get_custom_field(const entity& entity, const std::string& field)
|
||||
{
|
||||
auto fields = custom_fields[entity.get_entity_id()];
|
||||
const auto _field = fields.find(field);
|
||||
if (_field != fields.end())
|
||||
{
|
||||
return _field->second;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void set_custom_field(const entity& entity, const std::string& field, const script_value& value)
|
||||
{
|
||||
const auto id = entity.get_entity_id();
|
||||
|
||||
if (custom_fields[id].find(field) != custom_fields[id].end())
|
||||
{
|
||||
custom_fields[id][field] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
custom_fields[id].insert(std::make_pair(field, value));
|
||||
}
|
||||
|
||||
void clear_entity_fields(const entity& entity)
|
||||
{
|
||||
const auto id = entity.get_entity_id();
|
||||
|
||||
if (custom_fields.find(id) != custom_fields.end())
|
||||
{
|
||||
custom_fields[id].clear();
|
||||
}
|
||||
}
|
||||
|
||||
void clear_custom_fields()
|
||||
{
|
||||
custom_fields.clear();
|
||||
}
|
||||
|
||||
void set_entity_field(const entity& entity, const std::string& field, const script_value& value)
|
||||
{
|
||||
const auto entref = entity.get_entity_reference();
|
||||
const int id = get_field_id(entref.classnum, field);
|
||||
|
||||
if (id != -1)
|
||||
{
|
||||
stack_isolation _;
|
||||
push_value(value);
|
||||
|
||||
game::scr_VmPub->outparamcount = game::scr_VmPub->inparamcount;
|
||||
game::scr_VmPub->inparamcount = 0;
|
||||
|
||||
if (!safe_execution::set_entity_field(entref, id))
|
||||
{
|
||||
throw std::runtime_error("Failed to set value for field '" + field + "'");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Read custom fields
|
||||
set_custom_field(entity, field, value);
|
||||
}
|
||||
}
|
||||
|
||||
script_value get_entity_field(const entity& entity, const std::string& field)
|
||||
{
|
||||
const auto entref = entity.get_entity_reference();
|
||||
const auto id = get_field_id(entref.classnum, field);
|
||||
|
||||
if (id != -1)
|
||||
{
|
||||
stack_isolation _;
|
||||
|
||||
game::VariableValue value{};
|
||||
if (!safe_execution::get_entity_field(entref, id, &value))
|
||||
{
|
||||
throw std::runtime_error("Failed to get value for field '" + field + "'");
|
||||
}
|
||||
|
||||
const auto __ = gsl::finally([value]()
|
||||
{
|
||||
game::RemoveRefToValue(value.type, value.u);
|
||||
});
|
||||
|
||||
return value;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add custom fields
|
||||
return get_custom_field(entity, field);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
31
src/client/game/scripting/execution.hpp
Normal file
31
src/client/game/scripting/execution.hpp
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
#include "entity.hpp"
|
||||
#include "script_value.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
script_value call_function(const std::string& name, const std::vector<script_value>& arguments);
|
||||
script_value call_function(const std::string& name, const entity& entity,
|
||||
const std::vector<script_value>& arguments);
|
||||
|
||||
template <typename T = script_value>
|
||||
T call(const std::string& name, const std::vector<script_value>& arguments = {});
|
||||
|
||||
template <>
|
||||
script_value call(const std::string& name, const std::vector<script_value>& arguments);
|
||||
|
||||
template <typename T>
|
||||
T call(const std::string& name, const std::vector<script_value>& arguments)
|
||||
{
|
||||
return call<script_value>(name, arguments).as<T>();
|
||||
}
|
||||
|
||||
void clear_entity_fields(const entity& entity);
|
||||
void clear_custom_fields();
|
||||
|
||||
void set_entity_field(const entity& entity, const std::string& field, const script_value& value);
|
||||
script_value get_entity_field(const entity& entity, const std::string& field);
|
||||
|
||||
void notify(const entity& entity, const std::string& event, const std::vector<script_value>& arguments);
|
||||
}
|
1493
src/client/game/scripting/function_tables.cpp
Normal file
1493
src/client/game/scripting/function_tables.cpp
Normal file
File diff suppressed because it is too large
Load Diff
81
src/client/game/scripting/functions.cpp
Normal file
81
src/client/game/scripting/functions.cpp
Normal file
@ -0,0 +1,81 @@
|
||||
#include <std_include.hpp>
|
||||
#include "functions.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::unordered_map<std::string, unsigned> lowercase_map(
|
||||
const std::unordered_map<std::string, unsigned>& old_map)
|
||||
{
|
||||
std::unordered_map<std::string, unsigned> new_map{};
|
||||
for (auto& entry : old_map)
|
||||
{
|
||||
new_map[utils::string::to_lower(entry.first)] = entry.second;
|
||||
}
|
||||
|
||||
return new_map;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, unsigned>& get_methods()
|
||||
{
|
||||
static auto methods = lowercase_map(method_map);
|
||||
return methods;
|
||||
}
|
||||
|
||||
const std::unordered_map<std::string, unsigned>& get_functions()
|
||||
{
|
||||
static auto function = lowercase_map(function_map);
|
||||
return function;
|
||||
}
|
||||
|
||||
int find_function_index(const std::string& name, const bool prefer_global)
|
||||
{
|
||||
const auto target = utils::string::to_lower(name);
|
||||
|
||||
const auto& primary_map = prefer_global
|
||||
? get_functions()
|
||||
: get_methods();
|
||||
const auto& secondary_map = !prefer_global
|
||||
? get_functions()
|
||||
: get_methods();
|
||||
|
||||
auto function_entry = primary_map.find(target);
|
||||
if (function_entry != primary_map.end())
|
||||
{
|
||||
return function_entry->second;
|
||||
}
|
||||
|
||||
function_entry = secondary_map.find(target);
|
||||
if (function_entry != secondary_map.end())
|
||||
{
|
||||
return function_entry->second;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
script_function get_function_by_index(const unsigned index)
|
||||
{
|
||||
static const auto function_table = SELECT_VALUE(0x149668F50, 0x147DD1850);
|
||||
static const auto method_table = SELECT_VALUE(0x14966A670, 0x147DD2F50);
|
||||
|
||||
if (index < 0x2DF)
|
||||
{
|
||||
return reinterpret_cast<script_function*>(function_table)[index];
|
||||
}
|
||||
|
||||
return reinterpret_cast<script_function*>(method_table)[index - 0x8000];
|
||||
}
|
||||
}
|
||||
|
||||
script_function find_function(const std::string& name, const bool prefer_global)
|
||||
{
|
||||
const auto index = find_function_index(name, prefer_global);
|
||||
if (index < 0) return nullptr;
|
||||
|
||||
return get_function_by_index(index);
|
||||
}
|
||||
}
|
12
src/client/game/scripting/functions.hpp
Normal file
12
src/client/game/scripting/functions.hpp
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
extern std::unordered_map<std::string, unsigned> method_map;
|
||||
extern std::unordered_map<std::string, unsigned> function_map;
|
||||
|
||||
using script_function = void(*)(game::scr_entref_t);
|
||||
|
||||
script_function find_function(const std::string& name, const bool prefer_global);
|
||||
}
|
331
src/client/game/scripting/lua/context.cpp
Normal file
331
src/client/game/scripting/lua/context.cpp
Normal file
@ -0,0 +1,331 @@
|
||||
#include <std_include.hpp>
|
||||
#include "context.hpp"
|
||||
#include "error.hpp"
|
||||
#include "value_conversion.hpp"
|
||||
|
||||
#include "../execution.hpp"
|
||||
#include "../functions.hpp"
|
||||
|
||||
#include "../../../component/command.hpp"
|
||||
#include "../../../component/logfile.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace scripting::lua
|
||||
{
|
||||
namespace
|
||||
{
|
||||
std::vector<std::string> load_game_constants()
|
||||
{
|
||||
std::vector<std::string> result{};
|
||||
|
||||
const auto constants = game::GScr_LoadConsts.get();
|
||||
|
||||
ud_t ud;
|
||||
ud_init(&ud);
|
||||
ud_set_mode(&ud, 64);
|
||||
ud_set_pc(&ud, uint64_t(constants));
|
||||
ud_set_input_buffer(&ud, reinterpret_cast<const uint8_t*>(constants), INT32_MAX);
|
||||
|
||||
while (true)
|
||||
{
|
||||
ud_disassemble(&ud);
|
||||
|
||||
if (ud_insn_mnemonic(&ud) == UD_Iret)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (ud_insn_mnemonic(&ud) == UD_Imov)
|
||||
{
|
||||
const auto* operand = ud_insn_opr(&ud, 0);
|
||||
if (operand && operand->type == UD_OP_REG && operand->base == UD_R_ECX)
|
||||
{
|
||||
operand = ud_insn_opr(&ud, 1);
|
||||
if (operand && operand->type == UD_OP_IMM && (operand->base == UD_R_RAX || operand->base == UD_R_EAX))
|
||||
{
|
||||
result.emplace_back(reinterpret_cast<const char**>(0x1409C1CE0)[operand->lval.udword]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ud_insn_mnemonic(&ud) == UD_Ilea)
|
||||
{
|
||||
const auto* operand = ud_insn_opr(&ud, 0);
|
||||
if (!operand || operand->type != UD_OP_REG || operand->base != UD_R_RCX)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
operand = ud_insn_opr(&ud, 1);
|
||||
if (operand && operand->type == UD_OP_MEM && operand->base == UD_R_RIP)
|
||||
{
|
||||
auto* operand_ptr = reinterpret_cast<char*>(ud_insn_len(&ud) + ud_insn_off(&ud) + operand->lval.
|
||||
sdword);
|
||||
if (!utils::memory::is_bad_read_ptr(operand_ptr) && utils::memory::is_rdata_ptr(operand_ptr) &&
|
||||
strlen(operand_ptr) > 0)
|
||||
{
|
||||
result.emplace_back(operand_ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (*reinterpret_cast<unsigned char*>(ud.pc) == 0xCC) break; // int 3
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const std::vector<std::string>& get_game_constants()
|
||||
{
|
||||
static auto constants = load_game_constants();
|
||||
return constants;
|
||||
}
|
||||
|
||||
void setup_entity_type(sol::state& state, event_handler& handler, scheduler& scheduler)
|
||||
{
|
||||
state["level"] = entity{*game::levelEntityId};
|
||||
|
||||
auto vector_type = state.new_usertype<vector>("vector", sol::constructors<vector(float, float, float)>());
|
||||
vector_type["x"] = sol::property(&vector::get_x, &vector::set_x);
|
||||
vector_type["y"] = sol::property(&vector::get_y, &vector::set_y);
|
||||
vector_type["z"] = sol::property(&vector::get_z, &vector::set_z);
|
||||
|
||||
vector_type["r"] = sol::property(&vector::get_x, &vector::set_x);
|
||||
vector_type["g"] = sol::property(&vector::get_y, &vector::set_y);
|
||||
vector_type["b"] = sol::property(&vector::get_z, &vector::set_z);
|
||||
|
||||
auto entity_type = state.new_usertype<entity>("entity");
|
||||
|
||||
for (const auto& func : method_map)
|
||||
{
|
||||
const auto name = utils::string::to_lower(func.first);
|
||||
entity_type[name.data()] = [name](const entity& entity, const sol::this_state s, sol::variadic_args va)
|
||||
{
|
||||
std::vector<script_value> arguments{};
|
||||
|
||||
for (auto arg : va)
|
||||
{
|
||||
arguments.push_back(convert({s, arg}));
|
||||
}
|
||||
|
||||
return convert(s, entity.call(name, arguments));
|
||||
};
|
||||
}
|
||||
|
||||
for (const auto& constant : get_game_constants())
|
||||
{
|
||||
entity_type[constant] = sol::property(
|
||||
[constant](const entity& entity, const sol::this_state s)
|
||||
{
|
||||
return convert(s, entity.get(constant));
|
||||
},
|
||||
[constant](const entity& entity, const sol::this_state s, const sol::lua_value& value)
|
||||
{
|
||||
entity.set(constant, convert({s, value}));
|
||||
});
|
||||
}
|
||||
|
||||
entity_type["set"] = [](const entity& entity, const std::string& field,
|
||||
const sol::lua_value& value)
|
||||
{
|
||||
entity.set(field, convert(value));
|
||||
};
|
||||
|
||||
entity_type["get"] = [](const entity& entity, const sol::this_state s, const std::string& field)
|
||||
{
|
||||
return convert(s, entity.get(field));
|
||||
};
|
||||
|
||||
entity_type["notify"] = [](const entity& entity, const sol::this_state s, const std::string& event,
|
||||
sol::variadic_args va)
|
||||
{
|
||||
std::vector<script_value> arguments{};
|
||||
|
||||
for (auto arg : va)
|
||||
{
|
||||
arguments.push_back(convert({s, arg}));
|
||||
}
|
||||
|
||||
notify(entity, event, arguments);
|
||||
};
|
||||
|
||||
entity_type["onnotify"] = [&handler](const entity& entity, const std::string& event,
|
||||
const event_callback& callback)
|
||||
{
|
||||
event_listener listener{};
|
||||
listener.callback = callback;
|
||||
listener.entity = entity;
|
||||
listener.event = event;
|
||||
listener.is_volatile = false;
|
||||
|
||||
return handler.add_event_listener(std::move(listener));
|
||||
};
|
||||
|
||||
entity_type["onnotifyonce"] = [&handler](const entity& entity, const std::string& event,
|
||||
const event_callback& callback)
|
||||
{
|
||||
event_listener listener{};
|
||||
listener.callback = callback;
|
||||
listener.entity = entity;
|
||||
listener.event = event;
|
||||
listener.is_volatile = true;
|
||||
|
||||
return handler.add_event_listener(std::move(listener));
|
||||
};
|
||||
|
||||
entity_type["call"] = [](const entity& entity, const sol::this_state s, const std::string& function,
|
||||
sol::variadic_args va)
|
||||
{
|
||||
std::vector<script_value> arguments{};
|
||||
|
||||
for (auto arg : va)
|
||||
{
|
||||
arguments.push_back(convert({s, arg}));
|
||||
}
|
||||
|
||||
return convert(s, entity.call(function, arguments));
|
||||
};
|
||||
|
||||
entity_type[sol::meta_function::new_index] = [](const entity& entity, const std::string& field,
|
||||
const sol::lua_value& value)
|
||||
{
|
||||
entity.set(field, convert(value));
|
||||
};
|
||||
|
||||
entity_type[sol::meta_function::index] = [](const entity& entity, const sol::this_state s, const std::string& field)
|
||||
{
|
||||
return convert(s, entity.get(field));
|
||||
};
|
||||
|
||||
struct game
|
||||
{
|
||||
};
|
||||
auto game_type = state.new_usertype<game>("game_");
|
||||
state["game"] = game();
|
||||
|
||||
for (const auto& func : function_map)
|
||||
{
|
||||
const auto name = utils::string::to_lower(func.first);
|
||||
game_type[name] = [name](const game&, const sol::this_state s, sol::variadic_args va)
|
||||
{
|
||||
std::vector<script_value> arguments{};
|
||||
|
||||
for (auto arg : va)
|
||||
{
|
||||
arguments.push_back(convert({s, arg}));
|
||||
}
|
||||
|
||||
return convert(s, call(name, arguments));
|
||||
};
|
||||
}
|
||||
|
||||
game_type["call"] = [](const game&, const sol::this_state s, const std::string& function,
|
||||
sol::variadic_args va)
|
||||
{
|
||||
std::vector<script_value> arguments{};
|
||||
|
||||
for (auto arg : va)
|
||||
{
|
||||
arguments.push_back(convert({s, arg}));
|
||||
}
|
||||
|
||||
return convert(s, call(function, arguments));
|
||||
};
|
||||
|
||||
game_type["ontimeout"] = [&scheduler](const game&, const sol::protected_function& callback,
|
||||
const long long milliseconds)
|
||||
{
|
||||
return scheduler.add(callback, milliseconds, true);
|
||||
};
|
||||
|
||||
game_type["oninterval"] = [&scheduler](const game&, const sol::protected_function& callback,
|
||||
const long long milliseconds)
|
||||
{
|
||||
return scheduler.add(callback, milliseconds, false);
|
||||
};
|
||||
|
||||
game_type["executecommand"] = [](const game&, const std::string& command)
|
||||
{
|
||||
command::execute(command, false);
|
||||
};
|
||||
|
||||
game_type["onplayerdamage"] = [](const game&, const sol::protected_function& callback)
|
||||
{
|
||||
logfile::add_player_damage_callback(callback);
|
||||
};
|
||||
|
||||
game_type["onplayerkilled"] = [](const game&, const sol::protected_function& callback)
|
||||
{
|
||||
logfile::add_player_killed_callback(callback);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
context::context(std::string folder)
|
||||
: folder_(std::move(folder))
|
||||
, scheduler_(state_)
|
||||
, event_handler_(state_)
|
||||
|
||||
{
|
||||
this->state_.open_libraries(sol::lib::base,
|
||||
sol::lib::package,
|
||||
sol::lib::io,
|
||||
sol::lib::string,
|
||||
sol::lib::os,
|
||||
sol::lib::math,
|
||||
sol::lib::table);
|
||||
|
||||
this->state_["include"] = [this](const std::string& file)
|
||||
{
|
||||
this->load_script(file);
|
||||
};
|
||||
|
||||
sol::function old_require = this->state_["require"];
|
||||
auto base_path = utils::string::replace(this->folder_, "/", ".") + ".";
|
||||
this->state_["require"] = [base_path, old_require](const std::string& path)
|
||||
{
|
||||
return old_require(base_path + path);
|
||||
};
|
||||
|
||||
this->state_["scriptdir"] = [this]()
|
||||
{
|
||||
return this->folder_;
|
||||
};
|
||||
|
||||
setup_entity_type(this->state_, this->event_handler_, this->scheduler_);
|
||||
|
||||
printf("Loading script '%s'\n", this->folder_.data());
|
||||
this->load_script("__init__");
|
||||
}
|
||||
|
||||
context::~context()
|
||||
{
|
||||
this->state_.collect_garbage();
|
||||
this->scheduler_.clear();
|
||||
this->event_handler_.clear();
|
||||
this->state_ = {};
|
||||
}
|
||||
|
||||
void context::run_frame()
|
||||
{
|
||||
this->scheduler_.run_frame();
|
||||
this->state_.collect_garbage();
|
||||
}
|
||||
|
||||
void context::notify(const event& e)
|
||||
{
|
||||
this->event_handler_.dispatch(e);
|
||||
}
|
||||
|
||||
void context::load_script(const std::string& script)
|
||||
{
|
||||
if (!this->loaded_scripts_.emplace(script).second)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto file = (std::filesystem::path{this->folder_} / (script + ".lua")).generic_string();
|
||||
handle_error(this->state_.safe_script_file(file, &sol::script_pass_on_error));
|
||||
}
|
||||
}
|
39
src/client/game/scripting/lua/context.hpp
Normal file
39
src/client/game/scripting/lua/context.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "../event.hpp"
|
||||
|
||||
#define SOL_ALL_SAFETIES_ON 1
|
||||
#define SOL_PRINT_ERRORS 0
|
||||
#include <sol/sol.hpp>
|
||||
|
||||
#include "scheduler.hpp"
|
||||
#include "event_handler.hpp"
|
||||
|
||||
namespace scripting::lua
|
||||
{
|
||||
class context
|
||||
{
|
||||
public:
|
||||
context(std::string folder);
|
||||
~context();
|
||||
|
||||
context(context&&) noexcept = delete;
|
||||
context& operator=(context&&) noexcept = delete;
|
||||
|
||||
context(const context&) = delete;
|
||||
context& operator=(const context&) = delete;
|
||||
|
||||
void run_frame();
|
||||
void notify(const event& e);
|
||||
|
||||
private:
|
||||
sol::state state_{};
|
||||
std::string folder_;
|
||||
std::unordered_set<std::string> loaded_scripts_;
|
||||
|
||||
scheduler scheduler_;
|
||||
event_handler event_handler_;
|
||||
|
||||
void load_script(const std::string& script);
|
||||
};
|
||||
}
|
75
src/client/game/scripting/lua/engine.cpp
Normal file
75
src/client/game/scripting/lua/engine.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
#include <std_include.hpp>
|
||||
#include "engine.hpp"
|
||||
#include "context.hpp"
|
||||
|
||||
#include "../execution.hpp"
|
||||
#include "../../../component/logfile.hpp"
|
||||
|
||||
#include <utils/io.hpp>
|
||||
|
||||
namespace scripting::lua::engine
|
||||
{
|
||||
namespace
|
||||
{
|
||||
auto& get_scripts()
|
||||
{
|
||||
static std::vector<std::unique_ptr<context>> scripts{};
|
||||
return scripts;
|
||||
}
|
||||
|
||||
void load_scripts()
|
||||
{
|
||||
const auto script_dir = "s1x/scripts/"s;
|
||||
|
||||
if (!utils::io::directory_exists(script_dir))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto scripts = utils::io::list_files(script_dir);
|
||||
|
||||
for (const auto& script : scripts)
|
||||
{
|
||||
if (std::filesystem::is_directory(script) && utils::io::file_exists(script + "/__init__.lua"))
|
||||
{
|
||||
get_scripts().push_back(std::make_unique<context>(script));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void start()
|
||||
{
|
||||
// No SP until there is a concept
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
clear_custom_fields();
|
||||
get_scripts().clear();
|
||||
load_scripts();
|
||||
}
|
||||
|
||||
void stop()
|
||||
{
|
||||
logfile::clear_callbacks();
|
||||
get_scripts().clear();
|
||||
}
|
||||
|
||||
void notify(const event& e)
|
||||
{
|
||||
for (auto& script : get_scripts())
|
||||
{
|
||||
script->notify(e);
|
||||
}
|
||||
}
|
||||
|
||||
void run_frame()
|
||||
{
|
||||
for (auto& script : get_scripts())
|
||||
{
|
||||
script->run_frame();
|
||||
}
|
||||
}
|
||||
}
|
11
src/client/game/scripting/lua/engine.hpp
Normal file
11
src/client/game/scripting/lua/engine.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "../event.hpp"
|
||||
|
||||
namespace scripting::lua::engine
|
||||
{
|
||||
void start();
|
||||
void stop();
|
||||
void notify(const event& e);
|
||||
void run_frame();
|
||||
}
|
35
src/client/game/scripting/lua/error.cpp
Normal file
35
src/client/game/scripting/lua/error.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
#include <std_include.hpp>
|
||||
#include "error.hpp"
|
||||
#include "../execution.hpp"
|
||||
|
||||
namespace scripting::lua
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void notify_error()
|
||||
{
|
||||
try
|
||||
{
|
||||
call("iprintln", {"^1Script execution error!"});
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handle_error(const sol::protected_function_result& result)
|
||||
{
|
||||
if (!result.valid())
|
||||
{
|
||||
printf("************** Script execution error **************\n");
|
||||
|
||||
const sol::error err = result;
|
||||
printf("%s\n", err.what());
|
||||
|
||||
printf("****************************************************\n");
|
||||
|
||||
notify_error();
|
||||
}
|
||||
}
|
||||
}
|
8
src/client/game/scripting/lua/error.hpp
Normal file
8
src/client/game/scripting/lua/error.hpp
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace scripting::lua
|
||||
{
|
||||
void handle_error(const sol::protected_function_result& result);
|
||||
}
|
72
src/client/game/scripting/lua/event_handler.cpp
Normal file
72
src/client/game/scripting/lua/event_handler.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
#include "std_include.hpp"
|
||||
#include "context.hpp"
|
||||
#include "error.hpp"
|
||||
#include "value_conversion.hpp"
|
||||
|
||||
namespace scripting::lua
|
||||
{
|
||||
event_handler::event_handler(sol::state& state)
|
||||
: state_(state)
|
||||
{
|
||||
auto event_listener_handle_type = state.new_usertype<event_listener_handle>("event_listener_handle");
|
||||
|
||||
event_listener_handle_type["clear"] = [this](const event_listener_handle& handle)
|
||||
{
|
||||
this->remove(handle);
|
||||
};
|
||||
}
|
||||
|
||||
void event_handler::dispatch(const event& event)
|
||||
{
|
||||
std::vector<sol::lua_value> arguments;
|
||||
|
||||
for (const auto& argument : event.arguments)
|
||||
{
|
||||
arguments.emplace_back(convert(this->state_, argument));
|
||||
}
|
||||
|
||||
this->dispatch_to_specific_listeners(event, arguments);
|
||||
}
|
||||
|
||||
void event_handler::dispatch_to_specific_listeners(const event& event,
|
||||
const event_arguments& arguments)
|
||||
{
|
||||
for (auto listener : this->event_listeners_)
|
||||
{
|
||||
if (listener->event == event.name && listener->entity == event.entity)
|
||||
{
|
||||
if (listener->is_volatile)
|
||||
{
|
||||
this->event_listeners_.remove(listener);
|
||||
}
|
||||
|
||||
handle_error(listener->callback(sol::as_args(arguments)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event_listener_handle event_handler::add_event_listener(event_listener&& listener)
|
||||
{
|
||||
const uint64_t id = ++this->current_listener_id_;
|
||||
listener.id = id;
|
||||
this->event_listeners_.add(std::move(listener));
|
||||
return {id};
|
||||
}
|
||||
|
||||
void event_handler::clear()
|
||||
{
|
||||
this->event_listeners_.clear();
|
||||
}
|
||||
|
||||
void event_handler::remove(const event_listener_handle& handle)
|
||||
{
|
||||
for (const auto task : this->event_listeners_)
|
||||
{
|
||||
if (task->id == handle.id)
|
||||
{
|
||||
this->event_listeners_.remove(task);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
51
src/client/game/scripting/lua/event_handler.hpp
Normal file
51
src/client/game/scripting/lua/event_handler.hpp
Normal file
@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
#include <utils/concurrent_list.hpp>
|
||||
|
||||
namespace scripting::lua
|
||||
{
|
||||
using event_arguments = std::vector<sol::lua_value>;
|
||||
using event_callback = sol::protected_function;
|
||||
|
||||
class event_listener_handle
|
||||
{
|
||||
public:
|
||||
unsigned long long id = 0;
|
||||
};
|
||||
|
||||
class event_listener final : public event_listener_handle
|
||||
{
|
||||
public:
|
||||
std::string event = {};
|
||||
entity entity{};
|
||||
event_callback callback = {};
|
||||
bool is_volatile = false;
|
||||
};
|
||||
|
||||
class event_handler final
|
||||
{
|
||||
public:
|
||||
event_handler(sol::state& state);
|
||||
|
||||
event_handler(event_handler&&) noexcept = delete;
|
||||
event_handler& operator=(event_handler&&) noexcept = delete;
|
||||
|
||||
event_handler(const scheduler&) = delete;
|
||||
event_handler& operator=(const event_handler&) = delete;
|
||||
|
||||
void dispatch(const event& event);
|
||||
|
||||
event_listener_handle add_event_listener(event_listener&& listener);
|
||||
|
||||
void clear();
|
||||
|
||||
private:
|
||||
sol::state& state_;
|
||||
std::atomic_int64_t current_listener_id_ = 0;
|
||||
|
||||
utils::concurrent_list<event_listener> event_listeners_;
|
||||
|
||||
void dispatch_to_specific_listeners(const event& event, const event_arguments& arguments);
|
||||
|
||||
void remove(const event_listener_handle& handle);
|
||||
};
|
||||
}
|
72
src/client/game/scripting/lua/scheduler.cpp
Normal file
72
src/client/game/scripting/lua/scheduler.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
#include "std_include.hpp"
|
||||
#include "context.hpp"
|
||||
#include "error.hpp"
|
||||
|
||||
namespace scripting::lua
|
||||
{
|
||||
scheduler::scheduler(sol::state& state)
|
||||
{
|
||||
auto task_handle_type = state.new_usertype<task_handle>("task_handle");
|
||||
|
||||
task_handle_type["clear"] = [this](const task_handle& handle)
|
||||
{
|
||||
this->remove(handle);
|
||||
};
|
||||
}
|
||||
|
||||
void scheduler::run_frame()
|
||||
{
|
||||
for (auto task : this->tasks_)
|
||||
{
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if ((now - task->last_execution) > task->delay)
|
||||
{
|
||||
task->last_execution = now;
|
||||
if (task->is_volatile)
|
||||
{
|
||||
this->tasks_.remove(task);
|
||||
}
|
||||
|
||||
handle_error(task->callback());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void scheduler::clear()
|
||||
{
|
||||
this->tasks_.clear();
|
||||
}
|
||||
|
||||
task_handle scheduler::add(const sol::protected_function& callback, const long long milliseconds,
|
||||
const bool is_volatile)
|
||||
{
|
||||
return this->add(callback, std::chrono::milliseconds(milliseconds), is_volatile);
|
||||
}
|
||||
|
||||
task_handle scheduler::add(const sol::protected_function& callback, const std::chrono::milliseconds delay,
|
||||
const bool is_volatile)
|
||||
{
|
||||
task task;
|
||||
task.is_volatile = is_volatile;
|
||||
task.callback = callback;
|
||||
task.delay = delay;
|
||||
task.last_execution = std::chrono::steady_clock::now();
|
||||
task.id = ++this->current_task_id_;
|
||||
|
||||
this->tasks_.add(task);
|
||||
|
||||
return {task.id};
|
||||
}
|
||||
|
||||
void scheduler::remove(const task_handle& handle)
|
||||
{
|
||||
for (auto task : this->tasks_)
|
||||
{
|
||||
if (task->id == handle.id)
|
||||
{
|
||||
this->tasks_.remove(task);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
46
src/client/game/scripting/lua/scheduler.hpp
Normal file
46
src/client/game/scripting/lua/scheduler.hpp
Normal file
@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
#include <utils/concurrent_list.hpp>
|
||||
|
||||
namespace scripting::lua
|
||||
{
|
||||
class context;
|
||||
|
||||
class task_handle
|
||||
{
|
||||
public:
|
||||
unsigned long long id = 0;
|
||||
};
|
||||
|
||||
class task final : public task_handle
|
||||
{
|
||||
public:
|
||||
std::chrono::steady_clock::time_point last_execution{};
|
||||
sol::protected_function callback{};
|
||||
std::chrono::milliseconds delay{};
|
||||
bool is_volatile = false;
|
||||
};
|
||||
|
||||
class scheduler final
|
||||
{
|
||||
public:
|
||||
scheduler(sol::state& state);
|
||||
|
||||
scheduler(scheduler&&) noexcept = delete;
|
||||
scheduler& operator=(scheduler&&) noexcept = delete;
|
||||
|
||||
scheduler(const scheduler&) = delete;
|
||||
scheduler& operator=(const scheduler&) = delete;
|
||||
|
||||
void run_frame();
|
||||
void clear();
|
||||
|
||||
task_handle add(const sol::protected_function& callback, long long milliseconds, bool is_volatile);
|
||||
task_handle add(const sol::protected_function& callback, std::chrono::milliseconds delay, bool is_volatile);
|
||||
|
||||
private:
|
||||
utils::concurrent_list<task> tasks_;
|
||||
std::atomic_int64_t current_task_id_ = 0;
|
||||
|
||||
void remove(const task_handle& handle);
|
||||
};
|
||||
}
|
192
src/client/game/scripting/lua/value_conversion.cpp
Normal file
192
src/client/game/scripting/lua/value_conversion.cpp
Normal file
@ -0,0 +1,192 @@
|
||||
#include <std_include.hpp>
|
||||
#include "value_conversion.hpp"
|
||||
|
||||
namespace scripting::lua
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct array_value
|
||||
{
|
||||
int index;
|
||||
script_value value;
|
||||
};
|
||||
|
||||
sol::lua_value entity_to_array(lua_State* state, unsigned int id)
|
||||
{
|
||||
auto table = sol::table::create(state);
|
||||
auto metatable = sol::table::create(state);
|
||||
|
||||
std::unordered_map<std::string, array_value> values;
|
||||
|
||||
const auto offset = 64000 * (id & 3);
|
||||
|
||||
auto current = game::scr_VarGlob->objectVariableChildren[id].firstChild;
|
||||
auto idx = 1;
|
||||
|
||||
for (auto i = offset + current; current; i = offset + current)
|
||||
{
|
||||
const auto var = game::scr_VarGlob->childVariableValue[i];
|
||||
|
||||
if (var.type == game::SCRIPT_NONE)
|
||||
{
|
||||
current = var.nextSibling;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto string_value = (game::scr_string_t)((unsigned __int8)var.name_lo + (var.k.keys.name_hi << 8));
|
||||
const auto* str = game::SL_ConvertToString(string_value);
|
||||
|
||||
std::string key = string_value < 0x40000 && str
|
||||
? str
|
||||
: std::to_string(idx++);
|
||||
|
||||
game::VariableValue variable{};
|
||||
variable.type = var.type;
|
||||
variable.u = var.u.u;
|
||||
|
||||
array_value value;
|
||||
value.index = i;
|
||||
value.value = variable;
|
||||
|
||||
values[key] = value;
|
||||
|
||||
current = var.nextSibling;
|
||||
}
|
||||
|
||||
table["getkeys"] = [values]()
|
||||
{
|
||||
std::vector<std::string> _keys;
|
||||
|
||||
for (const auto& entry : values)
|
||||
{
|
||||
_keys.push_back(entry.first);
|
||||
}
|
||||
|
||||
return _keys;
|
||||
};
|
||||
|
||||
metatable[sol::meta_function::new_index] = [values](const sol::table t, const sol::this_state s,
|
||||
const sol::lua_value& key_value, const sol::lua_value& value)
|
||||
{
|
||||
const auto key = key_value.is<int>()
|
||||
? std::to_string(key_value.as<int>())
|
||||
: key_value.as<std::string>();
|
||||
|
||||
if (values.find(key) == values.end())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto variable = convert({s, value}).get_raw();
|
||||
const auto i = values.at(key).index;
|
||||
|
||||
game::scr_VarGlob->childVariableValue[i].type = (char)variable.type;
|
||||
game::scr_VarGlob->childVariableValue[i].u.u = variable.u;
|
||||
};
|
||||
|
||||
metatable[sol::meta_function::index] = [values](const sol::table t, const sol::this_state s,
|
||||
const sol::lua_value& key_value)
|
||||
{
|
||||
const auto key = key_value.is<int>()
|
||||
? std::to_string(key_value.as<int>())
|
||||
: key_value.as<std::string>();
|
||||
|
||||
if (values.find(key) == values.end())
|
||||
{
|
||||
return sol::lua_value{};
|
||||
}
|
||||
|
||||
return convert(s, values.at(key).value);
|
||||
};
|
||||
|
||||
metatable[sol::meta_function::length] = [values]()
|
||||
{
|
||||
return values.size();
|
||||
};
|
||||
|
||||
table[sol::metatable_key] = metatable;
|
||||
|
||||
return {state, table};
|
||||
}
|
||||
}
|
||||
|
||||
script_value convert(const sol::lua_value& value)
|
||||
{
|
||||
if (value.is<int>())
|
||||
{
|
||||
return {value.as<int>()};
|
||||
}
|
||||
|
||||
if (value.is<unsigned int>())
|
||||
{
|
||||
return {value.as<unsigned int>()};
|
||||
}
|
||||
|
||||
if (value.is<bool>())
|
||||
{
|
||||
return {value.as<bool>()};
|
||||
}
|
||||
|
||||
if (value.is<double>())
|
||||
{
|
||||
return {value.as<double>()};
|
||||
}
|
||||
|
||||
if (value.is<float>())
|
||||
{
|
||||
return {value.as<float>()};
|
||||
}
|
||||
|
||||
if (value.is<std::string>())
|
||||
{
|
||||
return {value.as<std::string>()};
|
||||
}
|
||||
|
||||
if (value.is<entity>())
|
||||
{
|
||||
return {value.as<entity>()};
|
||||
}
|
||||
|
||||
if (value.is<vector>())
|
||||
{
|
||||
return {value.as<vector>()};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
sol::lua_value convert(lua_State* state, const script_value& value)
|
||||
{
|
||||
if (value.is<int>())
|
||||
{
|
||||
return {state, value.as<int>()};
|
||||
}
|
||||
|
||||
if (value.is<float>())
|
||||
{
|
||||
return {state, value.as<float>()};
|
||||
}
|
||||
|
||||
if (value.is<std::string>())
|
||||
{
|
||||
return {state, value.as<std::string>()};
|
||||
}
|
||||
|
||||
if (value.is<std::vector<script_value>>())
|
||||
{
|
||||
return entity_to_array(state, value.get_raw().u.uintValue);
|
||||
}
|
||||
|
||||
if (value.is<entity>())
|
||||
{
|
||||
return {state, value.as<entity>()};
|
||||
}
|
||||
|
||||
if (value.is<vector>())
|
||||
{
|
||||
return {state, value.as<vector>()};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
9
src/client/game/scripting/lua/value_conversion.hpp
Normal file
9
src/client/game/scripting/lua/value_conversion.hpp
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace scripting::lua
|
||||
{
|
||||
script_value convert(const sol::lua_value& value);
|
||||
sol::lua_value convert(lua_State* state, const script_value& value);
|
||||
}
|
72
src/client/game/scripting/safe_execution.cpp
Normal file
72
src/client/game/scripting/safe_execution.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
#include <std_include.hpp>
|
||||
#include "safe_execution.hpp"
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4611)
|
||||
|
||||
namespace scripting::safe_execution
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool execute_with_seh(const script_function function, const game::scr_entref_t& entref)
|
||||
{
|
||||
__try
|
||||
{
|
||||
function(entref);
|
||||
return true;
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool call(const script_function function, const game::scr_entref_t& entref)
|
||||
{
|
||||
*game::g_script_error_level += 1;
|
||||
if (game::_setjmp(&game::g_script_error[*game::g_script_error_level]))
|
||||
{
|
||||
*game::g_script_error_level -= 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto result = execute_with_seh(function, entref);
|
||||
*game::g_script_error_level -= 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool set_entity_field(const game::scr_entref_t& entref, const int offset)
|
||||
{
|
||||
*game::g_script_error_level += 1;
|
||||
if (game::_setjmp(&game::g_script_error[*game::g_script_error_level]))
|
||||
{
|
||||
*game::g_script_error_level -= 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
game::Scr_SetObjectField(entref.classnum, entref.entnum, offset);
|
||||
|
||||
*game::g_script_error_level -= 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool get_entity_field(const game::scr_entref_t& entref, const int offset, game::VariableValue* value)
|
||||
{
|
||||
*game::g_script_error_level += 1;
|
||||
if (game::_setjmp(&game::g_script_error[*game::g_script_error_level]))
|
||||
{
|
||||
value->type = game::SCRIPT_NONE;
|
||||
value->u.intValue = 0;
|
||||
*game::g_script_error_level -= 1;
|
||||
return false;
|
||||
}
|
||||
|
||||
game::GetEntityFieldValue(value, entref.classnum, entref.entnum, offset);
|
||||
|
||||
*game::g_script_error_level -= 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning(pop)
|
10
src/client/game/scripting/safe_execution.hpp
Normal file
10
src/client/game/scripting/safe_execution.hpp
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "functions.hpp"
|
||||
|
||||
namespace scripting::safe_execution
|
||||
{
|
||||
bool call(script_function function, const game::scr_entref_t& entref);
|
||||
|
||||
bool set_entity_field(const game::scr_entref_t& entref, int offset);
|
||||
bool get_entity_field(const game::scr_entref_t& entref, int offset, game::VariableValue* value);
|
||||
}
|
250
src/client/game/scripting/script_value.cpp
Normal file
250
src/client/game/scripting/script_value.cpp
Normal file
@ -0,0 +1,250 @@
|
||||
#include <std_include.hpp>
|
||||
#include "script_value.hpp"
|
||||
#include "entity.hpp"
|
||||
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
/***************************************************************
|
||||
* Constructors
|
||||
**************************************************************/
|
||||
|
||||
script_value::script_value(const game::VariableValue& value)
|
||||
: value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
script_value::script_value(const int value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::SCRIPT_INTEGER;
|
||||
variable.u.intValue = value;
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
script_value::script_value(const unsigned int value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::SCRIPT_INTEGER;
|
||||
variable.u.uintValue = value;
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
script_value::script_value(const bool value)
|
||||
: script_value(static_cast<unsigned>(value))
|
||||
{
|
||||
}
|
||||
|
||||
script_value::script_value(const float value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::SCRIPT_FLOAT;
|
||||
variable.u.floatValue = value;
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
script_value::script_value(const double value)
|
||||
: script_value(static_cast<float>(value))
|
||||
{
|
||||
}
|
||||
|
||||
script_value::script_value(const char* value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::SCRIPT_STRING;
|
||||
variable.u.stringValue = game::SL_GetString(value, 0);
|
||||
|
||||
const auto _ = gsl::finally([&variable]()
|
||||
{
|
||||
game::RemoveRefToValue(variable.type, variable.u);
|
||||
});
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
script_value::script_value(const std::string& value)
|
||||
: script_value(value.data())
|
||||
{
|
||||
}
|
||||
|
||||
script_value::script_value(const entity& value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::SCRIPT_OBJECT;
|
||||
variable.u.pointerValue = value.get_entity_id();
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
script_value::script_value(const vector& value)
|
||||
{
|
||||
game::VariableValue variable{};
|
||||
variable.type = game::SCRIPT_VECTOR;
|
||||
variable.u.vectorValue = game::Scr_AllocVector(value);
|
||||
|
||||
const auto _ = gsl::finally([&variable]()
|
||||
{
|
||||
game::RemoveRefToValue(variable.type, variable.u);
|
||||
});
|
||||
|
||||
this->value_ = variable;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Integer
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<int>() const
|
||||
{
|
||||
return this->get_raw().type == game::SCRIPT_INTEGER;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::is<unsigned int>() const
|
||||
{
|
||||
return this->is<int>();
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::is<bool>() const
|
||||
{
|
||||
return this->is<int>();
|
||||
}
|
||||
|
||||
template <>
|
||||
int script_value::get() const
|
||||
{
|
||||
return this->get_raw().u.intValue;
|
||||
}
|
||||
|
||||
template <>
|
||||
unsigned int script_value::get() const
|
||||
{
|
||||
return this->get_raw().u.uintValue;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::get() const
|
||||
{
|
||||
return this->get_raw().u.uintValue != 0;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Float
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<float>() const
|
||||
{
|
||||
return this->get_raw().type == game::SCRIPT_FLOAT;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::is<double>() const
|
||||
{
|
||||
return this->is<float>();
|
||||
}
|
||||
|
||||
template <>
|
||||
float script_value::get() const
|
||||
{
|
||||
return this->get_raw().u.floatValue;
|
||||
}
|
||||
|
||||
template <>
|
||||
double script_value::get() const
|
||||
{
|
||||
return static_cast<double>(this->get_raw().u.floatValue);
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* String
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<const char*>() const
|
||||
{
|
||||
return this->get_raw().type == game::SCRIPT_STRING;
|
||||
}
|
||||
|
||||
template <>
|
||||
bool script_value::is<std::string>() const
|
||||
{
|
||||
return this->is<const char*>();
|
||||
}
|
||||
|
||||
template <>
|
||||
const char* script_value::get() const
|
||||
{
|
||||
return game::SL_ConvertToString(static_cast<game::scr_string_t>(this->get_raw().u.stringValue));
|
||||
}
|
||||
|
||||
template <>
|
||||
std::string script_value::get() const
|
||||
{
|
||||
return this->get<const char*>();
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Array
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<std::vector<script_value>>() const
|
||||
{
|
||||
if (this->get_raw().type != game::SCRIPT_OBJECT)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto id = this->get_raw().u.uintValue;
|
||||
const auto type = game::scr_VarGlob->objectVariableValue[id].w.type;
|
||||
|
||||
return type == game::SCRIPT_ARRAY;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Entity
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<entity>() const
|
||||
{
|
||||
return this->get_raw().type == game::SCRIPT_OBJECT;
|
||||
}
|
||||
|
||||
template <>
|
||||
entity script_value::get() const
|
||||
{
|
||||
return entity(this->get_raw().u.pointerValue);
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Vector
|
||||
**************************************************************/
|
||||
|
||||
template <>
|
||||
bool script_value::is<vector>() const
|
||||
{
|
||||
return this->get_raw().type == game::SCRIPT_VECTOR;
|
||||
}
|
||||
|
||||
template <>
|
||||
vector script_value::get() const
|
||||
{
|
||||
return this->get_raw().u.vectorValue;
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
*
|
||||
**************************************************************/
|
||||
|
||||
const game::VariableValue& script_value::get_raw() const
|
||||
{
|
||||
return this->value_.get();
|
||||
}
|
||||
}
|
52
src/client/game/scripting/script_value.hpp
Normal file
52
src/client/game/scripting/script_value.hpp
Normal file
@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
#include "variable_value.hpp"
|
||||
#include "vector.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class entity;
|
||||
|
||||
class script_value
|
||||
{
|
||||
public:
|
||||
script_value() = default;
|
||||
script_value(const game::VariableValue& value);
|
||||
|
||||
script_value(int value);
|
||||
script_value(unsigned int value);
|
||||
script_value(bool value);
|
||||
|
||||
script_value(float value);
|
||||
script_value(double value);
|
||||
|
||||
script_value(const char* value);
|
||||
script_value(const std::string& value);
|
||||
|
||||
script_value(const entity& value);
|
||||
|
||||
script_value(const vector& value);
|
||||
|
||||
template <typename T>
|
||||
bool is() const;
|
||||
|
||||
template <typename T>
|
||||
T as() const
|
||||
{
|
||||
if (!this->is<T>())
|
||||
{
|
||||
throw std::runtime_error("Invalid type");
|
||||
}
|
||||
|
||||
return get<T>();
|
||||
}
|
||||
|
||||
const game::VariableValue& get_raw() const;
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
T get() const;
|
||||
|
||||
variable_value value_{};
|
||||
};
|
||||
}
|
27
src/client/game/scripting/stack_isolation.cpp
Normal file
27
src/client/game/scripting/stack_isolation.cpp
Normal file
@ -0,0 +1,27 @@
|
||||
#include <std_include.hpp>
|
||||
#include "stack_isolation.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
stack_isolation::stack_isolation()
|
||||
{
|
||||
this->in_param_count_ = game::scr_VmPub->inparamcount;
|
||||
this->out_param_count_ = game::scr_VmPub->outparamcount;
|
||||
this->top_ = game::scr_VmPub->top;
|
||||
this->max_stack_ = game::scr_VmPub->maxstack;
|
||||
|
||||
game::scr_VmPub->top = this->stack_;
|
||||
game::scr_VmPub->maxstack = &this->stack_[ARRAYSIZE(this->stack_) - 1];
|
||||
game::scr_VmPub->inparamcount = 0;
|
||||
game::scr_VmPub->outparamcount = 0;
|
||||
}
|
||||
|
||||
stack_isolation::~stack_isolation()
|
||||
{
|
||||
game::Scr_ClearOutParams();
|
||||
game::scr_VmPub->inparamcount = this->in_param_count_;
|
||||
game::scr_VmPub->outparamcount = this->out_param_count_;
|
||||
game::scr_VmPub->top = this->top_;
|
||||
game::scr_VmPub->maxstack = this->max_stack_;
|
||||
}
|
||||
}
|
25
src/client/game/scripting/stack_isolation.hpp
Normal file
25
src/client/game/scripting/stack_isolation.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class stack_isolation final
|
||||
{
|
||||
public:
|
||||
stack_isolation();
|
||||
~stack_isolation();
|
||||
|
||||
stack_isolation(stack_isolation&&) = delete;
|
||||
stack_isolation(const stack_isolation&) = delete;
|
||||
stack_isolation& operator=(stack_isolation&&) = delete;
|
||||
stack_isolation& operator=(const stack_isolation&) = delete;
|
||||
|
||||
private:
|
||||
game::VariableValue stack_[512]{};
|
||||
|
||||
game::VariableValue* max_stack_;
|
||||
game::VariableValue* top_;
|
||||
unsigned int in_param_count_;
|
||||
unsigned int out_param_count_;
|
||||
};
|
||||
}
|
68
src/client/game/scripting/variable_value.cpp
Normal file
68
src/client/game/scripting/variable_value.cpp
Normal file
@ -0,0 +1,68 @@
|
||||
#include <std_include.hpp>
|
||||
#include "variable_value.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
variable_value::variable_value(const game::VariableValue& value)
|
||||
{
|
||||
this->assign(value);
|
||||
}
|
||||
|
||||
variable_value::variable_value(const variable_value& other) noexcept
|
||||
{
|
||||
this->operator=(other);
|
||||
}
|
||||
|
||||
variable_value::variable_value(variable_value&& other) noexcept
|
||||
{
|
||||
this->operator=(std::move(other));
|
||||
}
|
||||
|
||||
variable_value& variable_value::operator=(const variable_value& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
this->release();
|
||||
this->assign(other.value_);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
variable_value& variable_value::operator=(variable_value&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
this->release();
|
||||
this->value_ = other.value_;
|
||||
other.value_.type = game::SCRIPT_NONE;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
variable_value::~variable_value()
|
||||
{
|
||||
this->release();
|
||||
}
|
||||
|
||||
const game::VariableValue& variable_value::get() const
|
||||
{
|
||||
return this->value_;
|
||||
}
|
||||
|
||||
void variable_value::assign(const game::VariableValue& value)
|
||||
{
|
||||
this->value_ = value;
|
||||
game::AddRefToValue(this->value_.type, this->value_.u);
|
||||
}
|
||||
|
||||
void variable_value::release()
|
||||
{
|
||||
if (this->value_.type != game::SCRIPT_NONE)
|
||||
{
|
||||
game::RemoveRefToValue(this->value_.type, this->value_.u);
|
||||
this->value_.type = game::SCRIPT_NONE;
|
||||
}
|
||||
}
|
||||
}
|
27
src/client/game/scripting/variable_value.hpp
Normal file
27
src/client/game/scripting/variable_value.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class variable_value
|
||||
{
|
||||
public:
|
||||
variable_value() = default;
|
||||
variable_value(const game::VariableValue& value);
|
||||
variable_value(const variable_value& other) noexcept;
|
||||
variable_value(variable_value&& other) noexcept;
|
||||
|
||||
variable_value& operator=(const variable_value& other) noexcept;
|
||||
variable_value& operator=(variable_value&& other) noexcept;
|
||||
|
||||
~variable_value();
|
||||
|
||||
const game::VariableValue& get() const;
|
||||
|
||||
private:
|
||||
void assign(const game::VariableValue& value);
|
||||
void release();
|
||||
|
||||
game::VariableValue value_{{0}, game::SCRIPT_NONE};
|
||||
};
|
||||
}
|
85
src/client/game/scripting/vector.cpp
Normal file
85
src/client/game/scripting/vector.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
#include <std_include.hpp>
|
||||
#include "vector.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
vector::vector(const float* value)
|
||||
{
|
||||
for (auto i = 0; i < 3; ++i)
|
||||
{
|
||||
this->value_[i] = value[i];
|
||||
}
|
||||
}
|
||||
|
||||
vector::vector(const game::vec3_t& value)
|
||||
: vector(&value[0])
|
||||
{
|
||||
}
|
||||
|
||||
vector::vector(const float x, const float y, const float z)
|
||||
{
|
||||
this->value_[0] = x;
|
||||
this->value_[1] = y;
|
||||
this->value_[2] = z;
|
||||
}
|
||||
|
||||
vector::operator game::vec3_t&()
|
||||
{
|
||||
return this->value_;
|
||||
}
|
||||
|
||||
vector::operator const game::vec3_t&() const
|
||||
{
|
||||
return this->value_;
|
||||
}
|
||||
|
||||
game::vec_t& vector::operator[](const size_t i)
|
||||
{
|
||||
if (i >= 3)
|
||||
{
|
||||
throw std::runtime_error("Out of bounds.");
|
||||
}
|
||||
|
||||
return this->value_[i];
|
||||
}
|
||||
|
||||
const game::vec_t& vector::operator[](const size_t i) const
|
||||
{
|
||||
if (i >= 3)
|
||||
{
|
||||
throw std::runtime_error("Out of bounds.");
|
||||
}
|
||||
|
||||
return this->value_[i];
|
||||
}
|
||||
|
||||
float vector::get_x() const
|
||||
{
|
||||
return this->operator[](0);
|
||||
}
|
||||
|
||||
float vector::get_y() const
|
||||
{
|
||||
return this->operator[](1);
|
||||
}
|
||||
|
||||
float vector::get_z() const
|
||||
{
|
||||
return this->operator[](2);
|
||||
}
|
||||
|
||||
void vector::set_x(const float value)
|
||||
{
|
||||
this->operator[](0) = value;
|
||||
}
|
||||
|
||||
void vector::set_y(const float value)
|
||||
{
|
||||
this->operator[](1) = value;
|
||||
}
|
||||
|
||||
void vector::set_z(const float value)
|
||||
{
|
||||
this->operator[](2) = value;
|
||||
}
|
||||
}
|
31
src/client/game/scripting/vector.hpp
Normal file
31
src/client/game/scripting/vector.hpp
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include "game/game.hpp"
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
class vector final
|
||||
{
|
||||
public:
|
||||
vector() = default;
|
||||
vector(const float* value);
|
||||
vector(const game::vec3_t& value);
|
||||
vector(float x, float y, float z);
|
||||
|
||||
operator game::vec3_t&();
|
||||
operator const game::vec3_t&() const;
|
||||
|
||||
game::vec_t& operator[](size_t i);
|
||||
const game::vec_t& operator[](size_t i) const;
|
||||
|
||||
float get_x() const;
|
||||
float get_y() const;
|
||||
float get_z() const;
|
||||
|
||||
void set_x(float value);
|
||||
void set_y(float value);
|
||||
void set_z(float value);
|
||||
|
||||
private:
|
||||
game::vec3_t value_{0};
|
||||
};
|
||||
}
|
@ -9,11 +9,187 @@ namespace game
|
||||
typedef vec_t vec3_t[3];
|
||||
typedef vec_t vec4_t[4];
|
||||
|
||||
// * scripting
|
||||
enum scr_string_t
|
||||
{
|
||||
scr_string_t_dummy = 0x0,
|
||||
};
|
||||
|
||||
struct scr_entref_t
|
||||
{
|
||||
unsigned short entnum;
|
||||
unsigned short classnum;
|
||||
};
|
||||
|
||||
enum scriptType_e
|
||||
{
|
||||
SCRIPT_NONE = 0,
|
||||
SCRIPT_OBJECT = 1,
|
||||
SCRIPT_STRING = 2,
|
||||
SCRIPT_ISTRING = 3,
|
||||
SCRIPT_VECTOR = 4,
|
||||
SCRIPT_FLOAT = 5,
|
||||
SCRIPT_INTEGER = 6,
|
||||
SCRIPT_END = 8,
|
||||
SCRIPT_ARRAY = 22
|
||||
};
|
||||
|
||||
struct VariableStackBuffer
|
||||
{
|
||||
const char* pos;
|
||||
unsigned __int16 size;
|
||||
unsigned __int16 bufLen;
|
||||
unsigned __int16 localId;
|
||||
char time;
|
||||
char buf[1];
|
||||
};
|
||||
|
||||
union VariableUnion
|
||||
{
|
||||
int intValue;
|
||||
unsigned int uintValue;
|
||||
float floatValue;
|
||||
unsigned int stringValue;
|
||||
const float* vectorValue;
|
||||
const char* codePosValue;
|
||||
unsigned int pointerValue;
|
||||
VariableStackBuffer* stackValue;
|
||||
unsigned int entityOffset;
|
||||
};
|
||||
|
||||
struct VariableValue
|
||||
{
|
||||
VariableUnion u;
|
||||
int type;
|
||||
};
|
||||
|
||||
struct function_stack_t
|
||||
{
|
||||
const char* pos;
|
||||
unsigned int localId;
|
||||
unsigned int localVarCount;
|
||||
VariableValue* top;
|
||||
VariableValue* startTop;
|
||||
};
|
||||
|
||||
struct function_frame_t
|
||||
{
|
||||
function_stack_t fs;
|
||||
int topType;
|
||||
};
|
||||
|
||||
struct scrVmPub_t
|
||||
{
|
||||
unsigned int* localVars;
|
||||
VariableValue* maxstack;
|
||||
int function_count;
|
||||
function_frame_t* function_frame;
|
||||
VariableValue* top;
|
||||
unsigned int inparamcount;
|
||||
unsigned int outparamcount;
|
||||
function_frame_t function_frame_start[32];
|
||||
VariableValue stack[2048];
|
||||
};
|
||||
|
||||
struct scr_classStruct_t
|
||||
{
|
||||
unsigned __int16 id;
|
||||
unsigned __int16 entArrayId;
|
||||
char charId;
|
||||
const char* name;
|
||||
};
|
||||
|
||||
struct ObjectVariableChildren
|
||||
{
|
||||
unsigned __int16 firstChild;
|
||||
unsigned __int16 lastChild;
|
||||
};
|
||||
|
||||
struct ObjectVariableValue_u_f
|
||||
{
|
||||
unsigned __int16 prev;
|
||||
unsigned __int16 next;
|
||||
};
|
||||
|
||||
union ObjectVariableValue_u_o_u
|
||||
{
|
||||
unsigned __int16 size;
|
||||
unsigned __int16 entnum;
|
||||
unsigned __int16 nextEntId;
|
||||
unsigned __int16 self;
|
||||
};
|
||||
|
||||
struct ObjectVariableValue_u_o
|
||||
{
|
||||
unsigned __int16 refCount;
|
||||
ObjectVariableValue_u_o_u u;
|
||||
};
|
||||
|
||||
union ObjectVariableValue_w
|
||||
{
|
||||
unsigned int type;
|
||||
unsigned int classnum;
|
||||
unsigned int notifyName;
|
||||
unsigned int waitTime;
|
||||
unsigned int parentLocalId;
|
||||
};
|
||||
|
||||
struct ChildVariableValue_u_f
|
||||
{
|
||||
unsigned __int16 prev;
|
||||
unsigned __int16 next;
|
||||
};
|
||||
|
||||
union ChildVariableValue_u
|
||||
{
|
||||
ChildVariableValue_u_f f;
|
||||
VariableUnion u;
|
||||
};
|
||||
|
||||
struct ChildBucketMatchKeys_keys
|
||||
{
|
||||
unsigned __int16 name_hi;
|
||||
unsigned __int16 parentId;
|
||||
};
|
||||
|
||||
union ChildBucketMatchKeys
|
||||
{
|
||||
ChildBucketMatchKeys_keys keys;
|
||||
unsigned int match;
|
||||
};
|
||||
|
||||
struct ChildVariableValue
|
||||
{
|
||||
ChildVariableValue_u u;
|
||||
unsigned __int16 next;
|
||||
char type;
|
||||
char name_lo;
|
||||
ChildBucketMatchKeys k;
|
||||
unsigned __int16 nextSibling;
|
||||
unsigned __int16 prevSibling;
|
||||
};
|
||||
|
||||
union ObjectVariableValue_u
|
||||
{
|
||||
ObjectVariableValue_u_f f;
|
||||
ObjectVariableValue_u_o o;
|
||||
};
|
||||
|
||||
struct ObjectVariableValue
|
||||
{
|
||||
ObjectVariableValue_u u;
|
||||
ObjectVariableValue_w w;
|
||||
};
|
||||
|
||||
struct scrVarGlob_t
|
||||
{
|
||||
ObjectVariableValue objectVariableValue[40960];
|
||||
ObjectVariableChildren objectVariableChildren[40960];
|
||||
unsigned __int16 childVariableBucket[65536];
|
||||
ChildVariableValue childVariableValue[384000];
|
||||
};
|
||||
// *
|
||||
|
||||
enum Sys_Folder
|
||||
{
|
||||
SF_ZONE = 0x0,
|
||||
|
@ -8,8 +8,13 @@ namespace game
|
||||
* Functions
|
||||
**************************************************************/
|
||||
|
||||
WEAK symbol<void(int type, VariableUnion u)> AddRefToValue{0x140315830, 0x1403F1F20};
|
||||
WEAK symbol<void(int type, VariableUnion u)> RemoveRefToValue{0x140317340, 0x1403F3A50};
|
||||
|
||||
WEAK symbol<void(void*, void*)> AimAssist_AddToTargetList{0, 0x140001730};
|
||||
|
||||
WEAK symbol<void(unsigned int weapon, bool isAlternate, char* output, unsigned int maxStringLen)> BG_GetWeaponNameComplete{0x0, 0x140165580};
|
||||
|
||||
WEAK symbol<void(errorParm code, const char* message, ...)> Com_Error{0x1402F7570, 0x1403CE480};
|
||||
WEAK symbol<void()> Com_Frame_Try_Block_Function{0x1402F7E10, 0x1403CEF30};
|
||||
WEAK symbol<CodPlayMode()> Com_GetCurrentCoDPlayMode{0, 0x1404C9690};
|
||||
@ -43,9 +48,9 @@ namespace game
|
||||
WEAK symbol<void(XAssetType type, void (__cdecl* func)(XAssetHeader, void*), void* inData, bool includeOverride)>
|
||||
DB_EnumXAssets_FastFile{0x14017D7C0, 0x14026EC10};
|
||||
WEAK symbol<void(XAssetType type, void(__cdecl* func)(game::XAssetHeader, void*), const void* inData, bool includeOverride)>
|
||||
DB_EnumXAssets_Internal{ 0x14017D830, 0x14026EC80 };
|
||||
DB_EnumXAssets_Internal{0x14017D830, 0x14026EC80};
|
||||
WEAK symbol<game::XAssetEntry(game::XAssetType type, const char* name)>
|
||||
DB_FindXAssetEntry{ 0x14017D830, 0x14026F020 };
|
||||
DB_FindXAssetEntry{0x14017D830, 0x14026F020};
|
||||
WEAK symbol<const char* (const XAsset* asset)> DB_GetXAssetName{0x140151C00, 0x140240DD0};
|
||||
WEAK symbol<int(XAssetType type)> DB_GetXAssetTypeSize{0x140151C20, 0x140240DF0};
|
||||
WEAK symbol<void(XZoneInfo* zoneInfo, unsigned int zoneCount, DBSyncMode syncMode)> DB_LoadXAssets{
|
||||
@ -82,6 +87,14 @@ namespace game
|
||||
WEAK symbol<long long(const char* qpath, char** buffer)> FS_ReadFile{0x140362390, 0x1404AF380};
|
||||
WEAK symbol<void(void* buffer)> FS_FreeFile{0x140362380, 0x1404AF370};
|
||||
|
||||
WEAK symbol<void()> GScr_LoadConsts{0x140283970, 0x1403479C0};
|
||||
WEAK symbol<unsigned int(unsigned int parentId, unsigned int name)> FindVariable{0x1403166D0, 0x1403F2DC0};
|
||||
WEAK symbol<scr_string_t(unsigned int parentId, unsigned int id)> GetVariableName{0x1403170E0, 0x1403F37F0};
|
||||
WEAK symbol<void(VariableValue* result, unsigned int classnum, int entnum, int offset)> GetEntityFieldValue{
|
||||
0x14031AAD0, 0x1403F72A0
|
||||
};
|
||||
WEAK symbol<unsigned int(unsigned int)> GetObjectType{0x140316F70, 0x1403F3670};
|
||||
|
||||
WEAK symbol<void()> G_Glass_Update{0x14021D540, 0x1402EDEE0};
|
||||
|
||||
WEAK symbol<unsigned int(const char* name)> G_GetWeaponForName{0x140274590, 0x14033FF60};
|
||||
@ -133,8 +146,15 @@ namespace game
|
||||
WEAK symbol<float(int index)> Scr_GetFloat{0x14031C090, 0x1403F8820};
|
||||
WEAK symbol<int()> Scr_GetNumParam{0x14031C2A0, 0x1403F8980};
|
||||
WEAK symbol<void()> Scr_ClearOutParams{0x14031B7C0, 0x1403F8040};
|
||||
WEAK symbol<scr_entref_t(unsigned int entId)> Scr_GetEntityIdRef{0x14031A0D0, 0x1403F68A0};
|
||||
WEAK symbol<int(unsigned int classnum, int entnum, int offset)> Scr_SetObjectField{0x14026B620, 0x140339450};
|
||||
WEAK symbol<void(unsigned int id, scr_string_t stringValue, unsigned int paramcount)> Scr_NotifyId{
|
||||
0x14031CB80, 0x1403F92D0
|
||||
};
|
||||
|
||||
WEAK symbol<const char*(scr_string_t stringValue)> SL_ConvertToString{0x140314850, 0x1403F0F10};
|
||||
WEAK symbol<scr_string_t(const char* str)> SL_FindString{0x140314AF0, 0x1403F11C0};
|
||||
WEAK symbol<scr_string_t(const char* str, unsigned int user)> SL_GetString{0x140314D90, 0x1403F1440};
|
||||
|
||||
WEAK symbol<void(const char* text_in)> SV_Cmd_TokenizeString{0, 0x1403B0640};
|
||||
WEAK symbol<void()> SV_Cmd_EndTokenizedString{0, 0x1403B0600};
|
||||
@ -182,6 +202,9 @@ namespace game
|
||||
WEAK symbol<void(unsigned int localClientNum, const char** args)> UI_RunMenuScript{0, 0x140490060};
|
||||
WEAK symbol<int(const char* text, int maxChars, Font_s* font, float scale)> UI_TextWidth{0, 0x140492380};
|
||||
|
||||
WEAK symbol<void*(jmp_buf* Buf, int Value)> longjmp{0x14059C5C0, 0x1406FD930};
|
||||
WEAK symbol<int(jmp_buf* Buf)> _setjmp{0x14059CD00, 0x1406FE070};
|
||||
|
||||
/***************************************************************
|
||||
* Variables
|
||||
**************************************************************/
|
||||
@ -196,6 +219,14 @@ namespace game
|
||||
WEAK symbol<int> dvarCount{0x14A7BFF34, 0x14B32AA30};
|
||||
WEAK symbol<dvar_t*> sortedDvars{0x14A7BFF50, 0x14B32AA50};
|
||||
|
||||
WEAK symbol<unsigned int> levelEntityId{0x149AF55B0, 0x14815DEB0};
|
||||
WEAK symbol<int> g_script_error_level{0x14A1917A8, 0x1487F9FA4};
|
||||
WEAK symbol<jmp_buf> g_script_error{0x14A1917B0, 0x1487FA0C0};
|
||||
WEAK symbol<scr_classStruct_t> g_classMap{0x14080A840, 0x1409BE1B0};
|
||||
|
||||
WEAK symbol<scrVarGlob_t> scr_VarGlob{0x149B1D680, 0x148185F80};
|
||||
WEAK symbol<scrVmPub_t> scr_VmPub{0x14A1938C0, 0x1487FC1C0};
|
||||
|
||||
WEAK symbol<const char*> command_whitelist{0x140808EF0, 0x1409B8DC0};
|
||||
|
||||
WEAK symbol<SOCKET> query_socket{0, 0x14B5B9180};
|
||||
|
Loading…
Reference in New Issue
Block a user