// MassBuilderSaveTool // Copyright (C) 2021-2024 Guillaume Jacquemin // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <https://www.gnu.org/licenses/>. #include <imgui_internal.h> #include "../FontAwesome/IconsFontAwesome5.h" #include "../GameData/ArmourSets.h" #include "../GameData/StyleNames.h" #include "Application.h" namespace mbst { void Application::drawArmour() { if(!_currentMass || _currentMass->state() != GameObjects::Mass::State::Valid) { return; } constexpr static Containers::StringView slot_labels[] = { #define c(enumerator, strenum, name) name, #include "../Maps/ArmourSlots.hpp" #undef c }; auto labels_view = arrayView(slot_labels); const static float footer_height_to_reserve = ImGui::GetFrameHeightWithSpacing(); ImGui::BeginGroup(); if(ImGui::BeginTable("##SlotsTable", 1, ImGuiTableFlags_ScrollY|ImGuiTableFlags_BordersOuter|ImGuiTableFlags_BordersInnerH, {ImGui::GetContentRegionAvail().x * 0.15f, -footer_height_to_reserve})) { ImGui::TableSetupColumn("##Slots", ImGuiTableColumnFlags_WidthStretch); for(std::size_t i = 0; i < labels_view.size(); i++) { ImGui::TableNextRow(); ImGui::TableNextColumn(); if(ImGui::Selectable(labels_view[i].data(), _selectedArmourSlot && (*_selectedArmourSlot) == i, ImGuiSelectableFlags_SpanAvailWidth)) { _selectedArmourSlot = i; } } ImGui::EndTable(); } if(ImGui::Button(ICON_FA_UNDO_ALT " Reset all")) { _currentMass->getArmourParts(); } ImGui::EndGroup(); ImGui::SameLine(); if(!_selectedArmourSlot) { ImGui::TextUnformatted("No selected armour slot."); return; } auto& part = _currentMass->armourParts()[*_selectedArmourSlot]; ImGui::BeginGroup(); if(ImGui::BeginChild("##ArmourEditor", {0.0f, -footer_height_to_reserve})) { ImGui::SeparatorText("Part"); if(GameData::armour_sets.find(part.id) != GameData::armour_sets.cend()) { ImGui::Text("Set name: %s", GameData::armour_sets.at(part.id).name.data()); } else { ImGui::Text("Set ID: %d", part.id); } ImGui::SameLine(); if(ImGui::SmallButton("Change")) { ImGui::OpenPopup("##ArmourPartPopup"); } if(ImGui::BeginPopup("##ArmourPartPopup")) { if(ImGui::BeginListBox("##ChangePart")) { for(const auto& [id, set] : GameData::armour_sets) { if((part.slot == GameObjects::ArmourPart::Slot::Neck && !set.neck_compatible) || (id == -2 && !(part.slot == GameObjects::ArmourPart::Slot::LeftFrontSkirt || part.slot == GameObjects::ArmourPart::Slot::RightFrontSkirt || part.slot == GameObjects::ArmourPart::Slot::LeftSideSkirt || part.slot == GameObjects::ArmourPart::Slot::RightSideSkirt || part.slot == GameObjects::ArmourPart::Slot::LeftBackSkirt || part.slot == GameObjects::ArmourPart::Slot::RightBackSkirt || part.slot == GameObjects::ArmourPart::Slot::LeftAnkle || part.slot == GameObjects::ArmourPart::Slot::RightAnkle) ) ) { continue; } if(ImGui::Selectable(set.name.data(), id == part.id, ImGuiSelectableFlags_SpanAvailWidth)) { part.id = id; } } ImGui::EndListBox(); } ImGui::EndPopup(); } ImGui::SeparatorText("Styles"); for(std::int32_t i = 0; i < 4; i++) { drawAlignedText("Slot %d:", i + 1); ImGui::SameLine(); ImGui::PushID(i); if(ImGui::BeginCombo("##Style", getStyleName(part.styles[i], _currentMass->armourCustomStyles()).data())) { for(const auto& style : GameData::builtin_style_names) { if(ImGui::Selectable(getStyleName(style.first, _currentMass->armourCustomStyles()).data(), part.styles[i] == style.first)) { part.styles[i] = style.first; } } ImGui::EndCombo(); } ImGui::PopID(); } ImGui::SeparatorText("Decals"); constexpr static float selectable_width = 25.0f; drawAlignedText("Showing/editing decal:"); ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, {0.5f, 0.0f}); for(std::uint32_t i = 0; i < part.decals.size(); i++) { ImGui::SameLine(); if(ImGui::Selectable(std::to_string(i + 1).c_str(), _selectedArmourDecals[*_selectedArmourSlot] == int(i), ImGuiSelectableFlags_None, {selectable_width, 0.0f})) { _selectedArmourDecals[*_selectedArmourSlot] = int(i); } if(i != part.decals.size() - 1) { ImGui::SameLine(); ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); } } ImGui::PopStyleVar(); drawDecalEditor(part.decals[_selectedArmourDecals[*_selectedArmourSlot]]); if(!part.accessories.isEmpty()) { ImGui::SeparatorText("Accessories"); drawAlignedText("Showing/editing accessory:"); ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, {0.5f, 0.0f}); for(std::uint32_t i = 0; i < part.accessories.size(); i++) { ImGui::SameLine(); if(ImGui::Selectable((std::string{} + char(i + 65)).c_str(), _selectedArmourAccessories[*_selectedArmourSlot] == int(i), ImGuiSelectableFlags_None, {selectable_width, 0.0f})) { _selectedArmourAccessories[*_selectedArmourSlot] = int(i); } if(i != part.accessories.size() - 1) { ImGui::SameLine(); ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); } } ImGui::PopStyleVar(); drawAccessoryEditor(part.accessories[_selectedArmourAccessories[*_selectedArmourSlot]], _currentMass->armourCustomStyles()); } } ImGui::EndChild(); if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { _modifiedBySaveTool = true; if(!_currentMass->writeArmourPart(part.slot)) { _modifiedBySaveTool = false; _queue.addToast(Toast::Type::Error, _currentMass->lastError()); } } ImGui::EndGroup(); } void Application::drawBLAttachment() { if(!_currentMass || _currentMass->state() != GameObjects::Mass::State::Valid) { return; } drawAlignedText("Attachment style:"_s); ImGui::SameLine(); if(ImGui::RadioButton("Active one", _currentMass->bulletLauncherAttachmentStyle() == GameObjects::BulletLauncherAttachmentStyle::ActiveOne)) { _currentMass->bulletLauncherAttachmentStyle() = GameObjects::BulletLauncherAttachmentStyle::ActiveOne; } ImGui::SameLine(); if(ImGui::RadioButton("Active one per slot", _currentMass->bulletLauncherAttachmentStyle() == GameObjects::BulletLauncherAttachmentStyle::ActiveOnePerSlot)) { _currentMass->bulletLauncherAttachmentStyle() = GameObjects::BulletLauncherAttachmentStyle::ActiveOnePerSlot; } ImGui::SameLine(); if(ImGui::RadioButton("All equipped", _currentMass->bulletLauncherAttachmentStyle() == GameObjects::BulletLauncherAttachmentStyle::AllEquipped)) { _currentMass->bulletLauncherAttachmentStyle() = GameObjects::BulletLauncherAttachmentStyle::ActiveOnePerSlot; } ImGui::Separator(); constexpr static float selectable_width = 25.0f; drawAlignedText("Launcher slot:"); ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, {0.5f, 0.0f}); for(auto i = 0u; i < _currentMass->bulletLauncherAttachments().size(); i++) { ImGui::SameLine(); if(ImGui::Selectable(std::to_string(i).c_str(), _selectedBLPlacement == i, ImGuiSelectableFlags_None, {selectable_width, 0.0f})) { _selectedBLPlacement = i; } if(i != _currentMass->bulletLauncherAttachments().size() - 1) { ImGui::SameLine(); ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); } } ImGui::PopStyleVar(); auto& placement = _currentMass->bulletLauncherAttachments()[_selectedBLPlacement]; static const Containers::StringView socket_labels[] = { #define c(enumerator, enumstr, name) name, #include "../Maps/BulletLauncherSockets.hpp" #undef c }; drawAlignedText("Socket:"); ImGui::SameLine(); if(ImGui::BeginCombo("##Socket", socket_labels[std::uint32_t(placement.socket)].data())) { for(std::uint32_t i = 0; i < (sizeof(socket_labels) / sizeof(socket_labels[0])); i++) { if(ImGui::Selectable(socket_labels[i].data(), i == std::uint32_t(placement.socket), ImGuiSelectableFlags_SpanAvailWidth)) { placement.socket = static_cast<GameObjects::BulletLauncherAttachment::Socket>(i); } } ImGui::EndCombo(); } if(placement.socket != GameObjects::BulletLauncherAttachment::Socket::Auto) { ImGui::BeginGroup(); drawAlignedText("Relative position:"); drawAlignedText("Offset position:"); drawAlignedText("Relative rotation:"); drawAlignedText("Offset rotation:"); drawAlignedText("Scale:"); ImGui::EndGroup(); ImGui::SameLine(); ImGui::BeginGroup(); std::visit( [this](auto& arg){ using T = std::decay_t<decltype(arg)>; if constexpr (std::is_same_v<T, Vector3>) { drawVector3Drag("##RelativePosition", arg, 1.0f, Vector3{-FLT_MAX}, Vector3{FLT_MAX}, "X: %.3f", "Y: %.3f", "Z: %.3f"); } else if constexpr (std::is_same_v<T, Vector3d>) { drawVector3dDrag("##RelativePosition", arg, 1.0f, Vector3d{-DBL_MAX}, Vector3d{DBL_MAX}, "X: %.3f", "Y: %.3f", "Z: %.3f"); } }, placement.relativeLocation ); std::visit( [this](auto& arg){ using T = std::decay_t<decltype(arg)>; if constexpr (std::is_same_v<T, Vector3>) { drawVector3Slider("##OffsetPosition", arg, Vector3{-500.0f}, Vector3{500.0f}, "X: %.3f", "Y: %.3f", "Z: %.3f"); } else if constexpr (std::is_same_v<T, Vector3d>) { drawVector3dSlider("##OffsetPosition", arg, Vector3d{-500.0}, Vector3d{500.0}, "X: %.3f", "Y: %.3f", "Z: %.3f"); } }, placement.offsetLocation ); ImGui::SameLine(); drawHelpMarker("+/-500.0 = +/-250 in-game"); std::visit( [this](auto& arg){ using T = std::decay_t<decltype(arg)>; if constexpr (std::is_same_v<T, Vector3>) { drawVector3Drag("##RelativeRotation", arg, 1.0f, Vector3{-FLT_MAX}, Vector3{FLT_MAX}, "Pitch: %.3f", "Yaw: %.3f", "Roll: %.3f"); } else if constexpr (std::is_same_v<T, Vector3d>) { drawVector3dDrag("##RelativeRotation", arg, 1.0f, Vector3d{-DBL_MAX}, Vector3d{DBL_MAX}, "Pitch: %.3f", "Yaw: %.3f", "Roll: %.3f"); } }, placement.relativeRotation ); std::visit( [this](auto& arg){ using T = std::decay_t<decltype(arg)>; if constexpr (std::is_same_v<T, Vector3>) { drawVector3Slider("##OffsetRotation", arg, Vector3{-30.0f, -30.0f, -180.0f}, Vector3{30.0f, 30.0f, 180.0f}, "Pitch: %.3f", "Yaw: %.3f", "Roll: %.3f"); } else if constexpr (std::is_same_v<T, Vector3d>) { drawVector3dSlider("##OffsetRotation", arg, Vector3d{-30.0, -30.0, -180.0}, Vector3d{30.0, 30.0, 180.0}, "Pitch: %.3f", "Yaw: %.3f", "Roll: %.3f"); } }, placement.offsetRotation ); std::visit( [this](auto& arg){ using T = std::decay_t<decltype(arg)>; if constexpr (std::is_same_v<T, Vector3>) { drawVector3Slider("##Scale", arg, Vector3{0.5f}, Vector3{1.5f}, "X: %.3f", "Y: %.3f", "Z: %.3f"); } else if constexpr (std::is_same_v<T, Vector3d>) { drawVector3dSlider("##Scale", arg, Vector3d{0.5}, Vector3d{1.5}, "X: %.3f", "Y: %.3f", "Z: %.3f"); } }, placement.relativeScale ); ImGui::SameLine(); drawHelpMarker("0.5 = 50 in-game\n1.5 = 150 in-game"); ImGui::EndGroup(); } _modifiedBySaveTool = true; if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { _modifiedBySaveTool = true; if(!_currentMass->writeBulletLauncherAttachments()) { _modifiedBySaveTool = false; _queue.addToast(Toast::Type::Error, _currentMass->lastError()); } } } void Application::drawCustomArmourStyles() { if(!_currentMass || _currentMass->state() != GameObjects::Mass::State::Valid) { return; } if(!ImGui::BeginChild("##ArmourStyles")) { ImGui::EndChild(); return; } ImGui::TextWrapped("In-game values are multiplied by 100. For example, 0.500 here is equal to 50 in-game."); for(std::uint32_t i = 0; i < _currentMass->armourCustomStyles().size(); i++) { ImGui::PushID(int(i)); auto result = drawCustomStyle(_currentMass->armourCustomStyles()[i]); switch(result) { case DCS_ResetStyle: _currentMass->getArmourCustomStyles(); break; case DCS_Save: _modifiedBySaveTool = true; if(!_currentMass->writeArmourCustomStyle(i)) { _modifiedBySaveTool = false; _queue.addToast(Toast::Type::Error, _currentMass->lastError()); } break; default: break; } ImGui::PopID(); } ImGui::EndChild(); } }