Refactored weapons.bin into weapons.json for extensibility and readability. (#1632)

* Refactored weapons.bin into weapons.json for extensibility and human readability.
Added weapon attachments scraping from the meta files (currently is missing a lot of attachments, more than half, requires RPF reading refactoring to fix.)
Added Ammunation to Self -> Weapons, because it's vital you protect yourself, the patriotic way.

* Fixed weapons.xml not properly populating all the components.
Refactored buttons to use components::button.

* Refactored the Attachments code to implicitly trust that the attachments will be there now.
Added proper versioning to the weapons.json file.
Removed debug logging from gta_data_service.cpp.

* Fixed Ammunation buttons.
Added loading message for the new weapons.json system.
Fixed a bug where two components shared the same name, the user could not select the 2nd component.
Fixed Attachments displaying an attachment from a previous weapon if the user changed weapons.

* Fixed Tint Apply button not using the components::button template.
This commit is contained in:
gir489 2023-07-07 18:52:52 -04:00 committed by GitHub
parent f10c698396
commit 6f40a38045
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 283 additions and 51 deletions

View File

@ -32,7 +32,6 @@ namespace big
gta_data_service::gta_data_service() :
m_peds_cache(g_file_manager->get_project_file("./cache/peds.bin"), 5),
m_vehicles_cache(g_file_manager->get_project_file("./cache/vehicles.bin"), 4),
m_weapons_cache(g_file_manager->get_project_file("./cache/weapons.bin"), 5),
m_update_state(eGtaDataUpdateState::IDLE)
{
if (!is_cache_up_to_date())
@ -120,12 +119,28 @@ namespace big
const weapon_item& gta_data_service::weapon_by_hash(std::uint32_t hash)
{
for (const auto& [name, weapon] : m_weapons)
for (const auto& [name, weapon] : m_weapons_cache.weapon_map)
if (rage::joaat(name) == hash)
return weapon;
return gta_data_service::empty_weapon;
}
const weapon_component& gta_data_service::weapon_component_by_hash(std::uint32_t hash)
{
for (const auto& component : m_weapons_cache.weapon_components)
if (component.second.m_hash == hash)
return component.second;
return gta_data_service::empty_component;
}
const weapon_component& gta_data_service::weapon_component_by_name(std::string name)
{
for (const auto& component : m_weapons_cache.weapon_components)
if (component.first == name)
return component.second;
return gta_data_service::empty_component;
}
string_vec& gta_data_service::ped_types()
{
return m_ped_types;
@ -145,7 +160,26 @@ namespace big
{
m_peds_cache.load();
m_vehicles_cache.load();
m_weapons_cache.load();
auto weapons_file = g_file_manager->get_project_file("./cache/weapons.json");
if (weapons_file.exists())
{
std::ifstream file(weapons_file.get_path());
file.open(weapons_file.get_path());
try
{
nlohmann::json weapons_file_json;
file >> weapons_file_json;
m_weapons_cache = weapons_file_json["weapons_cache"];
file.close();
}
catch (const std::exception& exception)
{
file.close();
LOG(WARNING) << "Detected corrupt weapons: " << exception.what();
}
}
const auto file_version = memory::module("GTA5.exe").size();
@ -158,7 +192,8 @@ namespace big
load_peds();
load_vehicles();
load_weapons();
LOG(INFO) << "Loading " << m_weapons_cache.weapon_map.size() << " weapons from cache.";
LOG(INFO) << "Loading " << m_weapons_cache.weapon_components.size() << " weapon components from cache.";
LOG(VERBOSE) << "Loaded all data from cache.";
}
@ -199,24 +234,6 @@ namespace big
m_vehicles_cache.free();
}
void gta_data_service::load_weapons()
{
const auto weapon_count = m_weapons_cache.data_size() / sizeof(weapon_item);
LOG(INFO) << "Loading " << weapon_count << " weapons from cache.";
auto cached_weapons = reinterpret_cast<const weapon_item*>(m_weapons_cache.data());
for (size_t i = 0; i < weapon_count; i++)
{
const auto weapon = cached_weapons[i];
add_if_not_exists(m_weapon_types, weapon.m_weapon_type);
m_weapons.insert({weapon.m_name, weapon});
}
std::sort(m_weapon_types.begin(), m_weapon_types.end());
m_weapons_cache.free();
}
inline void parse_ped(std::vector<ped_item>& peds, std::vector<std::uint32_t>& mapped_peds, pugi::xml_document& doc)
{
const auto& items = doc.select_nodes("/CPedModelInfo__InitDataList/InitDatas/Item");
@ -253,10 +270,12 @@ namespace big
hash_array mapped_peds;
hash_array mapped_vehicles;
hash_array mapped_weapons;
hash_array mapped_components;
std::vector<ped_item> peds;
std::vector<vehicle_item> vehicles;
std::vector<weapon_item> weapons;
std::vector<weapon_component> weapon_components;
constexpr auto exists = [](const hash_array& arr, std::uint32_t val) -> bool {
return std::find(arr.begin(), arr.end(), val) != arr.end();
@ -313,6 +332,46 @@ namespace big
}
});
}
else if (const auto file_str = path.string(); file_str.find("weaponcomponents") != std::string::npos && path.extension() == ".meta")
{
rpf_wrapper.read_xml_file(path, [&exists, &weapon_components, &mapped_components](pugi::xml_document& doc) {
const auto& items = doc.select_nodes("/CWeaponComponentInfoBlob/Infos/*[self::Item[@type='CWeaponComponentInfo'] or self::Item[@type='CWeaponComponentFlashLightInfo'] or self::Item[@type='CWeaponComponentScopeInfo'] or self::Item[@type='CWeaponComponentSuppressorInfo'] or self::Item[@type='CWeaponComponentVariantModelInfo'] or self::Item[@type='CWeaponComponentClipInfo']]");
for (const auto& item_node : items)
{
const auto item = item_node.node();
const std::string name = item.child("Name").text().as_string();
const auto hash = rage::joaat(name);
if (!name.starts_with("COMPONENT"))
{
continue;
}
if (exists(mapped_components, hash))
{
continue;
}
mapped_components.emplace_back(hash);
std::string LocName = item.child("LocName").text().as_string();
std::string LocDesc = item.child("LocDesc").text().as_string();
if (LocName.ends_with("INVALID") || LocName.ends_with("RAIL"))
{
continue;
}
weapon_component component;
component.m_name = name;
component.m_hash = hash;
component.m_display_name = LocName;
component.m_display_desc = LocDesc;
weapon_components.push_back(component);
}
});
}
else if (const auto file_str = path.string(); file_str.find("weapon") != std::string::npos && path.extension() == ".meta")
{
rpf_wrapper.read_xml_file(path, [&exists, &weapons, &mapped_weapons](pugi::xml_document& doc) {
@ -336,9 +395,9 @@ namespace big
auto weapon = weapon_item{};
std::strncpy(weapon.m_name, name, sizeof(weapon.m_name));
weapon.m_name = name;
std::strncpy(weapon.m_display_name, human_name_hash, sizeof(weapon.m_display_name));
weapon.m_display_name = human_name_hash;
auto weapon_flags = std::string(item.child("WeaponFlags").text().as_string());
@ -378,10 +437,10 @@ namespace big
if (std::strlen(category) > 6)
{
std::strncpy(weapon.m_weapon_type, category + 6, sizeof(weapon.m_weapon_type));
weapon.m_weapon_type = category + 6;
}
if (is_gun || !std::strcmp(weapon.m_weapon_type, "MELEE") || !std::strcmp(weapon.m_weapon_type, "UNARMED"))
if (is_gun || weapon.m_weapon_type == "MELEE" || weapon.m_weapon_type == "UNARMED")
{
const std::string reward_prefix = "REWARD_";
weapon.m_reward_hash = rage::joaat(reward_prefix + name);
@ -393,6 +452,14 @@ namespace big
}
}
for (pugi::xml_node attach_point : item.child("AttachPoints").children("Item"))
{
for (pugi::xml_node component : attach_point.child("Components").children("Item"))
{
weapon.m_attachments.push_back(component.child_value("Name"));
}
}
weapon.m_hash = hash;
weapons.emplace_back(std::move(weapon));
@ -453,7 +520,12 @@ namespace big
}
for (auto& item : weapons)
{
std::strncpy(item.m_display_name, HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION(item.m_display_name), sizeof(item.m_display_name));
item.m_display_name = HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION(item.m_display_name.c_str());
}
for (auto& item : weapon_components)
{
item.m_display_name = HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION(item.m_display_name.c_str());
item.m_display_desc = HUD::GET_FILENAME_FOR_AUDIO_CONVERSATION(item.m_display_desc.c_str());
}
for (auto it = peds.begin(); it != peds.end();)
{
@ -481,10 +553,10 @@ namespace big
m_update_state = eGtaDataUpdateState::IDLE;
LOG(INFO) << "Cache has been rebuilt.\n\tPeds: " << peds.size() << "\n\tVehicles: " << vehicles.size()
<< "\n\tWeapons: " << weapons.size();
<< "\n\tWeapons: " << weapons.size() << "\n\tWeaponComponents: " << weapon_components.size();
LOG(VERBOSE) << "Starting cache saving procedure...";
g_thread_pool->push([this, peds = std::move(peds), vehicles = std::move(vehicles), weapons = std::move(weapons)] {
g_thread_pool->push([this, peds = std::move(peds), vehicles = std::move(vehicles), weapons = std::move(weapons), weapon_components = std::move(weapon_components)] {
const auto file_version = memory::module("GTA5.exe").size();
{
@ -506,12 +578,34 @@ namespace big
}
{
const auto data_size = sizeof(weapon_item) * weapons.size();
m_weapons_cache.set_data(std::make_unique<std::uint8_t[]>(data_size), data_size);
std::memcpy(m_weapons_cache.data(), weapons.data(), data_size);
m_weapons_cache.version_info.m_game_build = g_pointers->m_gta.m_game_version;
m_weapons_cache.version_info.m_online_version = g_pointers->m_gta.m_online_version;
m_weapons_cache.version_info.m_file_version = file_version;
m_weapons_cache.set_header_version(file_version);
m_weapons_cache.write();
for (auto weapon : weapons)
{
add_if_not_exists(m_weapon_types, weapon.m_weapon_type);
m_weapons_cache.weapon_map.insert({weapon.m_name, weapon});
}
for (auto weapon_component : weapon_components)
{
m_weapons_cache.weapon_components.insert({weapon_component.m_name, weapon_component});
}
auto weapons_file = big::g_file_manager->get_project_file("./cache/weapons.json");
std::ofstream file(weapons_file.get_path());
try
{
nlohmann::json weapons_file_json;
weapons_file_json["weapons_cache"] = m_weapons_cache;
file << weapons_file_json;
file.flush();
}
catch (const std::exception& exception)
{
LOG(WARNING) << "Failed to write weapons JSON: " << exception.what();
}
}
LOG(INFO) << "Finished writing cache to disk.";
@ -519,4 +613,4 @@ namespace big
load_data();
});
}
}
}

View File

@ -2,7 +2,7 @@
#include "cache_file.hpp"
#include "ped_item.hpp"
#include "vehicle_item.hpp"
#include "weapon_item.hpp"
#include "weapon_file.hpp"
namespace big
{
@ -20,7 +20,6 @@ namespace big
using ped_map = std::map<std::string, ped_item>;
using vehicle_map = std::map<std::string, vehicle_item>;
using weapon_map = std::map<std::string, weapon_item>;
using string_vec = std::vector<std::string>;
class gta_data_service final
@ -39,6 +38,8 @@ namespace big
const ped_item& ped_by_hash(std::uint32_t hash);
const vehicle_item& vehicle_by_hash(std::uint32_t hash);
const weapon_item& weapon_by_hash(std::uint32_t hash);
const weapon_component& weapon_component_by_hash(std::uint32_t hash);
const weapon_component& weapon_component_by_name(std::string name);
string_vec& ped_types();
string_vec& vehicle_classes();
@ -52,9 +53,13 @@ namespace big
{
return m_vehicles;
}
weapon_map& weapons()
std::map<std::string, weapon_item>& weapons()
{
return m_weapons;
return m_weapons_cache.weapon_map;
}
std::map<std::string, weapon_component>& weapon_components()
{
return m_weapons_cache.weapon_components;
}
private:
@ -63,19 +68,17 @@ namespace big
void load_data();
void load_peds();
void load_vehicles();
void load_weapons();
void rebuild_cache();
private:
cache_file m_peds_cache;
cache_file m_vehicles_cache;
cache_file m_weapons_cache;
weapon_file m_weapons_cache;
// std::map is free sorting algo
ped_map m_peds;
vehicle_map m_vehicles;
weapon_map m_weapons;
string_vec m_ped_types;
string_vec m_vehicle_classes;
@ -87,6 +90,7 @@ namespace big
static constexpr ped_item empty_ped{};
static constexpr vehicle_item empty_vehicle{};
static constexpr weapon_item empty_weapon{};
static constexpr weapon_component empty_component{};
};
inline gta_data_service* g_gta_data_service{};

View File

@ -0,0 +1,15 @@
#pragma once
namespace big
{
class weapon_component final
{
public:
std::string m_name;
Hash m_hash;
std::string m_display_name;
std::string m_display_desc;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(weapon_component, m_name, m_hash, m_display_name, m_display_desc)
};
}

View File

@ -0,0 +1,30 @@
#pragma once
#include "weapon_item.hpp"
#include "weapon_component.hpp"
namespace big
{
class weapon_file
{
public:
struct version_info
{
std::string m_game_build;
std::string m_online_version;
std::uint32_t m_file_version;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(version_info, m_game_build, m_online_version, m_file_version)
} version_info{};
std::map<std::string, weapon_item> weapon_map;
std::map<std::string, weapon_component> weapon_components;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(weapon_file, version_info, weapon_map, weapon_components)
bool up_to_date(std::uint32_t file_version) const
{
return file_version == version_info.m_file_version;
}
};
}

View File

@ -2,17 +2,18 @@
namespace big
{
#pragma pack(push, 4)
class weapon_item final
{
public:
char m_name[32];
char m_display_name[32];
char m_weapon_type[16];
std::string m_name;
std::string m_display_name;
std::string m_weapon_type;
std::uint32_t m_hash;
std::uint32_t m_reward_hash;
std::uint32_t m_reward_ammo_hash;
std::vector<std::string> m_attachments;
bool m_throwable;
NLOHMANN_DEFINE_TYPE_INTRUSIVE(weapon_item, m_name, m_display_name, m_weapon_type, m_hash, m_reward_hash, m_reward_ammo_hash, m_attachments, m_throwable)
};
#pragma pack(pop)
}

View File

@ -181,4 +181,4 @@ namespace big
}
});
}
}
}

