Better mod stats + add some gsc funcs

This commit is contained in:
fed 2023-05-24 17:06:48 +02:00
parent 9ee00ba9e8
commit 234f29690f
12 changed files with 644 additions and 148 deletions

View File

@ -12,6 +12,37 @@
namespace gsc
{
std::array<const char*, 27> var_typename =
{
"undefined",
"object",
"string",
"localized string",
"vector",
"float",
"int",
"codepos",
"precodepos",
"function",
"builtin function",
"builtin method",
"stack",
"animation",
"pre animation",
"thread",
"thread",
"thread",
"thread",
"struct",
"removed entity",
"entity",
"array",
"removed thread",
"<free>",
"thread list",
"endon list",
};
namespace
{
utils::hook::detour scr_emit_function_hook;
@ -20,37 +51,6 @@ namespace gsc
std::string unknown_function_error;
std::array<const char*, 27> var_typename =
{
"undefined",
"object",
"string",
"localized string",
"vector",
"float",
"int",
"codepos",
"precodepos",
"function",
"builtin function",
"builtin method",
"stack",
"animation",
"pre animation",
"thread",
"thread",
"thread",
"thread",
"struct",
"removed entity",
"entity",
"array",
"removed thread",
"<free>",
"thread list",
"endon list",
};
void scr_emit_function_stub(unsigned int filename, unsigned int thread_name, char* code_pos)
{
current_filename = filename;

View File

@ -2,5 +2,7 @@
namespace gsc
{
extern std::array<const char*, 27> var_typename;
std::optional<std::pair<std::string, std::string>> find_function(const char* pos);
}

View File

@ -10,8 +10,15 @@
#include "component/console.hpp"
#include "component/command.hpp"
#include "component/notifies.hpp"
#include "component/scheduler.hpp"
#include "component/mods.hpp"
#include "component/mod_stats.hpp"
#include "component/scripting.hpp"
#include "game/scripting/script_value.hpp"
#include "game/scripting/execution.hpp"
#include "game/ui_scripting/execution.hpp"
#include <utils/string.hpp>
#include <utils/hook.hpp>
@ -225,6 +232,42 @@ namespace gsc
return game::scr_VmPub->top[-index];
}
nlohmann::json gsc_to_json(const scripting::script_value& value)
{
#define CHECK_TYPE(__type__) \
if (value.is<__type__>()) \
{ \
return value.as<__type__>(); \
} \
CHECK_TYPE(std::string);
CHECK_TYPE(int);
CHECK_TYPE(float);
CHECK_TYPE(bool);
return {};
#undef CHECK_TYPE
}
scripting::script_value json_to_gsc(const nlohmann::json& value)
{
#define CHECK_TYPE(__func__, __type__) \
if (value.__func__()) \
{ \
return value.get<__type__>(); \
} \
CHECK_TYPE(is_string, std::string);
CHECK_TYPE(is_number_integer, int);
CHECK_TYPE(is_number_float, float);
CHECK_TYPE(is_boolean, bool);
return {};
#undef CHECK_TYPE
}
}
void scr_error(bool force_print, const char* fmt, ...)
@ -322,6 +365,115 @@ namespace gsc
const auto cmd = game::Scr_GetString(0);
command::execute(cmd);
});
add_function("luinotify", []()
{
const std::string name = game::Scr_GetString(0);
const std::string data = game::Scr_GetString(1);
scheduler::once([=]()
{
ui_scripting::notify(name, {{"data", data}});
}, scheduler::pipeline::lui);
});
add_function("statsset", []()
{
const auto key = game::Scr_GetString(0);
const auto value = get_argument(1);
const auto json_value = gsc_to_json(value);
mod_stats::set(key, json_value);
});
add_function("statssetstruct", []()
{
const auto struct_ = game::Scr_GetString(0);
const auto field = game::Scr_GetString(1);
const auto value = get_argument(2);
const auto json_value = gsc_to_json(value);
mod_stats::set_struct(struct_, field, json_value);
});
add_function("statsget", []()
{
const auto key = game::Scr_GetString(0);
const auto& value = mod_stats::get(key);
scripting::push_value(json_to_gsc(value));
});
add_function("statsgetor", []()
{
const auto key = game::Scr_GetString(0);
const auto default_value = get_argument(1);
const auto json_default_value = gsc_to_json(default_value);
const auto value = mod_stats::get(key, json_default_value);
scripting::push_value(json_to_gsc(value));
});
add_function("statsgetstruct", []()
{
const auto struct_ = game::Scr_GetString(0);
const auto field = game::Scr_GetString(1);
const auto value = mod_stats::get_struct(struct_, field);
scripting::push_value(json_to_gsc(value));
});
add_function("statsgetstructor", []()
{
const auto struct_ = game::Scr_GetString(0);
const auto field = game::Scr_GetString(1);
const auto default_value = get_argument(2);
const auto json_default_value = gsc_to_json(default_value);
const auto value = mod_stats::get_struct(struct_, field, json_default_value);
scripting::push_value(json_to_gsc(value));
});
add_function("typeof", []()
{
const auto arg = get_argument(0);
const auto type = arg.get_raw().type;
if (type < var_typename.size())
{
game::Scr_AddString(var_typename[type]);
}
else
{
game::Scr_AddString("unknown");
}
});
add_function("sharedset", []()
{
const std::string key = game::Scr_GetString(0);
const std::string value = game::Scr_GetString(1);
scripting::shared_table.access([&](scripting::shared_table_t& table)
{
table[key] = value;
});
});
add_function("sharedget", []()
{
const std::string key = game::Scr_GetString(0);
const auto value = scripting::shared_table.access<std::string>(
[&](scripting::shared_table_t& table)
{
return table[key];
}
);
game::Scr_AddString(value.data());
});
add_function("sharedclear", []()
{
scripting::shared_table.access([&](scripting::shared_table_t& table)
{
table.clear();
});
});
}
};
}

