diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3cd84a0..a9b0e20 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -126,11 +126,13 @@ add_executable(MassBuilderSaveTool WIN32 MassManager/MassManager.cpp Mass/Mass.h Mass/Mass.cpp + Maps/Accessories.h Maps/ArmourSets.h Maps/ArmourSlots.h Maps/LastMissionId.h Maps/StoryProgress.h Maps/StyleNames.h + Maps/WeaponTypes.h ToastQueue/ToastQueue.h ToastQueue/ToastQueue.cpp FontAwesome/IconsFontAwesome5.h diff --git a/src/Maps/Accessories.h b/src/Maps/Accessories.h new file mode 100644 index 0000000..dc50ded --- /dev/null +++ b/src/Maps/Accessories.h @@ -0,0 +1,74 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021 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 . + +#include + +#include + +using namespace Magnum; + +static const std::map accessories { + // Primitives + {1, "Cube (S)"}, + {2, "Pentagon (S)"}, + {3, "Hexagon (S)"}, + {4, "Cylinder (S)"}, + {5, "Sphere (S)"}, + {6, "TriPyramid (S)"}, + {7, "SquPyramid (S)"}, + {8, "PenPyramid (S)"}, + {9, "HexPyramid (S)"}, + {10, "Cone (S)"}, + {11, "SquStick (S)"}, + {12, "PenStick (S)"}, + {13, "HexStick (S)"}, + {14, "CycStick (S)"}, + {15, "Capsule (S)"}, + {16, "Decal Pad 01 (S)"}, + {17, "Decal Pad 02 (S)"}, + {18, "Decal Pad 03 (S)"}, + {19, "Decal Pad 04 (S)"}, + {20, "Decal Pad 05 (S)"}, + + {51, "SquBevel (S)"}, + {52, "TriBevel (S)"}, + {53, "PenBevel (S)"}, + {54, "HexBevel (S)"}, + {55, "CycBevel (S)"}, + {56, "RecBevel (S)"}, + {57, "DaiBevel (S)"}, + {58, "MonBevel (S)"}, + {59, "CofBevel (S)"}, + {60, "JevBevel (S)"}, + {61, "SquEmboss (S)"}, + {62, "TriEmboss (S)"}, + {63, "PenEmboss (S)"}, + {64, "HexEmboss (S)"}, + {65, "CycEmboss (S)"}, + {66, "RecEmboss (S)"}, + {67, "DaiEmboss (S)"}, + {68, "MonEmboss (S)"}, + {69, "CofEmboss (S)"}, + {70, "JevEmboss (S)"}, + + // Armours + + // Components + + // Connectors +}; diff --git a/src/Maps/WeaponTypes.h b/src/Maps/WeaponTypes.h new file mode 100644 index 0000000..2d0a0b1 --- /dev/null +++ b/src/Maps/WeaponTypes.h @@ -0,0 +1,29 @@ +#pragma once + +// MassBuilderSaveTool +// Copyright (C) 2021 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 . + +#include +#include + +static const std::unordered_map weapon_types{ + {"enuWeaponTypes::NewEnumerator0", "Melee weapon"}, + {"enuWeaponTypes::NewEnumerator5", "Shield"}, + {"enuWeaponTypes::NewEnumerator1", "Bullet shooter"}, + {"enuWeaponTypes::NewEnumerator2", "Energy shooter"}, + {"enuWeaponTypes::NewEnumerator3", "Bullet launcher"}, + {"enuWeaponTypes::NewEnumerator4", "Energy launcher"}, +}; diff --git a/src/SaveTool/SaveTool.h b/src/SaveTool/SaveTool.h index 7ff8409..d390be1 100644 --- a/src/SaveTool/SaveTool.h +++ b/src/SaveTool/SaveTool.h @@ -112,8 +112,25 @@ class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener void drawMassViewer(); void drawFrameInfo(); void drawJointSliders(); - void drawFramePaint(); - void drawCustomStyles(Containers::ArrayView styles); + void drawFrameStyles(); + void drawEyeColourPicker(); + void drawCustomFrameStyles(); + void drawArmour(); + void drawCustomArmourStyles(); + void drawWeapons(); + void drawWeaponEditor(Weapon& weapon); + void drawGlobalStyles(); + void drawTuning(); + void drawDecalEditor(Decal& decal); + void drawAccessoryEditor(Accessory& accessory, Containers::ArrayView style_view); + auto getStyleName(Int id, Containers::ArrayView view) -> const char*; + + enum DCSResult { + DCS_Fail, + DCS_ResetStyle, + DCS_Save + }; + auto drawCustomStyle(CustomStyle& style) -> DCSResult; void drawAbout(); void drawGameState(); diff --git a/src/SaveTool/SaveTool_MassViewer.cpp b/src/SaveTool/SaveTool_MassViewer.cpp index 5b1fb86..7d3ff4e 100644 --- a/src/SaveTool/SaveTool_MassViewer.cpp +++ b/src/SaveTool/SaveTool_MassViewer.cpp @@ -14,17 +14,26 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#include + #include +#include "../Maps/Accessories.h" +#include "../Maps/ArmourSets.h" +#include "../Maps/ArmourSlots.h" #include "../Maps/StyleNames.h" +#include "../Maps/WeaponTypes.h" #include "../FontAwesome/IconsFontAwesome5.h" #include "SaveTool.h" +static Weapon* current_weapon = nullptr; + void SaveTool::drawMassViewer() { if(!_currentMass || _currentMass->state() != Mass::State::Valid) { _currentMass = nullptr; + current_weapon = nullptr; _uiState = UiState::MainManager; _queue.addToast(Toast::Type::Error, "The selected M.A.S.S. isn't valid anymore."); return; @@ -41,61 +50,93 @@ void SaveTool::drawMassViewer() { return; } - ImGui::Text("Current M.A.S.S.: %s (%s)", - (*_currentMass->name()).c_str(), - _currentMass->filename().c_str()); - ImGui::SameLine(); - if(ImGui::SmallButton(ICON_FA_ARROW_LEFT " Back to main manager")) { - _currentMass = nullptr; - _uiState = UiState::MainManager; - } - - ImGui::TextColored(ImColor(255, 255, 0), ICON_FA_EXCLAMATION_TRIANGLE); - ImGui::SameLine(); - ImGui::TextWrapped("WARNING: Colours in this app may look different from in-game colours, due to unavoidable differences in the rendering pipeline."); - - ImGui::TextColored(ImColor(255, 255, 0), ICON_FA_EXCLAMATION_TRIANGLE); - ImGui::SameLine(); - ImGui::TextWrapped("Real-time updates are disabled while on this screen. %s", _currentMass->dirty() ? "The save file has been changed." : ""); - if(_currentMass->dirty()) { - ImGui::SameLine(); - if(ImGui::SmallButton(ICON_FA_SYNC_ALT " Refresh")) { - _currentMass->refreshValues(); - _currentMass->setDirty(false); - } - } - if(ImGui::BeginChild("##MassInfo", - {ImGui::GetContentRegionAvailWidth() * 0.60f, 0.0f}, + {0.0f, 0.0f}, true, ImGuiWindowFlags_MenuBar)) { if(ImGui::BeginMenuBar()) { - ImGui::TextUnformatted("M.A.S.S. Information"); - ImGui::EndMenuBar(); - } + if(ImGui::BeginTable("##MassViewerMenuTable", 4)) { + ImGui::TableSetupColumn("##MassName"); + ImGui::TableSetupColumn("##Spacer", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("##Updates"); + ImGui::TableSetupColumn("##Close", ImGuiTableColumnFlags_WidthFixed); - if(ImGui::BeginTabBar("##MassTabBar")) { - if(ImGui::BeginTabItem("Frame")) { - drawFrameInfo(); - ImGui::EndTabItem(); + ImGui::TableNextRow(); + + ImGui::TableSetColumnIndex(0); + ImGui::Text("M.A.S.S.: %s", (*_currentMass->name()).c_str()); + drawTooltip(_currentMass->filename().c_str()); + + ImGui::TableSetColumnIndex(2); + if(_currentMass->dirty()) { + ImGui::TextUnformatted("External changes detected"); + ImGui::SameLine(); + if(ImGui::SmallButton(ICON_FA_SYNC_ALT " Refresh")) { + _currentMass->refreshValues(); + _currentMass->setDirty(false); + } + } + + ImGui::TableSetColumnIndex(3); + if(ImGui::SmallButton(ICON_FA_TIMES)) { + current_weapon = nullptr; + _currentMass = nullptr; + _uiState = UiState::MainManager; + } + + ImGui::EndTable(); } - ImGui::EndTabBar(); - } - } - ImGui::EndChild(); - - ImGui::SameLine(); - - if(ImGui::BeginChild("##GlobalStyles", {0.0f, 0.0f}, - true, ImGuiWindowFlags_MenuBar)) - { - if(ImGui::BeginMenuBar()) { - ImGui::TextUnformatted("Global styles"); ImGui::EndMenuBar(); } - ImGui::TextUnformatted("Placeholder"); + ImGui::TextColored(ImColor(255, 255, 0), ICON_FA_EXCLAMATION_TRIANGLE); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TextWrapped("WARNING: Colours in this app may look different from in-game colours, due to unavoidable differences in the rendering pipeline."); + ImGui::TextColored(ImColor(255, 255, 0), ICON_FA_EXCLAMATION_TRIANGLE); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TextWrapped("Real-time updates are disabled on this screen."); + + if(_currentMass) { + if(ImGui::BeginTabBar("##MassTabBar")) { + if(ImGui::BeginTabItem("Frame")) { + drawFrameInfo(); + ImGui::EndTabItem(); + } + + if(ImGui::BeginTabItem("Custom frame styles")) { + drawCustomFrameStyles(); + ImGui::EndTabItem(); + } + + if(ImGui::BeginTabItem("Armour parts")) { + drawArmour(); + ImGui::EndTabItem(); + } + + if(ImGui::BeginTabItem("Custom armour styles")) { + drawCustomArmourStyles(); + ImGui::EndTabItem(); + } + + if(ImGui::BeginTabItem("Weapons (WIP)")) { + drawWeapons(); + ImGui::EndTabItem(); + } + + if(!_currentMass->demo() && ImGui::BeginTabItem("Global styles")) { + drawGlobalStyles(); + ImGui::EndTabItem(); + } + + if(ImGui::BeginTabItem("Tuning (WIP)")) { + drawTuning(); + ImGui::EndTabItem(); + } + + ImGui::EndTabBar(); + } + } } ImGui::EndChild(); @@ -107,20 +148,59 @@ void SaveTool::drawFrameInfo() { return; } - ImGui::TextUnformatted("Frame type: Skeleton"); // Placeholder for now, replace with actual code once other frames are implemented. - - drawJointSliders(); - - drawFramePaint(); -} - -void SaveTool::drawJointSliders() { - if(!ImGui::CollapsingHeader("Joint sliders")) { + if(!ImGui::BeginChild("##FrameInfo")) { + ImGui::EndChild(); + return; + } + + ImGui::BeginGroup(); + + if(ImGui::BeginChild("##JointSliders", {(ImGui::GetContentRegionAvailWidth() / 2.0f) - (ImGui::GetStyle().WindowPadding.x / 2.0f), 300.0f}, true, ImGuiWindowFlags_MenuBar)) { + if(ImGui::BeginMenuBar()) { + ImGui::TextUnformatted("Joint sliders"); + + ImGui::EndMenuBar(); + } + + drawJointSliders(); + } + ImGui::EndChild(); + + if(ImGui::BeginChild("##FrameStyles", {(ImGui::GetContentRegionAvailWidth() / 2.0f) - (ImGui::GetStyle().WindowPadding.x / 2.0f), 0.0f}, true, ImGuiWindowFlags_MenuBar)) { + if(ImGui::BeginMenuBar()) { + ImGui::TextUnformatted("Frame styles"); + + ImGui::EndMenuBar(); + } + + drawFrameStyles(); + } + ImGui::EndChild(); + + ImGui::EndGroup(); + + ImGui::SameLine(); + + if(ImGui::BeginChild("##EyeFlare", {0.0f, 0.0f}, true, ImGuiWindowFlags_MenuBar)) { + if(ImGui::BeginMenuBar()) { + ImGui::TextUnformatted("Eye flare colour"); + drawHelpMarker("Right-click the picker for more options.", 250.0f); + + ImGui::EndMenuBar(); + } + + drawEyeColourPicker(); + } + ImGui::EndChild(); + + ImGui::EndChild(); +} + +void SaveTool::drawJointSliders() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { return; } - static Joints sliders = _currentMass->jointSliders(); - static bool joints_edit = false; static bool joints_dirty = false; ImGui::TextWrapped("In-game values are multiplied by 100.\nFor example, 0.500 here is equal to 50 in-game."); @@ -134,15 +214,9 @@ void SaveTool::drawJointSliders() { ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Neck"); ImGui::TableSetColumnIndex(1); - if(joints_edit) { - ImGui::SetNextItemWidth(-1.0f); - if(ImGui::SliderFloat("##NeckSlider", &sliders.neck, 0.0f, 1.0f)) { - joints_dirty = true; - } - } - else { - ImGui::AlignTextToFramePadding(); - ImGui::Text("%.3f", Double(_currentMass->jointSliders().neck)); + ImGui::SetNextItemWidth(-1.0f); + if(ImGui::SliderFloat("##NeckSlider", &_currentMass->jointSliders().neck, 0.0f, 1.0f)) { + joints_dirty = true; } ImGui::TableNextRow(); @@ -150,15 +224,9 @@ void SaveTool::drawJointSliders() { ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Body"); ImGui::TableSetColumnIndex(1); - if(joints_edit) { - ImGui::SetNextItemWidth(-1.0f); - if(ImGui::SliderFloat("##BodySlider", &sliders.body, 0.0f, 1.0f)) { - joints_dirty = true; - } - } - else { - ImGui::AlignTextToFramePadding(); - ImGui::Text("%.3f", Double(_currentMass->jointSliders().body)); + ImGui::SetNextItemWidth(-1.0f); + if(ImGui::SliderFloat("##BodySlider", &_currentMass->jointSliders().body, 0.0f, 1.0f)) { + joints_dirty = true; } ImGui::TableNextRow(); @@ -166,15 +234,9 @@ void SaveTool::drawJointSliders() { ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Shoulders"); ImGui::TableSetColumnIndex(1); - if(joints_edit) { - ImGui::SetNextItemWidth(-1.0f); - if(ImGui::SliderFloat("##ShouldersSlider", &sliders.shoulders, 0.0f, 1.0f)) { - joints_dirty = true; - } - } - else { - ImGui::AlignTextToFramePadding(); - ImGui::Text("%.3f", Double(_currentMass->jointSliders().shoulders)); + ImGui::SetNextItemWidth(-1.0f); + if(ImGui::SliderFloat("##ShouldersSlider", &_currentMass->jointSliders().shoulders, 0.0f, 1.0f)) { + joints_dirty = true; } ImGui::TableNextRow(); @@ -182,31 +244,9 @@ void SaveTool::drawJointSliders() { ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Hips"); ImGui::TableSetColumnIndex(1); - if(joints_edit) { - ImGui::SetNextItemWidth(-1.0f); - if(ImGui::SliderFloat("##HipsSlider", &sliders.hips, 0.0f, 1.0f)) { - joints_dirty = true; - } - } - else { - ImGui::AlignTextToFramePadding(); - ImGui::Text("%.3f", Double(_currentMass->jointSliders().hips)); - } - - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(1); - if(ImGui::BeginTable("##UpperLowerLayoutTable", 2, ImGuiTableFlags_BordersInnerV)) { - ImGui::TableSetupColumn("##Upper", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("##Lower", ImGuiTableColumnFlags_WidthStretch); - - ImGui::TableNextColumn(); - ImGui::AlignTextToFramePadding(); - ImGui::TextUnformatted("Upper"); - ImGui::TableNextColumn(); - ImGui::AlignTextToFramePadding(); - ImGui::TextUnformatted("Lower"); - - ImGui::EndTable(); + ImGui::SetNextItemWidth(-1.0f); + if(ImGui::SliderFloat("##HipsSlider", &_currentMass->jointSliders().hips, 0.0f, 1.0f)) { + joints_dirty = true; } ImGui::TableNextRow(); @@ -214,210 +254,1188 @@ void SaveTool::drawJointSliders() { ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Arms"); ImGui::TableSetColumnIndex(1); - if(ImGui::BeginTable("##UpperLowerArmsLayoutTable", 2, ImGuiTableFlags_BordersInnerV)) { + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{2.0f, 1.0f}); + if(ImGui::BeginTable("##UpperLowerArmsLayoutTable", 2)) { ImGui::TableSetupColumn("##UpperArms", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("##LowerArms", ImGuiTableColumnFlags_WidthStretch); ImGui::TableNextColumn(); - if(joints_edit) { - ImGui::SetNextItemWidth(-1.0f); - if(ImGui::SliderFloat("##UpperArmsSlider", &sliders.upperArms, 0.0f, 1.0f)) { - joints_dirty = true; - } - } - else { - ImGui::AlignTextToFramePadding(); - ImGui::Text("%.3f", Double(_currentMass->jointSliders().upperArms)); + ImGui::SetNextItemWidth(-1.0f); + if(ImGui::SliderFloat("##UpperArmsSlider", &_currentMass->jointSliders().upperArms, 0.0f, 1.0f, "Upper: %.3f")) { + joints_dirty = true; } ImGui::TableNextColumn(); - if(joints_edit) { - ImGui::SetNextItemWidth(-1.0f); - if(ImGui::SliderFloat("##LowerArmsSlider", &sliders.lowerArms, 0.0f, 1.0f)) { - joints_dirty = true; - } - } - else { - ImGui::AlignTextToFramePadding(); - ImGui::Text("%.3f", Double(_currentMass->jointSliders().lowerArms)); + ImGui::SetNextItemWidth(-1.0f); + if(ImGui::SliderFloat("##LowerArmsSlider", &_currentMass->jointSliders().lowerArms, 0.0f, 1.0f, "Lower: %.3f")) { + joints_dirty = true; } ImGui::EndTable(); } + ImGui::PopStyleVar(); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::AlignTextToFramePadding(); ImGui::TextUnformatted("Legs"); ImGui::TableSetColumnIndex(1); - if(ImGui::BeginTable("##UpperLowerLegsLayoutTable", 2, ImGuiTableFlags_BordersInnerV)) { + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{2.0f, 1.0f}); + if(ImGui::BeginTable("##UpperLowerLegsLayoutTable", 2)) { ImGui::TableSetupColumn("##UpperLegs", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("##LowerLegs", ImGuiTableColumnFlags_WidthStretch); ImGui::TableNextColumn(); - if(joints_edit) { - ImGui::SetNextItemWidth(-1.0f); - if(ImGui::SliderFloat("##UpperLegsSlider", &sliders.upperLegs, 0.0f, 1.0f)) { - joints_dirty = true; - } - } - else { - ImGui::AlignTextToFramePadding(); - ImGui::Text("%.3f", Double(_currentMass->jointSliders().upperLegs)); + ImGui::SetNextItemWidth(-1.0f); + if(ImGui::SliderFloat("##UpperLegsSlider", &_currentMass->jointSliders().upperLegs, 0.0f, 1.0f, "Upper: %.3f")) { + joints_dirty = true; } ImGui::TableNextColumn(); - if(joints_edit) { - ImGui::SetNextItemWidth(-1.0f); - if(ImGui::SliderFloat("##LowerLegsSlider", &sliders.lowerLegs, 0.0f, 1.0f)) { - joints_dirty = true; - } - } - else { - ImGui::AlignTextToFramePadding(); - ImGui::Text("%.3f", Double(_currentMass->jointSliders().lowerLegs)); + ImGui::SetNextItemWidth(-1.0f); + if(ImGui::SliderFloat("##LowerLegsSlider", &_currentMass->jointSliders().lowerLegs, 0.0f, 1.0f, "Lower: %.3f")) { + joints_dirty = true; } ImGui::EndTable(); } + ImGui::PopStyleVar(); ImGui::EndTable(); } - if(joints_edit) { - if(!joints_dirty) { - ImGui::BeginDisabled(); - ImGui::Button(ICON_FA_SAVE " Save changes"); - ImGui::SameLine(); - ImGui::Button(ICON_FA_UNDO " Reset sliders"); - ImGui::EndDisabled(); - } - else { - if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save changes"); })) { - if(!_currentMass->setSliders(sliders)) { - _queue.addToast(Toast::Type::Error, "Error writing the joint sliders."); - } - joints_dirty = false; - joints_edit = false; - } - ImGui::SameLine(); - if(ImGui::Button(ICON_FA_UNDO " Reset sliders")) { - sliders = _currentMass->jointSliders(); - joints_dirty = false; - } - } + if(!joints_dirty) { + ImGui::BeginDisabled(); + ImGui::Button(ICON_FA_SAVE " Save"); ImGui::SameLine(); - if(ImGui::Button(ICON_FA_TIMES " Cancel editing")) { - sliders = _currentMass->jointSliders(); - joints_dirty = false; - joints_edit = false; - } - - ImGui::TextUnformatted("To input out-of-range values, hold Ctrl and click on a slider."); + ImGui::Button(ICON_FA_UNDO " Reset"); + ImGui::EndDisabled(); } else { - if(ImGui::Button(ICON_FA_EDIT " Edit sliders")) { - sliders = _currentMass->jointSliders(); - joints_edit = true; + if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { + if(!_currentMass->writeJointSliders()) { + _queue.addToast(Toast::Type::Error, "Error: " + Mass::lastError()); + } + joints_dirty = false; + } + ImGui::SameLine(); + if(ImGui::Button(ICON_FA_UNDO " Reset")) { + _currentMass->getJointSliders(); + joints_dirty = false; } } } -void SaveTool::drawFramePaint() { - if(!ImGui::CollapsingHeader("Paint")) { +void SaveTool::drawFrameStyles() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { return; } - ImGui::TextUnformatted("Frame styles:"); + static bool styles_dirty = false; + for(Int i = 0; i < 4; i++) { ImGui::AlignTextToFramePadding(); ImGui::Text("Slot %d:", i + 1); + ImGui::SameLine(); + ImGui::PushID(i); - GameState game_state = _gameState; - if(!_unsafeMode && game_state != GameState::NotRunning) { - ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.5f); - } - if(ImGui::BeginCombo("##Style", style_names.at(_currentMass->frameStyles()[i]))) { + + if(ImGui::BeginCombo("##Style", getStyleName(_currentMass->frameStyles()[i], _currentMass->frameCustomStyles()))) { for(const auto& style : style_names) { - if(ImGui::Selectable(style.second, _currentMass->frameStyles()[i] == style.first)) { - if(!_currentMass->setFrameStyle(i, style.first)) { - _queue.addToast(Toast::Type::Error, Mass::lastError()); - } - else { - _currentMass->refreshValues(); - } + if(ImGui::Selectable(getStyleName(style.first, _currentMass->frameCustomStyles()), _currentMass->frameStyles()[i] == style.first)) { + _currentMass->frameStyles()[i] = style.first; + styles_dirty = true; } } ImGui::EndCombo(); } - if(!_unsafeMode && game_state != GameState::NotRunning) { - ImGui::PopItemFlag(); - ImGui::PopStyleVar(); + + ImGui::PopID(); + } + + if(!styles_dirty) { + ImGui::BeginDisabled(); + ImGui::Button(ICON_FA_SAVE " Save"); + ImGui::SameLine(); + ImGui::Button(ICON_FA_UNDO " Reset"); + ImGui::EndDisabled(); + } + else { + if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { + if(!_currentMass->writeFrameStyles()) { + _queue.addToast(Toast::Type::Error, "Error: " + Mass::lastError()); + } + styles_dirty = false; + } + ImGui::SameLine(); + if(ImGui::Button(ICON_FA_UNDO " Reset")) { + _currentMass->getFrameStyles(); + styles_dirty = false; + } + } +} + +void SaveTool::drawEyeColourPicker() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + return; + } + + static bool eye_flare_dirty = false; + + if(ImGui::ColorPicker3("##EyeFlarePicker", &_currentMass->eyeFlareColour().x())) { + eye_flare_dirty = true; + } + + if(!eye_flare_dirty) { + ImGui::BeginDisabled(); + ImGui::Button(ICON_FA_SAVE " Save"); + ImGui::SameLine(); + ImGui::Button(ICON_FA_UNDO " Reset"); + ImGui::EndDisabled(); + } + else { + if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { + if(!_currentMass->writeEyeFlareColour()) { + _queue.addToast(Toast::Type::Error, "Error writing the eye flare colour."); + } + eye_flare_dirty = false; + } + ImGui::SameLine(); + if(ImGui::Button(ICON_FA_UNDO " Reset")) { + _currentMass->getEyeFlareColour(); + eye_flare_dirty = false; + } + } +} + +void SaveTool::drawCustomFrameStyles() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + return; + } + + if(!ImGui::BeginChild("##FrameStyles")) { + ImGui::EndChild(); + return; + } + + ImGui::TextWrapped("In-game values are multiplied by 100. For example, 0.500 here is equal to 50 in-game."); + + for(UnsignedInt i = 0; i < _currentMass->frameCustomStyles().size(); i++) { + ImGui::PushID(i); + DCSResult result; + result = drawCustomStyle(_currentMass->frameCustomStyles()[i]); + switch(result) { + case DCS_ResetStyle: + _currentMass->getFrameCustomStyles(); + break; + case DCS_Save: + _currentMass->writeFrameCustomStyle(i); + break; + default: + break; } ImGui::PopID(); } + ImGui::EndChild(); +} + +void SaveTool::drawArmour() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + return; + } + + if(ImGui::Button(ICON_FA_UNDO_ALT " Reset all")) { + _currentMass->getArmourParts(); + } + + if(!ImGui::BeginChild("##ArmourParts")) { + ImGui::EndChild(); + return; + } + + static Containers::StaticArray<38, Int> selected_decals{ValueInit}; + static Containers::StaticArray<38, Int> selected_accessories{ValueInit}; + + for(UnsignedInt i = 0; i < _currentMass->armourParts().size(); i++) { + ImGui::PushID(i); + + auto& part = _currentMass->armourParts()[i]; + + static char header[129] = {'\0'}; + + std::memset(header, '\0', 129); + + if(armour_sets.find(part.id) != armour_sets.cend()) { + std::snprintf(header, 128, "%s: %s###%s", armour_slots.at(part.slot), armour_sets.at(part.id).name, part.slot.c_str()); + } + else { + std::snprintf(header, 128, "%s: %i###%s", armour_slots.at(part.slot), part.id, part.slot.c_str()); + } + + if(ImGui::CollapsingHeader(header)) { + ImGui::BeginGroup(); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth() * 0.491f); + if(ImGui::BeginListBox("##ChangePart")) { + if(std::strncmp("Neck", armour_slots.at(part.slot), 4) != 0) { + for(auto& set : armour_sets) { + if(ImGui::Selectable(set.second.name, set.first == part.id, ImGuiSelectableFlags_SpanAvailWidth)) { + part.id = set.first; + } + } + } + else { + for(auto& set : armour_sets) { + if(!set.second.neck_compatible) { + continue; + } + + if(ImGui::Selectable(set.second.name, set.first == part.id, ImGuiSelectableFlags_SpanAvailWidth)) { + part.id = set.first; + } + } + } + ImGui::EndListBox(); + } + + ImGui::EndGroup(); + + ImGui::SameLine(); + + ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); + + ImGui::SameLine(); + + ImGui::BeginGroup(); + + ImGui::TextUnformatted("Styles:"); + + for(Int j = 0; j < 4; j++) { + ImGui::AlignTextToFramePadding(); + ImGui::Text("Slot %d:", j + 1); + + ImGui::SameLine(); + + ImGui::PushID(j); + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvailWidth() - 2.0f); + if(ImGui::BeginCombo("##Style", getStyleName(part.styles[j], _currentMass->armourCustomStyles()))) { + for(const auto& style : style_names) { + if(ImGui::Selectable(getStyleName(style.first, _currentMass->armourCustomStyles()), part.styles[j] == style.first)) { + part.styles[j] = style.first; + } + } + + ImGui::EndCombo(); + } + + ImGui::PopID(); + } + + ImGui::EndGroup(); + + ImGui::Separator(); + + ImGui::PushID("Decal"); + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Showing/editing decal"); + for(UnsignedLong j = 0; j < part.demoDecals; j++) { + ImGui::SameLine(); + ImGui::RadioButton(std::to_string(j + 1).c_str(), &selected_decals[i], j); + } + + drawDecalEditor(part.decals[selected_decals[i]]); + + ImGui::PopID(); + + if(!_currentMass->demo()) { + ImGui::Separator(); + + ImGui::PushID("Accessory"); + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Showing/editing accessory"); + for(UnsignedInt j = 0; j < part.accessories.size(); j++) { + ImGui::SameLine(); + ImGui::RadioButton(std::string{char(65 + j)}.c_str(), &selected_accessories[i], j); + } + + drawAccessoryEditor(part.accessories[selected_accessories[i]], _currentMass->armourCustomStyles()); + + ImGui::PopID(); + } + + ImGui::Separator(); + + if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { + _currentMass->writeArmourPart(i); + } + } + + ImGui::PopID(); + } + + ImGui::EndChild(); +} + +void SaveTool::drawCustomArmourStyles() { + if(!_currentMass || _currentMass->state() != 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(UnsignedInt i = 0; i < _currentMass->armourCustomStyles().size(); i++) { + ImGui::PushID(i); + DCSResult result; + result = drawCustomStyle(_currentMass->armourCustomStyles()[i]); + switch(result) { + case DCS_ResetStyle: + _currentMass->getArmourCustomStyles(); + break; + case DCS_Save: + _currentMass->writeArmourCustomStyle(i); + break; + default: + break; + } + ImGui::PopID(); + } + + ImGui::EndChild(); +} + +void SaveTool::drawWeapons() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + current_weapon = nullptr; + return; + } + + if(!ImGui::BeginTable("##WeaponsList", 1, ImGuiTableFlags_ScrollY|ImGuiTableFlags_BordersOuter|ImGuiTableFlags_BordersInnerH, {ImGui::GetContentRegionAvailWidth() * 0.2f, 0.0f})) { + return; + } + + ImGui::TableSetupColumn("Weapon"); + + int id = 0; + +#define weapcat(header, array, payload_type, payload_tooltip) \ + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); \ + ImGui::TableNextColumn(); \ + ImGui::TextUnformatted(header); \ + \ + for(UnsignedInt i = 0; i < _currentMass->array().size(); i++) { \ + auto& weapon = _currentMass->array()[i]; \ + \ + ImGui::TableNextRow(); \ + ImGui::TableNextColumn(); \ + ImGui::PushID(id); \ + if(ImGui::Selectable(weapon.name.c_str(), current_weapon == &weapon)) { \ + current_weapon = &weapon; \ + } \ + if(ImGui::BeginDragDropSource()) { \ + ImGui::SetDragDropPayload(payload_type, &i, sizeof(UnsignedInt)); \ + ImGui::Text(payload_tooltip " %i - %s", i + 1, weapon.name.c_str()); \ + ImGui::EndDragDropSource(); \ + } \ + if(ImGui::BeginDragDropTarget()) { \ + if(const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(payload_type)) { \ + int index = *static_cast(payload->Data); \ + \ + if(current_weapon == &_currentMass->array()[index]) { \ + current_weapon = &_currentMass->array()[i]; \ + } \ + else if (current_weapon == &_currentMass->array()[i]) { \ + current_weapon = &_currentMass->array()[index]; \ + } \ + \ + std::swap(_currentMass->array()[index], _currentMass->array()[i]); \ + } \ + \ + ImGui::EndDragDropTarget(); \ + } \ + \ + ImGui::PopID(); \ + id++; \ + \ + if(weapon.attached == true) { \ + ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, 0x1F008CFFu); \ + } \ + } + + weapcat("Melee weapons", meleeWeapons, "MeleeWeapon", "Melee weapon") + weapcat("Shield", shields, "Shield", "Shield") + weapcat("Bullet shooters", bulletShooters, "BShooter", "Bullet shooter") + weapcat("Energy shooters", energyShooters, "EShooter", "Energy shooter") + weapcat("Bullet launchers", bulletLaunchers, "BLauncher", "Bullet launcher") + weapcat("Energy launchers", energyLaunchers, "ELauncher", "Energy launcher") + +#undef weapcat + + ImGui::EndTable(); + + ImGui::SameLine(); + + if(!current_weapon) { + ImGui::TextUnformatted("No weapon selected."); + return; + } + + ImGui::BeginGroup(); + + const Float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); + if(!ImGui::BeginChild("##WeaponChild", {0.0f, -footer_height_to_reserve})) { + ImGui::EndChild(); + return; + } + + drawWeaponEditor(*current_weapon); + + ImGui::EndChild(); + ImGui::Separator(); - static bool eye_flare_edit = false; - static bool eye_flare_dirty = false; + if(drawUnsafeWidget([](){ return ImGui::Button(ICON_FA_SAVE " Save changes to weapon category"); })) { + if(current_weapon->type == "enuWeaponTypes::NewEnumerator0") { + if(!_currentMass->writeMeleeWeapons()) { + _queue.addToast(Toast::Type::Error, "Couldn't save melee weapons"); + } + } + else if(current_weapon->type == "enuWeaponTypes::NewEnumerator5") { + if(!_currentMass->writeShields()) { + _queue.addToast(Toast::Type::Error, "Couldn't save shields"); + } + } + else if(current_weapon->type == "enuWeaponTypes::NewEnumerator1") { + if(!_currentMass->writeBulletShooters()) { + _queue.addToast(Toast::Type::Error, "Couldn't save bullet shooters"); + } + } + else if(current_weapon->type == "enuWeaponTypes::NewEnumerator2") { + if(!_currentMass->writeEnergyShooters()) { + _queue.addToast(Toast::Type::Error, "Couldn't save energy shooters"); + } + } + else if(current_weapon->type == "enuWeaponTypes::NewEnumerator3") { + if(!_currentMass->writeBulletLaunchers()) { + _queue.addToast(Toast::Type::Error, "Couldn't save bullet launchers"); + } + } + else if(current_weapon->type == "enuWeaponTypes::NewEnumerator4") { + if(!_currentMass->writeEnergyLaunchers()) { + _queue.addToast(Toast::Type::Error, "Couldn't save energy launchers"); + } + } + else { + _queue.addToast(Toast::Type::Error, "Unknown weapon type"); + } + } - static Color4 eye_flare = _currentMass->eyeFlareColour(); + ImGui::SameLine(); + + if(ImGui::Button(ICON_FA_UNDO_ALT " Reset weapon category")) { + if(current_weapon->type == "enuWeaponTypes::NewEnumerator0") { + _currentMass->getMeleeWeapons(); + } + else if(current_weapon->type == "enuWeaponTypes::NewEnumerator5") { + _currentMass->getShields(); + } + else if(current_weapon->type == "enuWeaponTypes::NewEnumerator1") { + _currentMass->getBulletShooters(); + } + else if(current_weapon->type == "enuWeaponTypes::NewEnumerator2") { + _currentMass->getEnergyShooters(); + } + else if(current_weapon->type == "enuWeaponTypes::NewEnumerator3") { + _currentMass->getBulletLaunchers(); + } + else if(current_weapon->type == "enuWeaponTypes::NewEnumerator4") { + _currentMass->getEnergyLaunchers(); + } + else { + _queue.addToast(Toast::Type::Error, "Unknown weapon type"); + } + } + + ImGui::EndGroup(); +} + +void SaveTool::drawWeaponEditor(Weapon& weapon) { + if(!_currentMass || _currentMass->state() != Mass::State::Valid || !current_weapon) { + return; + } ImGui::AlignTextToFramePadding(); - ImGui::TextUnformatted("Eye flare colour:"); + ImGui::Text("%s: %s", weapon_types.at(weapon.type), weapon.name.c_str()); + + ImGui::SameLine(); + + static Containers::StaticArray<33, char> name_buf{ValueInit}; + if(ImGui::Button(ICON_FA_EDIT " Rename")) { + for(auto& c : name_buf) { + c = '\0'; + } + std::strncpy(name_buf.data(), weapon.name.c_str(), 32); + ImGui::OpenPopup("name_edit"); + } + if(drawRenamePopup(name_buf)) { + weapon.name = name_buf.data(); + } + + ImGui::BeginGroup(); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Equipped:"); + + if(weapon.type != "enuWeaponTypes::NewEnumerator5") { + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Damage type:"); + } + + if(weapon.type == "enuWeaponTypes::NewEnumerator0") { + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Dual-wield:"); + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Custom effect mode:"); + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Custom effect colour:"); + } + ImGui::EndGroup(); ImGui::SameLine(); ImGui::BeginGroup(); - if(eye_flare_edit) { - if(ImGui::ColorEdit3("##EyeFlarePicker", &eye_flare.x())) { - eye_flare_dirty = true; + ImGui::Checkbox("##EquippedCheckbox", &weapon.attached); + + if(weapon.type != "enuWeaponTypes::NewEnumerator5") { + if(weapon.type == "enuWeaponTypes::NewEnumerator0" && + ImGui::RadioButton("Physical##NoElement", weapon.damageType == "enuDamageProperty::NewEnumerator0")) + { + weapon.damageType = "enuDamageProperty::NewEnumerator0"; + } + else if((weapon.type == "enuWeaponTypes::NewEnumerator1" || weapon.type == "enuWeaponTypes::NewEnumerator3") && + ImGui::RadioButton("Piercing##NoElement", weapon.damageType == "enuDamageProperty::NewEnumerator1")) + { + weapon.damageType = "enuDamageProperty::NewEnumerator1"; + } + else if((weapon.type == "enuWeaponTypes::NewEnumerator2" || weapon.type == "enuWeaponTypes::NewEnumerator4") && + ImGui::RadioButton("Plasma##NoElement", weapon.damageType == "enuDamageProperty::NewEnumerator5")) + { + weapon.damageType = "enuDamageProperty::NewEnumerator5"; } ImGui::SameLine(); - drawHelpMarker("Right-click for more option, click the coloured square for the full picker.", 250.0f); - - if(!eye_flare_dirty) { - ImGui::BeginDisabled(); - ImGui::Button(ICON_FA_SAVE " Save"); - ImGui::SameLine(); - ImGui::Button(ICON_FA_UNDO " Reset"); - ImGui::EndDisabled(); + if(ImGui::RadioButton("Heat##Heat", weapon.damageType == "enuDamageProperty::NewEnumerator2")) { + weapon.damageType = "enuDamageProperty::NewEnumerator2"; } - else { - if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { - if(!_currentMass->setEyeFlareColour(eye_flare)) { - _queue.addToast(Toast::Type::Error, "Error writing the eye flare colour."); - } - eye_flare_dirty = false; - eye_flare_edit = false; - } - ImGui::SameLine(); - if(ImGui::Button(ICON_FA_UNDO " Reset")) { - eye_flare = _currentMass->eyeFlareColour(); - eye_flare_dirty = false; - } - } - ImGui::SameLine(); - - if(ImGui::Button(ICON_FA_TIMES " Cancel")) { - eye_flare = _currentMass->eyeFlareColour(); - eye_flare_dirty = false; - eye_flare_edit = false; + if(ImGui::RadioButton("Freeze##Freeze", weapon.damageType == "enuDamageProperty::NewEnumerator3")) { + weapon.damageType = "enuDamageProperty::NewEnumerator3"; + } + ImGui::SameLine(); + if(ImGui::RadioButton("Shock##Shock", weapon.damageType == "enuDamageProperty::NewEnumerator4")) { + weapon.damageType = "enuDamageProperty::NewEnumerator4"; } } - else { - ImGui::BeginDisabled(); - ImColor colour{_currentMass->eyeFlareColour()}; - ImGui::ColorEdit3("##EyeFlarePicker", &colour.Value.x); - ImGui::EndDisabled(); - if(ImGui::Button(ICON_FA_EDIT " Edit")) { - eye_flare = _currentMass->eyeFlareColour(); - eye_flare_edit = true; + if(weapon.type == "enuWeaponTypes::NewEnumerator0") { + ImGui::Checkbox("##DualWield", &weapon.dualWield); + + if(ImGui::RadioButton("Default##Default", weapon.effectColourMode == "enuWeaponEffectColorMode::NewEnumerator0")) { + weapon.effectColourMode = "enuWeaponEffectColorMode::NewEnumerator0"; + } + ImGui::SameLine(); + if(ImGui::RadioButton("Custom##Custom", weapon.effectColourMode == "enuWeaponEffectColorMode::NewEnumerator1")) { + weapon.effectColourMode = "enuWeaponEffectColorMode::NewEnumerator1"; + } + + bool custom_effect = (weapon.effectColourMode == "enuWeaponEffectColorMode::NewEnumerator1"); + if(!custom_effect) { + ImGui::BeginDisabled(); + } + + ImGui::ColorEdit3("##CustomEffectColourPicker", &weapon.effectColour.x(), ImGuiColorEditFlags_HDR|ImGuiColorEditFlags_Float); + + if(!custom_effect) { + ImGui::EndDisabled(); } } ImGui::EndGroup(); + + ImGui::Separator(); + + if(ImGui::CollapsingHeader("Weapon parts")) { + static Int selected_part = 0; + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Viewing/editing part:"); + for(Int i = 0; UnsignedLong(i) < weapon.parts.size(); i++) { + if(UnsignedLong(selected_part) >= weapon.parts.size()) { + selected_part = 0; + } + ImGui::SameLine(); + ImGui::RadioButton(std::to_string(i + 1).c_str(), &selected_part, i); + } + + auto& part = weapon.parts[selected_part]; + + ImGui::Text("ID: %i", part.id); + + if(ImGui::BeginChild("##PartDetails", {0.0f, 0.0f}, true)) { + ImGui::TextUnformatted("Styles:"); + + for(Int i = 0; i < 4; i++) { + ImGui::AlignTextToFramePadding(); + ImGui::Text("Slot %d:", i + 1); + + ImGui::SameLine(); + + ImGui::PushID(i); + + if(ImGui::BeginCombo("##Style", getStyleName(part.styles[i], weapon.customStyles))) { + for(const auto& style: style_names) { + if(ImGui::Selectable(getStyleName(style.first, weapon.customStyles), + part.styles[i] == style.first)) { + part.styles[i] = style.first; + } + } + + ImGui::EndCombo(); + } + + ImGui::PopID(); + } + + ImGui::Separator(); + + ImGui::PushID("Decal"); + + static Int selected_decal = 0; + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Showing/editing decal"); + for(UnsignedLong i = 0; i < part.demoDecals; i++) { + ImGui::SameLine(); + ImGui::RadioButton(std::to_string(i + 1).c_str(), &selected_decal, i); + } + + drawDecalEditor(part.decals[selected_decal]); + + ImGui::PopID(); + + if(_currentMass->demo() == false) { + ImGui::Separator(); + + ImGui::PushID("Accessory"); + + static Int selected_accessory = 0; + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Showing/editing accessory"); + for(UnsignedLong i = 0; i < part.accessories.size(); i++) { + ImGui::SameLine(); + ImGui::RadioButton(std::string{char(65 + i)}.c_str(), &selected_accessory, i); + } + + drawAccessoryEditor(part.accessories[selected_accessory], weapon.customStyles); + + ImGui::PopID(); + } + } + + ImGui::EndChild(); + } +} + +void SaveTool::drawGlobalStyles() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + return; + } + + if(!ImGui::BeginChild("##GlobalStyles")) { + ImGui::EndChild(); + return; + } + + ImGui::TextWrapped("In-game values are multiplied by 100. For example, 0.500 here is equal to 50 in-game."); + + for(UnsignedInt i = 0; i < _currentMass->globalStyles().size(); i++) { + ImGui::PushID(i); + DCSResult result; + result = drawCustomStyle(_currentMass->globalStyles()[i]); + switch(result) { + case DCS_ResetStyle: + _currentMass->getGlobalStyles(); + break; + case DCS_Save: + _currentMass->writeGlobalStyle(i); + break; + default: + break; + } + ImGui::PopID(); + } + + ImGui::EndChild(); +} + +void SaveTool::drawTuning() { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + return; + } + + if(!ImGui::BeginTable("##TuningTable", 3)) { + return; + } + + ImGui::TableSetupColumn("##EngineColumn"); + ImGui::TableSetupColumn("##OSColumn"); + ImGui::TableSetupColumn("##ArchitectureColumn"); + + ImGui::TableNextRow(); + + ImGui::TableSetColumnIndex(0); + + if(ImGui::BeginTable("##EngineTable", 1, ImGuiTableFlags_Borders)) { + ImGui::TableSetupColumn("##Engine"); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("Engine"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%i", _currentMass->engine()); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("Gears"); + + for(UnsignedInt i = 0; i < _currentMass->gears().size(); i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%i", _currentMass->gears()[i]); + } + + ImGui::EndTable(); + } + + ImGui::TableSetColumnIndex(1); + + if(ImGui::BeginTable("##OSTable", 1, ImGuiTableFlags_Borders)) { + ImGui::TableSetupColumn("##OS"); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("OS"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%i", _currentMass->os()); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("Modules"); + + for(UnsignedInt i = 0; i < _currentMass->modules().size(); i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%i", _currentMass->modules()[i]); + } + + ImGui::EndTable(); + } + + ImGui::TableSetColumnIndex(2); + + if(ImGui::BeginTable("##ArchTable", 1, ImGuiTableFlags_Borders)) { + ImGui::TableSetupColumn("##Arch"); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("Architecture"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%i", _currentMass->architecture()); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("Techs"); + + for(UnsignedInt i = 0; i < _currentMass->techs().size(); i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%i", _currentMass->techs()[i]); + } + + ImGui::EndTable(); + } + + ImGui::EndTable(); +} + +auto SaveTool::drawCustomStyle(CustomStyle& style) -> DCSResult { + if(!_currentMass || _currentMass->state() != Mass::State::Valid) { + return DCS_Fail; + } + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, {8.0f, 0.0f}); + + Containers::ScopeGuard guard{[]{ ImGui::PopStyleVar(); }}; + + DCSResult return_value = DCS_Fail; + + if(!ImGui::BeginChild("##CustomStyle", {0.0f, 244.0f}, true, ImGuiWindowFlags_MenuBar)) { + ImGui::EndChild(); + return DCS_Fail; + } + + if(ImGui::BeginMenuBar()) { + ImGui::TextUnformatted(style.name.c_str()); + + static Containers::StaticArray<33, char> name_buf{ValueInit}; + if(ImGui::SmallButton(ICON_FA_EDIT " Rename")) { + for(auto& c : name_buf) { + c = '\0'; + } + std::strncpy(name_buf.data(), style.name.c_str(), 32); + ImGui::OpenPopup("name_edit"); + } + if(drawRenamePopup(name_buf)) { + style.name = name_buf.data(); + } + + ImGui::EndMenuBar(); + } + + if(ImGui::BeginTable("##StyleTable", 2, ImGuiTableFlags_BordersInnerV)) { + ImGui::TableSetupColumn("##Colour", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("##Pattern", ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + + ImGui::BeginGroup(); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Colour:"); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Metallic:"); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Gloss:"); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Glow:"); + ImGui::EndGroup(); + + ImGui::SameLine(); + + ImGui::BeginGroup(); + ImGui::ColorEdit3("##Picker", &style.colour.r()); + ImGui::SameLine(); + drawHelpMarker("Right-click for more option, click the coloured square for the full picker."); + + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::SliderFloat("##SliderMetallic", &style.metallic, 0.0f, 1.0f); + + ImGui::SetNextItemWidth(-FLT_MIN); + ImGui::SliderFloat("##SliderGloss", &style.gloss,0.0f, 1.0f); + + ImGui::Checkbox("##Glow", &style.glow); + ImGui::EndGroup(); + + ImGui::TableNextColumn(); + + ImGui::BeginGroup(); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Pattern:"); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Opacity:"); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("X offset:"); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Y offset:"); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Rotation:"); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Scale:"); + ImGui::EndGroup(); + + ImGui::SameLine(); + + ImGui::BeginGroup(); + ImGui::AlignTextToFramePadding(); + ImGui::Text("%i", style.patternId); + ImGui::PushItemWidth(-FLT_MIN); + ImGui::SliderFloat("##SliderOpacity", &style.opacity, 0.0f, 1.0f); + ImGui::SliderFloat("##SliderOffsetX", &style.offset.x(), 0.0f, 1.0f); + ImGui::SliderFloat("##SliderOffsetY", &style.offset.y(), 0.0f, 1.0f); + ImGui::SliderFloat("##SliderRotation", &style.rotation, 0.0f, 1.0f); + ImGui::SliderFloat("##SliderScale", &style.scale, 0.0f, 1.0f); + ImGui::PopItemWidth(); + ImGui::EndGroup(); + + ImGui::EndTable(); + } + + if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { + return_value = DCS_Save; + } + ImGui::SameLine(); + if(ImGui::Button(ICON_FA_UNDO " Reset")) { + return_value = DCS_ResetStyle; + } + + ImGui::EndChild(); + + return return_value; +} + +void SaveTool::drawDecalEditor(Decal& decal) { + ImGui::Text("ID: %i", decal.id); + + if(ImGui::BeginTable("##DecalTable", 2, ImGuiTableFlags_BordersInnerV)) { + ImGui::TableSetupColumn("##Normal", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("##Advanced", ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + + ImGui::TableNextColumn(); + + ImGui::BeginGroup(); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Colour:"); + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Offset:"); + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Rotation:"); + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Scale:"); + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Flip X:"); + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Surface wrap:"); + ImGui::EndGroup(); + + ImGui::SameLine(); + + ImGui::BeginGroup(); + ImGui::ColorEdit3("##Picker", &decal.colour.r()); + ImGui::SameLine(); + drawHelpMarker("Right-click for more option, click the coloured square for the full picker."); + + ImGui::PushMultiItemsWidths(2, ImGui::CalcItemWidth()); + ImGui::SliderFloat("##OffsetX", &decal.offset.x(), 0.0f, 1.0f, "X: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::SliderFloat("##OffsetY", &decal.offset.y(), 0.0f, 1.0f, "Y: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(); + drawHelpMarker("0.0 = -100 in-game\n1.0 = 100 in-game"); + + ImGui::SliderFloat("##Rotation", &decal.rotation, 0.0f, 1.0f); + ImGui::SameLine(); + drawHelpMarker("0.0 = 0 in-game\n1.0 = 360 in-game"); + + ImGui::SliderFloat("##Scale", &decal.scale, 0.0f, 1.0f); + ImGui::SameLine(); + drawHelpMarker("0.0 = 1 in-game\n1.0 = 100 in-game"); + + ImGui::Checkbox("##Flip", &decal.flip); + + ImGui::Checkbox("##Wrap", &decal.wrap); + ImGui::EndGroup(); + + + ImGui::TableNextColumn(); + + ImGui::TextColored(ImColor(255, 255, 0), ICON_FA_EXCLAMATION_TRIANGLE); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::TextUnformatted("Advanced settings. Touch these at your own risk."); + + ImGui::BeginGroup(); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Position:"); + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("U axis:"); + + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("V axis:"); + ImGui::EndGroup(); + + ImGui::SameLine(); + + ImGui::BeginGroup(); + ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); + ImGui::DragFloat("##PosX", &decal.position.x(), 1.0f, -FLT_MAX, +FLT_MAX, "X: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##PosY", &decal.position.y(), 1.0f, -FLT_MAX, +FLT_MAX, "Y: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##PosZ", &decal.position.z(), 1.0f, -FLT_MAX, +FLT_MAX, "Z: %.3f"); + ImGui::PopItemWidth(); + + ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); + ImGui::DragFloat("##UX", &decal.uAxis.x(), 1.0f, -FLT_MAX, +FLT_MAX, "X: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##UY", &decal.uAxis.y(), 1.0f, -FLT_MAX, +FLT_MAX, "Y: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##UZ", &decal.uAxis.z(), 1.0f, -FLT_MAX, +FLT_MAX, "Z: %.3f"); + ImGui::PopItemWidth(); + + ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); + ImGui::DragFloat("##VX", &decal.vAxis.x(), 1.0f, -FLT_MAX, +FLT_MAX, "X: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##VY", &decal.vAxis.y(), 1.0f, -FLT_MAX, +FLT_MAX, "Y: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##VZ", &decal.vAxis.z(), 1.0f, -FLT_MAX, +FLT_MAX, "Z: %.3f"); + ImGui::PopItemWidth(); + ImGui::EndGroup(); + + ImGui::EndTable(); + } +} + +void SaveTool::drawAccessoryEditor(Accessory& accessory, Containers::ArrayView style_view) { + if(accessory.id < 1) { + ImGui::TextUnformatted("Accessory: "); + } + else if(accessories.find(accessory.id) != accessories.cend()) { + ImGui::Text("Accessory #%i - %s", accessory.id, accessories.at(accessory.id)); + } + else { + ImGui::Text("Accessory #%i", accessory.id); + drawTooltip("WARNING: accessory mapping is a WIP."); + } + +#ifdef SAVETOOL_DEBUG_BUILD + ImGui::SameLine(0.0f, ImGui::GetStyle().FramePadding.x * 5.0f); + ImGui::Text("Attach index: %i", accessory.attachIndex); +#endif + + ImGui::BeginGroup(); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Styles:"); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Base position:"); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Position offset:"); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Base rotation:"); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Rotation offset:"); + ImGui::AlignTextToFramePadding(); + ImGui::TextUnformatted("Scale:"); + ImGui::EndGroup(); + + ImGui::SameLine(); + + ImGui::BeginGroup(); + ImGui::PushMultiItemsWidths(2, ImGui::CalcItemWidth()); + if(ImGui::BeginCombo("##Style1", getStyleName(accessory.styles[0], style_view))) { + for(const auto& style : style_names) { + if(ImGui::Selectable(getStyleName(style.first, style_view), accessory.styles[0] == style.first)) { + accessory.styles[0] = style.first; + } + } + + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + if(ImGui::BeginCombo("##Style2", getStyleName(accessory.styles[1], style_view))) { + for(const auto& style : style_names) { + if(ImGui::Selectable(getStyleName(style.first, style_view), accessory.styles[1] == style.first)) { + accessory.styles[1] = style.first; + } + } + + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + + ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); + ImGui::DragFloat("##PosX", &accessory.relativePosition.x(), 1.0f, -FLT_MAX, +FLT_MAX, "X: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##PosY", &accessory.relativePosition.y(), 1.0f, -FLT_MAX, +FLT_MAX, "Y: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##PosZ", &accessory.relativePosition.z(), 1.0f, -FLT_MAX, +FLT_MAX, "Z: %.3f"); + ImGui::PopItemWidth(); + + ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); + ImGui::SliderFloat("##PosOffsetX", &accessory.relativePositionOffset.x(), -500.0f, +500.0f, "X: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::SliderFloat("##PosOffsetY", &accessory.relativePositionOffset.y(), -500.0f, +500.0f, "Y: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::SliderFloat("##PosOffsetZ", &accessory.relativePositionOffset.z(), -500.0f, +500.0f, "Z: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(); + drawHelpMarker("+/-500.0 = +/-250 in-game"); + + ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); + ImGui::DragFloat("##RotX", &accessory.relativeRotation.x(), 1.0f, -FLT_MAX, +FLT_MAX, "Roll: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##RotY", &accessory.relativeRotation.y(), 1.0f, -FLT_MAX, +FLT_MAX, "Yaw: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::DragFloat("##RotZ", &accessory.relativeRotation.z(), 1.0f, -FLT_MAX, +FLT_MAX, "Pitch: %.3f"); + ImGui::PopItemWidth(); + + ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); + ImGui::SliderFloat("##RotOffsetX", &accessory.relativeRotationOffset.x(), -180.0f, +180.0f, "Roll: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::SliderFloat("##RotOffsetY", &accessory.relativeRotationOffset.y(), -180.0f, +180.0f, "Yaw: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::SliderFloat("##RotOffsetZ", &accessory.relativeRotationOffset.z(), -180.0f, +180.0f, "Pitch: %.3f"); + ImGui::PopItemWidth(); + + ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth()); + ImGui::SliderFloat("##ScaleX", &accessory.localScale.x(), -3.0f, +3.0f, "X: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::SliderFloat("##ScaleY", &accessory.localScale.y(), -3.0f, +3.0f, "Y: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::SliderFloat("##ScaleZ", &accessory.localScale.z(), -3.0f, +3.0f, "Z: %.3f"); + ImGui::PopItemWidth(); + ImGui::SameLine(); + drawHelpMarker("+/-3.0 = +/-150 in-game"); + ImGui::EndGroup(); +} + +auto SaveTool::getStyleName(Int id, Containers::ArrayView view) -> const char* { + if(id >= 0 && id <= 15) { + return view[id].name.c_str(); + } + else if(id >= 50 && id <= 65) { + return _currentMass->globalStyles()[id - 50].name.c_str(); + } + else { + return style_names.at(id); + } }