Legacy yim outfit editor (#865)

* add legacy yim outfit editor

* refractor code
This commit is contained in:
lonelybud
2026-07-29 18:03:30 +05:30
committed by GitHub
parent 57378938d8
commit f833a73a50
8 changed files with 534 additions and 214 deletions
+6
View File
@@ -25,6 +25,12 @@ namespace YimMenu
return std::string(start, end);
}
inline std::string TrimString(char* str)
{
std::string t = str;
return TrimString(t);
}
inline void StrCpySafe(char* dest, const char* src, int dest_size)
{
auto len = dest_size - 1;
+172
View File
@@ -0,0 +1,172 @@
#include "Outfit.hpp"
#include "game/backend/Self.hpp"
#include "game/gta/Natives.hpp"
#include "core/frontend/Notifications.hpp"
#include <fstream>
#include "core/util/Strings.hpp"
namespace YimMenu
{
namespace Outfit
{
void OutfitEditor::CheckBoundsDrawable(ComponentData& item, const int lower)
{
if (item.drawable_id > item.drawable_id_max)
item.drawable_id = item.drawable_id_max;
if (item.drawable_id < lower)
item.drawable_id = lower;
}
void OutfitEditor::CheckBoundsTexture(ComponentData& item, const int lower)
{
if (item.texture_id > item.texture_id_max)
item.texture_id = item.texture_id_max;
if (item.texture_id < lower)
item.texture_id = lower;
}
void OutfitEditor::SetSelfOutfit(OutfitComponents components, OutfitProps props, bool applyHair)
{
for (auto t : components.items)
{
auto item = t.second;
auto palette_var = item.palette_var;
if (item.palette_var == -1)
palette_var = PED::GET_PED_PALETTE_VARIATION(Self::GetPed().GetHandle(), t.first);
if (!applyHair && t.first == 2)
continue;
PED::SET_PED_COMPONENT_VARIATION(Self::GetPed().GetHandle(), t.first, item.drawable_id, item.texture_id, palette_var);
}
PED::CLEAR_ALL_PED_PROPS(Self::GetPed().GetHandle(), 0);
for (auto& t : props.items)
{
auto item = t.second;
PED::SET_PED_PROP_INDEX(Self::GetPed().GetHandle(), t.first, item.drawable_id, item.texture_id, TRUE, 0);
}
}
Folder OutfitEditor::CheckFolder(std::string folderName)
{
return FileMgr::GetProjectFolder("./saved_outfits/" + folderName);
}
void OutfitEditor::ApplyOutfitFromJson(std::string folderName, std::string fileName, bool applyHair)
{
const auto file = CheckFolder(folderName).GetFile(fileName).Path();
if (!std::filesystem::exists(file))
{
Notifications::Show("Outfit", "File does not exist.", NotificationType::Error);
return;
}
std::ifstream file_stream(file);
nlohmann::json j;
try
{
file_stream >> j;
}
catch (std::exception& e)
{
LOG(WARNING) << e.what();
}
file_stream.close();
OutfitComponents components;
OutfitProps props;
if (j["model"] != Self::GetPed().GetModel())
{
Notifications::Show("Oufit", "Ped models different.", NotificationType::Error);
return;
}
for (auto& item : j["components"].items())
{
std::stringstream ss(item.key());
int id = 0;
ss >> id;
components.items[id].drawable_id = item.value()["drawable_id"];
components.items[id].texture_id = item.value()["texture_id"];
if (auto palette_var = item.value()["palette_var"]; !palette_var.is_null())
components.items[id].palette_var = palette_var;
}
for (auto& item : j["props"].items())
{
std::stringstream ss(item.key());
int id = 0;
ss >> id;
props.items[id].drawable_id = item.value()["drawable_id"];
props.items[id].texture_id = item.value()["texture_id"];
}
SetSelfOutfit(components, props, applyHair);
}
void OutfitEditor::SaveOutfit(std::string fileName, std::string folder)
{
OutfitComponents components;
OutfitProps props;
nlohmann::json j;
nlohmann::json j_components;
nlohmann::json j_props;
ReplaceString(fileName, ".", ""); // filename say "bob.." will throw relative path error from Folder::GetFile
fileName += ".json";
auto model = Self::GetPed().GetModel();
auto ped = Self::GetPed().GetHandle();
for (auto& item : components.items)
{
nlohmann::json tmp;
tmp["drawable_id"] = PED::GET_PED_DRAWABLE_VARIATION(ped, item.first);
tmp["texture_id"] = PED::GET_PED_TEXTURE_VARIATION(ped, item.first);
tmp["palette_var"] = PED::GET_PED_PALETTE_VARIATION(ped, item.first);
j_components[std::to_string(item.first)] = tmp;
}
for (auto& item : props.items)
{
nlohmann::json tmp;
tmp["drawable_id"] = PED::GET_PED_PROP_INDEX(ped, item.first, 0);
tmp["texture_id"] = PED::GET_PED_PROP_TEXTURE_INDEX(ped, item.first);
j_props[std::to_string(item.first)] = tmp;
}
j["components"] = j_components;
j["props"] = j_props;
j["model"] = model;
auto path = CheckFolder(folder).GetFile(fileName).Path();
std::ofstream o(path);
o << std::setw(4) << j << std::endl;
o.close();
}
void OutfitEditor::RefreshList(std::string folderName, std::vector<std::string>& folders, std::vector<std::string>& files)
{
folders.clear();
const auto file_path = CheckFolder();
for (const auto& directory_entry : std::filesystem::directory_iterator(file_path.Path()))
if (directory_entry.is_directory())
folders.push_back(directory_entry.path().filename().generic_string());
files.clear();
const auto file_path2 = CheckFolder(folderName);
for (const auto& directory_entry : std::filesystem::directory_iterator(file_path2.Path()))
if (directory_entry.path().extension() == ".json")
files.push_back(directory_entry.path().filename().generic_string());
}
}
}
+62
View File
@@ -0,0 +1,62 @@
#pragma once
#include "core/filemgr/FileMgr.hpp"
#include <unordered_map>
namespace YimMenu
{
namespace Outfit
{
struct ComponentData
{
std::string label;
int drawable_id = 0;
int texture_id = 0;
int palette_var = -1;
int drawable_id_max = 0;
int texture_id_max = 0;
};
struct OutfitComponents
{
std::unordered_map<int, ComponentData> items = {
{1, {"Mask"}},
{2, {"Hair"}},
{3, {"Torso"}},
{4, {"Leg"}},
{5, {"Bag"}},
{6, {"Shoe"}},
{7, {"Accessory"}},
{8, {"Undershirt"}},
{9, {"Kevlar/Armor"}},
{10, {"Decals"}},
{11, {"Tops"}}};
};
struct OutfitProps
{
std::unordered_map<int, ComponentData> items = {
{0, {"Hats"}},
{1, {"Glasses"}},
{2, {"Ears"}},
{6, {"Watches"}},
{7, {"Bracelets"}}};
};
class OutfitEditor {
public:
static void SetSelfOutfit(OutfitComponents components, OutfitProps props, bool applyHair);
// json
static Folder CheckFolder(std::string folderName = "");
static void ApplyOutfitFromJson(std::string folderName, std::string fileName, bool applyHair);
static void SaveOutfit(std::string fileName, std::string folder);
static void RefreshList(std::string folderName, std::vector<std::string>& folders, std::vector<std::string>& files);
// check & fix bounds
static void CheckBoundsDrawable(ComponentData& item, const int lower);
static void CheckBoundsTexture(ComponentData& item, const int lower);
};
}
}
+4
View File
@@ -5,6 +5,7 @@
#include "game/gta/VehicleModel.hpp"
#include "game/backend/Self.hpp"
#include "game/gta/Natives.hpp"
#include "core/util/Strings.hpp"
#include "game/gta/Vehicle.hpp"
#include "game/gta/data/VehicleValues.hpp"
@@ -131,6 +132,9 @@ namespace YimMenu
{
if (auto veh = Self::GetVehicle(); veh && veh.IsValid())
{
ReplaceString(fileName, ".", ""); // filename say "bob.." will throw relative path error from Folder::GetFile
fileName += ".json";
const auto file = SavedVehicles::CheckFolder(folderName).GetFile(fileName);
std::ofstream file_stream(file.Path(), std::ios::out | std::ios::trunc);
file_stream << SavedVehicles::GetJson(veh).dump(4);
+248 -209
View File
@@ -1,235 +1,274 @@
#include "OutfitEditor.hpp"
#include "core/frontend/manager/UIManager.hpp"
#include "game/backend/Self.hpp"
#include "game/frontend/items/Items.hpp"
#include "core/backend/FiberPool.hpp"
#include "game/backend/Outfit.hpp"
#include "core/frontend/Notifications.hpp"
#include "game/backend/Self.hpp"
#include "game/gta/Natives.hpp"
#include <random>
// TODO: clean up more AI generated junk from this file
#include "core/util/Strings.hpp"
#include "misc/cpp/imgui_stdlib.h"
namespace YimMenu
{
int GetMaxDrawable(int slot)
class OutfitEditorMenu
{
auto ped = Self::GetPed();
return PED::GET_NUMBER_OF_PED_DRAWABLE_VARIATIONS(static_cast<int>(ped.GetHandle()), slot);
}
Outfit::OutfitComponents components{};
Outfit::OutfitProps props{};
std::vector<std::string> folders{}, files{};
std::string folder{}, file{};
char outfitName[64]{}, newFolder[50]{};
int GetMaxTexture(int slot, int drawable)
{
auto ped = Self::GetPed();
return PED::GET_NUMBER_OF_PED_TEXTURE_VARIATIONS(static_cast<int>(ped.GetHandle()), slot, drawable);
}
int GetMaxPropDrawable(int slot)
{
auto ped = Self::GetPed();
return PED::GET_NUMBER_OF_PED_PROP_DRAWABLE_VARIATIONS(static_cast<int>(ped.GetHandle()), slot);
}
int GetMaxPropTexture(int slot, int drawable)
{
auto ped = Self::GetPed();
return PED::GET_NUMBER_OF_PED_PROP_TEXTURE_VARIATIONS(static_cast<int>(ped.GetHandle()), slot, drawable);
}
void GetOutfitSlot(int slot, int& drawable, int& texture)
{
auto ped = Self::GetPed();
drawable = PED::GET_PED_DRAWABLE_VARIATION(static_cast<int>(ped.GetHandle()), slot);
texture = PED::GET_PED_TEXTURE_VARIATION(static_cast<int>(ped.GetHandle()), slot);
}
void GetPropSlot(int slot, int& drawable, int& texture)
{
auto ped = Self::GetPed();
drawable = PED::GET_PED_PROP_INDEX(static_cast<int>(ped.GetHandle()), slot, 0);
if (drawable == -1)
public:
// refreshes the outfit editor data to current ped outfit
void RefreshStats()
{
drawable = 0;
texture = 0;
return;
auto ped = Self::GetPed().GetHandle();
for (auto& t : components.items)
{
auto& item = t.second;
item.drawable_id = PED::GET_PED_DRAWABLE_VARIATION(ped, t.first);
item.drawable_id_max = PED::GET_NUMBER_OF_PED_DRAWABLE_VARIATIONS(ped, t.first) - 1;
item.texture_id = PED::GET_PED_TEXTURE_VARIATION(ped, t.first);
item.texture_id_max = PED::GET_NUMBER_OF_PED_TEXTURE_VARIATIONS(ped, t.first, item.drawable_id) - 1;
}
for (auto& t : props.items)
{
auto& item = t.second;
item.drawable_id = PED::GET_PED_PROP_INDEX(ped, t.first, 0);
item.drawable_id_max = PED::GET_NUMBER_OF_PED_PROP_DRAWABLE_VARIATIONS(ped, t.first) - 1;
item.texture_id = PED::GET_PED_PROP_TEXTURE_INDEX(ped, t.first);
item.texture_id_max = PED::GET_NUMBER_OF_PED_PROP_TEXTURE_VARIATIONS(ped, t.first, item.drawable_id) - 1;
}
}
texture = PED::GET_PED_PROP_TEXTURE_INDEX(static_cast<int>(ped.GetHandle()), slot);
}
void SetOutfitSlot(int slot, int drawable, int texture)
{
auto ped = Self::GetPed();
PED::SET_PED_COMPONENT_VARIATION(static_cast<int>(ped.GetHandle()), slot, drawable, texture, 0);
}
void RenderComponents()
{
ImGui::BeginGroup();
for (auto& t : components.items)
{
auto& item = t.second;
ImGui::SetNextItemWidth(120);
if (ImGui::InputInt(std::format("{} [0,{}]##1", item.label, item.drawable_id_max).c_str(), &item.drawable_id))
{
Outfit::OutfitEditor::CheckBoundsDrawable(item, 0);
FiberPool::Push([id = t.first, item, this] {
PED::SET_PED_COMPONENT_VARIATION(Self::GetPed().GetHandle(), id, item.drawable_id, 0, PED::GET_PED_PALETTE_VARIATION(Self::GetPed().GetHandle(), id));
RefreshStats();
});
}
}
ImGui::EndGroup();
}
void SetPropSlot(int slot, int drawable, int texture)
{
auto ped = Self::GetPed();
PED::SET_PED_PROP_INDEX(static_cast<int>(ped.GetHandle()), slot, drawable, texture, true, 0);
}
void RenderComponentsTextures()
{
ImGui::BeginGroup();
for (auto& t : components.items)
{
auto& item = t.second;
ImGui::SetNextItemWidth(120);
if (ImGui::InputInt(std::format("{} TEX [0,{}]##2", item.label, item.texture_id_max).c_str(), &item.texture_id))
{
Outfit::OutfitEditor::CheckBoundsTexture(item, 0);
FiberPool::Push([id = t.first, item, this] {
PED::SET_PED_COMPONENT_VARIATION(Self::GetPed().GetHandle(), id, item.drawable_id, item.texture_id, PED::GET_PED_PALETTE_VARIATION(Self::GetPed().GetHandle(), id));
RefreshStats();
});
}
}
ImGui::EndGroup();
}
// Helper function for underlined text
static void TextUnderlined(const char* text)
{
ImGui::Text("%s", text);
ImVec2 min = ImGui::GetItemRectMin();
ImVec2 max = ImGui::GetItemRectMax();
min.y = max.y;
ImGui::GetWindowDrawList()->AddLine(min, max, ImGui::GetColorU32(ImGui::GetStyle().Colors[ImGuiCol_Text]));
}
void RenderProps()
{
for (auto& t : props.items)
{
auto& item = t.second;
ImGui::SetNextItemWidth(120);
if (ImGui::InputInt(std::format("{} [0,{}]##3", item.label, item.drawable_id_max).c_str(), &item.drawable_id))
{
Outfit::OutfitEditor::CheckBoundsDrawable(item, -1);
FiberPool::Push([id = t.first, item, this] {
if (item.drawable_id == -1)
PED::CLEAR_PED_PROP(Self::GetPed().GetHandle(), id, 1);
else
PED::SET_PED_PROP_INDEX(Self::GetPed().GetHandle(), id, item.drawable_id, 0, TRUE, 0);
RefreshStats();
});
}
}
}
static void TextUnderlinedAt(const char* text, float y)
{
auto old_cursor = ImGui::GetCursorPos();
ImGui::SetCursorPosY(y);
TextUnderlined(text);
ImGui::SetCursorPos(old_cursor);
}
void RenderPropsTextures()
{
for (auto& t : props.items)
{
auto& item = t.second;
ImGui::SetNextItemWidth(120);
if (ImGui::InputInt(std::format("{} TEX [0,{}]##4", item.label, item.texture_id_max).c_str(), &item.texture_id))
{
Outfit::OutfitEditor::CheckBoundsTexture(item, -1);
FiberPool::Push([id = t.first, item, this] {
PED::SET_PED_PROP_INDEX(Self::GetPed().GetHandle(), id, item.drawable_id, item.texture_id, TRUE, 0);
RefreshStats();
});
}
}
}
void RenderOutfitList()
{
ImGui::BeginGroup();
{
// folders
ImGui::SetNextItemWidth(300.f);
if (ImGui::BeginCombo("", folder.empty() ? "Root" : folder.c_str()))
{
if (ImGui::Selectable("Root", folder == ""))
{
folder.clear();
FiberPool::Push([this] {
Outfit::OutfitEditor::RefreshList(folder, folders, files);
});
}
for (std::string folderName : folders)
if (ImGui::Selectable(folderName.c_str(), folder == folderName))
{
folder = folderName;
FiberPool::Push([this] {
Outfit::OutfitEditor::RefreshList(folder, folders, files);
});
}
ImGui::EndCombo();
}
// files
static std::string search;
ImGui::SetNextItemWidth(300);
if (ImGui::InputTextWithHint("###outfitname", "Search", &search))
std::transform(search.begin(), search.end(), search.begin(), tolower);
if (ImGui::BeginListBox("##saved_outfits", ImVec2(300, 300)))
{
for (const auto& pair : files)
{
std::string pair_lower = pair;
std::transform(pair_lower.begin(), pair_lower.end(), pair_lower.begin(), tolower);
if (pair_lower.contains(search))
{
auto fileName = pair.c_str();
if (ImGui::Selectable(fileName, file == pair, ImGuiSelectableFlags_AllowItemOverlap))
file = pair;
}
}
ImGui::EndListBox();
}
}
ImGui::EndGroup();
}
void RenderSaveButton(bool saveToNewFolder)
{
if (ImGui::Button("Save Outfit"))
FiberPool::Push([saveToNewFolder, this] {
std::string fileName = TrimString(outfitName);
strcpy(outfitName, "");
if (!fileName.size())
{
Notifications::Show("Outfit", "Filename empty!", NotificationType::Warning);
return;
}
Outfit::OutfitEditor::SaveOutfit(fileName, folder);
if (saveToNewFolder)
{
folder = newFolder; // set current folder to newly created folder
strcpy(newFolder, "");
}
Outfit::OutfitEditor::RefreshList(folder, folders, files);
});
};
void RenderOutfitListControls()
{
ImGui::BeginGroup();
{
if (ImGui::Button("Refresh list"))
FiberPool::Push([this] {
Outfit::OutfitEditor::RefreshList(folder, folders, files);
});
ImGui::Spacing();
static bool applyHair = false;
ImGui::Checkbox("Apply hair", &applyHair);
ImGui::Spacing();
if (ImGui::Button("Apply Selected Outfit"))
FiberPool::Push([this] {
Outfit::OutfitEditor::ApplyOutfitFromJson(folder, file, applyHair);
applyHair = false; // reset everytime
RefreshStats();
});
ImGui::Spacing();
// save outfit
ImGui::Text("Outfit Name");
ImGui::SameLine();
ImGui::SetNextItemWidth(250);
ImGui::InputText("##filename", outfitName, IM_ARRAYSIZE(outfitName));
if (folder.empty())
{
ImGui::Text("Folder Name");
ImGui::SameLine();
ImGui::SetNextItemWidth(250);
ImGui::InputText("##foldername", newFolder, IM_ARRAYSIZE(newFolder));
RenderSaveButton(true);
}
else
RenderSaveButton(false);
}
ImGui::EndGroup();
}
};
std::shared_ptr<Category> CreateOutfitsMenu()
{
static OutfitEditorMenu editor{};
auto category = std::make_shared<Category>("Outfit Editor");
category->AddItem(std::make_shared<ImGuiItem>([] {
if (!NativeInvoker::AreHandlersCached())
return ImGui::TextDisabled("Natives not cached yet");
auto ped = Self::GetPed();
if (!ped)
return ImGui::TextDisabled("Player ped not found");
// Create two columns layout
const float windowWidth = ImGui::GetContentRegionAvail().x;
const float columnWidth = windowWidth * 0.5f;
const float inputWidth = 120.0f; // Minimal width for number input
ImGui::Columns(2, "OutfitColumns", false);
ImGui::SetColumnWidth(0, columnWidth);
// Components section (Left column)
float header_y = ImGui::GetCursorPosY();
TextUnderlined("Components");
const struct
{
const char* name;
int slot;
} componentSlots[] = {{"Top", 11}, {"Undershirt", 8}, {"Legs", 4}, {"Feet", 6}, {"Accessories", 7}, {"Bags", 5}, {"Mask", 1}, {"Gloves", 3}, {"Decals", 10}, {"Armor", 9}};
bool first_iter = true;
for (const auto& component : componentSlots)
{
ImGui::PushID(component.slot);
int drawable, texture;
GetOutfitSlot(component.slot, drawable, texture);
ImGui::Text("%s", component.name);
ImGui::SameLine();
ImGui::SetCursorPosX(columnWidth - inputWidth * 2 - 10);
ImGui::PushItemWidth(inputWidth);
if (first_iter)
TextUnderlinedAt("Drawable", header_y);
if (ImGui::InputInt("##{}drawable", &drawable))
{
drawable = std::clamp(drawable, 0, GetMaxDrawable(component.slot) - 1);
SetOutfitSlot(component.slot, drawable, texture);
}
ImGui::SameLine();
if (first_iter)
TextUnderlinedAt("Texture", header_y); // TODO: this heading is slightly misaligned and I'm not sure why (caused by the above SameLine?)
if (ImGui::InputInt("##{}texture", &texture))
{
texture = std::clamp(texture, 0, GetMaxTexture(component.slot, drawable) - 1);
SetOutfitSlot(component.slot, drawable, texture);
}
ImGui::PopItemWidth();
ImGui::PopID();
first_iter = false;
}
// Props section (Right column)
ImGui::NextColumn();
TextUnderlined("Props");
const struct
{
const char* name;
int slot;
} propSlots[] = {{"Hats", 0}, {"Glasses", 1}, {"Ears", 2}, {"Watches", 6}};
first_iter = true;
for (const auto& prop : propSlots)
{
ImGui::PushID(prop.slot);
int drawable, texture;
GetPropSlot(prop.slot, drawable, texture);
ImGui::Text("%s", prop.name);
ImGui::SameLine();
ImGui::SetCursorPosX(columnWidth + (columnWidth - inputWidth * 2 - 10));
ImGui::PushItemWidth(inputWidth);
if (first_iter)
TextUnderlinedAt("Drawable", header_y);
if (ImGui::InputInt("##pdrawable", &drawable))
{
drawable = std::clamp(drawable, 0, GetMaxPropDrawable(prop.slot) - 1);
SetPropSlot(prop.slot, drawable, texture);
}
ImGui::SameLine();
if (first_iter)
TextUnderlinedAt("Texture", header_y);
if (ImGui::InputInt("##ptexture", &texture))
{
texture = std::clamp(texture, 0, GetMaxPropTexture(prop.slot, drawable) - 1);
SetPropSlot(prop.slot, drawable, texture);
}
ImGui::PopItemWidth();
ImGui::PopID();
first_iter = false;
}
ImGui::Columns(1);
if (ImGui::Button("Refresh Stats"))
FiberPool::Push([] {
editor.RefreshStats();
});
ImGui::SameLine();
if (ImGui::Button("Randomize Outfit"))
FiberPool::Push([] {
Self::GetPed().RandomizeOutfit2();
});
editor.RenderComponents();
ImGui::SameLine();
editor.RenderComponentsTextures();
ImGui::SameLine();
ImGui::BeginGroup();
{
std::random_device rd;
std::mt19937 gen(rd());
// Randomize components
for (int i = 0; i < 12; ++i)
{
int maxDrawable = GetMaxDrawable(i);
if (maxDrawable > 0)
{
int drawable = std::uniform_int_distribution<>(0, maxDrawable - 1)(gen);
int maxTexture = GetMaxTexture(i, drawable);
int texture = maxTexture > 0 ? std::uniform_int_distribution<>(0, maxTexture - 1)(gen) : 0;
SetOutfitSlot(i, drawable, texture);
}
}
// Randomize props
for (int i : {0, 1, 2, 6, 7})
{
int maxDrawable = GetMaxPropDrawable(i);
if (maxDrawable > 0)
{
int drawable = std::uniform_int_distribution<>(0, maxDrawable - 1)(gen);
int maxTexture = GetMaxPropTexture(i, drawable);
int texture = maxTexture > 0 ? std::uniform_int_distribution<>(0, maxTexture - 1)(gen) : 0;
SetPropSlot(i, drawable, texture);
}
}
editor.RenderProps();
ImGui::Spacing();
editor.RenderPropsTextures();
}
ImGui::EndGroup();
ImGui::Spacing();
editor.RenderOutfitList();
ImGui::SameLine();
editor.RenderOutfitListControls();
}));
return category;
@@ -31,18 +31,15 @@ namespace YimMenu::Submenus
if (ImGui::Button("Save"))
FiberPool::Push([saveToNewFolder] {
std::string fileName = vehicle_file_name_input;
std::string fileName = TrimString(vehicle_file_name_input);
strcpy(vehicle_file_name_input, "");
if (!TrimString(fileName).size())
if (!fileName.size())
{
Notifications::Show("Saved Vehicles", "Filename empty!", NotificationType::Warning);
return;
}
ReplaceString(fileName, ".", ""); // filename say "bob.." will throw relative path error from Folder::GetFile
fileName += ".json";
SavedVehicles::Save(saveToNewFolder ? newFolder : folder, fileName);
if (saveToNewFolder)
+39
View File
@@ -1,8 +1,10 @@
#include "Ped.hpp"
#include "Natives.hpp"
#include "game/backend/Outfit.hpp"
#include "core/backend/ScriptMgr.hpp"
#include "game/pointers/Pointers.hpp"
#include <random>
namespace YimMenu
{
@@ -266,6 +268,43 @@ namespace YimMenu
PED::SET_PED_RANDOM_COMPONENT_VARIATION(GetHandle(), 0);
}
void Ped::RandomizeOutfit2()
{
ENTITY_ASSERT_VALID();
ENTITY_ASSERT_CONTROL();
Outfit::OutfitComponents components{};
Outfit::OutfitProps props{};
auto ped = GetHandle();
std::random_device rd;
std::mt19937 gen(rd());
// Randomize components
for (auto& item : components.items)
{
int drawable_id_max = PED::GET_NUMBER_OF_PED_DRAWABLE_VARIATIONS(ped, item.first) - 1;
if (drawable_id_max <= 0) // 0 if one variation, -1 if no variation
continue;
int drawable_id = std::uniform_int_distribution<>(0, drawable_id_max)(gen); // i in [a, b]
int texture_id_max = PED::GET_NUMBER_OF_PED_TEXTURE_VARIATIONS(ped, item.first, drawable_id) - 1;
int texture_id = texture_id_max > 0 ? std::uniform_int_distribution<>(0, texture_id_max)(gen) : 0;
PED::SET_PED_COMPONENT_VARIATION(ped, item.first, drawable_id, texture_id, PED::GET_PED_PALETTE_VARIATION(ped, item.first));
}
// Randomize props
for (auto& item : props.items)
{
int drawable_id_max = PED::GET_NUMBER_OF_PED_PROP_DRAWABLE_VARIATIONS(ped, item.first) - 1;
if (drawable_id_max <= 0) // 0 if one variation, -1 if no variation
continue;
int drawable_id = std::uniform_int_distribution<>(0, drawable_id_max)(gen); // i in [a, b]
int texture_id_max = PED::GET_NUMBER_OF_PED_PROP_TEXTURE_VARIATIONS(ped, item.first, drawable_id) - 1;
int texture_id = texture_id_max > 0 ? std::uniform_int_distribution<>(0, texture_id_max)(gen) : 0;
PED::SET_PED_PROP_INDEX(ped, item.first, drawable_id, texture_id, TRUE, 0);
}
}
void Ped::StartScenario(std::string_view name, int duration, bool entry_anim)
{
ENTITY_ASSERT_VALID();
+1
View File
@@ -69,6 +69,7 @@ namespace YimMenu
// outfits
void RandomizeOutfit();
void RandomizeOutfit2();
// tasks
void StartScenario(std::string_view name, int duration = -1, bool entry_anim = true);