Add gui mapents viewer + fixes
This commit is contained in:
parent
a5d3b55681
commit
7f4eb242d9
@ -9,7 +9,7 @@ namespace gui::asset_list
|
|||||||
void add_view_button(int id, game::XAssetType type, const char* name);
|
void add_view_button(int id, game::XAssetType type, const char* name);
|
||||||
|
|
||||||
template <typename T>
|
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;
|
static std::unordered_set<std::string> opened_assets;
|
||||||
add_asset_view_callback(type, [](const std::string& name)
|
add_asset_view_callback(type, [](const std::string& name)
|
||||||
@ -32,7 +32,10 @@ namespace gui::asset_list
|
|||||||
auto is_open = true;
|
auto is_open = true;
|
||||||
if (ImGui::Begin(name.data(), &is_open))
|
if (ImGui::Begin(name.data(), &is_open))
|
||||||
{
|
{
|
||||||
draw_callback(header);
|
if (!draw_callback(header))
|
||||||
|
{
|
||||||
|
is_open = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
|
146
src/client/component/gui/assets/mapents.cpp
Normal file
146
src/client/component/gui/assets/mapents.cpp
Normal 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)
|
@ -46,7 +46,7 @@ namespace gui::asset_list::material
|
|||||||
return image_type_names[type];
|
return image_type_names[type];
|
||||||
}
|
}
|
||||||
|
|
||||||
void draw_material_window(game::Material* asset)
|
bool draw_material_window(game::Material* asset)
|
||||||
{
|
{
|
||||||
ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
|
ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
|
||||||
if (ImGui::TreeNode("textures"))
|
if (ImGui::TreeNode("textures"))
|
||||||
@ -99,6 +99,8 @@ namespace gui::asset_list::material
|
|||||||
DRAW_ASSET_PROPERTY(materialType, "%i");
|
DRAW_ASSET_PROPERTY(materialType, "%i");
|
||||||
DRAW_ASSET_PROPERTY(layerCount, "%i");
|
DRAW_ASSET_PROPERTY(layerCount, "%i");
|
||||||
DRAW_ASSET_PROPERTY(assetFlags, "%X");
|
DRAW_ASSET_PROPERTY(assetFlags, "%X");
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
static bool flip_axis = false;
|
||||||
ImGui::Checkbox("flip axis", &flip_axis);
|
ImGui::Checkbox("flip axis", &flip_axis);
|
||||||
@ -252,8 +252,17 @@ namespace gui::asset_list::xmodel
|
|||||||
|
|
||||||
if (ImGui::TreeNode("surface materials"))
|
if (ImGui::TreeNode("surface materials"))
|
||||||
{
|
{
|
||||||
|
game::Material* prev_material = nullptr;
|
||||||
|
|
||||||
for (auto i = 0; i < asset->numsurfs; i++)
|
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))
|
if (ImGui::Button(asset->materialHandles[i]->name))
|
||||||
{
|
{
|
||||||
gui::copy_to_clipboard(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::copy_to_clipboard(asset->compositeModels[i]->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gui::asset_list::add_view_button(i, game::ASSET_TYPE_XMODEL, asset->compositeModels[i]->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::TreePop();
|
ImGui::TreePop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
163
src/client/utils/mapents.cpp
Normal file
163
src/client/utils/mapents.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
34
src/client/utils/mapents.hpp
Normal file
34
src/client/utils/mapents.hpp
Normal 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);
|
||||||
|
}
|
@ -242,4 +242,9 @@ namespace utils::string
|
|||||||
|
|
||||||
return *b_ == '\0';
|
return *b_ == '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_numeric(const std::string& text)
|
||||||
|
{
|
||||||
|
return std::to_string(atoi(text.data())) == text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,4 +105,6 @@ namespace utils::string
|
|||||||
std::string truncate(const std::string& text, const size_t length, const std::string& end);
|
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 strstr_lower(const char* a, const char* b);
|
||||||
|
|
||||||
|
bool is_numeric(const std::string& text);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user