View File

@ -176,5 +176,93 @@ namespace big
ImGui::SliderFloat("Distance", &g.weapons.aimbot.distance, 1.f, 1000.f, "%.0f");
ImGui::PopItemWidth();
}
ImGui::SeparatorText("Ammunation");
static Hash selected_weapon_hash, selected_weapon_attachment_hash{};
static std::string selected_weapon, selected_weapon_attachment;
ImGui::PushItemWidth(300);
if (ImGui::BeginCombo("Weapons", selected_weapon.c_str()))
{
for (auto& weapon : g_gta_data_service->weapons())
{
bool is_selected = weapon.second.m_hash == selected_weapon_hash;
if (weapon.second.m_display_name != "NULL" && ImGui::Selectable(weapon.second.m_display_name.c_str(), is_selected, ImGuiSelectableFlags_None))
{
selected_weapon = weapon.second.m_display_name;
selected_weapon_hash = weapon.second.m_hash;
selected_weapon_attachment_hash = {};
selected_weapon_attachment.clear();
}
if (is_selected)
ImGui::SetItemDefaultFocus();
}
ImGui::EndCombo();
}
ImGui::PopItemWidth();
ImGui::SameLine();
components::button("Give Weapon", [] {
WEAPON::GIVE_WEAPON_TO_PED(self::ped, selected_weapon_hash, 9999, false, true);
});
ImGui::SameLine();
components::button("Remove Weapon", [] {
WEAPON::REMOVE_WEAPON_FROM_PED(self::ped, selected_weapon_hash);
});
ImGui::PushItemWidth(250);
if (ImGui::BeginCombo("Attachments", selected_weapon_attachment.c_str()))
{
auto weapon = g_gta_data_service->weapon_by_hash(selected_weapon_hash);
if (!weapon.m_attachments.empty())
{
for (std::string attachment : weapon.m_attachments)
{
auto attachment_component = g_gta_data_service->weapon_component_by_name(attachment);
std::string attachment_name = attachment_component.m_display_name;
Hash attachment_hash = attachment_component.m_hash;
if (attachment_hash == NULL)
{
attachment_name = attachment;
attachment_hash = rage::joaat(attachment);
}
bool is_selected = attachment_hash == selected_weapon_attachment_hash;
std::string display_name = attachment_name.append("##").append(std::to_string(attachment_hash));
if (ImGui::Selectable(display_name.c_str(), is_selected, ImGuiSelectableFlags_None))
{
selected_weapon_attachment = attachment_name;
selected_weapon_attachment_hash = attachment_hash;
}
if (is_selected)
ImGui::SetItemDefaultFocus();
}
}
ImGui::EndCombo();
}
ImGui::SameLine();
components::button("Add to Weapon", [] {
WEAPON::GIVE_WEAPON_COMPONENT_TO_PED(self::ped, selected_weapon_hash, selected_weapon_attachment_hash);
});
ImGui::SameLine();
components::button("Remove from Weapon", [] {
WEAPON::REMOVE_WEAPON_COMPONENT_FROM_PED(self::ped, selected_weapon_hash, selected_weapon_attachment_hash);
});
ImGui::PopItemWidth();
static const char* default_tints[]{"Black tint", "Green tint", "Gold tint", "Pink tint", "Army tint", "LSPD tint", "Orange tint", "Platinum tint"};
static const char* mk2_tints[]{"Classic Black", "Classic Grey", "Classic Two - Tone", "Classic White", "Classic Beige", "Classic Green", "Classic Blue", "Classic Earth", "Classic Brown & Black", "Red Contrast", "Blue Contrast", "Yellow Contrast", "Orange Contrast", "Bold Pink", "Bold Purple & Yellow", "Bold Orange", "Bold Green & Purple", "Bold Red Features", "Bold Green Features", "Bold Cyan Features", "Bold Yellow Features", "Bold Red & White", "Bold Blue & White", "Metallic Gold", "Metallic Platinum", "Metallic Grey & Lilac", "Metallic Purple & Lime", "Metallic Red", "Metallic Green", "Metallic Blue", "Metallic White & Aqua", "Metallic Red & Yellow"};
static int tint;
if (selected_weapon.ends_with("Mk II"))
{
ImGui::Combo("Tints", &tint, mk2_tints, IM_ARRAYSIZE(mk2_tints));
}
else
{
ImGui::Combo("Tints", &tint, default_tints, IM_ARRAYSIZE(default_tints));
}
ImGui::SameLine();
components::button("Apply", [] {
WEAPON::SET_PED_WEAPON_TINT_INDEX(self::ped, selected_weapon_hash, tint);
});
}
}