View File

@ -20,6 +20,23 @@ namespace gui::debug
{
namespace
{
struct debug_line
{
float start[3];
float end[3];
float color[4];
};
struct debug_square
{
float origin[3];
float color[4];
};
std::vector<debug_line> debug_lines;
std::vector<debug_square> debug_squares;
std::mutex debug_items_mutex;
game::dvar_t* cl_paused = nullptr;
enum object_type
@ -570,6 +587,12 @@ namespace gui::debug
continue;
}
float screen_center[2]{};
ImGuiWindow* window = ImGui::GetCurrentWindow();
world_pos_to_screen_pos(origin, screen_center);
window->DrawList->AddText(ImGui::GetDefaultFont(), ImGui::GetFontSize(), ImVec2(screen_center[0],
screen_center[1]), ImColor(255, 255, 0, 255), utils::string::va("%i", node->constant.type), 0, 0.f, 0);
switch (path_node_settings.type)
{
case object_type::square:
@ -654,6 +677,21 @@ namespace gui::debug
}
}
void draw_debug_items()
{
std::lock_guard _0(debug_items_mutex);
for (auto& line : debug_lines)
{
draw_line(line.start, line.end, line.color, 1.f);
}
for (auto& square : debug_squares)
{
draw_square(square.origin, 100.f, square.color);
}
}
void update_camera()
{
camera[0] = game::refdef->org[0];
@ -688,6 +726,62 @@ namespace gui::debug
}
}
size_t add_debug_line(const float* start, const float* end, const float* color)
{
debug_line line{};
std::memcpy(line.start, start, sizeof(float[3]));
std::memcpy(line.end, end, sizeof(float[3]));
std::memcpy(line.color, color, sizeof(float[4]));
std::lock_guard _0(debug_items_mutex);
const auto index = debug_squares.size();
debug_lines.emplace_back(line);
return index;
}
void set_debug_line_color(size_t line, const float* color)
{
std::lock_guard _0(debug_items_mutex);
if (line >= debug_lines.size())
{
return;
}
auto& line_ = debug_lines.at(line);
std::memcpy(line_.color, color, sizeof(float[4]));
}
size_t add_debug_square(const float* origin, const float* color)
{
debug_square line{};
std::memcpy(line.origin, origin, sizeof(float[3]));
std::memcpy(line.color, color, sizeof(float[4]));
std::lock_guard _0(debug_items_mutex);
const auto index = debug_squares.size();
debug_squares.emplace_back(line);
return index;
}
void set_debug_square_color(size_t square, const float* color)
{
std::lock_guard _0(debug_items_mutex);
if (square >= debug_squares.size())
{
return;
}
auto& square_ = debug_squares.at(square);
std::memcpy(square_.color, color, sizeof(float[4]));
}
void reset_debug_items()
{
std::lock_guard _0(debug_items_mutex);
debug_lines.clear();
debug_squares.clear();
}
class component final : public component_interface
{
public:
@ -705,6 +799,7 @@ namespace gui::debug
begin_render_window();
draw_nodes();
draw_triggers();
draw_debug_items();
end_render_window();
}, true);

