Add gui mapents viewer + fixes

This commit is contained in:
fed 2023-07-15 23:31:55 +02:00
parent a5d3b55681
commit 7f4eb242d9
8 changed files with 372 additions and 4 deletions

View File

@ -9,7 +9,7 @@ namespace gui::asset_list
void add_view_button(int id, game::XAssetType type, const char* name);
template <typename T>
void add_asset_view(game::XAssetType type, const std::function<void(T*)>& draw_callback)
void add_asset_view(game::XAssetType type, const std::function<bool(T*)>& draw_callback)
{
static std::unordered_set<std::string> opened_assets;
add_asset_view_callback(type, [](const std::string& name)
@ -32,7 +32,10 @@ namespace gui::asset_list
auto is_open = true;
if (ImGui::Begin(name.data(), &is_open))
{
draw_callback(header);
if (!draw_callback(header))
{
is_open = false;
}
}
ImGui::End();

View File

@ -0,0 +1,146 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "component/scheduler.hpp"
#include "component/command.hpp"
#include "component/fastfiles.hpp"
#include "component/gsc/script_loading.hpp"
#include "../gui.hpp"
#include "../asset_list.hpp"
#include "utils/mapents.hpp"
#include <utils/string.hpp>
#include <utils/hook.hpp>
#include <utils/concurrency.hpp>
#include <utils/io.hpp>
namespace gui::asset_list::mapents
{
namespace
{
using entity_t = std::vector<std::pair<std::string, std::string>>;
struct mapents_t
{
game::MapEnts* asset;
std::string converted_mapents;
std::thread parse_thread;
std::atomic_bool is_parsing = false;
std::atomic_bool done_parsing = false;
};
std::unordered_set<std::string> temp_files;
utils::concurrency::container<mapents_t, std::recursive_mutex> mapents;
void parse_mapents(game::MapEnts* asset, mapents_t& data)
{
data.is_parsing = true;
data.asset = asset;
const auto str_data = std::string{asset->entityString, static_cast<size_t>(asset->numEntityChars)};
data.parse_thread = std::thread([=]
{
const auto new_data = ::mapents::parse(str_data, [](const std::uint16_t id)
{
return gsc::gsc_ctx->token_name(id);
});
std::string converted_mapents;
for (const auto& entity : new_data.entities)
{
converted_mapents.append("{\n");
const auto var_list = entity.get_var_list();
for (const auto& var : var_list)
{
if (var.sl_string)
{
converted_mapents.append(
utils::string::va("0 \"%s\" \"%s\"\n", var.key.data(), var.value.data()));
}
else
{
converted_mapents.append(
utils::string::va("\"%s\" \"%s\"\n", var.key.data(), var.value.data()));
}
}
converted_mapents.append("}\n");
}
mapents.access([=](mapents_t& data)
{
data.is_parsing = false;
data.done_parsing = true;
data.converted_mapents = converted_mapents;
});
});
}
void draw_entity_string(game::MapEnts* asset)
{
ImGui::InputTextMultiline("entity_string", asset->entityString, asset->numEntityChars,
ImGui::GetWindowSize(), ImGuiInputTextFlags_ReadOnly);
}
bool create_and_open_mapents_file(mapents_t& data)
{
const auto current_path = std::filesystem::current_path().generic_string();
const std::string path = utils::string::va("%s\\h2-mod\\tmp\\%s", current_path.data(), data.asset->name);
temp_files.insert(path);
utils::io::write_file(path, data.converted_mapents, false);
ShellExecuteA(nullptr, nullptr, path.data(), nullptr, nullptr, SW_SHOWNORMAL);
return false;
}
bool draw_asset(game::MapEnts* asset)
{
return mapents.access<bool>([&](mapents_t& data)
{
if (!data.is_parsing && data.done_parsing)
{
if (data.parse_thread.joinable())
{
data.parse_thread.join();
}
else
{
create_and_open_mapents_file(data);
return false;
}
}
if (!data.is_parsing && !data.done_parsing)
{
parse_mapents(asset, data);
}
ImGui::Text("Parsing mapents...");
return true;
});
}
}
class component final : public component_interface
{
public:
void post_unpack() override
{
gui::asset_list::add_asset_view<game::MapEnts>(game::ASSET_TYPE_MAP_ENTS, draw_asset);
}
void pre_destroy() override
{
for (const auto& file : temp_files)
{
utils::io::remove_file(file);
}
}
};
}
REGISTER_COMPONENT(gui::asset_list::mapents::component)

View File

@ -46,7 +46,7 @@ namespace gui::asset_list::material
return image_type_names[type];
}
void draw_material_window(game::Material* asset)
bool draw_material_window(game::Material* asset)
{
ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
if (ImGui::TreeNode("textures"))
@ -99,6 +99,8 @@ namespace gui::asset_list::material
DRAW_ASSET_PROPERTY(materialType, "%i");
DRAW_ASSET_PROPERTY(layerCount, "%i");
DRAW_ASSET_PROPERTY(assetFlags, "%X");
return true;
}
}

View File