View File

@ -491,7 +491,7 @@ namespace big
if (ImGui::BeginCombo("##ped_weapon",
selected_ped_weapon_type == SPAWN_PED_NO_WEAPONS ? "NO_WEAPONS"_T.data() :
selected_ped_weapon_hash == 0 ? "ALL"_T.data() :
g_gta_data_service->weapon_by_hash(selected_ped_weapon_hash).m_display_name))
g_gta_data_service->weapon_by_hash(selected_ped_weapon_hash).m_display_name.c_str()))
{
if (selected_ped_weapon_type != SPAWN_PED_NO_WEAPONS)
{
@ -509,7 +509,7 @@ namespace big
{
if (selected_ped_weapon_type == SPAWN_PED_ALL_WEAPONS || weapon.m_weapon_type == weapon_type_arr[selected_ped_weapon_type])
{
if (ImGui::Selectable(weapon.m_display_name, weapon.m_hash == selected_ped_weapon_hash))
if (ImGui::Selectable(weapon.m_display_name.c_str(), weapon.m_hash == selected_ped_weapon_hash))
{
selected_ped_weapon_hash = weapon.m_hash;
}

View File

@ -186,7 +186,7 @@ namespace big
std::string filter = new_template.m_weapon_model;
std::transform(p_model.begin(), p_model.end(), p_model.begin(), ::tolower);
std::transform(filter.begin(), filter.end(), filter.begin(), ::tolower);
if (p_model.find(filter) != std::string::npos && ImGui::Selectable(p.m_name))
if (p_model.find(filter) != std::string::npos && ImGui::Selectable(p.m_name.c_str()))
{
new_template.m_weapon_model = p.m_name;
}