View File

@ -0,0 +1,10 @@
namespace gui::debug
{
size_t add_debug_line(const float* start, const float* end, const float* color);
void set_debug_line_color(size_t line, const float* color);
size_t add_debug_square(const float* origin, const float* color);
void set_debug_square_color(size_t square, const float* color);
void reset_debug_items();
}

View File

@ -0,0 +1,245 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "console.hpp"
#include "filesystem.hpp"
#include "mods.hpp"
#include "mod_stats.hpp"
#include <utils/hook.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
#include <utils/concurrency.hpp>
#include <utils/thread.hpp>
#include <utils/properties.hpp>
namespace mod_stats
{
namespace
{
struct
{
utils::concurrency::container<mod_stats_t> current_stats;
std::atomic_bool modified_stats;
std::atomic_bool kill_thread;
std::thread stats_write_thread;
} globals{};
std::optional<std::string> get_mod_basename()
{
const auto mod = mods::get_mod();
if (!mod.has_value())
{
return {};
}
const auto& value = mod.value();
const auto last_index = value.find_last_of('/');
const auto basename = value.substr(last_index + 1);
return {basename};
}
std::optional<std::string> get_stats_path()
{
const auto current_mod = get_mod_basename();
if (!current_mod.has_value())
{
return {};
}
const auto path = utils::properties::get_appdata_path() /
"player/modstats" / (current_mod.value() + ".json");
return {path.generic_string()};
}
nlohmann::json default_mod_stats()
{
nlohmann::json json;
json["maps"] = {};
return json;
}
nlohmann::json verify_mod_stats(nlohmann::json& json)
{
if (!json.is_object())
{
json = {};
}
if (!json.contains("maps") || !json["maps"].is_object())
{
json["maps"] = {};
}
return json;
}
nlohmann::json parse_mod_stats()
{
const auto stats_file = get_stats_path();
if (!stats_file.has_value())
{
return default_mod_stats();
}
const auto& stats_file_value = stats_file.value();
if (!utils::io::file_exists(stats_file_value))
{
return default_mod_stats();
}
const auto data = utils::io::read_file(stats_file_value);
try
{
auto json = nlohmann::json::parse(data);
return verify_mod_stats(json);
}
catch (const std::exception& e)
{
console::error("Failed to parse json mod stats file \"%s\": %s",
stats_file_value.data(), e.what());
}
return default_mod_stats();
}
}
void initialize()
{
globals.modified_stats = false;
globals.current_stats.access([](mod_stats_t& stats)
{
stats = parse_mod_stats();
});
}
utils::concurrency::container<mod_stats_t>& get_stats()
{
return globals.current_stats;
}
void set_modified()
{
globals.modified_stats = true;
}
void write()
{
const auto path = get_stats_path();
if (!path.has_value())
{
return;
}
console::debug("[Mod stats] writing stats\n");
globals.current_stats.access([&](mod_stats_t& stats)
{
const auto dump = stats.dump(4);
const auto& path_value = path.value();
utils::io::write_file(path_value, dump, false);
globals.modified_stats = false;
});
}
nlohmann::json get(const std::string& key, const nlohmann::json& default_value)
{
return get_stats().access<nlohmann::json>([&](mod_stats_t& stats)
-> nlohmann::json
{
if (!stats.is_object() || stats[key].is_null())
{
return default_value;
}
return stats[key];
});
}
nlohmann::json get_all()
{
return get_stats().access<nlohmann::json>([&](mod_stats_t& stats)
-> nlohmann::json
{
return stats;
});
}
nlohmann::json get_struct(const std::string& name, const std::string& field, const nlohmann::json& default_value)
{
return get_stats().access<nlohmann::json>([&](mod_stats_t& stats)
-> nlohmann::json
{
if (!stats.is_object() || !stats[name].is_object() || stats[name].is_null())
{
return default_value;
}
return stats[name][field];
});
}
void set(const std::string& key, const nlohmann::json& value)
{
get_stats().access([&](mod_stats_t& stats)
{
stats[key] = value;
set_modified();
});
}
void set_struct(const std::string& name, const std::string& field, const nlohmann::json& value)
{
get_stats().access([&](mod_stats_t& stats)
{
stats[name][field] = value;
set_modified();
});
}
void set_all(const nlohmann::json& value)
{
get_stats().access([&](mod_stats_t& stats)
{
stats = value;
set_modified();
});
}
class component final : public component_interface
{
public:
void post_unpack() override
{
globals.stats_write_thread = utils::thread::create_named_thread("Stats Write (H2-Mod)", []()
{
while (!globals.kill_thread)
{
const auto _0 = gsl::finally([]
{
std::this_thread::sleep_for(50ms);
});
if (!globals.modified_stats)
{
continue;
}
write();
}
});
}
void pre_destroy() override
{
globals.kill_thread = true;
if (globals.stats_write_thread.joinable())
{
globals.stats_write_thread.join();
}
}
};
}
REGISTER_COMPONENT(mod_stats::component)

