MassBuilderSaveTool/src/Application/Application_MassViewer_Armour.cpp

406 lines
16 KiB
C++

// 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();
}
}