diff --git a/src/core/util/Strings.hpp b/src/core/util/Strings.hpp index 9424bf43..09b8cc91 100644 --- a/src/core/util/Strings.hpp +++ b/src/core/util/Strings.hpp @@ -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; diff --git a/src/game/backend/Outfit.cpp b/src/game/backend/Outfit.cpp new file mode 100644 index 00000000..68638002 --- /dev/null +++ b/src/game/backend/Outfit.cpp @@ -0,0 +1,172 @@ +#include "Outfit.hpp" +#include "game/backend/Self.hpp" +#include "game/gta/Natives.hpp" +#include "core/frontend/Notifications.hpp" +#include +#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& folders, std::vector& 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()); + } + + } +} \ No newline at end of file diff --git a/src/game/backend/Outfit.hpp b/src/game/backend/Outfit.hpp new file mode 100644 index 00000000..ed4f0a85 --- /dev/null +++ b/src/game/backend/Outfit.hpp @@ -0,0 +1,62 @@ +#pragma once +#include "core/filemgr/FileMgr.hpp" +#include + +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 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 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& folders, std::vector& files); + + // check & fix bounds + static void CheckBoundsDrawable(ComponentData& item, const int lower); + static void CheckBoundsTexture(ComponentData& item, const int lower); + }; + } +} \ No newline at end of file diff --git a/src/game/backend/SavedVehicles.cpp b/src/game/backend/SavedVehicles.cpp index 99536428..a739009b 100644 --- a/src/game/backend/SavedVehicles.cpp +++ b/src/game/backend/SavedVehicles.cpp @@ -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); diff --git a/src/game/frontend/submenus/Self/OutfitEditor.cpp b/src/game/frontend/submenus/Self/OutfitEditor.cpp index 5ac47f19..7c270cea 100644 --- a/src/game/frontend/submenus/Self/OutfitEditor.cpp +++ b/src/game/frontend/submenus/Self/OutfitEditor.cpp @@ -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 - -// 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) - { - auto ped = Self::GetPed(); - return PED::GET_NUMBER_OF_PED_DRAWABLE_VARIATIONS(static_cast(ped.GetHandle()), slot); - } - - int GetMaxTexture(int slot, int drawable) - { - auto ped = Self::GetPed(); - return PED::GET_NUMBER_OF_PED_TEXTURE_VARIATIONS(static_cast(ped.GetHandle()), slot, drawable); - } - - int GetMaxPropDrawable(int slot) - { - auto ped = Self::GetPed(); - return PED::GET_NUMBER_OF_PED_PROP_DRAWABLE_VARIATIONS(static_cast(ped.GetHandle()), slot); - } - - int GetMaxPropTexture(int slot, int drawable) + class OutfitEditorMenu { - auto ped = Self::GetPed(); - return PED::GET_NUMBER_OF_PED_PROP_TEXTURE_VARIATIONS(static_cast(ped.GetHandle()), slot, drawable); - } + Outfit::OutfitComponents components{}; + Outfit::OutfitProps props{}; + std::vector folders{}, files{}; + std::string folder{}, file{}; + char outfitName[64]{}, newFolder[50]{}; + + public: + // refreshes the outfit editor data to current ped outfit + void RefreshStats() + { + 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; + } - void GetOutfitSlot(int slot, int& drawable, int& texture) - { - auto ped = Self::GetPed(); - drawable = PED::GET_PED_DRAWABLE_VARIATION(static_cast(ped.GetHandle()), slot); - texture = PED::GET_PED_TEXTURE_VARIATION(static_cast(ped.GetHandle()), slot); - } + 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; + } + } - void GetPropSlot(int slot, int& drawable, int& texture) - { - auto ped = Self::GetPed(); - drawable = PED::GET_PED_PROP_INDEX(static_cast(ped.GetHandle()), slot, 0); - if (drawable == -1) + void RenderComponents() { - drawable = 0; - texture = 0; - return; + 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(); } - texture = PED::GET_PED_PROP_TEXTURE_INDEX(static_cast(ped.GetHandle()), slot); - } - - void SetOutfitSlot(int slot, int drawable, int texture) - { - auto ped = Self::GetPed(); - PED::SET_PED_COMPONENT_VARIATION(static_cast(ped.GetHandle()), slot, drawable, texture, 0); - } - - void SetPropSlot(int slot, int drawable, int texture) - { - auto ped = Self::GetPed(); - PED::SET_PED_PROP_INDEX(static_cast(ped.GetHandle()), slot, drawable, texture, true, 0); - } - - // 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])); - } - - static void TextUnderlinedAt(const char* text, float y) - { - auto old_cursor = ImGui::GetCursorPos(); - ImGui::SetCursorPosY(y); - TextUnderlined(text); - ImGui::SetCursorPos(old_cursor); - } - - std::shared_ptr CreateOutfitsMenu() - { - auto category = std::make_shared("Outfit Editor"); - - category->AddItem(std::make_shared([] { - 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 + void RenderComponentsTextures() + { + ImGui::BeginGroup(); + for (auto& t : components.items) { - 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}}; + 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(); + } - bool first_iter = true; - for (const auto& component : componentSlots) + void RenderProps() + { + for (auto& t : props.items) { - ImGui::PushID(component.slot); + 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(); + }); + } + } + } - int drawable, texture; - GetOutfitSlot(component.slot, drawable, texture); + 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(); + }); + } + } + } - ImGui::Text("%s", component.name); - ImGui::SameLine(); + 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); + }); + } - ImGui::SetCursorPosX(columnWidth - inputWidth * 2 - 10); + 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::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::EndCombo(); } - 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)) + + // 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))) { - texture = std::clamp(texture, 0, GetMaxTexture(component.slot, drawable) - 1); - SetOutfitSlot(component.slot, drawable, texture); + 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::PopItemWidth(); - ImGui::PopID(); - - first_iter = false; } + ImGui::EndGroup(); + } - // Props section (Right column) - ImGui::NextColumn(); - TextUnderlined("Props"); - - const struct - { - const char* name; - int slot; - } propSlots[] = {{"Hats", 0}, {"Glasses", 1}, {"Ears", 2}, {"Watches", 6}}; + void RenderSaveButton(bool saveToNewFolder) + { + if (ImGui::Button("Save Outfit")) + FiberPool::Push([saveToNewFolder, this] { + std::string fileName = TrimString(outfitName); + strcpy(outfitName, ""); - first_iter = true; - for (const auto& prop : propSlots) - { - ImGui::PushID(prop.slot); + if (!fileName.size()) + { + Notifications::Show("Outfit", "Filename empty!", NotificationType::Warning); + return; + } - int drawable, texture; - GetPropSlot(prop.slot, drawable, texture); + Outfit::OutfitEditor::SaveOutfit(fileName, folder); - ImGui::Text("%s", prop.name); - ImGui::SameLine(); + if (saveToNewFolder) + { + folder = newFolder; // set current folder to newly created folder + strcpy(newFolder, ""); + } - ImGui::SetCursorPosX(columnWidth + (columnWidth - inputWidth * 2 - 10)); + Outfit::OutfitEditor::RefreshList(folder, folders, files); + }); + }; - 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); - } + 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(); - if (first_iter) - TextUnderlinedAt("Texture", header_y); - if (ImGui::InputInt("##ptexture", &texture)) + ImGui::SetNextItemWidth(250); + ImGui::InputText("##filename", outfitName, IM_ARRAYSIZE(outfitName)); + + if (folder.empty()) { - texture = std::clamp(texture, 0, GetMaxPropTexture(prop.slot, drawable) - 1); - SetPropSlot(prop.slot, drawable, texture); + ImGui::Text("Folder Name"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(250); + ImGui::InputText("##foldername", newFolder, IM_ARRAYSIZE(newFolder)); + RenderSaveButton(true); } - ImGui::PopItemWidth(); - ImGui::PopID(); - - first_iter = false; + else + RenderSaveButton(false); } + ImGui::EndGroup(); + } + }; - ImGui::Columns(1); + std::shared_ptr CreateOutfitsMenu() + { + static OutfitEditorMenu editor{}; + auto category = std::make_shared("Outfit Editor"); + category->AddItem(std::make_shared([] { + 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()); + editor.RenderProps(); + ImGui::Spacing(); + editor.RenderPropsTextures(); + } + ImGui::EndGroup(); - // 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); - } - } + ImGui::Spacing(); - // 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.RenderOutfitList(); + ImGui::SameLine(); + editor.RenderOutfitListControls(); })); return category; diff --git a/src/game/frontend/submenus/Vehicle/SavedVehicles.cpp b/src/game/frontend/submenus/Vehicle/SavedVehicles.cpp index db99ba81..671bea3d 100644 --- a/src/game/frontend/submenus/Vehicle/SavedVehicles.cpp +++ b/src/game/frontend/submenus/Vehicle/SavedVehicles.cpp @@ -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) diff --git a/src/game/gta/Ped.cpp b/src/game/gta/Ped.cpp index 9b52208c..a8f3fc6a 100644 --- a/src/game/gta/Ped.cpp +++ b/src/game/gta/Ped.cpp @@ -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 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(); diff --git a/src/game/gta/Ped.hpp b/src/game/gta/Ped.hpp index dd1c8765..176c4d06 100644 --- a/src/game/gta/Ped.hpp +++ b/src/game/gta/Ped.hpp @@ -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);