Merge branch 'develop' into rich-presence
This commit is contained in:
commit
f8f61d6634
3
data/ui_scripts/mods/__init__.lua
Normal file
3
data/ui_scripts/mods/__init__.lua
Normal file
@ -0,0 +1,3 @@
|
||||
if (game:issingleplayer()) then
|
||||
require("loading")
|
||||
end
|
116
data/ui_scripts/mods/loading.lua
Normal file
116
data/ui_scripts/mods/loading.lua
Normal file
@ -0,0 +1,116 @@
|
||||
game:addlocalizedstring("MENU_MODS", "MODS")
|
||||
game:addlocalizedstring("MENU_MODS_DESC", "Load installed mods.")
|
||||
game:addlocalizedstring("LUA_MENU_MOD_DESC_DEFAULT", "Load &&1.")
|
||||
game:addlocalizedstring("LUA_MENU_MOD_DESC", "&&1\nAuthor: &&2\nVersion: &&3")
|
||||
game:addlocalizedstring("LUA_MENU_OPEN_STORE", "Open store")
|
||||
game:addlocalizedstring("LUA_MENU_OPEN_STORE_DESC", "Download and install mods.")
|
||||
game:addlocalizedstring("LUA_MENU_LOADED_MOD", "Loaded mod: ^2&&1")
|
||||
game:addlocalizedstring("LUA_MENU_AVAILABLE_MODS", "Available mods")
|
||||
game:addlocalizedstring("LUA_MENU_UNLOAD", "Unload")
|
||||
game:addlocalizedstring("LUA_MENU_UNLOAD_DESC", "Unload the currently loaded mod.")
|
||||
|
||||
function createdivider(menu, text)
|
||||
local element = LUI.UIElement.new( {
|
||||
leftAnchor = true,
|
||||
rightAnchor = true,
|
||||
left = 0,
|
||||
right = 0,
|
||||
topAnchor = true,
|
||||
bottomAnchor = false,
|
||||
top = 0,
|
||||
bottom = 33.33
|
||||
})
|
||||
|
||||
element.scrollingToNext = true
|
||||
element:addElement(LUI.MenuBuilder.BuildRegisteredType("h1_option_menu_titlebar", {
|
||||
title_bar_text = Engine.ToUpperCase(text)
|
||||
}))
|
||||
|
||||
menu.list:addElement(element)
|
||||
end
|
||||
|
||||
function string:truncate(length)
|
||||
if (#self <= length) then
|
||||
return self
|
||||
end
|
||||
|
||||
return self:sub(1, length - 3) .. "..."
|
||||
end
|
||||
|
||||
LUI.addmenubutton("main_campaign", {
|
||||
index = 6,
|
||||
text = "@MENU_MODS",
|
||||
description = Engine.Localize("@MENU_MODS_DESC"),
|
||||
callback = function()
|
||||
LUI.FlowManager.RequestAddMenu(nil, "mods_menu")
|
||||
end
|
||||
})
|
||||
|
||||
function getmodname(path)
|
||||
local name = path
|
||||
local desc = Engine.Localize("@LUA_MENU_MOD_DESC_DEFAULT", name)
|
||||
local infofile = path .. "/info.json"
|
||||
|
||||
if (io.fileexists(infofile)) then
|
||||
pcall(function()
|
||||
local data = json.decode(io.readfile(infofile))
|
||||
desc = Engine.Localize("@LUA_MENU_MOD_DESC",
|
||||
data.description, data.author, data.version)
|
||||
name = data.name
|
||||
end)
|
||||
end
|
||||
|
||||
return name, desc
|
||||
end
|
||||
|
||||
LUI.MenuBuilder.m_types_build["mods_menu"] = function(a1)
|
||||
local menu = LUI.MenuTemplate.new(a1, {
|
||||
menu_title = "@MENU_MODS",
|
||||
exclusiveController = 0,
|
||||
menu_width = 400,
|
||||
menu_top_indent = LUI.MenuTemplate.spMenuOffset,
|
||||
showTopRightSmallBar = true
|
||||
})
|
||||
|
||||
local modfolder = game:getloadedmod()
|
||||
if (modfolder ~= "") then
|
||||
createdivider(menu, Engine.Localize("@LUA_MENU_LOADED_MOD", getmodname(modfolder):truncate(24)))
|
||||
|
||||
menu:AddButton("@LUA_MENU_UNLOAD", function()
|
||||
Engine.Exec("unloadmod")
|
||||
end, nil, true, nil, {
|
||||
desc_text = Engine.Localize("@LUA_MENU_UNLOAD_DESC")
|
||||
})
|
||||
end
|
||||
|
||||
createdivider(menu, Engine.Localize("@LUA_MENU_AVAILABLE_MODS"))
|
||||
|
||||
if (io.directoryexists("mods")) then
|
||||
local mods = io.listfiles("mods/")
|
||||
for i = 1, #mods do
|
||||
if (io.directoryexists(mods[i]) and not io.directoryisempty(mods[i])) then
|
||||
local name, desc = getmodname(mods[i])
|
||||
|
||||
if (mods[i] ~= modfolder) then
|
||||
game:addlocalizedstring(name, name)
|
||||
menu:AddButton(name, function()
|
||||
Engine.Exec("loadmod " .. mods[i])
|
||||
end, nil, true, nil, {
|
||||
desc_text = desc
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
menu:AddBackButton(function(a1)
|
||||
Engine.PlaySound(CoD.SFX.MenuBack)
|
||||
LUI.FlowManager.RequestLeaveMenu(a1)
|
||||
end)
|
||||
|
||||
LUI.Options.InitScrollingList(menu.list, nil)
|
||||
menu:CreateBottomDivider()
|
||||
menu.optionTextInfo = LUI.Options.AddOptionTextInfo(menu)
|
||||
|
||||
return menu
|
||||
end
|
2
deps/GSL
vendored
2
deps/GSL
vendored
@ -1 +1 @@
|
||||
Subproject commit 4377f6e603c64a86c934f1546aa9db482f2e1a4e
|
||||
Subproject commit 383723676cd548d615159701ac3d050f8dd1e128
|
2
deps/asmjit
vendored
2
deps/asmjit
vendored
@ -1 +1 @@
|
||||
Subproject commit f1a399c4fe74d1535a4190a2b8727c51045cc914
|
||||
Subproject commit 752eb38a4dbe590995cbadaff06baadd8378eeeb
|
2
deps/libtomcrypt
vendored
2
deps/libtomcrypt
vendored
@ -1 +1 @@
|
||||
Subproject commit 673f5ce29015a9bba3c96792920a10601b5b0718
|
||||
Subproject commit 06a81aeb227424182125363f7554fad5146d6d2a
|
2
deps/libtommath
vendored
2
deps/libtommath
vendored
@ -1 +1 @@
|
||||
Subproject commit 66de86426e9cdb88526974c765108f01554af2b0
|
||||
Subproject commit 5108f12350b6daa4aa5dbc846517ad1db2f8388a
|
2
deps/zlib
vendored
2
deps/zlib
vendored
@ -1 +1 @@
|
||||
Subproject commit 2014a993addbc8f1b9785d97f55fd189792c2f78
|
||||
Subproject commit ec3df00224d4b396e2ac6586ab5d25f673caa4c2
|
@ -264,6 +264,7 @@ filter {}
|
||||
|
||||
filter "configurations:Debug"
|
||||
optimize "Debug"
|
||||
buildoptions {"/bigobj"}
|
||||
defines {"DEBUG", "_DEBUG"}
|
||||
filter {}
|
||||
|
||||
@ -320,6 +321,10 @@ if _OPTIONS["copy-to"] then
|
||||
postbuildcommands {"copy /y \"$(TargetPath)\" \"" .. _OPTIONS["copy-to"] .. "\""}
|
||||
end
|
||||
|
||||
if _OPTIONS["debug-dir"] then
|
||||
debugdir ( _OPTIONS["debug-dir"] )
|
||||
end
|
||||
|
||||
dependencies.imports()
|
||||
|
||||
project "tlsdll"
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/io.hpp>
|
||||
|
||||
namespace filesystem
|
||||
{
|
||||
@ -70,6 +71,40 @@ namespace filesystem
|
||||
return this->name_;
|
||||
}
|
||||
|
||||
std::unordered_set<std::string>& get_search_paths()
|
||||
{
|
||||
static std::unordered_set<std::string> search_paths{};
|
||||
return search_paths;
|
||||
}
|
||||
|
||||
std::string read_file(const std::string& path)
|
||||
{
|
||||
for (const auto& search_path : get_search_paths())
|
||||
{
|
||||
const auto path_ = search_path + "/" + path;
|
||||
if (utils::io::file_exists(path_))
|
||||
{
|
||||
return utils::io::read_file(path_);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool read_file(const std::string& path, std::string* data)
|
||||
{
|
||||
for (const auto& search_path : get_search_paths())
|
||||
{
|
||||
const auto path_ = search_path + "/" + path;
|
||||
if (utils::io::read_file(path_, data))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
@ -87,6 +122,10 @@ namespace filesystem
|
||||
utils::hook::call(SELECT_VALUE(0x1403B8D31, 0x1404EE3D0), register_custom_path_stub);
|
||||
utils::hook::call(SELECT_VALUE(0x1403B8D51, 0x1404EE3F0), register_custom_path_stub);
|
||||
utils::hook::call(SELECT_VALUE(0x1403B8D90, 0x1404EE42F), register_custom_path_stub);
|
||||
|
||||
get_search_paths().insert(".");
|
||||
get_search_paths().insert("h1-mod");
|
||||
get_search_paths().insert("data");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -16,4 +16,8 @@ namespace filesystem
|
||||
std::string name_;
|
||||
std::string buffer_;
|
||||
};
|
||||
|
||||
std::unordered_set<std::string>& get_search_paths();
|
||||
std::string read_file(const std::string& path);
|
||||
bool read_file(const std::string& path, std::string* data);
|
||||
}
|
136
src/client/component/fonts.cpp
Normal file
136
src/client/component/fonts.cpp
Normal file
@ -0,0 +1,136 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "fonts.hpp"
|
||||
#include "console.hpp"
|
||||
#include "filesystem.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/memory.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/image.hpp>
|
||||
#include <utils/concurrency.hpp>
|
||||
|
||||
namespace fonts
|
||||
{
|
||||
namespace
|
||||
{
|
||||
struct font_data_t
|
||||
{
|
||||
std::unordered_map<std::string, game::TTF*> fonts;
|
||||
std::unordered_map<std::string, std::string> raw_fonts;
|
||||
};
|
||||
|
||||
utils::concurrency::container<font_data_t> font_data;
|
||||
|
||||
game::TTF* create_font(const std::string& name, const std::string& data)
|
||||
{
|
||||
const auto font = utils::memory::get_allocator()->allocate<game::TTF>();
|
||||
font->name = utils::memory::get_allocator()->duplicate_string(name);
|
||||
font->buffer = utils::memory::get_allocator()->duplicate_string(data);
|
||||
font->len = static_cast<int>(data.size());
|
||||
font->fontFace = 0;
|
||||
return font;
|
||||
}
|
||||
|
||||
void free_font(game::TTF* font)
|
||||
{
|
||||
utils::memory::get_allocator()->free(font->buffer);
|
||||
utils::memory::get_allocator()->free(font->name);
|
||||
utils::memory::get_allocator()->free(font);
|
||||
}
|
||||
|
||||
game::TTF* load_font(const std::string& name)
|
||||
{
|
||||
return font_data.access<game::TTF*>([&](font_data_t& data_) -> game::TTF*
|
||||
{
|
||||
if (const auto i = data_.fonts.find(name); i != data_.fonts.end())
|
||||
{
|
||||
return i->second;
|
||||
}
|
||||
|
||||
std::string data{};
|
||||
if (const auto i = data_.raw_fonts.find(name); i != data_.raw_fonts.end())
|
||||
{
|
||||
data = i->second;
|
||||
}
|
||||
|
||||
if (data.empty() && !filesystem::read_file(name, &data))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto material = create_font(name, data);
|
||||
data_.fonts[name] = material;
|
||||
|
||||
return material;
|
||||
});
|
||||
}
|
||||
|
||||
game::TTF* try_load_font(const std::string& name)
|
||||
{
|
||||
try
|
||||
{
|
||||
return load_font(name);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
console::error("Failed to load font %s: %s\n", name.data(), e.what());
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
game::TTF* db_find_xasset_header_stub(game::XAssetType type, const char* name, int create_default)
|
||||
{
|
||||
auto result = try_load_font(name);
|
||||
if (result == nullptr)
|
||||
{
|
||||
result = game::DB_FindXAssetHeader(type, name, create_default).ttf;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
void add(const std::string& name, const std::string& data)
|
||||
{
|
||||
font_data.access([&](font_data_t& data_)
|
||||
{
|
||||
data_.raw_fonts[name] = data;
|
||||
});
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
font_data.access([&](font_data_t& data_)
|
||||
{
|
||||
for (auto& font : data_.fonts)
|
||||
{
|
||||
free_font(font.second);
|
||||
}
|
||||
|
||||
data_.fonts.clear();
|
||||
utils::hook::set<int>(SELECT_VALUE(0x14F09DBB8, 0x14FD61EE8), 0); // reset registered font count
|
||||
});
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
utils::hook::call(SELECT_VALUE(0x1404D41B6, 0x1405D9296), db_find_xasset_header_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(fonts::component)
|
7
src/client/component/fonts.hpp
Normal file
7
src/client/component/fonts.hpp
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace fonts
|
||||
{
|
||||
void add(const std::string& name, const std::string& data);
|
||||
void clear();
|
||||
}
|
@ -571,7 +571,7 @@ namespace game_console
|
||||
{
|
||||
if (key == game::keyNum_t::K_F10)
|
||||
{
|
||||
if (!game::Com_InFrontEnd())
|
||||
if (!game::Com_InFrontend())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "game/game.hpp"
|
||||
|
||||
#include "game_console.hpp"
|
||||
#include "game/ui_scripting/execution.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
|
||||
@ -14,8 +15,22 @@ namespace input
|
||||
utils::hook::detour cl_char_event_hook;
|
||||
utils::hook::detour cl_key_event_hook;
|
||||
|
||||
bool lui_running()
|
||||
{
|
||||
return *game::hks::lua_state != nullptr;
|
||||
}
|
||||
|
||||
void cl_char_event_stub(const int local_client_num, const int key)
|
||||
{
|
||||
if (lui_running())
|
||||
{
|
||||
ui_scripting::notify("keypress",
|
||||
{
|
||||
{"keynum", key},
|
||||
{"key", game::Key_KeynumToString(key, 0, 1)},
|
||||
});
|
||||
}
|
||||
|
||||
if (!game_console::console_char_event(local_client_num, key))
|
||||
{
|
||||
return;
|
||||
@ -26,6 +41,15 @@ namespace input
|
||||
|
||||
void cl_key_event_stub(const int local_client_num, const int key, const int down)
|
||||
{
|
||||
if (lui_running())
|
||||
{
|
||||
ui_scripting::notify(down ? "keydown" : "keyup",
|
||||
{
|
||||
{"keynum", key},
|
||||
{"key", game::Key_KeynumToString(key, 0, 1)},
|
||||
});
|
||||
}
|
||||
|
||||
if (!game_console::console_key_event(local_client_num, key, down))
|
||||
{
|
||||
return;
|
||||
|
@ -212,7 +212,7 @@ namespace logfile
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto hook = vm_execute_hooks[pos];
|
||||
const auto& hook = vm_execute_hooks[pos];
|
||||
const auto state = hook.lua_state();
|
||||
|
||||
const scripting::entity self = local_id_to_entity(game::scr_VmPub->function_frame->fs.localId);
|
||||
@ -296,6 +296,8 @@ namespace logfile
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
utils::hook::jump(SELECT_VALUE(0x140376655, 0x140444645), utils::hook::assemble(vm_execute_stub), true);
|
||||
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
@ -308,8 +310,6 @@ namespace logfile
|
||||
|
||||
utils::hook::call(0x140484EC0, g_shutdown_game_stub);
|
||||
utils::hook::call(0x1404853C1, g_shutdown_game_stub);
|
||||
|
||||
utils::hook::jump(SELECT_VALUE(0x140376655, 0x140444645), utils::hook::assemble(vm_execute_stub), true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include "materials.hpp"
|
||||
#include "console.hpp"
|
||||
#include "filesystem.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
@ -20,6 +21,7 @@ namespace materials
|
||||
{
|
||||
utils::hook::detour db_material_streaming_fail_hook;
|
||||
utils::hook::detour material_register_handle_hook;
|
||||
utils::hook::detour db_get_material_index_hook;
|
||||
|
||||
struct material_data_t
|
||||
{
|
||||
@ -27,6 +29,8 @@ namespace materials
|
||||
std::unordered_map<std::string, std::string> images;
|
||||
};
|
||||
|
||||
char constant_table[0x20] = {};
|
||||
|
||||
utils::concurrency::container<material_data_t> material_data;
|
||||
|
||||
game::GfxImage* setup_image(game::GfxImage* image, const utils::image& raw_image)
|
||||
@ -47,8 +51,7 @@ namespace materials
|
||||
|
||||
game::Material* create_material(const std::string& name, const std::string& data)
|
||||
{
|
||||
const auto white = *reinterpret_cast<game::Material**>(SELECT_VALUE(0x141F3D860, 0x14282C330));
|
||||
|
||||
const auto white = material_register_handle_hook.invoke<game::Material*>("white");
|
||||
const auto material = utils::memory::get_allocator()->allocate<game::Material>();
|
||||
const auto texture_table = utils::memory::get_allocator()->allocate<game::MaterialTextureDef>();
|
||||
const auto image = utils::memory::get_allocator()->allocate<game::GfxImage>();
|
||||
@ -57,6 +60,7 @@ namespace materials
|
||||
std::memcpy(texture_table, white->textureTable, sizeof(game::MaterialTextureDef));
|
||||
std::memcpy(image, white->textureTable->u.image, sizeof(game::GfxImage));
|
||||
|
||||
material->constantTable = &constant_table;
|
||||
material->name = utils::memory::get_allocator()->duplicate_string(name);
|
||||
image->name = material->name;
|
||||
|
||||
@ -66,6 +70,16 @@ namespace materials
|
||||
return material;
|
||||
}
|
||||
|
||||
void free_material(game::Material* material)
|
||||
{
|
||||
material->textureTable->u.image->textures.___u0.map->Release();
|
||||
material->textureTable->u.image->textures.shaderView->Release();
|
||||
utils::memory::get_allocator()->free(material->textureTable->u.image);
|
||||
utils::memory::get_allocator()->free(material->textureTable);
|
||||
utils::memory::get_allocator()->free(material->name);
|
||||
utils::memory::get_allocator()->free(material);
|
||||
}
|
||||
|
||||
game::Material* load_material(const std::string& name)
|
||||
{
|
||||
return material_data.access<game::Material*>([&](material_data_t& data_) -> game::Material*
|
||||
@ -81,10 +95,9 @@ namespace materials
|
||||
data = i->second;
|
||||
}
|
||||
|
||||
if (data.empty()
|
||||
&& !utils::io::read_file(utils::string::va("h1-mod/materials/%s.png", name.data()), &data)
|
||||
&& !utils::io::read_file(utils::string::va("data/materials/%s.png", name.data()), &data))
|
||||
if (data.empty() && !filesystem::read_file(utils::string::va("materials/%s.png", name.data()), &data))
|
||||
{
|
||||
data_.materials[name] = nullptr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@ -124,24 +137,24 @@ namespace materials
|
||||
return result;
|
||||
}
|
||||
|
||||
bool db_material_streaming_fail_stub(game::Material* material)
|
||||
int db_material_streaming_fail_stub(game::Material* material)
|
||||
{
|
||||
const auto found = material_data.access<bool>([material](material_data_t& data_)
|
||||
if (material->constantTable == &constant_table)
|
||||
{
|
||||
if (data_.materials.find(material->name) != data_.materials.end())
|
||||
{
|
||||
return true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if (found)
|
||||
{
|
||||
return false;
|
||||
return db_material_streaming_fail_hook.invoke<int>(material);
|
||||
}
|
||||
|
||||
return db_material_streaming_fail_hook.invoke<bool>(material);
|
||||
unsigned int db_get_material_index_stub(game::Material* material)
|
||||
{
|
||||
if (material->constantTable == &constant_table)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return db_get_material_index_hook.invoke<unsigned int>(material);
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,6 +166,24 @@ namespace materials
|
||||
});
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
material_data.access([&](material_data_t& data_)
|
||||
{
|
||||
for (auto& material : data_.materials)
|
||||
{
|
||||
if (material.second == nullptr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
free_material(material.second);
|
||||
}
|
||||
|
||||
data_.materials.clear();
|
||||
});
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
@ -165,6 +196,7 @@ namespace materials
|
||||
|
||||
material_register_handle_hook.create(game::Material_RegisterHandle, material_register_handle_stub);
|
||||
db_material_streaming_fail_hook.create(SELECT_VALUE(0x1401D3180, 0x1402C6260), db_material_streaming_fail_stub);
|
||||
db_get_material_index_hook.create(SELECT_VALUE(0x1401CAD00, 0x1402BBB20), db_get_material_index_stub);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -3,4 +3,5 @@
|
||||
namespace materials
|
||||
{
|
||||
void add(const std::string& name, const std::string& data);
|
||||
void clear();
|
||||
}
|
||||
|
119
src/client/component/mods.cpp
Normal file
119
src/client/component/mods.cpp
Normal file
@ -0,0 +1,119 @@
|
||||
#include <std_include.hpp>
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include "command.hpp"
|
||||
#include "console.hpp"
|
||||
#include "scheduler.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "materials.hpp"
|
||||
#include "fonts.hpp"
|
||||
#include "mods.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
|
||||
namespace mods
|
||||
{
|
||||
std::string mod_path{};
|
||||
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour db_release_xassets_hook;
|
||||
bool release_assets = false;
|
||||
|
||||
void db_release_xassets_stub()
|
||||
{
|
||||
if (release_assets)
|
||||
{
|
||||
materials::clear();
|
||||
fonts::clear();
|
||||
}
|
||||
|
||||
db_release_xassets_hook.invoke<void>();
|
||||
}
|
||||
|
||||
void restart()
|
||||
{
|
||||
scheduler::once([]()
|
||||
{
|
||||
release_assets = true;
|
||||
game::Com_Shutdown("");
|
||||
release_assets = false;
|
||||
}, scheduler::pipeline::main);
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
{
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (!game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!utils::io::directory_exists("mods"))
|
||||
{
|
||||
utils::io::create_directory("mods");
|
||||
}
|
||||
|
||||
db_release_xassets_hook.create(SELECT_VALUE(0x1401CD560, 0x1402BF160), db_release_xassets_stub);
|
||||
|
||||
command::add("loadmod", [](const command::params& params)
|
||||
{
|
||||
if (params.size() < 2)
|
||||
{
|
||||
console::info("Usage: loadmod mods/<modname>");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!game::Com_InFrontend())
|
||||
{
|
||||
console::info("Cannot load mod while in-game!\n");
|
||||
game::CG_GameMessage(0, "^1Cannot unload mod while in-game!");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto path = params.get(1);
|
||||
if (!utils::io::directory_exists(path))
|
||||
{
|
||||
console::info("Mod %s not found!\n", path);
|
||||
return;
|
||||
}
|
||||
|
||||
console::info("Loading mod %s\n", path);
|
||||
filesystem::get_search_paths().erase(mod_path);
|
||||
filesystem::get_search_paths().insert(path);
|
||||
mod_path = path;
|
||||
restart();
|
||||
});
|
||||
|
||||
command::add("unloadmod", [](const command::params& params)
|
||||
{
|
||||
if (mod_path.empty())
|
||||
{
|
||||
console::info("No mod loaded\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!game::Com_InFrontend())
|
||||
{
|
||||
console::info("Cannot unload mod while in-game!\n");
|
||||
game::CG_GameMessage(0, "^1Cannot unload mod while in-game!");
|
||||
return;
|
||||
}
|
||||
|
||||
console::info("Unloading mod %s\n", mod_path.data());
|
||||
filesystem::get_search_paths().erase(mod_path);
|
||||
mod_path.clear();
|
||||
restart();
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
REGISTER_COMPONENT(mods::component)
|
6
src/client/component/mods.hpp
Normal file
6
src/client/component/mods.hpp
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace mods
|
||||
{
|
||||
extern std::string mod_path;
|
||||
}
|
@ -241,10 +241,10 @@ namespace patches
|
||||
// unlock safeArea_*
|
||||
utils::hook::jump(0x1402624F5, 0x140262503);
|
||||
utils::hook::jump(0x14026251C, 0x140262547);
|
||||
dvars::override::register_int("safeArea_adjusted_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED);
|
||||
dvars::override::register_int("safeArea_adjusted_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED);
|
||||
dvars::override::register_int("safeArea_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED);
|
||||
dvars::override::register_int("safeArea_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED);
|
||||
dvars::override::register_float("safeArea_adjusted_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED);
|
||||
dvars::override::register_float("safeArea_adjusted_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED);
|
||||
dvars::override::register_float("safeArea_horizontal", 1, 0, 1, game::DVAR_FLAG_SAVED);
|
||||
dvars::override::register_float("safeArea_vertical", 1, 0, 1, game::DVAR_FLAG_SAVED);
|
||||
|
||||
// allow servers to check for new packages more often
|
||||
dvars::override::register_int("sv_network_fps", 1000, 20, 1000, game::DVAR_FLAG_SAVED);
|
||||
@ -257,7 +257,7 @@ namespace patches
|
||||
|
||||
dvars::register_int("scr_game_spectatetype", 1, 0, 99, game::DVAR_FLAG_REPLICATED, "");
|
||||
|
||||
dvars::override::register_bool("ui_drawcrosshair", true, game::DVAR_FLAG_WRITE);
|
||||
dvars::override::register_bool("ui_drawCrosshair", true, game::DVAR_FLAG_WRITE);
|
||||
|
||||
dvars::override::register_int("com_maxfps", 0, 0, 1000, game::DVAR_FLAG_SAVED);
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
#include "loader/component_loader.hpp"
|
||||
|
||||
#include "game/game.hpp"
|
||||
#include <utils/hook.hpp>
|
||||
#include "game/dvars.hpp"
|
||||
|
||||
#include "game/scripting/entity.hpp"
|
||||
#include "game/scripting/functions.hpp"
|
||||
@ -13,14 +13,20 @@
|
||||
#include "scheduler.hpp"
|
||||
#include "scripting.hpp"
|
||||
|
||||
#include <utils/hook.hpp>
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/string.hpp>
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
std::unordered_map<int, std::unordered_map<std::string, int>> fields_table;
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
|
||||
utils::concurrency::container<shared_table_t> shared_table;
|
||||
|
||||
namespace
|
||||
{
|
||||
utils::hook::detour vm_notify_hook;
|
||||
utils::hook::detour vm_execute_hook;
|
||||
utils::hook::detour scr_load_level_hook;
|
||||
utils::hook::detour g_shutdown_game_hook;
|
||||
|
||||
@ -29,7 +35,14 @@ namespace scripting
|
||||
utils::hook::detour scr_set_thread_position_hook;
|
||||
utils::hook::detour process_script_hook;
|
||||
|
||||
utils::hook::detour sl_get_canonical_string_hook;
|
||||
|
||||
utils::hook::detour db_find_xasset_header_hook;
|
||||
|
||||
std::string current_file;
|
||||
unsigned int current_file_id{};
|
||||
|
||||
game::dvar_t* g_dump_scripts;
|
||||
|
||||
void vm_notify_stub(const unsigned int notify_list_owner_id, const game::scr_string_t string_value,
|
||||
game::VariableValue* top)
|
||||
@ -48,11 +61,6 @@ namespace scripting
|
||||
e.arguments.emplace_back(*value);
|
||||
}
|
||||
|
||||
if (e.name == "entitydeleted")
|
||||
{
|
||||
scripting::clear_entity_fields(e.entity);
|
||||
}
|
||||
|
||||
lua::engine::notify(e);
|
||||
}
|
||||
}
|
||||
@ -60,6 +68,16 @@ namespace scripting
|
||||
vm_notify_hook.invoke<void>(notify_list_owner_id, string_value, top);
|
||||
}
|
||||
|
||||
unsigned int vm_execute_stub()
|
||||
{
|
||||
if (!lua::engine::is_running())
|
||||
{
|
||||
lua::engine::start();
|
||||
}
|
||||
|
||||
return vm_execute_hook.invoke<unsigned int>();
|
||||
}
|
||||
|
||||
void scr_load_level_stub()
|
||||
{
|
||||
scr_load_level_hook.invoke<void>();
|
||||
@ -71,20 +89,25 @@ namespace scripting
|
||||
|
||||
void g_shutdown_game_stub(const int free_scripts)
|
||||
{
|
||||
if (free_scripts)
|
||||
{
|
||||
script_function_table.clear();
|
||||
}
|
||||
|
||||
lua::engine::stop();
|
||||
return g_shutdown_game_hook.invoke<void>(free_scripts);
|
||||
}
|
||||
|
||||
void scr_add_class_field_stub(unsigned int classnum, game::scr_string_t _name, unsigned int canonicalString, unsigned int offset)
|
||||
void scr_add_class_field_stub(unsigned int classnum, game::scr_string_t name, unsigned int canonical_string, unsigned int offset)
|
||||
{
|
||||
const auto name = game::SL_ConvertToString(_name);
|
||||
const auto name_str = game::SL_ConvertToString(name);
|
||||
|
||||
if (fields_table[classnum].find(name) == fields_table[classnum].end())
|
||||
if (fields_table[classnum].find(name_str) == fields_table[classnum].end())
|
||||
{
|
||||
fields_table[classnum][name] = offset;
|
||||
fields_table[classnum][name_str] = offset;
|
||||
}
|
||||
|
||||
scr_add_class_field_hook.invoke<void>(classnum, _name, canonicalString, offset);
|
||||
scr_add_class_field_hook.invoke<void>(classnum, name, canonical_string, offset);
|
||||
}
|
||||
|
||||
void process_script_stub(const char* filename)
|
||||
@ -92,21 +115,69 @@ namespace scripting
|
||||
const auto file_id = atoi(filename);
|
||||
if (file_id)
|
||||
{
|
||||
current_file = scripting::find_token(file_id);
|
||||
current_file_id = file_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
current_file_id = 0;
|
||||
current_file = filename;
|
||||
}
|
||||
|
||||
process_script_hook.invoke<void>(filename);
|
||||
}
|
||||
|
||||
void scr_set_thread_position_stub(unsigned int threadName, const char* codePos)
|
||||
void add_function(const std::string& file, unsigned int id, const char* pos)
|
||||
{
|
||||
const auto function_name = scripting::find_token(threadName);
|
||||
script_function_table[current_file][function_name] = codePos;
|
||||
scr_set_thread_position_hook.invoke<void>(threadName, codePos);
|
||||
const auto function_names = scripting::find_token(id);
|
||||
for (const auto& name : function_names)
|
||||
{
|
||||
script_function_table[file][name] = pos;
|
||||
}
|
||||
}
|
||||
|
||||
void scr_set_thread_position_stub(unsigned int thread_name, const char* code_pos)
|
||||
{
|
||||
if (current_file_id)
|
||||
{
|
||||
const auto names = scripting::find_token(current_file_id);
|
||||
for (const auto& name : names)
|
||||
{
|
||||
add_function(name, thread_name, code_pos);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
add_function(current_file, thread_name, code_pos);
|
||||
}
|
||||
|
||||
scr_set_thread_position_hook.invoke<void>(thread_name, code_pos);
|
||||
}
|
||||
|
||||
unsigned int sl_get_canonical_string_stub(const char* str)
|
||||
{
|
||||
const auto result = sl_get_canonical_string_hook.invoke<unsigned int>(str);
|
||||
scripting::token_map[str] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
game::XAssetHeader db_find_xasset_header_stub(game::XAssetType type, const char* name, int allow_create_default)
|
||||
{
|
||||
const auto result = db_find_xasset_header_hook.invoke<game::XAssetHeader>(type, name, allow_create_default);
|
||||
if (!g_dump_scripts->current.enabled || type != game::XAssetType::ASSET_TYPE_SCRIPTFILE)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string buffer;
|
||||
buffer.append(result.scriptfile->name, strlen(result.scriptfile->name) + 1);
|
||||
buffer.append(reinterpret_cast<char*>(&result.scriptfile->compressedLen), 4);
|
||||
buffer.append(reinterpret_cast<char*>(&result.scriptfile->len), 4);
|
||||
buffer.append(reinterpret_cast<char*>(&result.scriptfile->bytecodeLen), 4);
|
||||
buffer.append(result.scriptfile->buffer, result.scriptfile->compressedLen);
|
||||
buffer.append(result.scriptfile->bytecode, result.scriptfile->bytecodeLen);
|
||||
utils::io::write_file(utils::string::va("gsc_dump/%s.gscbin", name), buffer);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,21 +186,28 @@ namespace scripting
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
vm_notify_hook.create(SELECT_VALUE(0x140379A00, 0x1404479F0), vm_notify_stub);
|
||||
|
||||
scr_add_class_field_hook.create(SELECT_VALUE(0x140370370, 0x14043E2C0), scr_add_class_field_stub);
|
||||
|
||||
scr_set_thread_position_hook.create(SELECT_VALUE(0x14036A180, 0x140437D10), scr_set_thread_position_stub);
|
||||
process_script_hook.create(SELECT_VALUE(0x1403737E0, 0x1404417E0), process_script_stub);
|
||||
sl_get_canonical_string_hook.create(game::SL_GetCanonicalString, sl_get_canonical_string_stub);
|
||||
|
||||
if (!game::environment::is_sp())
|
||||
{
|
||||
scr_load_level_hook.create(SELECT_VALUE(0x1402A5BE0, 0x1403727C0), scr_load_level_stub);
|
||||
}
|
||||
else
|
||||
{
|
||||
vm_execute_hook.create(SELECT_VALUE(0x140376590, 0x140444580), vm_execute_stub);
|
||||
}
|
||||
|
||||
g_shutdown_game_hook.create(SELECT_VALUE(0x140277D40, 0x140345A60), g_shutdown_game_stub);
|
||||
|
||||
db_find_xasset_header_hook.create(game::DB_FindXAssetHeader, db_find_xasset_header_stub);
|
||||
g_dump_scripts = dvars::register_bool("g_dumpScripts", false, game::DVAR_FLAG_NONE, "Dump GSC scripts");
|
||||
|
||||
scheduler::loop([]()
|
||||
{
|
||||
lua::engine::run_frame();
|
||||
|
@ -3,6 +3,9 @@
|
||||
|
||||
namespace scripting
|
||||
{
|
||||
using shared_table_t = std::unordered_map<std::string, std::string>;
|
||||
|
||||
extern std::unordered_map<int, std::unordered_map<std::string, int>> fields_table;
|
||||
extern std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
|
||||
extern utils::concurrency::container<shared_table_t> shared_table;
|
||||
}
|
@ -21,7 +21,7 @@ namespace stats
|
||||
utils::hook::detour is_item_unlocked_hook2;
|
||||
utils::hook::detour is_item_unlocked_hook3;
|
||||
|
||||
int is_item_unlocked_stub(void* a1, void* a2, void* a3)
|
||||
int is_item_unlocked_stub(int a1, void* a2, int a3)
|
||||
{
|
||||
if (cg_unlock_all_items->current.enabled)
|
||||
{
|
||||
@ -31,7 +31,7 @@ namespace stats
|
||||
return is_item_unlocked_hook.invoke<int>(a1, a2, a3);
|
||||
}
|
||||
|
||||
int is_item_unlocked_stub2(void* a1, void* a2, void* a3, void* a4, void* a5, void* a6)
|
||||
int is_item_unlocked_stub2(int a1, void* a2, void* a3, void* a4, int a5, void* a6)
|
||||
{
|
||||
if (cg_unlock_all_items->current.enabled)
|
||||
{
|
||||
@ -41,7 +41,7 @@ namespace stats
|
||||
return is_item_unlocked_hook2.invoke<int>(a1, a2, a3, a4, a5, a6);
|
||||
}
|
||||
|
||||
int is_item_unlocked_stub3(void* a1)
|
||||
int is_item_unlocked_stub3(int a1)
|
||||
{
|
||||
if (cg_unlock_all_items->current.enabled)
|
||||
{
|
||||
@ -50,6 +50,11 @@ namespace stats
|
||||
|
||||
return is_item_unlocked_hook3.invoke<int>(a1);
|
||||
}
|
||||
|
||||
int is_item_unlocked()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
class component final : public component_interface
|
||||
@ -57,11 +62,19 @@ namespace stats
|
||||
public:
|
||||
void post_unpack() override
|
||||
{
|
||||
if (!game::environment::is_mp())
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (game::environment::is_dedi())
|
||||
{
|
||||
utils::hook::jump(0x140413E60, is_item_unlocked);
|
||||
utils::hook::jump(0x140413860, is_item_unlocked);
|
||||
utils::hook::jump(0x140412B70, is_item_unlocked);
|
||||
}
|
||||
else
|
||||
{
|
||||
cg_unlock_all_items = dvars::register_bool("cg_unlockall_items", false, game::DVAR_FLAG_SAVED,
|
||||
"Whether items should be locked based on the player's stats or always unlocked.");
|
||||
dvars::register_bool("cg_unlockall_classes", false, game::DVAR_FLAG_SAVED,
|
||||
@ -71,6 +84,7 @@ namespace stats
|
||||
is_item_unlocked_hook2.create(0x140413860, is_item_unlocked_stub2);
|
||||
is_item_unlocked_hook3.create(0x140412B70, is_item_unlocked_stub3);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -145,47 +145,6 @@ namespace scripting
|
||||
return exec_ent_thread(entity, pos, 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();
|
||||
@ -206,8 +165,7 @@ namespace scripting
|
||||
}
|
||||
else
|
||||
{
|
||||
// Read custom fields
|
||||
set_custom_field(entity, field, value);
|
||||
set_object_variable(entity.get_entity_id(), field, value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,8 +192,7 @@ namespace scripting
|
||||
return value;
|
||||
}
|
||||
|
||||
// Add custom fields
|
||||
return get_custom_field(entity, field);
|
||||
return get_object_variable(entity.get_entity_id(), field);
|
||||
}
|
||||
|
||||
unsigned int make_array()
|
||||
@ -248,4 +205,47 @@ namespace scripting
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
void set_object_variable(const unsigned int parent_id, const unsigned int id, const script_value& value)
|
||||
{
|
||||
const auto offset = 0xFA00 * (parent_id & 3);
|
||||
const auto variable_id = game::GetVariable(parent_id, id);
|
||||
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + offset];
|
||||
const auto& raw_value = value.get_raw();
|
||||
|
||||
game::AddRefToValue(raw_value.type, raw_value.u);
|
||||
game::RemoveRefToValue(variable->type, variable->u.u);
|
||||
|
||||
variable->type = static_cast<char>(raw_value.type);
|
||||
variable->u.u = raw_value.u;
|
||||
}
|
||||
|
||||
void set_object_variable(const unsigned int parent_id, const std::string& name, const script_value& value)
|
||||
{
|
||||
const auto id = scripting::find_token_id(name);
|
||||
set_object_variable(parent_id, id, value);
|
||||
}
|
||||
|
||||
script_value get_object_variable(const unsigned int parent_id, const unsigned int id)
|
||||
{
|
||||
const auto offset = 0xFA00 * (parent_id & 3);
|
||||
const auto variable_id = game::FindVariable(parent_id, id);
|
||||
if (!variable_id)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + offset];
|
||||
game::VariableValue value{};
|
||||
value.type = static_cast<int>(variable->type);
|
||||
value.u = variable->u.u;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
script_value get_object_variable(const unsigned int parent_id, const std::string& name)
|
||||
{
|
||||
const auto id = scripting::find_token_id(name);
|
||||
return get_object_variable(parent_id, id);
|
||||
}
|
||||
}
|
||||
|
@ -27,13 +27,16 @@ namespace scripting
|
||||
script_value call_script_function(const entity& entity, const std::string& filename,
|
||||
const std::string& function, const std::vector<script_value>& arguments);
|
||||
|
||||
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);
|
||||
|
||||
unsigned int make_array();
|
||||
|
||||
script_value get_object_variable(const unsigned int parent_id, const unsigned int id);
|
||||
script_value get_object_variable(const unsigned int parent_id, const std::string& name);
|
||||
|
||||
void set_object_variable(const unsigned int parent_id, const std::string& name, const script_value& value);
|
||||
void set_object_variable(const unsigned int parent_id, const unsigned int id, const script_value& value);
|
||||
}
|
||||
|
@ -931,12 +931,12 @@ namespace scripting
|
||||
{"setstablemissile", 0x8092}, // SP 0x1402AD800 MP 0x000000000
|
||||
{"playersetgroundreferenceent", 0x8093}, // SP 0x1402A9070 MP 0x1403752A0
|
||||
{"dontinterpolate", 0x8094}, // SP 0x1402A0070 MP 0x140358360
|
||||
{"_meth_8095", 0x8095}, // SP 0x1402AAC80 MP 0x000000000
|
||||
{"_meth_8096", 0x8096}, // SP 0x1402AAD20 MP 0x000000000
|
||||
{"dospawn", 0x8095}, // SP 0x1402AAC80 MP 0x000000000
|
||||
{"stalingradspawn", 0x8096}, // SP 0x1402AAD20 MP 0x000000000
|
||||
{"getorigin", 0x8097}, // SP 0x1402AADC0 MP 0x140377CA0
|
||||
{"_meth_8098", 0x8098}, // SP 0x1402AAE70 MP 0x000000000
|
||||
{"_meth_8099", 0x8099}, // SP 0x1402AAF90 MP 0x000000000
|
||||
{"_meth_809a", 0x809A}, // SP 0x1402AC1D0 MP 0x000000000
|
||||
{"getcentroid", 0x8098}, // SP 0x1402AAE70 MP 0x000000000
|
||||
{"getshootatpos", 0x8099}, // SP 0x1402AAF90 MP 0x000000000
|
||||
{"getdebugeye", 0x809A}, // SP 0x1402AC1D0 MP 0x000000000
|
||||
{"useby", 0x809B}, // SP 0x1402AC470 MP 0x140377D00
|
||||
{"playsound", 0x809C}, // SP 0x1402AC9B0 MP 0x140377F40
|
||||
{"_meth_809d", 0x809D},
|
||||
@ -1336,14 +1336,14 @@ namespace scripting
|
||||
{"vehicleturretcontroloff", 0x8227}, // SP 0x140464260 MP 0x140562970
|
||||
{"isturretready", 0x8228}, // SP 0x140464340 MP 0x1405629E0
|
||||
{"_meth_8229", 0x8229}, // SP 0x140464570 MP 0x140562C70
|
||||
{"dospawn", 0x822A}, // SP 0x1404646D0 MP 0x140562D90
|
||||
{"isphysveh", 0x822B}, // SP 0x1404647A0 MP 0x140562E80
|
||||
{"crash", 0x822C}, // SP 0x140464840 MP 0x140562F80
|
||||
{"launch", 0x822D}, // SP 0x140464980 MP 0x1405630A0
|
||||
{"disablecrashing", 0x822E}, // SP 0x140464B20 MP 0x140563240
|
||||
{"enablecrashing", 0x822F}, // SP 0x140464C10 MP 0x140563310
|
||||
{"setspeed", 0x8230}, // SP 0x140464C90 MP 0x1405633E0
|
||||
{"setconveyorbelt", 0x8231}, // SP 0x140464E50 MP 0x140563610
|
||||
{"vehicle_dospawn", 0x822A}, // SP 0x1404646D0 MP 0x140562D90
|
||||
{"vehicle_isphysveh", 0x822B}, // SP 0x1404647A0 MP 0x140562E80
|
||||
{"vehphys_crash", 0x822C}, // SP 0x140464840 MP 0x140562F80
|
||||
{"vehphys_launch", 0x822D}, // SP 0x140464980 MP 0x1405630A0
|
||||
{"vehphys_disablecrashing", 0x822E}, // SP 0x140464B20 MP 0x140563240
|
||||
{"vehphys_enablecrashing", 0x822F}, // SP 0x140464C10 MP 0x140563310
|
||||
{"vehphys_setspeed", 0x8230}, // SP 0x140464C90 MP 0x1405633E0
|
||||
{"vehphys_setconveyorbelt", 0x8231}, // SP 0x140464E50 MP 0x140563610
|
||||
{"freevehicle", 0x8232}, // SP 0x000000000 MP 0x1405609B0
|
||||
{"_meth_8233", 0x8233}, // SP 0x140290E70 MP 0x000000000
|
||||
{"_meth_8234", 0x8234}, // SP 0x1402910E0 MP 0x000000000
|
||||
@ -1393,8 +1393,8 @@ namespace scripting
|
||||
{"canturrettargetpoint", 0x8260}, // SP 0x1404635A0 MP 0x140561DF0
|
||||
{"setlookatent", 0x8261}, // SP 0x1404638A0 MP 0x1405620F0
|
||||
{"clearlookatent", 0x8262}, // SP 0x140463950 MP 0x1405621A0
|
||||
{"setweapon", 0x8263}, // SP 0x140463AB0 MP 0x140562280
|
||||
{"_meth_8264", 0x8264}, // SP 0x140463B20 MP 0x1405622F0
|
||||
{"setvehweapon", 0x8263}, // SP 0x140463AB0 MP 0x140562280
|
||||
{"fireweapon", 0x8264}, // SP 0x140463B20 MP 0x1405622F0
|
||||
{"vehicleturretcontrolon", 0x8265}, // SP 0x1404641D0 MP 0x1405628F0
|
||||
{"finishplayerdamage", 0x8266}, // SP 0x000000000 MP 0x1403337A0
|
||||
{"suicide", 0x8267}, // SP 0x000000000 MP 0x140333E20
|
||||
@ -1421,16 +1421,16 @@ namespace scripting
|
||||
{"setswitchnode", 0x827C}, // SP 0x140465740 MP 0x14055F4D0
|
||||
{"setwaitspeed", 0x827D}, // SP 0x140465840 MP 0x14055F560
|
||||
{"finishdamage", 0x827E}, // SP 0x000000000 MP 0x14055F5E0
|
||||
{"setspeed", 0x827F}, // SP 0x1404658C0 MP 0x14055F840
|
||||
{"setspeedimmediate", 0x8280}, // SP 0x140465930 MP 0x14055F8B0
|
||||
{"_meth_8281", 0x8281}, // SP 0x140465AC0 MP 0x14055FA60
|
||||
{"getspeed", 0x8282}, // SP 0x140465BE0 MP 0x14055FB80
|
||||
{"getvelocity", 0x8283}, // SP 0x140465CD0 MP 0x14055FC70
|
||||
{"getbodyvelocity", 0x8284}, // SP 0x140465D40 MP 0x14055FCE0
|
||||
{"getsteering", 0x8285}, // SP 0x140465DB0 MP 0x14055FD50
|
||||
{"getthrottle", 0x8286}, // SP 0x140465E30 MP 0x14055FDE0
|
||||
{"turnengineoff", 0x8287}, // SP 0x140465EA0 MP 0x14055FE50
|
||||
{"turnengineon", 0x8288}, // SP 0x140465F00 MP 0x14055FEC0
|
||||
{"vehicle_setspeed", 0x827F}, // SP 0x1404658C0 MP 0x14055F840
|
||||
{"vehicle_setspeedimmediate", 0x8280}, // SP 0x140465930 MP 0x14055F8B0
|
||||
{"vehicle_rotateyaw", 0x8281}, // SP 0x140465AC0 MP 0x14055FA60
|
||||
{"vehicle_getspeed", 0x8282}, // SP 0x140465BE0 MP 0x14055FB80
|
||||
{"vehicle_getvelocity", 0x8283}, // SP 0x140465CD0 MP 0x14055FC70
|
||||
{"vehicle_getbodyvelocity", 0x8284}, // SP 0x140465D40 MP 0x14055FCE0
|
||||
{"vehicle_getsteering", 0x8285}, // SP 0x140465DB0 MP 0x14055FD50
|
||||
{"vehicle_getthrottle", 0x8286}, // SP 0x140465E30 MP 0x14055FDE0
|
||||
{"vehicle_turnengineoff", 0x8287}, // SP 0x140465EA0 MP 0x14055FE50
|
||||
{"vehicle_turnengineon", 0x8288}, // SP 0x140465F00 MP 0x14055FEC0
|
||||
{"_meth_8289", 0x8289}, // SP 0x140465F60 MP 0x000000000
|
||||
{"getgoalspeedmph", 0x828A}, // SP 0x140466020 MP 0x14055FF30
|
||||
{"_meth_828b", 0x828B}, // SP 0x140466090 MP 0x14055FFA0
|
||||
@ -1457,36 +1457,36 @@ namespace scripting
|
||||
{"visionsyncwithplayer", 0x82A0}, // SP 0x000000000 MP 0x14032ED90
|
||||
{"showhudsplash", 0x82A1}, // SP 0x140263850 MP 0x14032FB10
|
||||
{"setperk", 0x82A2}, // SP 0x140265490 MP 0x1403297E0
|
||||
{"_meth_82a3", 0x82A3}, // SP 0x1402659A0 MP 0x140329D00
|
||||
{"_meth_82a4", 0x82A4}, // SP 0x1402661B0 MP 0x14032A460
|
||||
{"_meth_82a5", 0x82A5}, // SP 0x140265D40 MP 0x14032A0A0
|
||||
{"hasperk", 0x82A3}, // SP 0x1402659A0 MP 0x140329D00
|
||||
{"clearperks", 0x82A4}, // SP 0x1402661B0 MP 0x14032A460
|
||||
{"unsetperk", 0x82A5}, // SP 0x140265D40 MP 0x14032A0A0
|
||||
{"registerparty", 0x82A6}, // SP 0x000000000 MP 0x1403323C0
|
||||
{"_meth_82a7", 0x82A7}, // SP 0x000000000 MP 0x1403324F0
|
||||
{"_meth_82a8", 0x82A8}, // SP 0x1405D92F0 MP 0x14032A8F0
|
||||
{"_meth_82a9", 0x82A9}, // SP 0x1405D92F0 MP 0x14032A900
|
||||
{"getfireteammembers", 0x82A7}, // SP 0x000000000 MP 0x1403324F0
|
||||
{"noclip", 0x82A8}, // SP 0x1405D92F0 MP 0x14032A8F0
|
||||
{"ufo", 0x82A9}, // SP 0x1405D92F0 MP 0x14032A900
|
||||
{"moveto", 0x82AA}, // SP 0x1402B2A10 MP 0x14037E950
|
||||
{"rotatepitch", 0x82AB}, // SP 0x1402B2F60 MP 0x14037EEB0
|
||||
{"rotateyaw", 0x82AC}, // SP 0x1402B2F70 MP 0x14037EEC0
|
||||
{"rotateroll", 0x82AD}, // SP 0x1402B2F90 MP 0x14037EEE0
|
||||
{"movex", 0x82AB}, // SP 0x1402B2F60 MP 0x14037EEB0
|
||||
{"movey", 0x82AC}, // SP 0x1402B2F70 MP 0x14037EEC0
|
||||
{"movez", 0x82AD}, // SP 0x1402B2F90 MP 0x14037EEE0
|
||||
{"movegravity", 0x82AE}, // SP 0x1402B2C10 MP 0x14037EB00
|
||||
{"_meth_82af", 0x82AF}, // SP 0x1402B2D70 MP 0x14037EC90
|
||||
{"_meth_82b0", 0x82B0}, // SP 0x1402B2EE0 MP 0x14037EE20
|
||||
{"moveslide", 0x82AF}, // SP 0x1402B2D70 MP 0x14037EC90
|
||||
{"stopmoveslide", 0x82B0}, // SP 0x1402B2EE0 MP 0x14037EE20
|
||||
{"rotateto", 0x82B1}, // SP 0x1402B3030 MP 0x14037EF10
|
||||
{"_meth_82b2", 0x82B2}, // SP 0x1402B3460 MP 0x14037F060
|
||||
{"rotatepitch", 0x82B2}, // SP 0x1402B3460 MP 0x14037F060
|
||||
{"rotateyaw", 0x82B3}, // SP 0x1402B3470 MP 0x14037F070
|
||||
{"_meth_82b4", 0x82B4}, // SP 0x1402B3490 MP 0x14037F090 // looks similar to moveto/rotateto, wtf
|
||||
{"rotateroll", 0x82B4}, // SP 0x1402B3490 MP 0x14037F090 // looks similar to moveto/rotateto, wtf
|
||||
{"addpitch", 0x82B5}, // SP 0x1402B3410 MP 0x14037F010
|
||||
{"addyaw", 0x82B6}, // SP 0x1402B3430 MP 0x14037F030
|
||||
{"addoll", 0x82B7}, // SP 0x1402B3450 MP 0x14037F050
|
||||
{"_meth_82b8", 0x82B8}, // SP 0x1402B34B0 MP 0x14037F0B0
|
||||
{"addroll", 0x82B7}, // SP 0x1402B3450 MP 0x14037F050
|
||||
{"vibrate", 0x82B8}, // SP 0x1402B34B0 MP 0x14037F0B0
|
||||
{"rotatevelocity", 0x82B9}, // SP 0x1402B3700 MP 0x14037F3C0
|
||||
{"solid", 0x82BA}, // SP 0x1402B45E0 MP 0x1403808A0
|
||||
{"notsolid", 0x82BB}, // SP 0x1402B4690 MP 0x140380950
|
||||
{"setcandamage", 0x82BC}, // SP 0x1402B3880 MP 0x14037F590
|
||||
{"setcanradiusdamage", 0x82BD}, // SP 0x1402B38E0 MP 0x14037F5F0
|
||||
{"physicslaunchclient", 0x82BE}, // SP 0x1402B3960 MP 0x14037F670
|
||||
{"_meth_82bf", 0x82BF}, // SP 0x000000000 MP 0x1403351A0
|
||||
{"_meth_82c0", 0x82C0}, // SP 0x000000000 MP 0x1403351B0
|
||||
{"setcardicon", 0x82BF}, // SP 0x000000000 MP 0x1403351A0
|
||||
{"setcardnameplate", 0x82C0}, // SP 0x000000000 MP 0x1403351B0
|
||||
{"setcarddisplayslot", 0x82C1}, // SP 0x000000000 MP 0x1403351C0
|
||||
{"regweaponforfxremoval", 0x82C2}, // SP 0x000000000 MP 0x1403352B0
|
||||
{"laststandrevive", 0x82C3}, // SP 0x000000000 MP 0x140331E00
|
||||
@ -1509,15 +1509,15 @@ namespace scripting
|
||||
{"visionsetthermalforplayer", 0x82D4}, // SP 0x140263710 MP 0x14032FD20
|
||||
{"visionsetpainforplayer", 0x82D5}, // SP 0x140263730 MP 0x14032FD40
|
||||
{"setblurforplayer", 0x82D6}, // SP 0x140264890 MP 0x140330B80
|
||||
{"_meth_82d7", 0x82D7}, // SP 0x140264C80 MP 0x140331310
|
||||
{"_meth_82d8", 0x82D8}, // SP 0x140264C80 MP 0x140331330
|
||||
{"_meth_82d9", 0x82D9}, // SP 0x1402ABE90 MP 0x000000000
|
||||
{"getbuildnumber", 0x82DA}, // SP 0x1402663A0 MP 0x14032A910
|
||||
{"_meth_82db", 0x82DB}, // SP 0x140266AF0 MP 0x14032AE90
|
||||
{"_meth_82dc", 0x82DC}, // SP 0x140266CD0 MP 0x14032B120
|
||||
{"getplayerweaponmodel", 0x82D7}, // SP 0x140264C80 MP 0x140331310
|
||||
{"getplayerknifemodel", 0x82D8}, // SP 0x140264C80 MP 0x140331330
|
||||
{"updateplayermodelwithweapons", 0x82D9}, // SP 0x1402ABE90 MP 0x000000000
|
||||
{"notifyonplayercommand", 0x82DA}, // SP 0x1402663A0 MP 0x14032A910
|
||||
{"canmantle", 0x82DB}, // SP 0x140266AF0 MP 0x14032AE90
|
||||
{"forcemantle", 0x82DC}, // SP 0x140266CD0 MP 0x14032B120
|
||||
{"ismantling", 0x82DD}, // SP 0x140266FE0 MP 0x14032B500
|
||||
{"playfx", 0x82DE}, // SP 0x140267330 MP 0x14032B9F0
|
||||
{"playerrecoilscaleon", 0x82DF}, // SP 0x140267530 MP 0x14032BD00
|
||||
{"player_recoilscaleon", 0x82DF}, // SP 0x140267530 MP 0x14032BD00
|
||||
{"player_recoilscaleoff", 0x82E0}, // SP 0x140267600 MP 0x14032BDD0
|
||||
{"weaponlockstart", 0x82E1}, // SP 0x1402676E0 MP 0x14032C000
|
||||
{"weaponlockfinalize", 0x82E2}, // SP 0x140260240 MP 0x14032C240
|
||||
|
@ -69,19 +69,40 @@ namespace scripting
|
||||
|
||||
return reinterpret_cast<script_function*>(method_table)[index - 0x8000];
|
||||
}
|
||||
|
||||
unsigned int parse_token_id(const std::string& name)
|
||||
{
|
||||
if (name.starts_with("_ID"))
|
||||
{
|
||||
return static_cast<unsigned int>(std::strtol(name.substr(3).data(), nullptr, 10));
|
||||
}
|
||||
|
||||
std::string find_token(unsigned int id)
|
||||
if (name.starts_with("_id_"))
|
||||
{
|
||||
return static_cast<unsigned int>(std::strtol(name.substr(4).data(), nullptr, 16));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> find_token(unsigned int id)
|
||||
{
|
||||
std::vector<std::string> results;
|
||||
|
||||
results.push_back(utils::string::va("_id_%X", id));
|
||||
results.push_back(utils::string::va("_ID%i", id));
|
||||
|
||||
for (const auto& token : token_map)
|
||||
{
|
||||
if (token.second == id)
|
||||
{
|
||||
return token.first;
|
||||
results.push_back(token.first);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return utils::string::va("_ID%i", id);
|
||||
return results;
|
||||
}
|
||||
|
||||
unsigned int find_token_id(const std::string& name)
|
||||
@ -93,7 +114,13 @@ namespace scripting
|
||||
return result->second;
|
||||
}
|
||||
|
||||
return 0;
|
||||
const auto parsed_id = parse_token_id(name);
|
||||
if (parsed_id)
|
||||
{
|
||||
return parsed_id;
|
||||
}
|
||||
|
||||
return game::SL_GetCanonicalString(name.data());
|
||||
}
|
||||
|
||||
script_function find_function(const std::string& name, const bool prefer_global)
|
||||
|
@ -9,7 +9,7 @@ namespace scripting
|
||||
|
||||
using script_function = void(*)(game::scr_entref_t);
|
||||
|
||||
std::string find_token(unsigned int id);
|
||||
std::vector<std::string> find_token(unsigned int id);
|
||||
unsigned int find_token_id(const std::string& name);
|
||||
|
||||
script_function find_function(const std::string& name, const bool prefer_global);
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "../../../component/command.hpp"
|
||||
#include "../../../component/logfile.hpp"
|
||||
#include "../../../component/scripting.hpp"
|
||||
#include "../../../component/fastfiles.hpp"
|
||||
|
||||
#include <utils/string.hpp>
|
||||
#include <utils/io.hpp>
|
||||
@ -17,7 +18,6 @@ namespace scripting::lua
|
||||
{
|
||||
namespace
|
||||
{
|
||||
|
||||
vector normalize_vector(const vector& vec)
|
||||
{
|
||||
const auto length = sqrt(
|
||||
@ -155,12 +155,52 @@ namespace scripting::lua
|
||||
{
|
||||
return normalize_vector(a);
|
||||
};
|
||||
|
||||
vector_type["normalize"] = [](const vector& a)
|
||||
{
|
||||
return normalize_vector(a);
|
||||
};
|
||||
|
||||
vector_type["toangles"] = [](const vector& a)
|
||||
{
|
||||
return call("vectortoangles", {a}).as<vector>();
|
||||
};
|
||||
|
||||
vector_type["toyaw"] = [](const vector& a)
|
||||
{
|
||||
return call("vectortoyaw", {a}).as<vector>();
|
||||
};
|
||||
|
||||
vector_type["tolerp"] = [](const vector& a)
|
||||
{
|
||||
return call("vectortolerp", {a}).as<vector>();
|
||||
};
|
||||
|
||||
vector_type["toup"] = [](const vector& a)
|
||||
{
|
||||
return call("anglestoup", {a}).as<vector>();
|
||||
};
|
||||
|
||||
vector_type["toright"] = [](const vector& a)
|
||||
{
|
||||
return call("anglestoright", {a}).as<vector>();
|
||||
};
|
||||
|
||||
vector_type["toforward"] = [](const vector& a)
|
||||
{
|
||||
return call("anglestoforward", {a}).as<vector>();
|
||||
};
|
||||
}
|
||||
|
||||
void setup_entity_type(sol::state& state, event_handler& handler, scheduler& scheduler)
|
||||
{
|
||||
state["level"] = entity{*game::levelEntityId};
|
||||
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
state["player"] = call("getentbynum", {0}).as<entity>();
|
||||
}
|
||||
|
||||
auto entity_type = state.new_usertype<entity>("entity");
|
||||
|
||||
for (const auto& func : method_map)
|
||||
@ -462,6 +502,76 @@ namespace scripting::lua
|
||||
|
||||
return detour;
|
||||
};
|
||||
|
||||
game_type["assetlist"] = [](const game&, const sol::this_state s, const std::string& type_string)
|
||||
{
|
||||
auto table = sol::table::create(s.lua_state());
|
||||
auto index = 1;
|
||||
auto type_index = -1;
|
||||
|
||||
for (auto i = 0; i < ::game::XAssetType::ASSET_TYPE_COUNT; i++)
|
||||
{
|
||||
if (type_string == ::game::g_assetNames[i])
|
||||
{
|
||||
type_index = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (type_index == -1)
|
||||
{
|
||||
throw std::runtime_error("Asset type does not exist");
|
||||
}
|
||||
|
||||
const auto type = static_cast<::game::XAssetType>(type_index);
|
||||
fastfiles::enum_assets(type, [type, &table, &index](const ::game::XAssetHeader header)
|
||||
{
|
||||
const auto asset = ::game::XAsset{type, header};
|
||||
const std::string asset_name = ::game::DB_GetXAssetName(&asset);
|
||||
table[index++] = asset_name;
|
||||
}, true);
|
||||
|
||||
return table;
|
||||
};
|
||||
|
||||
game_type["sharedset"] = [](const game&, const std::string& key, const std::string& value)
|
||||
{
|
||||
scripting::shared_table.access([key, value](scripting::shared_table_t& table)
|
||||
{
|
||||
table[key] = value;
|
||||
});
|
||||
};
|
||||
|
||||
game_type["sharedget"] = [](const game&, const std::string& key)
|
||||
{
|
||||
std::string result;
|
||||
scripting::shared_table.access([key, &result](scripting::shared_table_t& table)
|
||||
{
|
||||
result = table[key];
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
game_type["sharedclear"] = [](const game&)
|
||||
{
|
||||
scripting::shared_table.access([](scripting::shared_table_t& table)
|
||||
{
|
||||
table.clear();
|
||||
});
|
||||
};
|
||||
|
||||
game_type["getentbyref"] = [](const game&, const sol::this_state s,
|
||||
const unsigned int entnum, const unsigned int classnum)
|
||||
{
|
||||
const auto id = ::game::Scr_GetEntityId(entnum, classnum);
|
||||
if (id)
|
||||
{
|
||||
return convert(s, scripting::entity{id});
|
||||
}
|
||||
else
|
||||
{
|
||||
return sol::lua_value{s, sol::lua_nil};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include "../execution.hpp"
|
||||
#include "../../../component/logfile.hpp"
|
||||
#include "../../../component/filesystem.hpp"
|
||||
|
||||
#include <utils/io.hpp>
|
||||
|
||||
@ -11,6 +12,8 @@ namespace scripting::lua::engine
|
||||
{
|
||||
namespace
|
||||
{
|
||||
bool running = false;
|
||||
|
||||
auto& get_scripts()
|
||||
{
|
||||
static std::vector<std::unique_ptr<context>> scripts{};
|
||||
@ -38,21 +41,27 @@ namespace scripting::lua::engine
|
||||
|
||||
void stop()
|
||||
{
|
||||
running = false;
|
||||
logfile::clear_callbacks();
|
||||
get_scripts().clear();
|
||||
}
|
||||
|
||||
void start()
|
||||
{
|
||||
// No SP until there is a concept
|
||||
stop();
|
||||
running = true;
|
||||
for (const auto& path : filesystem::get_search_paths())
|
||||
{
|
||||
load_scripts(path + "/scripts/");
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
return;
|
||||
load_scripts(path + "/scripts/sp/");
|
||||
}
|
||||
else
|
||||
{
|
||||
load_scripts(path + "/scripts/mp/");
|
||||
}
|
||||
}
|
||||
|
||||
stop();
|
||||
load_scripts("h1-mod/scripts/");
|
||||
load_scripts("data/scripts/");
|
||||
}
|
||||
|
||||
void notify(const event& e)
|
||||
@ -70,4 +79,9 @@ namespace scripting::lua::engine
|
||||
script->run_frame();
|
||||
}
|
||||
}
|
||||
|
||||
bool is_running()
|
||||
{
|
||||
return running;
|
||||
}
|
||||
}
|
||||
|
@ -8,4 +8,5 @@ namespace scripting::lua::engine
|
||||
void stop();
|
||||
void notify(const event& e);
|
||||
void run_frame();
|
||||
bool is_running();
|
||||
}
|
||||
|
@ -165,56 +165,33 @@ namespace scripting::lua
|
||||
auto table = sol::table::create(state);
|
||||
auto metatable = sol::table::create(state);
|
||||
|
||||
const auto offset = 64000 * (parent_id & 3);
|
||||
|
||||
metatable[sol::meta_function::new_index] = [offset, parent_id](const sol::table t, const sol::this_state s,
|
||||
metatable[sol::meta_function::new_index] = [parent_id](const sol::table t, const sol::this_state s,
|
||||
const sol::lua_value& field, const sol::lua_value& value)
|
||||
{
|
||||
const auto id = field.is<std::string>()
|
||||
? scripting::find_token_id(field.as<std::string>())
|
||||
: field.as<int>();
|
||||
|
||||
if (!id)
|
||||
const auto new_variable = convert({s, value});
|
||||
if (field.is<unsigned int>())
|
||||
{
|
||||
return;
|
||||
scripting::set_object_variable(parent_id, field.as<unsigned int>(), new_variable);
|
||||
}
|
||||
else if (field.is<std::string>())
|
||||
{
|
||||
scripting::set_object_variable(parent_id, field.as<std::string>(), new_variable);
|
||||
}
|
||||
|
||||
const auto variable_id = game::GetVariable(parent_id, id);
|
||||
const auto variable = &game::scr_VarGlob->childVariableValue[variable_id + offset];
|
||||
const auto new_variable = convert({s, value}).get_raw();
|
||||
|
||||
game::AddRefToValue(new_variable.type, new_variable.u);
|
||||
game::RemoveRefToValue(variable->type, variable->u.u);
|
||||
|
||||
variable->type = (char)new_variable.type;
|
||||
variable->u.u = new_variable.u;
|
||||
};
|
||||
|
||||
metatable[sol::meta_function::index] = [offset, parent_id](const sol::table t, const sol::this_state s,
|
||||
metatable[sol::meta_function::index] = [parent_id](const sol::table t, const sol::this_state s,
|
||||
const sol::lua_value& field)
|
||||
{
|
||||
const auto id = field.is<std::string>()
|
||||
? scripting::find_token_id(field.as<std::string>())
|
||||
: field.as<int>();
|
||||
|
||||
if (!id)
|
||||
if (field.is<unsigned int>())
|
||||
{
|
||||
return sol::lua_value{s, sol::lua_nil};
|
||||
return convert(s, scripting::get_object_variable(parent_id, field.as<unsigned int>()));
|
||||
}
|
||||
else if (field.is<std::string>())
|
||||
{
|
||||
return convert(s, scripting::get_object_variable(parent_id, field.as<std::string>()));
|
||||
}
|
||||
|
||||
const auto variable_id = game::FindVariable(parent_id, id);
|
||||
if (!variable_id)
|
||||
{
|
||||
return sol::lua_value{s, sol::lua_nil};
|
||||
}
|
||||
|
||||
const auto variable = game::scr_VarGlob->childVariableValue[variable_id + offset];
|
||||
|
||||
game::VariableValue result{};
|
||||
result.u = variable.u.u;
|
||||
result.type = (game::scriptType_e)variable.type;
|
||||
|
||||
return convert(s, result);
|
||||
};
|
||||
|
||||
table[sol::metatable_key] = metatable;
|
||||
|
@ -1175,7 +1175,12 @@ namespace game
|
||||
const char* name;
|
||||
char __pad0[0x118];
|
||||
char textureCount;
|
||||
char __pad1[7];
|
||||
char constantCount;
|
||||
char stateBitsCount;
|
||||
char stateFlags;
|
||||
char cameraRegion;
|
||||
char materialType;
|
||||
char assetFlags;
|
||||
MaterialTechniqueSet* techniqueSet;
|
||||
MaterialTextureDef* textureTable;
|
||||
void* constantTable;
|
||||
@ -1311,6 +1316,14 @@ namespace game
|
||||
const char* buffer;
|
||||
};
|
||||
|
||||
struct TTF
|
||||
{
|
||||
const char* name;
|
||||
int len;
|
||||
const char* buffer;
|
||||
int fontFace;
|
||||
};
|
||||
|
||||
struct GfxImageLoadDef
|
||||
{
|
||||
char levelCount;
|
||||
@ -1382,6 +1395,7 @@ namespace game
|
||||
StringTable* stringTable;
|
||||
LuaFile* luaFile;
|
||||
GfxImage* image;
|
||||
TTF* ttf;
|
||||
};
|
||||
|
||||
struct XAsset
|
||||
|
@ -33,18 +33,23 @@ namespace game
|
||||
|
||||
WEAK symbol<void()> Com_Frame_Try_Block_Function{0x1401CE8D0, 0x1400D8310};
|
||||
WEAK symbol<CodPlayMode()> Com_GetCurrentCoDPlayMode{0, 0x1405039A0};
|
||||
WEAK symbol<bool()> Com_InFrontEnd{0x1400E4B30, 0x140176A30};
|
||||
WEAK symbol<bool()> Com_InFrontend{0x1400E4B30, 0x140176A30};
|
||||
WEAK symbol<void(float, float, int)> Com_SetSlowMotion{0, 0x1400DB790};
|
||||
WEAK symbol<void(errorParm code, const char* message, ...)> Com_Error{0x1403509C0, 0x1400D78A0};
|
||||
WEAK symbol<void()> Com_Quit_f{0x140352BE0, 0x1400DA830};
|
||||
WEAK symbol<void(char const* finalMessage)> Com_Shutdown{0x140353B70, 0x1400DB8A0};
|
||||
|
||||
WEAK symbol<void()> Quit{0x140352D90, 0x1400DA830};
|
||||
|
||||
WEAK symbol<void(int localClientNum, const char* message)> CG_GameMessage{0x1401389A0, 0x140220CC0};
|
||||
WEAK symbol<void(int localClientNum, const char* message)> CG_GameMessageBold{0x140138750, 0x140220620};
|
||||
WEAK symbol<void(int localClientNum, /*mp::cg_s**/void* cg,
|
||||
const char* dvar, const char* value)> CG_SetClientDvarFromServer{0, 0x140236120};
|
||||
WEAK symbol<char*(const unsigned int weapon,
|
||||
bool isAlternate, char* outputBuffer, int bufferLen)> CG_GetWeaponDisplayName{0x14016EC30, 0x1400B5840};
|
||||
|
||||
WEAK symbol<bool()> CL_IsCgameInitialized{0x14017EE30, 0x140245650};
|
||||
WEAK symbol<void(int a1)> CL_VirtualLobbyShutdown{0, 0x140256D40};
|
||||
|
||||
WEAK symbol<void(int hash, const char* name, const char* buffer)> Dvar_SetCommand{0x1403C72B0, 0x1404FD0A0};
|
||||
WEAK symbol<dvar_t*(const char* name)> Dvar_FindVar{0x1403C5D50, 0x1404FBB00};
|
||||
@ -70,7 +75,7 @@ namespace game
|
||||
WEAK symbol<void(const char* gameName)> FS_Startup{0x1403B85D0, 0x1404EDD30};
|
||||
WEAK symbol<void(const char* path, const char* dir)> FS_AddLocalizedGameDirectory{0x1403B6030, 0x1404EBE20};
|
||||
|
||||
WEAK symbol<unsigned int(unsigned int, unsigned int)> GetVariable{0x14036FDD0, 0x1403F3730};
|
||||
WEAK symbol<unsigned int(unsigned int, unsigned int)> GetVariable{0x14036FDD0, 0x14043DD70};
|
||||
WEAK symbol<unsigned int(unsigned int parentId, unsigned int unsignedValue)> GetNewVariable{0x14036FA00, 0x14043D990};
|
||||
WEAK symbol<unsigned int(unsigned int parentId, unsigned int unsignedValue)> GetNewArrayVariable{0x14036F880, 0x14043D810};
|
||||
WEAK symbol<void()> GScr_LoadConsts{0x1402D13E0, 0x140393810};
|
||||
@ -136,6 +141,7 @@ namespace game
|
||||
WEAK symbol<void()> Scr_ClearOutParams{0x140374460, 0x140442510};
|
||||
WEAK symbol<scr_entref_t(unsigned int entId)> Scr_GetEntityIdRef{0x140372D50, 0x140440D80};
|
||||
WEAK symbol<unsigned int(int classnum, unsigned int entnum)> Scr_GetEntityId{0x140372CA0, 0x140440CD0};
|
||||
WEAK symbol<int(unsigned int classnum, int entnum, int offset)> Scr_SetObjectField{0x1402B9F60, 0x140385330};
|
||||
|
||||
WEAK symbol<ScreenPlacement* ()> ScrPlace_GetViewPlacement{0x1401981F0, 0x140288550};
|
||||
|
||||
@ -143,16 +149,22 @@ namespace game
|
||||
DB_EnumXAssets_Internal{0x1401C9C10, 0x1402BA830};
|
||||
WEAK symbol<const char*(const XAsset* asset)> DB_GetXAssetName{0x14019A390, 0x14028BE50};
|
||||
WEAK symbol<int(XAssetType type)> DB_GetXAssetTypeSize{0x14019A3B0, 0x14028BE70};
|
||||
WEAK symbol<XAssetHeader(XAssetType type, const char* name,
|
||||
int createDefault)> DB_FindXAssetHeader{0x1401CA150, 0x1402BAC70};
|
||||
|
||||
WEAK symbol<void(int clientNum, const char* menu,
|
||||
int a3, int a4, unsigned int a5)> LUI_OpenMenu{0x14039D5F0, 0x1404CD210};
|
||||
WEAK symbol<bool(int clientNum, const char* name, hks::lua_State* s)> LUI_BeginEvent{0x1400D27F0, 0x140161A00};
|
||||
WEAK symbol<void(hks::lua_State* s)> LUI_EndEvent{0x1400D3A80, 0x140162CD0};
|
||||
WEAK symbol<void()> LUI_EnterCriticalSection{0x1400D3B70, 0x140162DC0};
|
||||
WEAK symbol<void()> LUI_LeaveCriticalSection{0x1400D8DB0, 0x140168150};
|
||||
|
||||
WEAK symbol<bool(int clientNum, const char* menu)> Menu_IsMenuOpenAndVisible{0x1404709C0, 0x1404C7320};
|
||||
|
||||
WEAK symbol<scr_string_t(const char* str)> SL_FindString{0x14036D700, 0x14043B470};
|
||||
WEAK symbol<scr_string_t(const char* str, unsigned int user)> SL_GetString{0x14036D9A0, 0x14043B840};
|
||||
WEAK symbol<const char*(scr_string_t stringValue)> SL_ConvertToString{0x14036D420, 0x14043B170};
|
||||
WEAK symbol<int(unsigned int classnum, int entnum, int offset)> Scr_SetObjectField{0x1402B9F60, 0x140385330};
|
||||
WEAK symbol<unsigned int(const char* str)> SL_GetCanonicalString{0x14036A310, 0x140437EA0};
|
||||
|
||||
WEAK symbol<void(netadr_s* from)> SV_DirectConnect{0, 0x140480860};
|
||||
WEAK symbol<void(int arg, char* buffer, int bufferLength)> SV_Cmd_ArgvBuffer{0x1403446C0, 0x140404CA0};
|
||||
@ -266,5 +278,6 @@ namespace game
|
||||
int internal_, int profilerTreatClosureAsFunc)> cclosure_Create{0x14008B5D0, 0x14011B540};
|
||||
WEAK symbol<int(lua_State* s, int t)> hksi_luaL_ref{0x1400A64D0, 0x140136D30};
|
||||
WEAK symbol<void(lua_State* s, int t, int ref)> hksi_luaL_unref{0x14009EF10, 0x14012F610};
|
||||
WEAK symbol<void(lua_State* s, HksObject* lfp)> closePendingUpvalues{0x14008EA00, 0x14011E970};
|
||||
}
|
||||
}
|
||||
|
@ -37,17 +37,56 @@ namespace ui_scripting
|
||||
return values;
|
||||
}
|
||||
|
||||
bool notify(const std::string& name, const event_arguments& arguments)
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
if (!state)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto _1 = gsl::finally(game::LUI_LeaveCriticalSection);
|
||||
game::LUI_EnterCriticalSection();
|
||||
|
||||
try
|
||||
{
|
||||
const auto globals = table((*::game::hks::lua_state)->globals.v.table);
|
||||
const auto engine = globals.get("Engine").as<table>();
|
||||
const auto root = engine.get("GetLuiRoot").as<function>().call({})[0].as<userdata>();
|
||||
const auto process_event = root.get("processEvent").as<function>();
|
||||
|
||||
table event{};
|
||||
event.set("name", name);
|
||||
|
||||
for (const auto& arg : arguments)
|
||||
{
|
||||
event.set(arg.first, arg.second);
|
||||
}
|
||||
|
||||
process_event.call({root, event});
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
printf("Error processing event '%s' %s\n", name.data(), e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
arguments call_script_function(const function& function, const arguments& arguments)
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
state->m_apistack.top = state->m_apistack.base;
|
||||
|
||||
stack stack;
|
||||
push_value(function);
|
||||
for (auto i = arguments.begin(); i != arguments.end(); ++i)
|
||||
{
|
||||
push_value(*i);
|
||||
}
|
||||
|
||||
const auto num_args = static_cast<int>(arguments.size());
|
||||
stack.save(num_args + 1);
|
||||
|
||||
const auto _1 = gsl::finally(&disable_error_hook);
|
||||
enable_error_hook();
|
||||
|
||||
@ -59,6 +98,7 @@ namespace ui_scripting
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
stack.fix();
|
||||
throw std::runtime_error(std::string("Error executing script function: ") + e.what());
|
||||
}
|
||||
}
|
||||
@ -66,9 +106,10 @@ namespace ui_scripting
|
||||
script_value get_field(const userdata& self, const script_value& key)
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
state->m_apistack.top = state->m_apistack.base;
|
||||
|
||||
stack stack;
|
||||
push_value(key);
|
||||
stack.save(1);
|
||||
|
||||
const auto _1 = gsl::finally(&disable_error_hook);
|
||||
enable_error_hook();
|
||||
@ -85,16 +126,18 @@ namespace ui_scripting
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
throw std::runtime_error(std::string("Error getting userdata field: ") + e.what());
|
||||
stack.fix();
|
||||
throw std::runtime_error("Error getting userdata field: "s + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
script_value get_field(const table& self, const script_value& key)
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
state->m_apistack.top = state->m_apistack.base;
|
||||
|
||||
stack stack;
|
||||
push_value(key);
|
||||
stack.save(1);
|
||||
|
||||
const auto _1 = gsl::finally(&disable_error_hook);
|
||||
enable_error_hook();
|
||||
@ -111,14 +154,17 @@ namespace ui_scripting
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
throw std::runtime_error(std::string("Error getting table field: ") + e.what());
|
||||
stack.fix();
|
||||
throw std::runtime_error("Error getting table field: "s + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void set_field(const userdata& self, const script_value& key, const script_value& value)
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
state->m_apistack.top = state->m_apistack.base;
|
||||
|
||||
stack stack;
|
||||
stack.save(0);
|
||||
|
||||
const auto _1 = gsl::finally(&disable_error_hook);
|
||||
enable_error_hook();
|
||||
@ -133,14 +179,17 @@ namespace ui_scripting
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
throw std::runtime_error(std::string("Error setting userdata field: ") + e.what());
|
||||
stack.fix();
|
||||
throw std::runtime_error("Error setting userdata field: "s + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void set_field(const table& self, const script_value& key, const script_value& value)
|
||||
{
|
||||
const auto state = *game::hks::lua_state;
|
||||
state->m_apistack.top = state->m_apistack.base;
|
||||
|
||||
stack stack;
|
||||
stack.save(0);
|
||||
|
||||
const auto _1 = gsl::finally(&disable_error_hook);
|
||||
enable_error_hook();
|
||||
@ -155,7 +204,8 @@ namespace ui_scripting
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
throw std::runtime_error(std::string("Error setting table field: ") + e.what());
|
||||
stack.fix();
|
||||
throw std::runtime_error("Error setting table field: "s + e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ namespace ui_scripting
|
||||
script_value get_return_value(int offset);
|
||||
arguments get_return_values(int count);
|
||||
|
||||
bool notify(const std::string& name, const event_arguments& arguments);
|
||||
|
||||
arguments call_script_function(const function& function, const arguments& arguments);
|
||||
|
||||
script_value get_field(const userdata& self, const script_value& key);
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "context.hpp"
|
||||
#include "error.hpp"
|
||||
#include "value_conversion.hpp"
|
||||
#include "../../scripting/execution.hpp"
|
||||
#include "../script_value.hpp"
|
||||
#include "../execution.hpp"
|
||||
|
||||
@ -10,6 +11,9 @@
|
||||
#include "../../../component/updater.hpp"
|
||||
#include "../../../component/fps.hpp"
|
||||
#include "../../../component/localized_strings.hpp"
|
||||
#include "../../../component/fastfiles.hpp"
|
||||
#include "../../../component/scripting.hpp"
|
||||
#include "../../../component/mods.hpp"
|
||||
|
||||
#include "component/game_console.hpp"
|
||||
#include "component/scheduler.hpp"
|
||||
@ -22,6 +26,31 @@ namespace ui_scripting::lua
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const auto json_script = utils::nt::load_resource(LUA_JSON);
|
||||
|
||||
void setup_json(sol::state& state)
|
||||
{
|
||||
const auto json = state.safe_script(json_script, &sol::script_pass_on_error);
|
||||
handle_error(json);
|
||||
state["json"] = json;
|
||||
}
|
||||
|
||||
void setup_io(sol::state& state)
|
||||
{
|
||||
state["io"]["fileexists"] = utils::io::file_exists;
|
||||
state["io"]["writefile"] = utils::io::write_file;
|
||||
state["io"]["movefile"] = utils::io::move_file;
|
||||
state["io"]["filesize"] = utils::io::file_size;
|
||||
state["io"]["createdirectory"] = utils::io::create_directory;
|
||||
state["io"]["directoryexists"] = utils::io::directory_exists;
|
||||
state["io"]["directoryisempty"] = utils::io::directory_is_empty;
|
||||
state["io"]["listfiles"] = utils::io::list_files;
|
||||
state["io"]["copyfolder"] = utils::io::copy_folder;
|
||||
state["io"]["removefile"] = utils::io::remove_file;
|
||||
state["io"]["removedirectory"] = utils::io::remove_directory;
|
||||
state["io"]["readfile"] = static_cast<std::string(*)(const std::string&)>(utils::io::read_file);
|
||||
}
|
||||
|
||||
void setup_types(sol::state& state, scheduler& scheduler)
|
||||
{
|
||||
struct game
|
||||
@ -68,6 +97,78 @@ namespace ui_scripting::lua
|
||||
localized_strings::override(string, value);
|
||||
};
|
||||
|
||||
game_type["sharedset"] = [](const game&, const std::string& key, const std::string& value)
|
||||
{
|
||||
scripting::shared_table.access([key, value](scripting::shared_table_t& table)
|
||||
{
|
||||
table[key] = value;
|
||||
});
|
||||
};
|
||||
|
||||
game_type["sharedget"] = [](const game&, const std::string& key)
|
||||
{
|
||||
std::string result;
|
||||
scripting::shared_table.access([key, &result](scripting::shared_table_t& table)
|
||||
{
|
||||
result = table[key];
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
game_type["sharedclear"] = [](const game&)
|
||||
{
|
||||
scripting::shared_table.access([](scripting::shared_table_t& table)
|
||||
{
|
||||
table.clear();
|
||||
});
|
||||
};
|
||||
|
||||
game_type["assetlist"] = [](const game&, const sol::this_state s, const std::string& type_string)
|
||||
{
|
||||
auto table = sol::table::create(s.lua_state());
|
||||
auto index = 1;
|
||||
auto type_index = -1;
|
||||
|
||||
for (auto i = 0; i < ::game::XAssetType::ASSET_TYPE_COUNT; i++)
|
||||
{
|
||||
if (type_string == ::game::g_assetNames[i])
|
||||
{
|
||||
type_index = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (type_index == -1)
|
||||
{
|
||||
throw std::runtime_error("Asset type does not exist");
|
||||
}
|
||||
|
||||
const auto type = static_cast<::game::XAssetType>(type_index);
|
||||
fastfiles::enum_assets(type, [type, &table, &index](const ::game::XAssetHeader header)
|
||||
{
|
||||
const auto asset = ::game::XAsset{type, header};
|
||||
const std::string asset_name = ::game::DB_GetXAssetName(&asset);
|
||||
table[index++] = asset_name;
|
||||
}, true);
|
||||
|
||||
return table;
|
||||
};
|
||||
|
||||
game_type["getweapondisplayname"] = [](const game&, const std::string& name)
|
||||
{
|
||||
const auto alternate = name.starts_with("alt_");
|
||||
const auto weapon = ::game::G_GetWeaponForName(name.data());
|
||||
|
||||
char buffer[0x400] = {0};
|
||||
::game::CG_GetWeaponDisplayName(weapon, alternate, buffer, 0x400);
|
||||
|
||||
return std::string(buffer);
|
||||
};
|
||||
|
||||
game_type["getloadedmod"] = [](const game&)
|
||||
{
|
||||
return mods::mod_path;
|
||||
};
|
||||
|
||||
auto userdata_type = state.new_usertype<userdata>("userdata_");
|
||||
|
||||
userdata_type["new"] = sol::property(
|
||||
@ -193,6 +294,51 @@ namespace ui_scripting::lua
|
||||
updater_table["getcurrentfile"] = updater::get_current_file;
|
||||
|
||||
state["updater"] = updater_table;
|
||||
|
||||
if (::game::environment::is_sp())
|
||||
{
|
||||
struct player
|
||||
{
|
||||
};
|
||||
auto player_type = state.new_usertype<player>("player_");
|
||||
state["player"] = player();
|
||||
|
||||
player_type["notify"] = [](const player&, const sol::this_state s, const std::string& name, sol::variadic_args va)
|
||||
{
|
||||
if (!::game::CL_IsCgameInitialized() || !::game::sp::g_entities[0].client)
|
||||
{
|
||||
throw std::runtime_error("Not in game");
|
||||
}
|
||||
|
||||
const sol::state_view view{s};
|
||||
const auto to_string = view["tostring"].get<sol::protected_function>();
|
||||
|
||||
std::vector<std::string> args{};
|
||||
for (auto arg : va)
|
||||
{
|
||||
args.push_back(to_string.call(arg).get<std::string>());
|
||||
}
|
||||
|
||||
::scheduler::once([s, name, args]()
|
||||
{
|
||||
try
|
||||
{
|
||||
std::vector<scripting::script_value> arguments{};
|
||||
|
||||
for (const auto& arg : args)
|
||||
{
|
||||
arguments.push_back(arg);
|
||||
}
|
||||
|
||||
const auto player = scripting::call("getentbynum", {0}).as<scripting::entity>();
|
||||
scripting::notify(player, name, arguments);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}, ::scheduler::pipeline::server);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,6 +353,8 @@ namespace ui_scripting::lua
|
||||
sol::lib::math,
|
||||
sol::lib::table);
|
||||
|
||||
setup_json(this->state_);
|
||||
setup_io(this->state_);
|
||||
setup_types(this->state_, this->scheduler_);
|
||||
|
||||
if (type == script_type::file)
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "context.hpp"
|
||||
|
||||
#include "../../../component/ui_scripting.hpp"
|
||||
#include "../../../component/filesystem.hpp"
|
||||
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/nt.hpp>
|
||||
@ -52,8 +53,18 @@ namespace ui_scripting::lua::engine
|
||||
load_code(lui_common);
|
||||
load_code(lui_updater);
|
||||
|
||||
load_scripts("h1-mod/ui_scripts/");
|
||||
load_scripts("data/ui_scripts/");
|
||||
for (const auto& path : filesystem::get_search_paths())
|
||||
{
|
||||
load_scripts(path + "/ui_scripts/");
|
||||
if (game::environment::is_sp())
|
||||
{
|
||||
load_scripts(path + "/ui_scripts/sp/");
|
||||
}
|
||||
else
|
||||
{
|
||||
load_scripts(path + "/ui_scripts/mp/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void stop()
|
||||
|
@ -53,4 +53,5 @@ namespace ui_scripting
|
||||
};
|
||||
|
||||
using arguments = std::vector<script_value>;
|
||||
using event_arguments = std::unordered_map<std::string, script_value>;
|
||||
}
|
||||
|
@ -273,4 +273,38 @@ namespace ui_scripting
|
||||
{
|
||||
return call_script_function(*this, arguments);
|
||||
}
|
||||
|
||||
/***************************************************************
|
||||
* Stack
|
||||
**************************************************************/
|
||||
|
||||
stack::stack()
|
||||
{
|
||||
this->state = *game::hks::lua_state;
|
||||
this->state->m_apistack.top = this->state->m_apistack.base;
|
||||
}
|
||||
|
||||
void stack::save(int num_args)
|
||||
{
|
||||
this->num_args_ = num_args;
|
||||
this->num_calls_ = state->m_numberOfCCalls;
|
||||
this->base_bottom_ = state->m_apistack.base - state->m_apistack.bottom;
|
||||
this->top_bottom_ = state->m_apistack.top - state->m_apistack.bottom;
|
||||
this->callstack_ = state->m_callStack.m_current - state->m_callStack.m_records;
|
||||
}
|
||||
|
||||
void stack::fix()
|
||||
{
|
||||
this->state->m_numberOfCCalls = this->num_calls_;
|
||||
|
||||
game::hks::closePendingUpvalues(this->state, &this->state->m_apistack.bottom[this->top_bottom_ - this->num_args_]);
|
||||
this->state->m_callStack.m_current = &this->state->m_callStack.m_records[this->callstack_];
|
||||
|
||||
this->state->m_apistack.base = &this->state->m_apistack.bottom[this->base_bottom_];
|
||||
this->state->m_apistack.top = &this->state->m_apistack.bottom[this->top_bottom_ - static_cast<uint64_t>(this->num_args_ + 1)];
|
||||
|
||||
this->state->m_apistack.bottom[this->top_bottom_].t = this->state->m_apistack.top[-1].t;
|
||||
this->state->m_apistack.bottom[this->top_bottom_].v.ptr = this->state->m_apistack.top[-1].v.ptr;
|
||||
this->state->m_apistack.top = &this->state->m_apistack.bottom[this->top_bottom_ + 1];
|
||||
}
|
||||
}
|
||||
|
@ -86,4 +86,28 @@ namespace ui_scripting
|
||||
|
||||
int ref{};
|
||||
};
|
||||
|
||||
class stack final
|
||||
{
|
||||
public:
|
||||
stack();
|
||||
|
||||
void save(int num_args);
|
||||
void fix();
|
||||
|
||||
stack(stack&&) = delete;
|
||||
stack(const stack&) = delete;
|
||||
stack& operator=(stack&&) = delete;
|
||||
stack& operator=(const stack&) = delete;
|
||||
|
||||
private:
|
||||
game::hks::lua_State* state;
|
||||
|
||||
int num_args_;
|
||||
int num_calls_;
|
||||
|
||||
uint64_t base_bottom_;
|
||||
uint64_t top_bottom_;
|
||||
uint64_t callstack_;
|
||||
};
|
||||
}
|
||||
|
@ -18,3 +18,5 @@
|
||||
|
||||
#define LUI_COMMON 309
|
||||
#define LUI_UPDATER 310
|
||||
|
||||
#define LUA_JSON 311
|
||||
|
@ -121,6 +121,8 @@ ICON_IMAGE RCDATA "resources/icon.png"
|
||||
LUI_COMMON RCDATA "resources/ui_scripts/common.lua"
|
||||
LUI_UPDATER RCDATA "resources/ui_scripts/updater.lua"
|
||||
|
||||
LUA_JSON RCDATA "resources/json.lua"
|
||||
|
||||
#endif // English (United States) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
388
src/client/resources/json.lua
Normal file
388
src/client/resources/json.lua
Normal file
@ -0,0 +1,388 @@
|
||||
--
|
||||
-- json.lua
|
||||
--
|
||||
-- Copyright (c) 2020 rxi
|
||||
--
|
||||
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
-- this software and associated documentation files (the "Software"), to deal in
|
||||
-- the Software without restriction, including without limitation the rights to
|
||||
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
-- of the Software, and to permit persons to whom the Software is furnished to do
|
||||
-- so, subject to the following conditions:
|
||||
--
|
||||
-- The above copyright notice and this permission notice shall be included in all
|
||||
-- copies or substantial portions of the Software.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
-- SOFTWARE.
|
||||
--
|
||||
|
||||
local json = { _version = "0.1.2" }
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Encode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local encode
|
||||
|
||||
local escape_char_map = {
|
||||
[ "\\" ] = "\\",
|
||||
[ "\"" ] = "\"",
|
||||
[ "\b" ] = "b",
|
||||
[ "\f" ] = "f",
|
||||
[ "\n" ] = "n",
|
||||
[ "\r" ] = "r",
|
||||
[ "\t" ] = "t",
|
||||
}
|
||||
|
||||
local escape_char_map_inv = { [ "/" ] = "/" }
|
||||
for k, v in pairs(escape_char_map) do
|
||||
escape_char_map_inv[v] = k
|
||||
end
|
||||
|
||||
|
||||
local function escape_char(c)
|
||||
return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte()))
|
||||
end
|
||||
|
||||
|
||||
local function encode_nil(val)
|
||||
return "null"
|
||||
end
|
||||
|
||||
|
||||
local function encode_table(val, stack)
|
||||
local res = {}
|
||||
stack = stack or {}
|
||||
|
||||
-- Circular reference?
|
||||
if stack[val] then error("circular reference") end
|
||||
|
||||
stack[val] = true
|
||||
|
||||
if rawget(val, 1) ~= nil or next(val) == nil then
|
||||
-- Treat as array -- check keys are valid and it is not sparse
|
||||
local n = 0
|
||||
for k in pairs(val) do
|
||||
if type(k) ~= "number" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
n = n + 1
|
||||
end
|
||||
if n ~= #val then
|
||||
error("invalid table: sparse array")
|
||||
end
|
||||
-- Encode
|
||||
for i, v in ipairs(val) do
|
||||
table.insert(res, encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "[" .. table.concat(res, ",") .. "]"
|
||||
|
||||
else
|
||||
-- Treat as an object
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= "string" then
|
||||
error("invalid table: mixed or invalid key types")
|
||||
end
|
||||
table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
|
||||
end
|
||||
stack[val] = nil
|
||||
return "{" .. table.concat(res, ",") .. "}"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function encode_string(val)
|
||||
return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
|
||||
end
|
||||
|
||||
|
||||
local function encode_number(val)
|
||||
-- Check for NaN, -inf and inf
|
||||
if val ~= val or val <= -math.huge or val >= math.huge then
|
||||
error("unexpected number value '" .. tostring(val) .. "'")
|
||||
end
|
||||
return string.format("%.14g", val)
|
||||
end
|
||||
|
||||
|
||||
local type_func_map = {
|
||||
[ "nil" ] = encode_nil,
|
||||
[ "table" ] = encode_table,
|
||||
[ "string" ] = encode_string,
|
||||
[ "number" ] = encode_number,
|
||||
[ "boolean" ] = tostring,
|
||||
}
|
||||
|
||||
|
||||
encode = function(val, stack)
|
||||
local t = type(val)
|
||||
local f = type_func_map[t]
|
||||
if f then
|
||||
return f(val, stack)
|
||||
end
|
||||
error("unexpected type '" .. t .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.encode(val)
|
||||
return ( encode(val) )
|
||||
end
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Decode
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
local parse
|
||||
|
||||
local function create_set(...)
|
||||
local res = {}
|
||||
for i = 1, select("#", ...) do
|
||||
res[ select(i, ...) ] = true
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local space_chars = create_set(" ", "\t", "\r", "\n")
|
||||
local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
|
||||
local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
|
||||
local literals = create_set("true", "false", "null")
|
||||
|
||||
local literal_map = {
|
||||
[ "true" ] = true,
|
||||
[ "false" ] = false,
|
||||
[ "null" ] = nil,
|
||||
}
|
||||
|
||||
|
||||
local function next_char(str, idx, set, negate)
|
||||
for i = idx, #str do
|
||||
if set[str:sub(i, i)] ~= negate then
|
||||
return i
|
||||
end
|
||||
end
|
||||
return #str + 1
|
||||
end
|
||||
|
||||
|
||||
local function decode_error(str, idx, msg)
|
||||
local line_count = 1
|
||||
local col_count = 1
|
||||
for i = 1, idx - 1 do
|
||||
col_count = col_count + 1
|
||||
if str:sub(i, i) == "\n" then
|
||||
line_count = line_count + 1
|
||||
col_count = 1
|
||||
end
|
||||
end
|
||||
error( string.format("%s at line %d col %d", msg, line_count, col_count) )
|
||||
end
|
||||
|
||||
|
||||
local function codepoint_to_utf8(n)
|
||||
-- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
|
||||
local f = math.floor
|
||||
if n <= 0x7f then
|
||||
return string.char(n)
|
||||
elseif n <= 0x7ff then
|
||||
return string.char(f(n / 64) + 192, n % 64 + 128)
|
||||
elseif n <= 0xffff then
|
||||
return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
elseif n <= 0x10ffff then
|
||||
return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
|
||||
f(n % 4096 / 64) + 128, n % 64 + 128)
|
||||
end
|
||||
error( string.format("invalid unicode codepoint '%x'", n) )
|
||||
end
|
||||
|
||||
|
||||
local function parse_unicode_escape(s)
|
||||
local n1 = tonumber( s:sub(1, 4), 16 )
|
||||
local n2 = tonumber( s:sub(7, 10), 16 )
|
||||
-- Surrogate pair?
|
||||
if n2 then
|
||||
return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
|
||||
else
|
||||
return codepoint_to_utf8(n1)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function parse_string(str, i)
|
||||
local res = ""
|
||||
local j = i + 1
|
||||
local k = j
|
||||
|
||||
while j <= #str do
|
||||
local x = str:byte(j)
|
||||
|
||||
if x < 32 then
|
||||
decode_error(str, j, "control character in string")
|
||||
|
||||
elseif x == 92 then -- `\`: Escape
|
||||
res = res .. str:sub(k, j - 1)
|
||||
j = j + 1
|
||||
local c = str:sub(j, j)
|
||||
if c == "u" then
|
||||
local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1)
|
||||
or str:match("^%x%x%x%x", j + 1)
|
||||
or decode_error(str, j - 1, "invalid unicode escape in string")
|
||||
res = res .. parse_unicode_escape(hex)
|
||||
j = j + #hex
|
||||
else
|
||||
if not escape_chars[c] then
|
||||
decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string")
|
||||
end
|
||||
res = res .. escape_char_map_inv[c]
|
||||
end
|
||||
k = j + 1
|
||||
|
||||
elseif x == 34 then -- `"`: End of string
|
||||
res = res .. str:sub(k, j - 1)
|
||||
return res, j + 1
|
||||
end
|
||||
|
||||
j = j + 1
|
||||
end
|
||||
|
||||
decode_error(str, i, "expected closing quote for string")
|
||||
end
|
||||
|
||||
|
||||
local function parse_number(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local s = str:sub(i, x - 1)
|
||||
local n = tonumber(s)
|
||||
if not n then
|
||||
decode_error(str, i, "invalid number '" .. s .. "'")
|
||||
end
|
||||
return n, x
|
||||
end
|
||||
|
||||
|
||||
local function parse_literal(str, i)
|
||||
local x = next_char(str, i, delim_chars)
|
||||
local word = str:sub(i, x - 1)
|
||||
if not literals[word] then
|
||||
decode_error(str, i, "invalid literal '" .. word .. "'")
|
||||
end
|
||||
return literal_map[word], x
|
||||
end
|
||||
|
||||
|
||||
local function parse_array(str, i)
|
||||
local res = {}
|
||||
local n = 1
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local x
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of array?
|
||||
if str:sub(i, i) == "]" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read token
|
||||
x, i = parse(str, i)
|
||||
res[n] = x
|
||||
n = n + 1
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "]" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local function parse_object(str, i)
|
||||
local res = {}
|
||||
i = i + 1
|
||||
while 1 do
|
||||
local key, val
|
||||
i = next_char(str, i, space_chars, true)
|
||||
-- Empty / end of object?
|
||||
if str:sub(i, i) == "}" then
|
||||
i = i + 1
|
||||
break
|
||||
end
|
||||
-- Read key
|
||||
if str:sub(i, i) ~= '"' then
|
||||
decode_error(str, i, "expected string for key")
|
||||
end
|
||||
key, i = parse(str, i)
|
||||
-- Read ':' delimiter
|
||||
i = next_char(str, i, space_chars, true)
|
||||
if str:sub(i, i) ~= ":" then
|
||||
decode_error(str, i, "expected ':' after key")
|
||||
end
|
||||
i = next_char(str, i + 1, space_chars, true)
|
||||
-- Read value
|
||||
val, i = parse(str, i)
|
||||
-- Set
|
||||
res[key] = val
|
||||
-- Next token
|
||||
i = next_char(str, i, space_chars, true)
|
||||
local chr = str:sub(i, i)
|
||||
i = i + 1
|
||||
if chr == "}" then break end
|
||||
if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
|
||||
end
|
||||
return res, i
|
||||
end
|
||||
|
||||
|
||||
local char_func_map = {
|
||||
[ '"' ] = parse_string,
|
||||
[ "0" ] = parse_number,
|
||||
[ "1" ] = parse_number,
|
||||
[ "2" ] = parse_number,
|
||||
[ "3" ] = parse_number,
|
||||
[ "4" ] = parse_number,
|
||||
[ "5" ] = parse_number,
|
||||
[ "6" ] = parse_number,
|
||||
[ "7" ] = parse_number,
|
||||
[ "8" ] = parse_number,
|
||||
[ "9" ] = parse_number,
|
||||
[ "-" ] = parse_number,
|
||||
[ "t" ] = parse_literal,
|
||||
[ "f" ] = parse_literal,
|
||||
[ "n" ] = parse_literal,
|
||||
[ "[" ] = parse_array,
|
||||
[ "{" ] = parse_object,
|
||||
}
|
||||
|
||||
|
||||
parse = function(str, idx)
|
||||
local chr = str:sub(idx, idx)
|
||||
local f = char_func_map[chr]
|
||||
if f then
|
||||
return f(str, idx)
|
||||
end
|
||||
decode_error(str, idx, "unexpected character '" .. chr .. "'")
|
||||
end
|
||||
|
||||
|
||||
function json.decode(str)
|
||||
if type(str) ~= "string" then
|
||||
error("expected argument of type string, got " .. type(str))
|
||||
end
|
||||
local res, idx = parse(str, next_char(str, 1, space_chars, true))
|
||||
idx = next_char(str, idx, space_chars, true)
|
||||
if idx <= #str then
|
||||
decode_error(str, idx, "trailing garbage")
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
|
||||
return json
|
File diff suppressed because one or more lines are too long
@ -104,6 +104,11 @@ namespace utils::io
|
||||
return std::filesystem::is_empty(directory);
|
||||
}
|
||||
|
||||
bool remove_directory(const std::string& directory)
|
||||
{
|
||||
return std::filesystem::remove_all(directory);
|
||||
}
|
||||
|
||||
std::vector<std::string> list_files(const std::string& directory)
|
||||
{
|
||||
std::vector<std::string> files;
|
||||
|
@ -16,6 +16,7 @@ namespace utils::io
|
||||
bool create_directory(const std::string& directory);
|
||||
bool directory_exists(const std::string& directory);
|
||||
bool directory_is_empty(const std::string& directory);
|
||||
bool remove_directory(const std::string& directory);
|
||||
std::vector<std::string> list_files(const std::string& directory);
|
||||
void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user