View File

@ -0,0 +1,21 @@
#pragma once
#include <utils/concurrency.hpp>
namespace mod_stats
{
using mod_stats_t = nlohmann::ordered_json;
utils::concurrency::container<mod_stats_t>& get_stats();
void initialize();
void set_modified();
void write();
nlohmann::json get(const std::string& key, const nlohmann::json& default_value = {});
nlohmann::json get_struct(const std::string& name, const std::string& field, const nlohmann::json& default_value = {});
nlohmann::json get_all();
void set(const std::string& key, const nlohmann::json& value);
void set_struct(const std::string& name, const std::string& field, const nlohmann::json& value);
void set_all(const nlohmann::json& value);
}

View File

@ -9,11 +9,13 @@
#include "filesystem.hpp"
#include "fonts.hpp"
#include "mods.hpp"
#include "mod_stats.hpp"
#include "loadscreen.hpp"
#include <utils/hook.hpp>
#include <utils/io.hpp>
#include <utils/string.hpp>
#include <utils/concurrency.hpp>
#define MOD_FOLDER "mods"
#define MOD_STATS_FOLDER "players2/modstats"
@ -174,97 +176,6 @@ namespace mods
}
}
}
std::optional<std::string> get_mod_basename()
{
const auto mod = get_mod();
if (!mod.has_value())
{
return {};
}
const auto& value = mod.value();
const auto last_index = value.find_last_of('/');
const auto basename = value.substr(last_index + 1);
return {basename};
}
nlohmann::json default_mod_stats()
{
nlohmann::json json;
json["maps"] = {};
return json;
}
nlohmann::json verify_mod_stats(nlohmann::json& json)
{
if (!json.is_object())
{
json = {};
}
if (!json.contains("maps") || !json["maps"].is_object())
{
json["maps"] = {};
}
return json;
}
nlohmann::json parse_mod_stats()
{
const auto name = get_mod_basename();
if (!name.has_value())
{
return default_mod_stats();
}
const auto& name_value = name.value();
const auto stat_file = MOD_STATS_FOLDER "/" + name_value + ".json";
if (!utils::io::file_exists(stat_file))
{
return default_mod_stats();
}
const auto data = utils::io::read_file(stat_file);
try
{
auto json = nlohmann::json::parse(data);
return verify_mod_stats(json);
}
catch (const std::exception& e)
{
console::error("Failed to parse json mod stat file \"%s.json\": %s",
name_value.data(), e.what());
}
return default_mod_stats();
}
void initialize_stats()
{
get_current_stats() = parse_mod_stats();
}
}
nlohmann::json& get_current_stats()
{
static nlohmann::json stats;
stats = verify_mod_stats(stats);
return stats;
}
void write_mod_stats()
{
const auto name = get_mod_basename();
if (!name.has_value())
{
return;
}
const auto& name_value = name.value();
const auto stat_file = MOD_STATS_FOLDER "/" + name_value + ".json";
utils::io::write_file(stat_file, get_current_stats().dump(), false);
}
bool mod_requires_restart(const std::string& path)
@ -281,9 +192,9 @@ namespace mods
filesystem::unregister_path(mod_info.path.value());
}
write_mod_stats();
initialize_stats();
mod_stats::write();
mod_info.path = path;
mod_stats::initialize();
filesystem::register_path(path);
parse_mod_zones();
}

