Merge branch 'main' into dev

This commit is contained in:
Federico Cecchetto 2022-01-07 19:21:24 +01:00
commit 4d9935b568
45 changed files with 5233 additions and 3229 deletions

View File

@ -12,6 +12,8 @@
## Download
**NOTE**: Cracked/Pirated versions of the game are NOT compatible with this mod, if you run such a version and have issues/crashes launching the client refer to [this issue](https://github.com/fedddddd/h2-mod/issues/111).
- **[Click here to get the latest release](https://ci.appveyor.com/api/projects/fedddddd/h2-mod/artifacts/build%2Fbin%2Fx64%2FRelease%2Fh2-mod.exe?branch=main&job=Environment%3A%20APPVEYOR_BUILD_WORKER_IMAGE%3DVisual%20Studio%202019%2C%20PREMAKE_ACTION%3Dvs2019%2C%20CI%3D1%3B%20Configuration%3A%20Release)**
- **You will need to drop this in your Call of Duty: Modern Warfare 2 Campaign Remastered installation folder. If you don't have Call of Duty: Modern Warfare 2 Campaign Remastered, get those game files first.**

2
deps/GSL vendored

@ -1 +1 @@
Subproject commit c412deb31e73c9b824abeb6619e11511b279222f
Subproject commit ebf0498363c53f0d3c403b0548212c147e3747fe

2
deps/imgui vendored

@ -1 +1 @@
Subproject commit e55dce841b4a5a738ae41745b534248596df7f5e
Subproject commit f07df31745296090ac7ecc01811f88c85766028a

2
deps/lua vendored

@ -1 +1 @@
Subproject commit 066e0f93c4901e601d93e31fb700f8f66f95feb8
Subproject commit 8dd2c912d299b84566c6f6d659336edfa9b18e9b

2
deps/protobuf vendored

@ -1 +1 @@
Subproject commit b360b9e388351aaed97f2ead98dd951fb5f1951e
Subproject commit b73f78d32cc6d1013986de76789481dcaec2d064

2
deps/sol2 vendored

@ -1 +1 @@
Subproject commit a7da2a8e889498b0c5a4ed6d9e3463af02bb6102
Subproject commit 50b62c9346750b7c2c406c9e4c546f96b0bf021d

View File

@ -0,0 +1,130 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace binding
{
namespace
{
std::vector<std::string> custom_binds = {};
utils::hook::detour cl_execute_key_hook;
utils::hook::detour key_write_bindings_to_buffer_hook;
int key_write_bindings_to_buffer_stub(int /*localClientNum*/, char* buffer, const int buffer_size)
{
auto bytes_used = 0;
const auto buffer_size_align = static_cast<std::int32_t>(buffer_size) - 4;
for (auto key_index = 0; key_index < 256; ++key_index)
{
const auto* const key_button = game::Key_KeynumToString(key_index, 0, 1);
auto value = game::playerKeys->keys[key_index].binding;
if (value && value < 100)
{
const auto len = sprintf_s(&buffer[bytes_used], (buffer_size_align - bytes_used),
"bind %s \"%s\"\n", key_button, game::command_whitelist[value]);
if (len < 0)
{
return bytes_used;
}
bytes_used += len;
}
else if (value >= 100)
{
value -= 100;
if (static_cast<size_t>(value) < custom_binds.size() && !custom_binds[value].empty())
{
const auto len = sprintf_s(&buffer[bytes_used], (buffer_size_align - bytes_used),
"bind %s \"%s\"\n", key_button, custom_binds[value].data());
if (len < 0)
{
return bytes_used;
}
bytes_used += len;
}
}
}
buffer[bytes_used] = 0;
return bytes_used;
}
int get_binding_for_custom_command(const char* command)
{
auto index = 0;
for (auto& bind : custom_binds)
{
if (bind == command)
{
return index;
}
index++;
}
custom_binds.emplace_back(command);
index = static_cast<unsigned int>(custom_binds.size()) - 1;
return index;
}
int key_get_binding_for_cmd_stub(const char* command)
{
// original binds
for (auto i = 0; i <= 100; i++)
{
if (game::command_whitelist[i] && !strcmp(command, game::command_whitelist[i]))
{
return i;
}
}
// custom binds
return 100 + get_binding_for_custom_command(command);
}
void cl_execute_key_stub(const int local_client_num, int key, const int down, const unsigned int time)
{
if (key >= 100)
{
key -= 100;
if (static_cast<size_t>(key) < custom_binds.size() && !custom_binds[key].empty())
{
game::Cbuf_AddText(local_client_num, utils::string::va("%s\n", custom_binds[key].data()));
}
return;
}
cl_execute_key_hook.invoke<void>(local_client_num, key, down, time);
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
// write all bindings to config file
key_write_bindings_to_buffer_hook.create(0x3D3840_b, key_write_bindings_to_buffer_stub);
// links a custom command to an index
utils::hook::jump(0x59AE30_b, key_get_binding_for_cmd_stub, true);
// execute custom binds
cl_execute_key_hook.create(0x3CF1E0_b, &cl_execute_key_stub);
}
};
}
REGISTER_COMPONENT(binding::component)

View File

@ -10,21 +10,14 @@
namespace branding
{
float color[4] = {0.9f, 0.9f, 0.5f, 1.f};
void draw()
{
const auto x = 15.f;
const auto y = 15.f;
const auto scale = 1.0f;
float color[4] = { 0.9f, 0.9f, 0.5f, 1.f };
const auto* text = "h2-mod";
auto* font = game::R_RegisterFont("fonts/defaultBold.otf", 22);
if (!font) return;
game::R_AddCmdDrawText(text, 0x7FFFFFFF, font, static_cast<float>(x),
y + static_cast<float>(font->pixelHeight) * scale,
scale, scale, 0.0f, color, 0);
const auto font = game::R_RegisterFont("fonts/defaultBold.otf", 22);
game::R_AddCmdDrawText("h2-mod", 0x7FFFFFFF, font, 15.f,
15.f + static_cast<float>(font->pixelHeight),
1.f, 1.f, 0.f, color, 0);
}
class component final : public component_interface

View File

@ -4,9 +4,13 @@
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "game/scripting/execution.hpp"
#include "command.hpp"
#include "scheduler.hpp"
#include "game_console.hpp"
#include "chat.hpp"
#include "fastfiles.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
@ -31,15 +35,6 @@ namespace command
}
}
void enum_assets(const game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, const bool includeOverride)
{
game::DB_EnumXAssets_Internal(type, static_cast<void(*)(game::XAssetHeader, void*)>([](game::XAssetHeader header, void* data)
{
const auto& cb = *static_cast<const std::function<void(game::XAssetHeader)>*>(data);
cb(header);
}), &callback, includeOverride);
}
game::dvar_t* dvar_command_stub()
{
const params args;
@ -154,7 +149,7 @@ namespace command
{
utils::hook::jump(0x5A74F0_b, dvar_command_stub, true);
add("quit", game::Com_Quit_f);
add("quit", game::Quit);
add("startmap", [](const params& params)
{
@ -200,7 +195,7 @@ namespace command
game_console::print(game_console::con_type_info, "Listing assets in pool %s\n", game::g_assetNames[type]);
enum_assets(type, [type](const game::XAssetHeader header)
fastfiles::enum_assets(type, [type](const game::XAssetHeader header)
{
const auto asset = game::XAsset{type, header};
const auto* const asset_name = game::DB_GetXAssetName(&asset);
@ -316,16 +311,141 @@ namespace command
return;
}
auto ps = game::g_entities[0].client;
const auto wp = game::G_GetWeaponForName(params.get(1));
if (wp)
try
{
if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0, 0))
const auto arg = params.get(1);
const scripting::entity player = scripting::call("getentbynum", {0}).as<scripting::entity>();
auto ps = game::g_entities[0].client;
if (arg == "ammo"s)
{
game::G_InitializeAmmo(ps, wp, 0);
game::G_SelectWeapon(0, wp);
const auto weapon = player.call("getcurrentweapon").as<std::string>();
player.call("givemaxammo", {weapon});
}
else if (arg == "allammo"s)
{
const auto weapons = player.call("getweaponslist").as<scripting::array>();
for (auto i = 0; i < weapons.size(); i++)
{
player.call("givemaxammo", {weapons[i]});
}
}
else if (arg == "health"s)
{
if (params.size() > 3)
{
const auto amount = atoi(params.get(2));
const auto health = player.get("health").as<int>();
player.set("health", {health + amount});
}
else
{
const auto amount = game::Dvar_FindVar("g_player_maxhealth")->current.integer;
player.set("health", {amount});
}
}
else if (arg == "all"s)
{
const auto type = game::XAssetType::ASSET_TYPE_WEAPON;
fastfiles::enum_assets(type, [&player, type](const game::XAssetHeader header)
{
const auto asset = game::XAsset{type, header};
const auto* const asset_name = game::DB_GetXAssetName(&asset);
player.call("giveweapon", {asset_name});
}, true);
}
else
{
const auto wp = game::G_GetWeaponForName(arg);
if (wp)
{
if (game::G_GivePlayerWeapon(ps, wp, 0, 0, 0, 0))
{
game::G_InitializeAmmo(ps, wp, 0);
game::G_SelectWeapon(0, wp);
}
}
else
{
game::CG_GameMessage(0, "Weapon does not exist");
}
}
}
catch (...)
{
}
});
add("dropweapon", [](const params& params)
{
if (!game::SV_Loaded())
{
return;
}
try
{
const scripting::entity player = scripting::call("getentbynum", {0}).as<scripting::entity>();
const auto weapon = player.call("getcurrentweapon");
player.call("dropitem", {weapon});
}
catch (...)
{
}
});
add("take", [](const params& params)
{
if (!game::SV_Loaded())
{
return;
}
if (params.size() < 2)
{
game::CG_GameMessage(0, "You did not specify a weapon name");
return;
}
const auto weapon = params.get(1);
try
{
const scripting::entity player = scripting::call("getentbynum", {0}).as<scripting::entity>();
if (weapon == "all"s)
{
player.call("takeallweapons");
}
else
{
player.call("takeweapon", {weapon});
}
}
catch (...)
{
}
});
add("kill", [](const params& params)
{
if (!game::SV_Loaded())
{
return;
}
scheduler::once([]()
{
try
{
const scripting::entity player = scripting::call("getentbynum", {0}).as<scripting::entity>();
player.call("kill");
}
catch (...)
{
}
}, scheduler::pipeline::server);
});
}
};

View File

@ -15,6 +15,7 @@ namespace console
namespace
{
std::thread console_thread;
bool kill = false;
}
class component final : public component_interface
@ -27,14 +28,25 @@ namespace console
console_thread = utils::thread::create_named_thread("Console", []()
{
std::string cmd;
while (true)
while (!kill)
{
std::getline(std::cin, cmd);
game_console::add(cmd.data(), false);
// to do: get input without blocking the thread
std::this_thread::sleep_for(1ms);
}
std::this_thread::yield();
});
}
void pre_destroy() override
{
kill = true;
if (console_thread.joinable())
{
console_thread.join();
}
}
};
}

