Merge branch 'main' into dev
This commit is contained in:
commit
4d9935b568
@ -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
2
deps/GSL
vendored
@ -1 +1 @@
|
||||
Subproject commit c412deb31e73c9b824abeb6619e11511b279222f
|
||||
Subproject commit ebf0498363c53f0d3c403b0548212c147e3747fe
|
2
deps/imgui
vendored
2
deps/imgui
vendored
@ -1 +1 @@
|
||||
Subproject commit e55dce841b4a5a738ae41745b534248596df7f5e
|
||||
Subproject commit f07df31745296090ac7ecc01811f88c85766028a
|
2
deps/lua
vendored
2
deps/lua
vendored
@ -1 +1 @@
|
||||
Subproject commit 066e0f93c4901e601d93e31fb700f8f66f95feb8
|
||||
Subproject commit 8dd2c912d299b84566c6f6d659336edfa9b18e9b
|
2
deps/protobuf
vendored
2
deps/protobuf
vendored
@ -1 +1 @@
|
||||
Subproject commit b360b9e388351aaed97f2ead98dd951fb5f1951e
|
||||
Subproject commit b73f78d32cc6d1013986de76789481dcaec2d064
|
2
deps/sol2
vendored
2
deps/sol2
vendored
@ -1 +1 @@
|
||||
Subproject commit a7da2a8e889498b0c5a4ed6d9e3463af02bb6102
|
||||
Subproject commit 50b62c9346750b7c2c406c9e4c546f96b0bf021d
|
130
src/client/component/binding.cpp
Normal file
130
src/client/component/binding.cpp
Normal 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)
|
@ -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
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
87
src/client/component/gameplay.cpp
Normal file
87
src/client/component/gameplay.cpp
Normal 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)
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
231
src/client/component/gui_script_console.cpp
Normal file
231
src/client/component/gui_script_console.cpp
Normal 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)
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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
@ -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();
|
||||
|
@ -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_;
|
||||
|
@ -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)};
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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};
|
||||
|
@ -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(
|
||||
|
@ -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:
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user