View File

@ -34,7 +34,4 @@ namespace mods
std::vector<std::string> get_mod_list();
std::optional<nlohmann::json> get_mod_info(const std::string& mod);
bool mod_exists(const std::string& folder);
nlohmann::json& get_current_stats();
void write_mod_stats();
}

View File

@ -7,6 +7,7 @@
#include "scheduler.hpp"
#include "scripting.hpp"
#include "console.hpp"
#include "command.hpp"
#include "gsc/script_loading.hpp"
@ -173,6 +174,7 @@ namespace scripting
void add_function_sort(unsigned int id, const char* pos)
{
std::string filename = current_file;
if (current_file_id)
{
@ -369,6 +371,12 @@ namespace scripting
utils::hook::call(0x1404B07D2, get_spawn_point_stub);
command::add("getfunctionptr", [](const command::params& params)
{
const auto func = find_function(params.get(1), false);
console::info("%p\n", func);
});
scheduler::loop([]()
{
lua::engine::run_frame();

View File

@ -11,6 +11,7 @@
#include "scripting.hpp"
#include "fastfiles.hpp"
#include "mods.hpp"
#include "mod_stats.hpp"
#include "updater.hpp"
#include "console.hpp"
#include "language.hpp"
@ -505,41 +506,37 @@ namespace ui_scripting
mods_stats_table["set"] = [](const std::string& key, const script_value& value)
{
const auto json_value = lua_to_json(value);
mods::get_current_stats()[key] = json_value;
mods::write_mod_stats();
mod_stats::set(key, json_value);
};
mods_stats_table["get"] = [](const std::string& key)
{
return json_to_lua(mods::get_current_stats());
return json_to_lua(mod_stats::get(key));
};
mods_stats_table["mapset"] = [](const std::string& mapname,
mods_stats_table["setstruct"] = [](const std::string& mapname,
const std::string& key, const script_value& value)
{
const auto json_value = lua_to_json(value);
auto& stats = mods::get_current_stats();
stats["maps"][mapname][key] = json_value;
mods::write_mod_stats();
mod_stats::set_struct(mapname, key, json_value);
};
mods_stats_table["mapget"] = [](const std::string& mapname,
mods_stats_table["getstruct"] = [](const std::string& mapname,
const std::string& key)
{
auto& stats = mods::get_current_stats();
return json_to_lua(stats["maps"][mapname][key]);
return json_to_lua(mod_stats::get_struct(mapname, key));
};
mods_stats_table["save"] = mods::write_mod_stats;
mods_stats_table["save"] = mod_stats::write;
mods_stats_table["getall"] = []()
{
return json_to_lua(mods::get_current_stats());
return json_to_lua(mod_stats::get_all());
};
mods_stats_table["setfromjson"] = [](const std::string& data)
{
const auto json = nlohmann::json::parse(data);
mods::get_current_stats() = json;
mod_stats::set_all(json);
};
auto config_table = table();
@ -742,6 +739,11 @@ namespace ui_scripting
lua["table"]["unpack"] = lua["unpack"];
lua["luiglobals"] = lua;
lua["printmemoryusage"] = []()
{
utils::hook::invoke<void>(0x14031F470);
};
load_script("lui_common", lui_common);
load_script("lui_updater", lui_updater);
load_script("lua_json", lua_json);

View File

@ -12,6 +12,7 @@
#include "component/mods.hpp"
#include "component/scheduler.hpp"
#include "component/filesystem.hpp"
#include "component/gui/debug.hpp"
#include "component/gsc/script_loading.hpp"
@ -478,6 +479,64 @@ namespace scripting::lua
return sol::as_returns(returns);
};
}
void setup_debug_funcs(sol::state& state)
{
struct debug
{
};
auto debug_type = state.new_usertype<debug>("debug_");
state["debug"] = debug();
debug_type["reset"] = [](const debug&)
{
gui::debug::reset_debug_items();
};
debug_type["addline"] = [](const debug&, const vector& start, const vector& end, const vector& color)
{
float color_[4]{};
color_[0] = color[0];
color_[1] = color[1];
color_[2] = color[2];
color_[3] = 1.f;
return gui::debug::add_debug_line(start, end, color_);
};
debug_type["addsquare"] = [](const debug&, const vector& origin, const vector& color)
{
float color_[4]{};
color_[0] = color[0];
color_[1] = color[1];
color_[2] = color[2];
color_[3] = 1.f;
return gui::debug::add_debug_square(origin, color_);
};
debug_type["setsquarecolor"] = [](const debug&, const size_t& square, const vector& color)
{
float color_[4]{};
color_[0] = color[0];
color_[1] = color[1];
color_[2] = color[2];
color_[3] = 1.f;
gui::debug::set_debug_square_color(square, color_);
};
debug_type["setlinecolor"] = [](const debug&, const size_t& line, const vector& color)
{
float color_[4]{};
color_[0] = color[0];
color_[1] = color[1];
color_[2] = color[2];
color_[3] = 1.f;
gui::debug::set_debug_line_color(line, color_);
};
}
void setup_game_type(sol::state& state, event_handler& handler, scheduler& scheduler)
{
@ -793,14 +852,6 @@ namespace scripting::lua
scripting::get_dvar_int_overrides.erase(dvar);
};
game_type["luinotify"] = [](const game&, const std::string& name, const std::string& data)
{
::scheduler::once([=]()
{
ui_scripting::notify(name, {{"data", data}});
}, ::scheduler::pipeline::lui);
};
auto function_ptr_type = state.new_usertype<function_ptr>("functionptr",
sol::constructors<function_ptr(const std::string&, const std::string&)>());
@ -845,6 +896,7 @@ namespace scripting::lua
setup_io(this->state_);
setup_json(this->state_);
setup_vector_type(this->state_);
setup_debug_funcs(this->state_);
setup_entity_type(this->state_, this->event_handler_, this->scheduler_);
setup_game_type(this->state_, this->event_handler_, this->scheduler_);
@ -877,6 +929,7 @@ namespace scripting::lua
setup_io(this->state_);
setup_json(this->state_);
setup_vector_type(this->state_);
setup_debug_funcs(this->state_);
setup_entity_type(this->state_, this->event_handler_, this->scheduler_);
setup_game_type(this->state_, this->event_handler_, this->scheduler_);
}