@ -177,7 +177,7 @@ namespace gui::asset_list::xmodel
}
}
void draw_xmodel_window(game::XModel* asset)
bool draw_xmodel_window(game::XModel* asset)
{
static bool flip_axis = false;
ImGui::Checkbox("flip axis", &flip_axis);
@ -252,8 +252,17 @@ namespace gui::asset_list::xmodel
if (ImGui::TreeNode("surface materials"))
{
game::Material* prev_material = nullptr;
for (auto i = 0; i < asset->numsurfs; i++)
{
if (prev_material == asset->materialHandles[i])
{
continue;
}
prev_material = asset->materialHandles[i];
if (ImGui::Button(asset->materialHandles[i]->name))
{
gui::copy_to_clipboard(asset->materialHandles[i]->name);
@ -275,11 +284,15 @@ namespace gui::asset_list::xmodel
{
gui::copy_to_clipboard(asset->compositeModels[i]->name);
}
gui::asset_list::add_view_button(i, game::ASSET_TYPE_XMODEL, asset->compositeModels[i]->name);
}
ImGui::TreePop();
}
}
return true;
}
}

View File

@ -0,0 +1,163 @@
#include <std_include.hpp>
#include "mapents.hpp"
#include <utils/string.hpp>
namespace mapents
{
void mapents_entity::add_var(const spawn_var& var)
{
this->vars.push_back(var);
}
std::string mapents_entity::get(const std::string& key) const
{
for (const auto& var : this->vars)
{
if (var.key == key)
{
return var.value;
}
}
return "";
}
std::vector<spawn_var> mapents_entity::get_var_list() const
{
return this->vars;
}
void mapents_entity::clear()
{
this->vars.clear();
}
mapents_list parse(const std::string& data, const token_name_callback& get_token_name)
{
mapents_list list;
mapents_entity current_entity;
const auto lines = utils::string::split(data, '\n');
auto in_map_ent = false;
auto in_comment = false;
for (auto i = 0; i < lines.size(); i++)
{
auto line = lines[i];
if (line.ends_with('\r'))
{
line.pop_back();
}
if (line.starts_with("/*") || line.ends_with("/*"))
{
in_comment = true;
continue;
}
if (line.starts_with("*/") || line.ends_with("*/"))
{
in_comment = false;
continue;
}
if (in_comment || line.starts_with("//") || line.empty())
{
continue;
}
if (line[0] == '{' && !in_map_ent)
{
current_entity.clear();
in_map_ent = true;
continue;
}
if (line[0] == '{' && in_map_ent)
{
throw std::runtime_error(utils::string::va("Unexpected '{' on line %i", i));
}
if (line[0] == '}' && in_map_ent)
{
list.entities.push_back(current_entity);
in_map_ent = false;
continue;
}
if (line[0] == '}' && !in_map_ent)
{
throw std::runtime_error(utils::string::va("Unexpected '}' on line %i", i));
}
if (line[0] == '\n' || line[0] == '\0' || line[0] == '\n')
{
continue;
}
spawn_var var{};
if (line.starts_with("0 \""))
{
std::regex expr(R"~(0 "(.+)" "(.*)")~");
std::smatch match{};
if (!std::regex_search(line, match, expr))
{
throw std::runtime_error(utils::string::va("Failed to parse line %i (%s)", i, line.data()));
continue;
}
var.key = utils::string::to_lower(match[1].str());
var.value = match[2].str();
var.sl_string = true;
}
else
{
std::regex expr(R"~((.+) "(.*)")~");
std::smatch match{};
if (!std::regex_search(line, match, expr))
{
throw std::runtime_error(
utils::string::va("Failed to parse line %i (%s)", i, line.data()));
continue;
}
var.key = utils::string::to_lower(match[1].str());
var.value = match[2].str();
if (utils::string::is_numeric(var.key) && !var.key.starts_with("\"") && !var.key.ends_with("\""))
{
var.key = get_token_name(static_cast<std::uint32_t>(std::atoi(var.key.data())));
}
else if (var.key.starts_with("\"") && var.key.ends_with("\"") && var.key.size() >= 3)
{
var.key = var.key.substr(1, var.key.size() - 2);
}
else
{
throw std::runtime_error(
utils::string::va("Invalid key ('%s') on line %i (%s)", var.key.data(), i, line.data()));
continue;
}
}
if (var.key.size() <= 0)
{
throw std::runtime_error(
utils::string::va("Invalid key ('%s') on line %i (%s)", var.key.data(), i, line.data()));
continue;
}
if (var.value.size() <= 0)
{
continue;
}
current_entity.add_var(var);
}
return list;
}
}

View File

@ -0,0 +1,34 @@
#pragma once
namespace mapents
{
using token_name_callback = std::function<std::string(const std::uint32_t)>;
using token_id_callback = std::function<std::uint32_t(const std::string&)>;
struct spawn_var
{
std::string key;
std::string value;
bool sl_string;
};
class mapents_entity
{
public:
void clear();
void add_var(const spawn_var& var);
std::string get(const std::string& key) const;
std::vector<spawn_var> get_var_list() const;
private:
std::vector<spawn_var> vars;
};
struct mapents_list
{
std::vector<mapents_entity> entities;
};
mapents_list parse(const std::string& data, const token_name_callback& token_name);
}

View File

@ -242,4 +242,9 @@ namespace utils::string
return *b_ == '\0';
}
bool is_numeric(const std::string& text)
{
return std::to_string(atoi(text.data())) == text;
}
}

View File

@ -105,4 +105,6 @@ namespace utils::string
std::string truncate(const std::string& text, const size_t length, const std::string& end);
bool strstr_lower(const char* a, const char* b);
bool is_numeric(const std::string& text);
}