View File

@ -28,7 +28,8 @@ namespace discord
discord_presence.startTimestamp = 0;
discord_presence.largeImageKey = "h2";
const auto background_index = static_cast<int>(game::Sys_Milliseconds() / 300000) % 10;
discord_presence.largeImageKey = utils::string::va("bg_%i", background_index);
}
else
{

View File

@ -235,8 +235,7 @@ namespace exception
void post_unpack() override
{
dvars::cg_legacyCrashHandling = dvars::register_bool("cg_legacyCrashHandling", false,
game::DVAR_FLAG_SAVED, "Disable new crash handling");
dvars::cg_legacyCrashHandling = dvars::register_bool("cg_legacyCrashHandling", false, game::DVAR_FLAG_SAVED);
}
};
}

View File

@ -27,6 +27,15 @@ namespace fastfiles
}
}
void enum_assets(const game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, const bool includeOverride)
{
game::DB_EnumXAssets_Internal(type, static_cast<void(*)(game::XAssetHeader, void*)>([](game::XAssetHeader header, void* data)
{
const auto& cb = *static_cast<const std::function<void(game::XAssetHeader)>*>(data);
cb(header);
}), &callback, includeOverride);
}
std::string get_current_fastfile()
{
std::string fastfile_copy;

View File

@ -4,5 +4,6 @@
namespace fastfiles
{
void enum_assets(const game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, const bool includeOverride);
std::string get_current_fastfile();
}

View File

@ -19,8 +19,6 @@ namespace fps
{
namespace
{
auto lastframe = std::chrono::high_resolution_clock::now();
game::dvar_t* cg_drawFps;
game::dvar_t* cg_drawSpeed;
@ -44,9 +42,78 @@ namespace fps
float screen_max[2];
int history_count = 20;
int history[20] = { 0 };
int index;
std::deque<float> speed_history;
utils::hook::detour sub_7C55D0_hook;
struct cg_perf_data
{
std::chrono::time_point<std::chrono::steady_clock> perf_start;
std::int32_t current_ms;
std::int32_t previous_ms;
std::int32_t frame_ms;
std::int32_t history[32];
std::int32_t count;
std::int32_t index;
std::int32_t instant;
std::int32_t total;
float average;
float variance;
std::int32_t min;
std::int32_t max;
};
cg_perf_data cg_perf{};
void perf_calc_fps(cg_perf_data* data, const std::int32_t value)
{
data->history[data->index % 32] = value;
data->instant = value;
data->min = 0x7FFFFFFF;
data->max = 0;
data->average = 0.0f;
data->variance = 0.0f;
data->total = 0;
for (auto i = 0; i < data->count; ++i)
{
const std::int32_t idx = (data->index - i) % 32;
if (idx < 0)
{
break;
}
data->total += data->history[idx];
if (data->min > data->history[idx])
{
data->min = data->history[idx];
}
if (data->max < data->history[idx])
{
data->max = data->history[idx];
}
}
data->average = static_cast<float>(data->total) / static_cast<float>(data->count);
++data->index;
}
void perf_update()
{
cg_perf.count = 32;
cg_perf.current_ms = static_cast<std::int32_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now() - cg_perf.perf_start).count());
cg_perf.frame_ms = cg_perf.current_ms - cg_perf.previous_ms;
cg_perf.previous_ms = cg_perf.current_ms;
perf_calc_fps(&cg_perf, cg_perf.frame_ms);
sub_7C55D0_hook.invoke<void>();
}
void check_resize()
{
@ -56,23 +123,9 @@ namespace fps
float relative(int x)
{
return ((float)x / 1000) * screen_max[0];
return (static_cast<float>(x) / 1000) * screen_max[0];
}
int average_fps()
{
auto total = 0;
for (auto i = 0; i < history_count; i++)
{
total += history[i];
}
return total / history_count;
}
std::deque<float> speed_history;
void draw_line(float x, float y, float width, float height)
{
game::R_AddCmdDrawStretchPic(x, y, width, height, 0.0f, 0.0f, 0.0f, 0.0f,
@ -83,17 +136,19 @@ namespace fps
void draw_speed()
{
if (cg_drawSpeed->current.integer < 1)
if (!cg_drawSpeed->current.enabled)
{
return;
}
const auto speed = (float)sqrt(pow(game::g_entities[0].client->velocity[0], 2) +
pow(game::g_entities[0].client->velocity[1], 2) +
pow(game::g_entities[0].client->velocity[2], 2));
const auto speed = static_cast<float>(sqrt(
pow(game::g_entities[0].client->velocity[0], 2) +
pow(game::g_entities[0].client->velocity[1], 2) +
pow(game::g_entities[0].client->velocity[2], 2)
));
const auto font = speed_font;
const auto speed_string = utils::string::va("%i\n", (int)speed);
const auto speed_string = utils::string::va("%i\n", static_cast<int>(speed));
const auto width = game::R_TextWidth(speed_string, 0x7FFFFFFF, font);
@ -128,21 +183,22 @@ namespace fps
void draw_speed_graph()
{
if (cg_speedGraph->current.integer < 1)
if (!cg_speedGraph->current.enabled)
{
return;
}
const auto speed = (float)sqrt(pow(game::g_entities[0].client->velocity[0], 2) +
pow(game::g_entities[0].client->velocity[1], 2) +
pow(game::g_entities[0].client->velocity[2], 2));
const auto speed = static_cast<float>(sqrt(
pow(game::g_entities[0].client->velocity[0], 2) +
pow(game::g_entities[0].client->velocity[1], 2) +
pow(game::g_entities[0].client->velocity[2], 2)
));
const auto base_width = relative(cg_speedGraphWidth->current.integer);
const auto base_height = relative(cg_speedGraphHeight->current.integer);
const auto max = static_cast<int>(base_width);
const auto max = (int)base_width;
if (speed_history.size() > max)
if (static_cast<int>(speed_history.size()) > max)
{
speed_history.pop_front();
}
@ -156,36 +212,28 @@ namespace fps
const auto base_x = relative(cg_speedGraphX->current.integer);
const auto base_y = screen_max[1] - relative(cg_speedGraphY->current.integer);
const auto width = 1.f;
draw_box(base_x, base_y - base_height - 4.f, base_width + 4.f, base_height + 4.f, cg_speedGraphBackgroundColor->current.vector);
draw_box(base_x, base_y - base_height - 4.f, base_width + 4.f,
base_height + 4.f, cg_speedGraphBackgroundColor->current.vector);
const auto diff = max - speed_history.size();
const auto diff = max - static_cast<int>(speed_history.size());
const auto start = diff >= 0
? 0
: diff * - 1;
const auto offset = diff > 0
? diff
: 0;
for (auto i = start; i < speed_history.size(); i++)
for (auto i = 0; i < speed_history.size(); i++)
{
const auto percentage = std::min(speed_history[i] / 1000.f, 1.f);
const auto height = percentage * base_height;
const auto x = base_x + (float)(offset + i) * width + 2.f;
const auto x = base_x + static_cast<float>(diff + i) * width + 2.f;
const auto y = base_y - height - 2.f;
draw_line(x, y, width, height);
}
const auto speed_string = utils::string::va("%i\n", (int)speed);
const auto speed_string = utils::string::va("%i\n", static_cast<int>(speed));
const auto font_height = relative(20);
const auto font = game::R_RegisterFont("fonts/fira_mono_regular.ttf", (int)font_height);
const auto font = game::R_RegisterFont("fonts/fira_mono_regular.ttf", static_cast<int>(font_height));
const auto text_x = base_x + relative(5);
const auto text_y = base_y - (base_height / 2.f) + (font_height / 2.f);
@ -201,20 +249,11 @@ namespace fps
return;
}
const auto now = std::chrono::high_resolution_clock::now();
const auto frametime = now - lastframe;
lastframe = now;
const auto fps = static_cast<std::int32_t>(static_cast<float>(1000.0f / static_cast<float>(cg_perf.
average))
+ 9.313225746154785e-10);
const int fps = (int)(1000000000 / (int)frametime.count());
if (index >= history_count)
{
index = 0;
}
history[index++] = fps;
const auto fps_string = utils::string::va("%i", average_fps());
const auto fps_string = utils::string::va("%i", fps);
const auto x = screen_max[0] - 15.f - game::R_TextWidth(fps_string, 0x7FFFFFFF, fps_font);
game::R_AddCmdDrawText(fps_string, 0x7FFFFFFF, fps_font, x, 35.f, 1.0f, 1.0f, 0.0f,
@ -262,10 +301,12 @@ namespace fps
public:
void post_unpack() override
{
cg_drawSpeed = dvars::register_int("cg_drawSpeed", 0, 0, 2, game::DVAR_FLAG_SAVED);
cg_drawFps = dvars::register_int("cg_drawFPS", 0, 0, 4, game::DVAR_FLAG_SAVED, false);
sub_7C55D0_hook.create(0x7C55D0_b, perf_update);
cg_speedGraph = dvars::register_bool("cg_speedGraph", false, game::DVAR_FLAG_SAVED);
cg_drawSpeed = dvars::register_bool("cg_drawSpeed", 0, game::DVAR_FLAG_SAVED);
cg_drawFps = dvars::register_int("cg_drawFPS", 0, 0, 4, game::DVAR_FLAG_SAVED);
cg_speedGraph = dvars::register_bool("cg_speedGraph", 0, game::DVAR_FLAG_SAVED);
cg_speedGraphColor = dvars::register_vec4("cg_speedGraphColor", 1.f, 0.f, 0.f, 1.0f, 0.f, 1.f, game::DVAR_FLAG_SAVED);
cg_speedGraphFontColor = dvars::register_vec4("cg_speedGraphFontColor", 1.f, 1.f, 1.f, 1.f, 0.f, 1.f, game::DVAR_FLAG_SAVED);

View File

@ -55,7 +55,7 @@ namespace game_console
std::deque<std::string> history;
std::string fixed_input;
std::vector<std::string> matches;
std::unordered_set<std::string> matches;
float color_white[4] = {1.0f, 1.0f, 1.0f, 1.0f};
float color_h2[4] = {0.9f, 0.9f, 0.5f, 1.0f};
@ -242,11 +242,12 @@ namespace game_console
}
else if (matches.size() == 1)
{
auto* const dvar = game::Dvar_FindVar(matches[0].data());
const auto first = *matches.begin();
auto* const dvar = game::Dvar_FindVar(first.data());
const auto line_count = dvar ? 2 : 1;
draw_hint_box(line_count, dvars::con_inputHintBoxColor->current.vector);
draw_hint_text(0, matches[0].data(),
draw_hint_text(0, first.data(),
dvar
? dvars::con_inputDvarMatchColor->current.vector
: dvars::con_inputCmdMatchColor->current.vector);
@ -262,7 +263,7 @@ namespace game_console
dvars::con_inputDvarInactiveValueColor->current.vector, offset);
}
strncpy_s(con.globals.auto_complete_choice, matches[0].data(), 64);
strncpy_s(con.globals.auto_complete_choice, first.data(), 64);
con.globals.may_auto_complete = true;
}
else if (matches.size() > 1)
@ -271,23 +272,26 @@ namespace game_console
const auto offset = (con.screen_max[0] - con.globals.x) / 2.5f;
for (size_t i = 0; i < matches.size(); i++)
auto index = 0;
for (const auto& match : matches)
{
auto* const dvar = game::Dvar_FindVar(matches[i].data());
auto* const dvar = game::Dvar_FindVar(match.data());
draw_hint_text(static_cast<int>(i), matches[i].data(),
draw_hint_text(static_cast<int>(index), match.data(),
dvar
? dvars::con_inputDvarMatchColor->current.vector
: dvars::con_inputCmdMatchColor->current.vector);
if (dvar)
{
draw_hint_text(static_cast<int>(i), game::Dvar_ValueToString(dvar, nullptr, &dvar->current),
draw_hint_text(static_cast<int>(index), game::Dvar_ValueToString(dvar, nullptr, &dvar->current),
dvars::con_inputDvarValueColor->current.vector, offset);
}
index++;
}
strncpy_s(con.globals.auto_complete_choice, matches[0].data(), 64);
strncpy_s(con.globals.auto_complete_choice, matches.begin()->data(), 64);
con.globals.may_auto_complete = true;
}
}
@ -493,6 +497,15 @@ namespace game_console
void execute(const char* cmd)
{
if (game::CL_IsCgameInitialized())
{
std::string cmd_ = cmd;
scheduler::once([cmd_]()
{
scripting::notify(*game::levelEntityId, "console_command", {cmd_});
}, scheduler::pipeline::server);
}
game::Cbuf_AddText(0, utils::string::va("%s \n", cmd));
}
@ -638,16 +651,16 @@ namespace game_console
return true;
}
void find_matches(std::string input, std::vector<std::string>& suggestions, const bool exact)
void find_matches(std::string input, std::unordered_set<std::string>& suggestions, const bool exact)
{
input = utils::string::to_lower(input);
for (const auto& dvar : dvars::dvar_list)
{
auto name = utils::string::to_lower(dvar);
if (match_compare(input, name, exact))
if (game::Dvar_FindVar(name.data()) && match_compare(input, name, exact))
{
suggestions.push_back(dvar);
suggestions.insert(dvar);
}
if (exact && suggestions.size() > 1)
@ -658,7 +671,7 @@ namespace game_console
if (suggestions.size() == 0 && game::Dvar_FindVar(input.data()))
{
suggestions.push_back(input.data());
suggestions.insert(input.data());
}
game::cmd_function_s* cmd = (*game::cmd_functions);
@ -670,7 +683,7 @@ namespace game_console
if (match_compare(input, name, exact))
{
suggestions.push_back(cmd->name);
suggestions.insert(cmd->name);
}
if (exact && suggestions.size() > 1)

View File

@ -16,7 +16,7 @@ namespace game_console
bool console_char_event(int local_client_num, int key);
bool console_key_event(int local_client_num, int key, int down);
void find_matches(std::string input, std::vector<std::string>& suggestions, const bool exact);
void find_matches(std::string input, std::unordered_set<std::string>& suggestions, const bool exact);
void execute(const char* cmd);
void clear_console();
void add(const std::string& cmd, bool print_ = true);

View File

@ -0,0 +1,87 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include <utils/hook.hpp>
namespace gameplay
{
namespace
{
utils::hook::detour pm_player_trace_hook;
utils::hook::detour pm_crashland_hook;
void pm_player_trace_stub(game::pmove_t* pm, game::trace_t* trace, const float* f3,
const float* f4, const game::Bounds* bounds, int a6, int a7)
{
pm_player_trace_hook.invoke<void>(pm, trace, f3, f4, bounds, a6, a7);
// By setting startsolid to false we allow the player to clip through solid objects above their head
if (dvars::g_enableElevators->current.enabled)
{
trace->startsolid = false;
}
}
void pm_trace_stub(utils::hook::assembler& a)
{
const auto stand = a.newLabel();
const auto allsolid = a.newLabel();
a.call(rsi); // Game code
a.push(rax);
a.mov(rax, qword_ptr(reinterpret_cast<int64_t>(&dvars::g_enableElevators)));
a.mov(al, byte_ptr(rax, 0x10));
a.cmp(al, 1);
a.pop(rax);
a.jz(stand); // Always stand up
a.cmp(byte_ptr(rsp, 0x89), 0); // Game code trace[0].allsolid == false
a.jnz(allsolid);
a.bind(stand);
a.jmp(0x6878CD_b);
a.bind(allsolid);
a.jmp(0x6878D4_b);
}
void pm_crashland_stub(void* ps, void* pm)
{
if (dvars::jump_enableFallDamage->current.enabled)
{
pm_crashland_hook.invoke<void>(ps, pm);
}
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
dvars::g_enableElevators = dvars::register_bool("g_enableElevators", false, game::DvarFlags::DVAR_FLAG_NONE);
dvars::jump_enableFallDamage = dvars::register_bool("jump_enableFallDamage", true, game::DVAR_FLAG_REPLICATED);
// Influence PM_JitterPoint code flow so the trace->startsolid checks are 'ignored'
pm_player_trace_hook.create(0x068F0A0_b, &pm_player_trace_stub);
// If g_enableElevators is 1 the 'ducked' flag will always be removed from the player state
utils::hook::jump(0x6878C1_b, utils::hook::assemble(pm_trace_stub), true);
pm_crashland_hook.create(0x688A20_b, pm_crashland_stub);
dvars::register_float("jump_height", 39, 0, 1000, game::DVAR_FLAG_REPLICATED);
dvars::register_float("g_gravity", 800, 1, 1000, game::DVAR_FLAG_REPLICATED);
dvars::register_int("g_speed", 190, 0, 1000, game::DVAR_FLAG_REPLICATED);
}
};
}
REGISTER_COMPONENT(gameplay::component)

View File

@ -19,8 +19,23 @@ namespace gui
namespace
{
utils::concurrency::container<std::vector<std::function<void()>>> on_frame_callbacks;
utils::concurrency::container<std::vector<notification_t>> notifications;
struct frame_callback
{
std::function<void()> callback;
bool always;
};
struct event
{
HWND hWnd;
UINT msg;
WPARAM wParam;
LPARAM lParam;
};
utils::concurrency::container<std::vector<frame_callback>> on_frame_callbacks;
utils::concurrency::container<std::deque<notification_t>> notifications;
utils::concurrency::container<std::vector<event>> event_queue;
ID3D11Device* device;
ID3D11DeviceContext* device_context;
@ -38,13 +53,29 @@ namespace gui
initialized = true;
}
void run_event_queue()
{
event_queue.access([](std::vector<event>& queue)
{
for (const auto& event : queue)
{
ImGui_ImplWin32_WndProcHandler(event.hWnd, event.msg, event.wParam, event.lParam);
}
queue.clear();
});
}
void new_gui_frame()
{
ImGui::GetIO().MouseDrawCursor = toggled;
*game::keyCatchers |= 0x10 * toggled;
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
run_event_queue();
*game::keyCatchers |= 0x10;
ImGui::NewFrame();
}
void end_gui_frame()
@ -61,29 +92,24 @@ namespace gui
void show_notifications()
{
static auto window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav |
ImGuiWindowFlags_NoMove;
static const auto window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav |
ImGuiWindowFlags_NoMove;
notifications.access([](std::vector<notification_t>& notifications_)
notifications.access([](std::deque<notification_t>& notifications_)
{
auto index = 0;
for (auto i = notifications_.begin(); i != notifications_.end(); ++i)
for (auto i = notifications_.begin(); i != notifications_.end();)
{
const auto now = std::chrono::high_resolution_clock::now();
if (now - i->creation_time >= i->duration)
{
notifications_.erase(i--);
i = notifications_.erase(i);
continue;
}
const auto title = i->title.size() <= 34
? i->title
: i->title.substr(0, 31) + "...";
const auto text = i->text.size() <= 34
? i->text
: i->text.substr(0, 31) + "...";
const auto title = utils::string::truncate(i->title, 34, "...");
const auto text = utils::string::truncate(i->text, 34, "...");
ImGui::SetNextWindowSizeConstraints(ImVec2(250, 50), ImVec2(250, 50));
ImGui::SetNextWindowBgAlpha(0.6f);
@ -96,30 +122,42 @@ namespace gui
ImGui::End();
index++;
++i;
++index;
}
});
}
void gui_draw()
void menu_checkbox(const std::string& name, const std::string& menu)
{
show_notifications();
ImGui::Checkbox(name.data(), &enabled_menus[menu]);
}
on_frame_callbacks.access([](std::vector<std::function<void()>>& callbacks)
void run_frame_callbacks()
{
on_frame_callbacks.access([](std::vector<frame_callback>& callbacks)
{
for (const auto& callback : callbacks)
{
callback();
if (callback.always || toggled)
{
callback.callback();
}
}
});
}
void draw_main_menu_bar()
{
if (ImGui::BeginMainMenuBar())
{
if (ImGui::BeginMenu("Windows"))
{
ImGui::Checkbox("Asset list", &enabled_menus["asset_list"]);
ImGui::Checkbox("Entity list", &enabled_menus["entity_list"]);
ImGui::Checkbox("Console", &enabled_menus["console"]);
menu_checkbox("Asset list", "asset_list");
menu_checkbox("Entity list", "entity_list");
menu_checkbox("Console", "console");
menu_checkbox("Script console", "script_console");
menu_checkbox("Debug", "debug");
ImGui::EndMenu();
}
@ -134,11 +172,10 @@ namespace gui
{
initialize_gui_context();
}
if (toggled)
else
{
new_gui_frame();
gui_draw();
run_frame_callbacks();
end_gui_frame();
}
}
@ -177,9 +214,12 @@ namespace gui
utils::hook::detour wnd_proc_hook;
LRESULT wnd_proc_stub(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (toggled && ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam))
if (wParam != VK_ESCAPE && toggled)
{
return TRUE;
event_queue.access([hWnd, msg, wParam, lParam](std::vector<event>& queue)
{
queue.push_back({hWnd, msg, wParam, lParam});
});
}
return wnd_proc_hook.invoke<LRESULT>(hWnd, msg, wParam, lParam);
@ -213,11 +253,11 @@ namespace gui
return !toggled;
}
void on_frame(const std::function<void()>& callback)
void on_frame(const std::function<void()>& callback, bool always)
{
on_frame_callbacks.access([callback](std::vector<std::function<void()>>& callbacks)
on_frame_callbacks.access([always, callback](std::vector<frame_callback>& callbacks)
{
callbacks.push_back(callback);
callbacks.push_back({callback, always});
});
}
@ -234,9 +274,9 @@ namespace gui
notification.duration = duration;
notification.creation_time = std::chrono::high_resolution_clock::now();
notifications.access([notification](std::vector<notification_t>& notifications_)
notifications.access([notification](std::deque<notification_t>& notifications_)
{
notifications_.insert(notifications_.begin(), notification);
notifications_.push_front(notification);
});
}
@ -263,11 +303,16 @@ namespace gui
{
utils::hook::jump(0x7A14C4_b, utils::hook::assemble(dxgi_swap_chain_present_stub), true);
wnd_proc_hook.create(0x650F10_b, wnd_proc_stub);
on_frame([]()
{
show_notifications();
draw_main_menu_bar();
});
}
void pre_destroy() override
{
ImGui_ImplDX11_Shutdown();
ImGui_ImplWin32_Shutdown();
ImGui::DestroyContext();
}

View File

@ -16,7 +16,7 @@ namespace gui
bool gui_char_event(const int local_client_num, const int key);
bool gui_mouse_event(const int local_client_num, int x, int y);
void on_frame(const std::function<void()>& callback);
void on_frame(const std::function<void()>& callback, bool always = false);
bool is_menu_open(const std::string& name);
void notification(const std::string& title, const std::string& text, const std::chrono::milliseconds duration = 3s);
void copy_to_clipboard(const std::string& text);

View File

@ -7,6 +7,7 @@
#include "scheduler.hpp"
#include "command.hpp"
#include "gui.hpp"
#include "fastfiles.hpp"
#include <utils/string.hpp>
#include <utils/hook.hpp>
@ -16,16 +17,8 @@ namespace asset_list
namespace
{
bool shown_assets[game::XAssetType::ASSET_TYPE_COUNT];
std::string filter_buffer{};
void enum_assets(const game::XAssetType type, const std::function<void(game::XAssetHeader)>& callback, const bool includeOverride)
{
game::DB_EnumXAssets_Internal(type, static_cast<void(*)(game::XAssetHeader, void*)>([](game::XAssetHeader header, void* data)
{
const auto& cb = *static_cast<const std::function<void(game::XAssetHeader)>*>(data);
cb(header);
}), &callback, includeOverride);
}
std::string asset_type_filter;
std::string assets_name_filter[game::XAssetType::ASSET_TYPE_COUNT];
void on_frame()
{
@ -37,18 +30,21 @@ namespace asset_list
{
ImGui::Begin("Asset list", &gui::enabled_menus["asset_list"]);
ImGui::InputText("asset type", &filter_buffer);
ImGui::InputText("asset type", &asset_type_filter);
ImGui::BeginChild("asset type list");
for (auto i = 0; i < game::XAssetType::ASSET_TYPE_COUNT; i++)
{
const auto name = game::g_assetNames[i];
const auto type = static_cast<game::XAssetType>(i);
if (utils::string::find_lower(name, filter_buffer))
if (utils::string::find_lower(name, asset_type_filter))
{
ImGui::Checkbox(name, &shown_assets[type]);
}
}
ImGui::EndChild();
ImGui::End();
}
@ -65,25 +61,21 @@ namespace asset_list
ImGui::SetNextWindowSizeConstraints(ImVec2(500, 500), ImVec2(1000, 1000));
ImGui::Begin(name, &shown_assets[type]);
static char filter[0x200]{};
ImGui::InputText("asset name", filter, IM_ARRAYSIZE(filter));
ImGui::InputText("asset name", &assets_name_filter[type]);
ImGui::BeginChild("assets list");
enum_assets(type, [type](const game::XAssetHeader header)
fastfiles::enum_assets(type, [type](const game::XAssetHeader header)
{
const auto asset = game::XAsset{type, header};
const auto* const asset_name = game::DB_GetXAssetName(&asset);
if (!utils::string::find_lower(asset_name, filter))
{
return;
}
if (ImGui::Button(asset_name))
if (utils::string::find_lower(asset_name, assets_name_filter[type]) && ImGui::Button(asset_name))
{
gui::copy_to_clipboard(asset_name);
}
}, true);
ImGui::EndChild();
ImGui::End();
}
}

View File

@ -19,9 +19,9 @@ namespace gui_console
{
bool auto_scroll = true;
int history_index = -1;
std::string input{};
std::string filter{};
std::vector<std::string> matches{};
std::string input;
std::string filter;
std::unordered_set<std::string> matches;
int input_text_edit(ImGuiInputTextCallbackData* data)
{
@ -41,9 +41,9 @@ namespace gui_console
game_console::find_matches(text, matches, false);
}
if (matches.size() < 24)
if (matches.size() < 24 && matches.size() > 0)
{
const auto match = matches[0].data();
const auto match = matches.begin()->data();
data->DeleteChars(0, data->BufTextLen);
data->InsertChars(0, match, match + strlen(match));
}
@ -96,8 +96,8 @@ namespace gui_console
{
std::string text{};
const auto history = game_console::get_output();
for (const auto& line : history)
const auto output = game_console::get_output();
for (const auto& line : output)
{
if (utils::string::find_lower(line, filter))
{
@ -116,17 +116,17 @@ namespace gui_console
void on_frame()
{
auto* open = &gui::enabled_menus["console"];
if (!*open)
if (!gui::enabled_menus["console"])
{
return;
}
auto filtered_text = get_filtered_text();
static auto input_text_flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion |
ImGuiInputTextFlags_CallbackHistory;
const auto filtered_text = get_filtered_text();
const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
static const auto input_text_flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion |
ImGuiInputTextFlags_CallbackHistory;
ImGui::Begin("Console", open);
ImGui::Begin("Console", &gui::enabled_menus["console"]);
if (ImGui::BeginPopup("Options"))
{
@ -157,10 +157,16 @@ namespace gui_console
ImGui::SameLine();
ImGui::InputText("Filter", &filter);
const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
ImGui::BeginChild("console_scroll", ImVec2(0, -footer_height_to_reserve), false);
ImGui::Text(filtered_text.data(), ImVec2(-1, -1));
const auto output = game_console::get_output();
for (const auto& line : output)
{
if (utils::string::find_lower(line, filter))
{
ImGui::Text(line.data(), ImVec2(-1, -1));
}
}
if (auto_scroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
{
@ -183,10 +189,10 @@ namespace gui_console
}
}
game_console::add(input.data());
input.clear();
ImGui::SetKeyboardFocusHere(-1);
game_console::add(input.data());
history_index = -1;
input.clear();
}
ImGui::End();

View File

@ -5,9 +5,16 @@
#include "game/dvars.hpp"
#include "scheduler.hpp"
#include "command.hpp"
#include "gui.hpp"
#include "game/scripting/lua/context.hpp"
#include "game/scripting/lua/engine.hpp"
#include "game/scripting/execution.hpp"
#include <utils/string.hpp>
#include <utils/hook.hpp>
#include <utils/concurrency.hpp>
namespace gui_debug
{

View File

@ -19,15 +19,15 @@ namespace entity_list
{
namespace
{
game::dvar_t* sv_running = nullptr;
enum entity_type
{
type_any,
entity,
actor,
spawner,
weapon,
vehicle,
node,
vehicle_node,
count,
};
@ -51,27 +51,27 @@ namespace entity_list
struct entity_info_t
{
unsigned int id;
unsigned int num;
game::scr_entref_t entref;
std::unordered_map<std::string, std::string> fields;
};
struct filters_t
{
bool filter_by_range{};
float range{};
entity_team team{};
entity_type type{};
bool filter_by_range;
float range;
entity_team team;
entity_type type;
std::vector<std::pair<std::string, std::string>> fields;
};
struct data_t
{
bool auto_update{};
bool force_update{};
bool auto_update;
bool force_update;
filters_t filters;
std::chrono::milliseconds interval{};
std::chrono::high_resolution_clock::time_point last_call{};
std::vector<entity_info_t> entity_info{};
std::chrono::milliseconds interval;
std::chrono::high_resolution_clock::time_point last_call;
std::vector<entity_info_t> entity_info;
std::vector<std::function<void()>> tasks;
std::unordered_map<std::string, bool> selected_fields =
{
@ -279,103 +279,71 @@ namespace entity_list
};
};
utils::concurrency::container<data_t> data_;
unsigned int selected_entity{};
utils::concurrency::container<data_t> data_{};
game::scr_entref_t selected_entity{};
bool set_field_window{};
bool selected_fields_window{};
int selected_type = game::SCRIPT_INTEGER;
bool is_sv_running()
{
if (!sv_running)
{
return false;
}
return sv_running->current.enabled;
}
bool verify_entity(unsigned int id)
{
const auto type = game::scr_VarGlob->objectVariableValue[id].w.type;
return type == game::SCRIPT_ENTITY;
}
std::string field_filter{};
std::string field_name_buffer{};
std::string field_value_buffer{};
std::string vector_input[3]{};
std::optional<scripting::array> get_entity_array(const entity_type type, const entity_team team)
{
const auto value = scripting::call("getentarray");
if (!value.is<scripting::array>())
if (type == entity_type::entity)
{
return {};
}
const auto all = value.as<scripting::array>();
if (type == entity_type::type_any)
{
return {all};
return {scripting::call("getentarray").as<scripting::array>()};
}
if (type == entity_type::actor)
{
scripting::array result{};
for (unsigned int i = 0; i < all.size(); i++)
const auto actors = scripting::call("getaiarray").as<scripting::array>();
for (auto i = 0; i < actors.size(); i++)
{
const auto raw = all[i].get_raw();
if (raw.type != game::SCRIPT_OBJECT)
{
continue;
}
const auto entity = actors[i].as<scripting::entity>();
const auto team_string = entity.get("team").as<std::string>();
if (!verify_entity(raw.u.uintValue))
{
continue;
}
const auto entity = all[i].as<scripting::entity>();
const auto classname_value = entity.get("classname");
if (!classname_value.is<std::string>())
{
continue;
}
const auto team_value = entity.get("team");
if (!team_value.is<std::string>())
{
continue;
}
const auto classname = classname_value.as<std::string>();
const auto team_ = team_value.as<std::string>();
if (utils::string::find_lower(classname, "actor_") &&
(team == entity_team::team_any || team_ == team_names[team]))
if (team == entity_team::team_any || team_string == team_names[team])
{
result.push(entity);
}
}
return result;
return {result};
}
if (type == entity_type::spawner && team == entity_team::team_any)
{
return scripting::call("getspawnerarray").as<scripting::array>();
return {scripting::call("getspawnerarray").as<scripting::array>()};
}
if (type == entity_type::spawner)
{
return scripting::call("getspawnerteamarray", {team_names[team]}).as<scripting::array>();
return {scripting::call("getspawnerteamarray", {team_names[team]}).as<scripting::array>()};
}
if (type == entity_type::weapon)
{
return scripting::call("getweaponarray").as<scripting::array>();
return {scripting::call("getweaponarray").as<scripting::array>()};
}
if (type == entity_type::node)
{
return scripting::call("getnodearray").as<scripting::array>();
return {scripting::call("getallnodes").as<scripting::array>()};
}
if (type == entity_type::vehicle_node)
{
return {scripting::call("getallvehiclenodes").as<scripting::array>()};
}
if (type == entity_type::vehicle)
{
return {scripting::call("vehicle_getarray").as<scripting::array>()};
}
return {};
@ -410,29 +378,18 @@ namespace entity_list
data.entity_info.clear();
const auto array = value.value();
const scripting::entity player{{0, 0}};
for (unsigned int i = 0; i < array.size(); i++)
for (auto i = 0; i < array.size(); i++)
{
const auto raw = array[i].get_raw();
if (raw.type != game::SCRIPT_OBJECT)
{
continue;
}
if (!verify_entity(raw.u.uintValue))
{
continue;
}
const auto entity = array[i].as<scripting::entity>();
entity_info_t info{};
info.id = raw.u.uintValue;
info.num = entity.get_entity_reference().entnum;
info.id = entity.get_entity_id();
info.entref = entity.get_entity_reference();
if (data.filters.filter_by_range)
{
const auto player = scripting::call("getentbynum", {0}).as<scripting::entity>();
const auto distance = scripting::call("distance", {player.get("origin"), entity.get("origin")}).as<float>();
if (distance > data.filters.range)
@ -478,72 +435,61 @@ namespace entity_list
});
}
void teleport_to(data_t& data, unsigned int id)
void teleport_to(data_t& data, game::scr_entref_t entref)
{
data.tasks.push_back([id]()
data.tasks.push_back([entref]()
{
if (!verify_entity(id))
try
{
return;
const scripting::entity entity{entref};
const scripting::entity player{{0, 0}};
player.call("setorigin", {entity.get("origin")});
}
const auto dest = scripting::entity{id};
const auto value = scripting::call("getentbynum", {0});
if (value.get_raw().type != game::SCRIPT_OBJECT)
catch (...)
{
return;
gui::notification("Error", utils::string::va("^1error teleporting player to entity num %i!", entref.entnum));
}
const auto player = value.as<scripting::entity>();
player.call("setorigin", {dest.get("origin")});
});
}
void teleport_to_reverse(data_t& data, unsigned int id)
void teleport_to_reverse(data_t& data, game::scr_entref_t entref)
{
data.tasks.push_back([id]()
data.tasks.push_back([entref]()
{
if (!verify_entity(id))
try
{
return;
const scripting::entity entity{entref};
const scripting::entity player{{0, 0}};
entity.set("origin", player.get("origin"));
}
const auto dest = scripting::entity{id};
const auto value = scripting::call("getentbynum", {0});
if (value.get_raw().type != game::SCRIPT_OBJECT)
catch (...)
{
return;
gui::notification("Error", utils::string::va("^1error teleporting entity num %i to player!", entref.entnum));
}
const auto player = value.as<scripting::entity>();
dest.set("origin", player.get("origin"));
});
}
void delete_entity(data_t& data, unsigned int id)
void delete_entity(data_t& data, game::scr_entref_t entref)
{
data.tasks.push_back([id]()
data.tasks.push_back([entref]()
{
if (!verify_entity(id))
try
{
return;
const scripting::entity entity{entref};
entity.call("delete");
}
catch (...)
{
gui::notification("Error", utils::string::va("^1error deleting entity num %i!", entref.entnum));
}
const auto target = scripting::entity{id};
target.call("delete");
});
}
void set_entity_field(data_t& data, unsigned int id,
void set_entity_field(data_t& data, game::scr_entref_t entref,
const std::string& name, const std::string& string_value, int type)
{
data.tasks.push_back([id, name, type, string_value, &data]()
data.tasks.push_back([entref, name, type, string_value, &data]()
{
if (!verify_entity(id))
{
return;
}
scripting::script_value value{};
if (type == game::SCRIPT_INTEGER)
@ -563,7 +509,8 @@ namespace entity_list
try
{
scripting::set_entity_field(id, name, value);
const scripting::entity entity{entref};
scripting::set_entity_field(entity, name, value);
data.force_update = true;
}
catch (...)
@ -573,19 +520,15 @@ namespace entity_list
});
}
void set_entity_field_vector(data_t& data, unsigned int id,
void set_entity_field_vector(data_t& data, game::scr_entref_t entref,
const std::string& name, const scripting::vector& value)
{
data.tasks.push_back([id, name, value, &data]()
data.tasks.push_back([entref, name, value, &data]()
{
if (!verify_entity(id))
{
return;
}
try
{
scripting::set_entity_field(id, name, value);
const scripting::entity entity{entref};
scripting::set_entity_field(entity, name, value);
data.force_update = true;
}
catch (...)
@ -597,13 +540,6 @@ namespace entity_list
void show_set_field_window(data_t& data)
{
static char name[0x100]{};
static char value[0x100]{};
static char x[0x100]{};
static char y[0x100]{};
static char z[0x100]{};
ImGui::SetNextWindowSizeConstraints(ImVec2(300, 300), ImVec2(300, 300));
ImGui::Begin("Set entity field", &set_field_window);
ImGui::SetWindowSize(ImVec2(300, 300));
@ -619,16 +555,16 @@ namespace entity_list
ImGui::TreePop();
}
ImGui::InputText("name", name, IM_ARRAYSIZE(name));
ImGui::InputText("name", &field_name_buffer);
if (selected_type == game::SCRIPT_VECTOR)
{
ImGui::InputText("x", x, IM_ARRAYSIZE(x));
ImGui::InputText("y", y, IM_ARRAYSIZE(y));
ImGui::InputText("z", z, IM_ARRAYSIZE(z));
ImGui::InputText("x", &vector_input[0]);
ImGui::InputText("y", &vector_input[1]);
ImGui::InputText("z", &vector_input[2]);
}
else
{
ImGui::InputText("value", value, IM_ARRAYSIZE(value));
ImGui::InputText("value", &field_value_buffer);
}
if (ImGui::Button("set", ImVec2(300, 0)))
@ -637,16 +573,18 @@ namespace entity_list
{
const scripting::vector vector
{
static_cast<float>(atof(x)),
static_cast<float>(atof(y)),
static_cast<float>(atof(z))
static_cast<float>(atof(vector_input[0].data())),
static_cast<float>(atof(vector_input[1].data())),
static_cast<float>(atof(vector_input[2].data()))
};
set_entity_field_vector(data, selected_entity, name, vector);
set_entity_field_vector(data, selected_entity,
field_name_buffer, vector);
}
else
{
set_entity_field(data, selected_entity, name, value, selected_type);
set_entity_field(data, selected_entity, field_name_buffer,
field_value_buffer, selected_type);
}
}
@ -664,6 +602,7 @@ namespace entity_list
}
ImGui::Checkbox("Auto update", &data.auto_update);
ImGui::Checkbox("Field list", &selected_fields_window);
auto interval = static_cast<int>(data.interval.count());
if (data.auto_update && ImGui::SliderInt("Interval", &interval, 0, 1000 * 30))
@ -689,11 +628,13 @@ namespace entity_list
{
auto result = 0;
result += ImGui::RadioButton("any", reinterpret_cast<int*>(&data.filters.type), entity_type::type_any);
result += ImGui::RadioButton("entity", reinterpret_cast<int*>(&data.filters.type), entity_type::entity);
result += ImGui::RadioButton("actor", reinterpret_cast<int*>(&data.filters.type), entity_type::actor);
result += ImGui::RadioButton("spawner", reinterpret_cast<int*>(&data.filters.type), entity_type::spawner);
result += ImGui::RadioButton("weapon", reinterpret_cast<int*>(&data.filters.type), entity_type::weapon);
result += ImGui::RadioButton("node", reinterpret_cast<int*>(&data.filters.type), entity_type::node);
result += ImGui::RadioButton("vehicle", reinterpret_cast<int*>(&data.filters.type), entity_type::vehicle);
result += ImGui::RadioButton("path node", reinterpret_cast<int*>(&data.filters.type), entity_type::node);
result += ImGui::RadioButton("vehicle node", reinterpret_cast<int*>(&data.filters.type), entity_type::vehicle_node);
if (result)
{
@ -703,7 +644,8 @@ namespace entity_list
ImGui::TreePop();
}
if (ImGui::TreeNode("Entity team"))
if ((data.filters.type == entity_type::actor || data.filters.type == entity_type::spawner)
&& ImGui::TreeNode("Entity team"))
{
auto result = 0;
@ -748,34 +690,65 @@ namespace entity_list
}
ImGui::Separator();
ImGui::BeginChild("entity list");
for (const auto& info : data.entity_info)
{
if (ImGui::TreeNode(utils::string::va("Entity num %i id %i", info.num, info.id)))
if (ImGui::TreeNode(utils::string::va("Entity num %i class %i",
info.entref.entnum, info.entref.classnum)))
{
ImGui::Text("Info");
const auto num_str = utils::string::va("%i", info.entref.entnum);
const auto classnum_str = utils::string::va("%i", info.entref.classnum);
ImGui::Text("Entity number");
ImGui::SameLine();
if (ImGui::Button(num_str))
{
gui::copy_to_clipboard(num_str);
}
ImGui::Text("Entity class");
ImGui::SameLine();
if (ImGui::Button(classnum_str))
{
gui::copy_to_clipboard(classnum_str);
}
const auto entity_code = utils::string::va("game:getentbyref(%i, %i)",
info.entref.entnum, info.entref.classnum);
if (ImGui::Button(entity_code))
{
gui::copy_to_clipboard(entity_code);
}
ImGui::Text("Commands");
if (ImGui::Button("Set field"))
{
set_field_window = true;
selected_entity = info.id;
selected_entity = info.entref;
}
if (ImGui::Button("Teleport to"))
{
teleport_to(data, info.id);
teleport_to(data, info.entref);
data.force_update = true;
}
if (ImGui::Button("Teleport to you"))
{
teleport_to_reverse(data, info.id);
teleport_to_reverse(data, info.entref);
data.force_update = true;
}
if (info.num != 0 && ImGui::Button("Delete"))
if (info.entref.classnum == 0 && info.entref.entnum != 0 && ImGui::Button("Delete"))
{
delete_entity(data, info.id);
delete_entity(data, info.entref);
data.force_update = true;
}
@ -805,22 +778,28 @@ namespace entity_list
}
}
ImGui::EndChild();
ImGui::End();
}
void show_selected_fields_window(data_t& data)
{
ImGui::SetNextWindowSizeConstraints(ImVec2(500, 500), ImVec2(1000, 1000));
ImGui::Begin("Selected fields");
ImGui::Begin("Selected fields", &selected_fields_window);
ImGui::InputText("field name", &field_filter);
ImGui::BeginChild("field list");
static char field_filter[0x100]{};
ImGui::InputText("field name", field_filter, IM_ARRAYSIZE(field_filter));
for (auto& field : data.selected_fields)
{
if (utils::string::find_lower(field.first, field_filter) &&
if (utils::string::find_lower(field.first, field_filter) &&
ImGui::Checkbox(field.first.data(), &field.second))
{
data.force_update = true;
}
}
ImGui::EndChild();
ImGui::End();
}
@ -833,15 +812,22 @@ namespace entity_list
data_.access([](data_t& data)
{
if (!is_sv_running())
if (!game::CL_IsCgameInitialized())
{
selected_entity = 0;
selected_entity = {};
data.entity_info = {};
data.tasks = {};
}
show_entity_list_window(data);
if (selected_entity && set_field_window)
if (selected_fields_window)
{
show_selected_fields_window(data);
}
if (set_field_window)
{
show_set_field_window(data);
}
@ -854,13 +840,17 @@ namespace entity_list
public:
void post_unpack() override
{
scheduler::on_game_initialized([]()
{
sv_running = game::Dvar_FindVar("sv_running");
});
gui::on_frame(on_frame);
scheduler::loop(update_entity_list, scheduler::pipeline::server, 0ms);
scheduler::loop([]()
{
try
{
update_entity_list();
}
catch (...)
{
}
}, scheduler::pipeline::server);
}
};
}

View File

@ -0,0 +1,231 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "scheduler.hpp"
#include "command.hpp"
#include "gui.hpp"
#include "game/scripting/lua/context.hpp"
#include "game/scripting/lua/engine.hpp"
#include <utils/string.hpp>
#include <utils/hook.hpp>
#include <utils/concurrency.hpp>
namespace gui_script_console
{
namespace
{
bool auto_scroll = true;
bool multi_line_input = false;
int history_index = -1;
std::string input{};
std::string filter{};
std::deque<std::string> history;
struct menu_data_t
{
std::deque<std::string> command_queue;
std::deque<std::string> output;
};
utils::concurrency::container<menu_data_t> menu_data;
std::string run_command(const std::string& code)
{
const auto result = scripting::lua::engine::load(code);
if (result.has_value())
{
return result.value();
}
else
{
return "Script not running";
}
}
void run_commands()
{
menu_data.access([](menu_data_t& menu_data_)
{
for (const auto& command : menu_data_.command_queue)
{
menu_data_.output.push_back(run_command(command));
}
menu_data_.command_queue.clear();
});
}
int multi_line_input_text_edit(ImGuiInputTextCallbackData* data)
{
if (data->EventKey == ImGuiKey_Tab)
{
data->InsertChars(data->CursorPos, "\t");
}
return 0;
}
int input_text_edit(ImGuiInputTextCallbackData* data)
{
switch (data->EventFlag)
{
case ImGuiInputTextFlags_CallbackHistory:
{
if (data->EventKey == ImGuiKey_UpArrow)
{
if (++history_index >= history.size())
{
history_index = static_cast<int>(history.size()) - 1;
}
data->DeleteChars(0, data->BufTextLen);
if (history_index != -1)
{
const auto text = history.at(history_index).data();
data->InsertChars(0, text, text + strlen(text));
}
}
else if (data->EventKey == ImGuiKey_DownArrow)
{
if (--history_index < -1)
{
history_index = -1;
}
data->DeleteChars(0, data->BufTextLen);
if (history_index != -1)
{
const auto text = history.at(history_index).data();
data->InsertChars(0, text, text + strlen(text));
}
}
break;
}
}
return 0;
}
void on_frame()
{
if (!gui::enabled_menus["script_console"])
{
return;
}
menu_data.access([](menu_data_t& menu_data_)
{
if (!game::CL_IsCgameInitialized())
{
menu_data_.command_queue.clear();
}
static const float footer_height = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
static const auto input_text_flags = ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_CallbackCompletion |
ImGuiInputTextFlags_CallbackHistory;
ImGui::Begin("Script console", &gui::enabled_menus["script_console"]);
if (ImGui::BeginPopup("Options"))
{
ImGui::Checkbox("Auto-scroll", &auto_scroll);
ImGui::Checkbox("Multi-line input", &multi_line_input);
ImGui::EndPopup();
}
if (ImGui::Button("Clear"))
{
menu_data_.output.clear();
input.clear();
history.clear();
}
ImGui::Separator();
if (ImGui::Button("Options"))
{
ImGui::OpenPopup("Options");
}
ImGui::SameLine();
ImGui::InputText("Filter", &filter);
ImGui::BeginChild("script_console_scroll", ImVec2(0, -1 * footer_height - 80.f * multi_line_input), false);
for (const auto& line : menu_data_.output)
{
ImGui::Text(line.data());
ImGui::Separator();
}
if (auto_scroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())
{
ImGui::SetScrollHereY(1.0f);
}
ImGui::EndChild();
bool execute = false;
if (multi_line_input)
{
ImGui::InputTextMultiline("", &input, ImVec2(0, 100),
ImGuiInputTextFlags_CallbackCompletion, multi_line_input_text_edit);
ImGui::SameLine();
execute = ImGui::Button("Execute", ImVec2(100, 100));
}
else
{
execute = ImGui::InputText("Input", &input, input_text_flags, input_text_edit);
}
if (execute)
{
menu_data_.output.push_back(input);
menu_data_.command_queue.push_back(input);
if (history_index != -1)
{
const auto itr = history.begin() + history_index;
if (*itr == input)
{
history.erase(history.begin() + history_index);
}
}
history.push_front(input);
if (history.size() > 10)
{
history.erase(history.begin() + 10);
}
ImGui::SetKeyboardFocusHere(-1);
history_index = -1;
input.clear();
}
ImGui::End();
});
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
gui::on_frame(on_frame);
scheduler::loop(run_commands, scheduler::pipeline::server);
}
};
}
REGISTER_COMPONENT(gui_script_console::component)

View File

@ -5,27 +5,50 @@
#include "game/dvars.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
namespace patches
{
namespace
{
utils::hook::detour pm_crashland_hook;
utils::hook::detour gscr_set_save_dvar_hook;
utils::hook::detour dvar_register_float_hook;
void pm_crashland_stub(void* ps, void* pm)
{
if (dvars::jump_enableFallDamage->current.enabled)
{
pm_crashland_hook.invoke<void>(ps, pm);
}
}
uint64_t off_11C52460;
void* sub_46148()
{
off_11C52460 = 0xAD0C58_b;
static uint64_t off_11C52460 = 0xAD0C58_b;
return &off_11C52460;
}
DECLSPEC_NORETURN void quit_stub()
{
component_loader::pre_destroy();
exit(0);
}
void gscr_set_save_dvar_stub()
{
const auto string = utils::string::to_lower(utils::hook::invoke<const char*>(0x5C7C20_b, 0));
if (string == "cg_fov" || string == "cg_fovscale")
{
return;
}
gscr_set_save_dvar_hook.invoke<void>();
}
game::dvar_t* dvar_register_float_stub(int hash, const char* dvarName, float value, float min, float max, unsigned int flags)
{
static const auto cg_fov_hash = game::generateHashValue("cg_fov");
static const auto cg_fov_scale_hash = game::generateHashValue("cg_fovscale");
if (hash == cg_fov_hash || hash == cg_fov_scale_hash)
{
flags |= game::DvarFlags::DVAR_FLAG_SAVED;
}
return dvar_register_float_hook.invoke<game::dvar_t*>(hash, dvarName, value, min, max, flags);
}
}
class component final : public component_interface
@ -33,23 +56,29 @@ namespace patches
public:
void post_unpack() override
{
// Not sure but it works
// Fix startup crashes
utils::hook::set(0x633080_b, 0xC301B0);
utils::hook::set(0x272F70_b, 0xC301B0);
utils::hook::jump(0x46148_b, sub_46148, true);
utils::hook::jump(0x64EF10_b, quit_stub, true);
// Unlock fps in main menu
utils::hook::set<BYTE>(0x3D8E1B_b, 0xEB);
// Disable battle net popup
utils::hook::nop(0x5F4496_b, 5);
pm_crashland_hook.create(0x688A20_b, pm_crashland_stub);
dvars::jump_enableFallDamage = dvars::register_bool("jump_enableFallDamage", 1, game::DVAR_FLAG_REPLICATED);
// Allow kbam input when gamepad is enabled
utils::hook::nop(0x3D2F8E_b, 2);
utils::hook::nop(0x3D0C9C_b, 6);
dvars::register_float("jump_height", 39, 0, 1000, game::DVAR_FLAG_REPLICATED);
dvars::register_float("g_gravity", 800, 1, 1000, game::DVAR_FLAG_REPLICATED);
dvars::register_int("g_speed", 190, 0, 1000, game::DVAR_FLAG_REPLICATED);
// Prevent game from overriding cg_fov and cg_fovscale values
gscr_set_save_dvar_hook.create(0x504C60_b, &gscr_set_save_dvar_stub);
// Make cg_fov and cg_fovscale saved dvars
dvar_register_float_hook.create(game::Dvar_RegisterFloat.get(), dvar_register_float_stub);
// Don't make the game reset cg_fov and cg_fovscale along with other dvars
utils::hook::nop(0x4C8A08_b, 5);
}
};
}

View File

@ -18,6 +18,7 @@ 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
{
@ -54,14 +55,24 @@ namespace scripting
vm_notify_hook.invoke<void>(notify_list_owner_id, string_value, top);
}
void clear_shared_table()
{
shared_table.access([](shared_table_t& table)
{
table.clear();
});
}
void player_spawn_stub(const game::gentity_s* player)
{
player_spawn_hook.invoke<void>(player);
clear_shared_table();
lua::engine::start();
}
void g_shutdown_game_stub(const int free_scripts)
{
clear_shared_table();
lua::engine::stop();
g_shutdown_game_hook.invoke<void>(free_scripts);
}
@ -97,6 +108,17 @@ namespace scripting
script_function_table[current_file][function_name] = codePos;
scr_set_thread_position_hook.invoke<void>(threadName, codePos);
}
utils::hook::detour sub_6B2940_hook;
char sub_6B2940_stub(void* a1)
{
const auto result = sub_6B2940_hook.invoke<char>(a1);
if (a1 != nullptr)
{
lua::engine::start();
}
return result;
}
}
class component final : public component_interface
@ -113,6 +135,11 @@ namespace scripting
scr_set_thread_position_hook.create(0x5BC7E0_b, scr_set_thread_position_stub);
process_script_hook.create(0x5C6160_b, process_script_stub);
// Loading last checkpoint doesn't call spawn player again (player_spawn_hook)
// Not sure what this function does but `a1` is != nullptr when loading
// the last checkpoint so we need to start lua in this context
sub_6B2940_hook.create(0x6B2940_b, sub_6B2940_stub);
scheduler::loop([]()
{
lua::engine::run_frame();

View File

@ -1,7 +1,11 @@
#pragma once
#include <utils/concurrency.hpp>
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;
}

File diff suppressed because it is too large Load Diff

View File

@ -22,13 +22,15 @@ namespace dvars
extern game::dvar_t* cg_legacyCrashHandling;
extern std::vector<std::string> dvar_list;
extern game::dvar_t* g_enableElevators;
extern std::unordered_set<std::string> dvar_list;
std::string dvar_get_vector_domain(const int components, const game::dvar_limits& domain);
std::string dvar_get_domain(const game::dvar_type type, const game::dvar_limits& domain);
game::dvar_t* register_int(const std::string& name, int value, int min, int max, game::DvarFlags flags, bool add_to_list = true);
game::dvar_t* register_bool(const std::string& name, bool value, game::DvarFlags flags, bool add_to_list = true);
game::dvar_t* register_float(const std::string& name, float value, float min, float max, game::DvarFlags flags, bool add_to_list = true);
game::dvar_t* register_vec4(const std::string& name, float x, float y, float z, float w, float min, float max, game::DvarFlags flags, bool add_to_list = true);
game::dvar_t* register_int(const std::string& name, int value, int min, int max, game::DvarFlags flags);
game::dvar_t* register_bool(const std::string& name, bool value, game::DvarFlags flags);
game::dvar_t* register_float(const std::string& name, float value, float min, float max, game::DvarFlags flags);
game::dvar_t* register_vec4(const std::string& name, float x, float y, float z, float w, float min, float max, game::DvarFlags flags);
}

View File

@ -165,9 +165,9 @@ namespace scripting
return result;
}
unsigned int array::size() const
int array::size() const
{
return game::scr_VarGlob->objectVariableValue[this->id_].u.f.next;
return static_cast<int>(game::scr_VarGlob->objectVariableValue[this->id_].u.f.next);
}
unsigned int array::push(script_value value) const
@ -212,8 +212,6 @@ namespace scripting
{
return this->get(key.as<std::string>());
}
return {};
}
script_value array::get(const std::string& key) const

View File

@ -32,7 +32,7 @@ namespace scripting
array& operator=(array&& other) noexcept;
std::vector<script_value> get_keys() const;
unsigned int size() const;
int size() const;
unsigned int push(script_value) const;
void erase(const unsigned int) const;

View File

@ -27,7 +27,7 @@ namespace scripting
}
entity::entity(game::scr_entref_t entref)
: entity(game::FindEntityId(entref.entnum, entref.classnum))
: entity(game::Scr_GetEntityId(entref.entnum, entref.classnum))
{
}

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,7 @@
#include "../../../component/scripting.hpp"
#include "../../../component/command.hpp"
#include "../../../component/chat.hpp"
#include "../../../component/fastfiles.hpp"
#include <utils/string.hpp>
#include <utils/io.hpp>
@ -31,7 +32,7 @@ namespace scripting::lua
void setup_entity_type(sol::state& state, event_handler& handler, scheduler& scheduler)
{
state["level"] = entity{*game::levelEntityId};
state["level"] = entity{*::game::levelEntityId};
state["player"] = call("getentbynum", {0}).as<entity>();
state["io"]["fileexists"] = utils::io::file_exists;
@ -300,6 +301,13 @@ namespace scripting::lua
return result;
};
entity_type["getentref"] = [](const entity& entity)
{
const auto entref = entity.get_entity_reference();
std::vector<unsigned int> returns = {entref.entnum, entref.classnum};
return sol::as_returns(returns);
};
struct game
{
};
@ -499,6 +507,68 @@ namespace scripting::lua
{
notifies::add_entity_damage_callback(callback);
};
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["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};
}
};
}
}
@ -539,6 +609,53 @@ namespace scripting::lua
this->load_script("__init__");
}
context::context()
: folder_({})
, scheduler_(state_)
, event_handler_(state_)
{
this->state_.open_libraries(sol::lib::base,
sol::lib::package,
sol::lib::io,
sol::lib::string,
sol::lib::os,
sol::lib::math,
sol::lib::table);
this->state_["include"] = []()
{
};
this->state_["require"] = []()
{
};
setup_entity_type(this->state_, this->event_handler_, this->scheduler_);
}
std::string context::load(const std::string& code)
{
try
{
const auto result = this->state_.safe_script(code, &sol::script_pass_on_error);
if (result.valid())
{
const auto object = result.get<sol::object>();
return this->state_["tostring"](object).get<std::string>();
}
else
{
const sol::error error = result;
return error.what();
}
}
catch (const std::exception& e)
{
return e.what();
}
}
context::~context()
{
this->state_.collect_garbage();

View File

@ -17,6 +17,7 @@ namespace scripting::lua
class context
{
public:
context();
context(std::string folder);
~context();
@ -29,6 +30,8 @@ namespace scripting::lua
void run_frame();
void notify(const event& e);
std::string load(const std::string& code);
private:
sol::state state_{};
std::string folder_;

View File

@ -19,6 +19,8 @@ namespace scripting::lua::engine
void load_scripts()
{
get_scripts().push_back(std::make_unique<context>());
const auto script_dir = "scripts/"s;
if (!utils::io::directory_exists(script_dir))
@ -66,4 +68,15 @@ namespace scripting::lua::engine
script->run_frame();
}
}
std::optional<std::string> load(const std::string& code)
{
if (get_scripts().size() == 0)
{
return {};
}
const auto& script = get_scripts()[0];
return {script->load(code)};
}
}

View File

@ -8,4 +8,6 @@ namespace scripting::lua::engine
void stop();
void notify(const event& e);
void run_frame();
std::optional<std::string> load(const std::string& code);
}

View File

@ -444,6 +444,7 @@ namespace game
dvar_value latched;
dvar_value reset;
dvar_limits domain;
char __pad0[0xC];
};
struct ScreenPlacement
@ -464,6 +465,16 @@ namespace game
vec2_t subScreenLeft;
};
struct refdef_t
{
char __pad0[0x10];
float fovX;
float fovY;
char __pad1[0x8];
float org[3];
float axis[3][3];
};
struct CmdArgs
{
int nesting;
@ -959,7 +970,24 @@ namespace game
uint64_t streams[4];
const char* name;
};
struct Bounds
{
vec3_t midPoint;
vec3_t halfSize;
};
struct pmove_t
{
};
struct trace_t
{
char __pad0[0x29];
bool allsolid; // Confirmed in CM_PositionTestCapsuleInTriangle
bool startsolid; // Confirmed in PM_JitterPoint
};
namespace hks
{
struct GenericChunkHeader

View File

@ -26,6 +26,7 @@ namespace game
WEAK symbol<void(errorParm code, const char* message, ...)> Com_Error{0x5A2D80};
WEAK symbol<void()> Com_Quit_f{0x5A50D0};
WEAK symbol<void()> Quit{0x5A52A0};
WEAK symbol<void(XAssetType type, void(__cdecl* func)(game::XAssetHeader, void*), const void* inData, bool includeOverride)>
DB_EnumXAssets_Internal{0x4129F0};
@ -39,13 +40,14 @@ namespace game
WEAK symbol<void(char* buffer, int index)> Dvar_GetCombinedString{0x5A75D0};
WEAK symbol<dvar_t*(int hash, const char* name, bool value, unsigned int flags)> Dvar_RegisterBool{0x617BB0};
WEAK symbol<dvar_t*(int hash, const char* name, int value, int min, int max, unsigned int flags)> Dvar_RegisterInt{0x618090};
WEAK symbol<dvar_t* (int hash, const char* dvarName, float value, float min, float max, unsigned int flags)>
WEAK symbol<dvar_t*(int hash, const char* dvarName, float value, float min, float max, unsigned int flags)>
Dvar_RegisterFloat{0x617F80};
WEAK symbol<dvar_t* (int hash, const char* dvarName, const char* value, unsigned int flags)>
WEAK symbol<dvar_t*(int hash, const char* dvarName, const char* value, unsigned int flags)>
Dvar_RegisterString{0x618170};
WEAK symbol<dvar_t*(int dvarName, const char* a2, float x, float y, float z, float w, float min, float max,
unsigned int flags)> Dvar_RegisterVec4{0x6185F0};
WEAK symbol<const char* (dvar_t* dvar, void* a2, void* value)> Dvar_ValueToString{0x61B8F0};
WEAK symbol<const char*(const dvar_t* dvar)> Dvar_DisplayableValue{0x618EA0};
WEAK symbol<const char*(dvar_t* dvar, void* a2, void* value)> Dvar_ValueToString{0x61B8F0};
WEAK symbol<void(int hash, const char* name, const char* buffer)> Dvar_SetCommand{0x61A5C0};
WEAK symbol<void(const char* dvarName, const char* string, DvarSetSource source)> Dvar_SetFromStringFromSource{0x61A910};
@ -56,7 +58,7 @@ namespace game
const float* color, int style, const float* glowColor, Material* fxMaterial, Material* fxMaterialGlow,
int fxBirthTime, int fxLetterTime, int fxDecayStartTime, int fxDecayDuration, int a17)> CL_DrawTextPhysicalWithEffects{0x3D4990};
WEAK symbol<unsigned int (unsigned int parentId, unsigned int name)> FindVariable{0x5C1D50};
WEAK symbol<unsigned int(unsigned int parentId, unsigned int name)> FindVariable{0x5C1D50};
WEAK symbol<unsigned int(int entnum, unsigned int classnum)> FindEntityId{0x5C1C50};
WEAK symbol<void(VariableValue* result, unsigned int classnum, int entnum, int offset)> GetEntityFieldValue{0x5C6100};
WEAK symbol<unsigned int(unsigned int parentId, unsigned int unsignedValue)> GetVariable{0x5C2690};
@ -70,23 +72,30 @@ namespace game
G_GivePlayerWeapon{0x51B660};
WEAK symbol<void(void* ps, const unsigned int weapon, int hadWeapon)> G_InitializeAmmo{0x4C4110};
WEAK symbol<void(int clientNum, const unsigned int weapon)> G_SelectWeapon{0x51C0D0};
WEAK symbol<bool(int localClientNum, ScreenPlacement* screenPlacement, const float* worldDir, float* outScreenPos)> WorldPosToScreenPos{0x36F310};
WEAK symbol<char*(char* string)> I_CleanStr{0x620660};
WEAK symbol<char* (GfxImage* image, uint32_t width, uint32_t height, uint32_t depth, uint32_t mipCount,
WEAK symbol<char*(GfxImage* image, uint32_t width, uint32_t height, uint32_t depth, uint32_t mipCount,
uint32_t imageFlags, DXGI_FORMAT imageFormat, int a8, const char* name, const void* initData)> Image_Setup{0x74B2A0};
WEAK symbol<const char*(int, int, int)> Key_KeynumToString{0x3D32D0};
WEAK symbol<void(int clientNum, const char* menu, int a3, int a4, unsigned int a5)> LUI_OpenMenu{0x5F0EE0};
WEAK symbol<bool(int clientNum, const char* menu)> Menu_IsMenuOpenAndVisible{0x5EE1A0};
WEAK symbol<Material*(const char* material)> Material_RegisterHandle{0x759BA0};
WEAK symbol<const float* (const float* v)> Scr_AllocVector{0x5C3220};
WEAK symbol<void(pathnode_t*, float* out)> PathNode_WorldifyPosFromParent{0x525830};
WEAK symbol<const float*(const float* v)> Scr_AllocVector{0x5C3220};
WEAK symbol<void()> Scr_ClearOutParams{0x5C6E50};
WEAK symbol<scr_entref_t(unsigned int entId)> Scr_GetEntityIdRef{0x5C56C0};
WEAK symbol<unsigned int(int entnum, unsigned int classnum)> Scr_GetEntityId{0x5C5610};
WEAK symbol<int(unsigned int classnum, int entnum, int offset)> Scr_SetObjectField{0x512190};
WEAK symbol<void(unsigned int id, scr_string_t stringValue, unsigned int paramcount)> Scr_NotifyId{0x5C8240};
WEAK symbol<unsigned int(unsigned int threadId)> Scr_GetSelf{0x5C57C0};
WEAK symbol<void()> Scr_ErrorInternal{0x5C6EC0};
WEAK symbol<unsigned int(unsigned int localId, const char* pos, unsigned int paramcount)> VM_Execute{0x5C8DB0};
@ -109,7 +118,8 @@ namespace game
WEAK symbol<void(const void* obj, void* pose, unsigned int entnum, unsigned int renderFxFlags, float* lightingOrigin,
float materialTime, __int64 a7, __int64 a8)> R_AddDObjToScene{0x775C40};
WEAK symbol<ScreenPlacement* ()> ScrPlace_GetViewPlacement{0x3E16A0};
WEAK symbol<ScreenPlacement*()> ScrPlace_GetViewPlacement{0x3E16A0};
WEAK symbol<ScreenPlacement*()> ScrPlace_GetView{0x3E1660};
WEAK symbol<const char*(scr_string_t stringValue)> SL_ConvertToString{0x5BFBB0};
WEAK symbol<scr_string_t(const char* str, unsigned int user)> SL_GetString{0x5C0170};
@ -118,10 +128,16 @@ namespace game
WEAK symbol<void()> Sys_ShowConsole{0x633080};
WEAK symbol<bool()> Sys_IsDatabaseReady2{0x5A9FE0};
WEAK symbol<int()> Sys_Milliseconds{0x650720};
WEAK symbol<const char*(const char* string)> UI_SafeTranslateString{0x5A2930};
WEAK symbol<int(int localClientNum, const char* sound)> UI_PlayLocalSoundAlias{0x606080};
WEAK symbol<void(pmove_t* move, trace_t*, const float*, const float*,
const Bounds*, int, int)> PM_playerTrace{0x68F0A0};
WEAK symbol<void(pmove_t*, trace_t*, const float*, const float*,
const Bounds*, int, int)> PM_trace{0x68F1D0};
WEAK symbol<void*(jmp_buf* Buf, int Value)> longjmp{0x89EED0};
WEAK symbol<int(jmp_buf* Buf)> _setjmp{0x8EC2E0};
@ -129,6 +145,7 @@ namespace game
WEAK symbol<cmd_function_s*> cmd_functions{0xAD17BB8};
WEAK symbol<CmdArgs> cmd_args{0xAD17A60};
WEAK symbol<const char*> command_whitelist{0xBF84E0};
WEAK symbol<HWND> hWnd{0xCCF81C0};
@ -136,17 +153,19 @@ namespace game
WEAK symbol<int> g_poolSize{0xBF2E40};
WEAK symbol<gentity_s> g_entities{0x52DDDA0};
WEAK symbol<PathData> pathData{0x52CCDA0};
WEAK symbol<DWORD> threadIds{0xB11DC80};
WEAK symbol<GfxDrawMethod_s> gfxDrawMethod{0xEDF9E00};
WEAK symbol<refdef_t> refdef{0x1BC2500};
WEAK symbol<int> keyCatchers{0x203F3C0};
WEAK symbol<PlayerKeyState> playerKeys{0x1E8767C};
WEAK symbol<int> dvarCount{0xBFBB310};
WEAK symbol<dvar_t*> sortedDvars{0xBFBB320};
WEAK symbol<dvar_t> dvarPool{0xBFBB320};
WEAK symbol<unsigned int> levelEntityId{0xB5E0B30};
WEAK symbol<int> g_script_error_level{0xBA9CC24};

View File

@ -7,6 +7,7 @@
#include "../execution.hpp"
#include "../../../component/ui_scripting.hpp"
#include "../../../component/scripting.hpp"
#include "../../../component/command.hpp"
#include "component/game_console.hpp"
@ -1006,6 +1007,24 @@ namespace ui_scripting::lua
return sol::as_returns(returns);
};
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;
};
auto userdata_type = state.new_usertype<userdata>("userdata_");
userdata_type["new"] = sol::property(

View File

@ -67,6 +67,12 @@ FARPROC load_binary(const launcher::mode mode, uint64_t* base_address)
{
case launcher::mode::singleplayer:
binary = "MW2CR.exe";
if (!utils::io::file_exists(binary))
{
binary = "h2_sp64_bnet_ship.exe";
}
break;
case launcher::mode::none:
default:

View File

@ -89,6 +89,7 @@
#include <d3d11.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <backends/imgui_impl_dx11.h>
#include <backends/imgui_impl_win32.h>
#include <misc/cpp/imgui_stdlib.h>

View File

@ -197,4 +197,11 @@ namespace utils::string
{
return to_lower(a).find(to_lower(b)) != std::string::npos;
}
std::string truncate(const std::string& text, const size_t length, const std::string& end)
{
return text.size() <= length
? text
: text.substr(0, length - end.size()) + end;
}
}

View File

@ -100,4 +100,6 @@ namespace utils::string
std::string replace(std::string str, const std::string& from, const std::string& to);
bool find_lower(const std::string& a, const std::string& b);
std::string truncate(const std::string& text, const size_t length, const std::string& end);
}