Compare commits

..

No commits in common. "master" and "v1.5.3" have entirely different histories.

32 changed files with 996 additions and 277 deletions

4
.gitmodules vendored
View file

@ -22,6 +22,10 @@
path = third-party/libzip path = third-party/libzip
url = https://github.com/nih-at/libzip url = https://github.com/nih-at/libzip
branch = main branch = main
[submodule "efsw"]
path = third-party/efsw
url = https://github.com/SpartanJ/efsw
branch = master
[submodule "libcurl"] [submodule "libcurl"]
path = third-party/curl path = third-party/curl
url = https://github.com/curl/curl url = https://github.com/curl/curl

View file

@ -27,6 +27,7 @@ include(CMakeDependentOption)
cmake_dependent_option(SAVETOOL_USE_SYSTEM_CORRADE_MAGNUM "Use system-wide versions of Corrade and Magnum." ON "SAVETOOL_USE_SYSTEM_LIBS" OFF) cmake_dependent_option(SAVETOOL_USE_SYSTEM_CORRADE_MAGNUM "Use system-wide versions of Corrade and Magnum." ON "SAVETOOL_USE_SYSTEM_LIBS" OFF)
cmake_dependent_option(SAVETOOL_USE_SYSTEM_SDL2 "Use a system-wide version of SDL2." ON "SAVETOOL_USE_SYSTEM_LIBS" OFF) cmake_dependent_option(SAVETOOL_USE_SYSTEM_SDL2 "Use a system-wide version of SDL2." ON "SAVETOOL_USE_SYSTEM_LIBS" OFF)
cmake_dependent_option(SAVETOOL_USE_SYSTEM_LIBZIP "Use a system-wide version of libzip." ON "SAVETOOL_USE_SYSTEM_LIBS" OFF) cmake_dependent_option(SAVETOOL_USE_SYSTEM_LIBZIP "Use a system-wide version of libzip." ON "SAVETOOL_USE_SYSTEM_LIBS" OFF)
cmake_dependent_option(SAVETOOL_USE_SYSTEM_EFSW "Use a system-wide version of EFSW." ON "SAVETOOL_USE_SYSTEM_LIBS" OFF)
cmake_dependent_option(SAVETOOL_USE_SYSTEM_LIBCURL "Use a system-wide version of libcurl." ON "SAVETOOL_USE_SYSTEM_LIBS" OFF) cmake_dependent_option(SAVETOOL_USE_SYSTEM_LIBCURL "Use a system-wide version of libcurl." ON "SAVETOOL_USE_SYSTEM_LIBS" OFF)
if(NOT SAVETOOL_USE_SYSTEM_LIBS OR NOT (SAVETOOL_USE_SYSTEM_CORRADE_MAGNUM AND SAVETOOL_USE_SYSTEM_SDL2 AND SAVETOOL_USE_SYSTEM_LIBZIP AND SAVETOOL_USE_SYSTEM_EFSW AND SAVETOOL_USE_SYSTEM_LIBCURL)) if(NOT SAVETOOL_USE_SYSTEM_LIBS OR NOT (SAVETOOL_USE_SYSTEM_CORRADE_MAGNUM AND SAVETOOL_USE_SYSTEM_SDL2 AND SAVETOOL_USE_SYSTEM_LIBZIP AND SAVETOOL_USE_SYSTEM_EFSW AND SAVETOOL_USE_SYSTEM_LIBCURL))
@ -113,6 +114,13 @@ if(NOT SAVETOOL_USE_SYSTEM_LIBZIP)
add_subdirectory(third-party/libzip EXCLUDE_FROM_ALL) add_subdirectory(third-party/libzip EXCLUDE_FROM_ALL)
endif(NOT SAVETOOL_USE_SYSTEM_LIBZIP) endif(NOT SAVETOOL_USE_SYSTEM_LIBZIP)
if(NOT SAVETOOL_USE_SYSTEM_EFSW)
set(VERBOSE OFF CACHE BOOL "" FORCE)
set(BUILD_TEST_APP OFF CACHE BOOL "" FORCE)
set(EFSW_INSTALL OFF CACHE BOOL "" FORCE)
add_subdirectory(third-party/efsw EXCLUDE_FROM_ALL)
endif(NOT SAVETOOL_USE_SYSTEM_EFSW)
if(NOT SAVETOOL_USE_SYSTEM_LIBCURL) if(NOT SAVETOOL_USE_SYSTEM_LIBCURL)
set(BUILD_CURL_EXE OFF CACHE BOOL "" FORCE) set(BUILD_CURL_EXE OFF CACHE BOOL "" FORCE)
set(ENABLE_UNICODE ON CACHE BOOL "" FORCE) set(ENABLE_UNICODE ON CACHE BOOL "" FORCE)

View file

@ -92,6 +92,7 @@ Application::Application(const Arguments& arguments):
} }
_updateEventId = _initEventId + 1; _updateEventId = _initEventId + 1;
_fileEventId = _initEventId + 2;
LOG_INFO("Initialising the timer subsystem."); LOG_INFO("Initialising the timer subsystem.");
if(SDL_InitSubSystem(SDL_INIT_TIMER) != 0) { if(SDL_InitSubSystem(SDL_INIT_TIMER) != 0) {
@ -190,23 +191,23 @@ Application::keyReleaseEvent(KeyEvent& event) {
} }
void void
Application::pointerPressEvent(PointerEvent& event) { Application::mousePressEvent(MouseEvent& event) {
if(_imgui.handlePointerPressEvent(event)) return; if(_imgui.handleMousePressEvent(event)) return;
} }
void void
Application::pointerReleaseEvent(PointerEvent& event) { Application::mouseReleaseEvent(MouseEvent& event) {
if(_imgui.handlePointerReleaseEvent(event)) return; if(_imgui.handleMouseReleaseEvent(event)) return;
} }
void void
Application::pointerMoveEvent(PointerMoveEvent& event) { Application::mouseMoveEvent(MouseMoveEvent& event) {
if(_imgui.handlePointerMoveEvent(event)) return; if(_imgui.handleMouseMoveEvent(event)) return;
} }
void void
Application::scrollEvent(ScrollEvent& event) { Application::mouseScrollEvent(MouseScrollEvent& event) {
if(_imgui.handleScrollEvent(event)) { if(_imgui.handleMouseScrollEvent(event)) {
event.setAccepted(); event.setAccepted();
return; return;
} }
@ -225,6 +226,9 @@ Application::anyEvent(SDL_Event& event) {
else if(event.type == _updateEventId) { else if(event.type == _updateEventId) {
updateCheckEvent(event); updateCheckEvent(event);
} }
else if(event.type == _fileEventId) {
fileUpdateEvent(event);
}
} }
void void

View file

@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <mutex> #include <mutex>
#include <string>
#include <thread> #include <thread>
#include <Corrade/Containers/Pointer.h> #include <Corrade/Containers/Pointer.h>
@ -34,6 +35,8 @@
#include <imgui.h> #include <imgui.h>
#include <efsw/efsw.hpp>
#include "../Managers/BackupManager.h" #include "../Managers/BackupManager.h"
#include "../Managers/MassManager.h" #include "../Managers/MassManager.h"
#include "../Managers/ProfileManager.h" #include "../Managers/ProfileManager.h"
@ -51,11 +54,17 @@ using namespace Magnum;
namespace mbst { namespace mbst {
class Application: public Platform::Sdl2Application { class Application: public Platform::Sdl2Application, public efsw::FileWatchListener {
public: public:
explicit Application(const Arguments& arguments); explicit Application(const Arguments& arguments);
virtual ~Application(); ~Application() override;
void handleFileAction(efsw::WatchID watch_id,
const std::string& dir,
const std::string& filename,
efsw::Action action,
std::string old_filename) override;
private: private:
// Events // Events
@ -65,10 +74,10 @@ class Application: public Platform::Sdl2Application {
void keyPressEvent(KeyEvent& event) override; void keyPressEvent(KeyEvent& event) override;
void keyReleaseEvent(KeyEvent& event) override; void keyReleaseEvent(KeyEvent& event) override;
void pointerPressEvent(PointerEvent& event) override; void mousePressEvent(MouseEvent& event) override;
void pointerReleaseEvent(PointerEvent& event) override; void mouseReleaseEvent(MouseEvent& event) override;
void pointerMoveEvent(PointerMoveEvent& event) override; void mouseMoveEvent(MouseMoveEvent& event) override;
void scrollEvent(ScrollEvent& event) override; void mouseScrollEvent(MouseScrollEvent& event) override;
void textInputEvent(TextInputEvent& event) override; void textInputEvent(TextInputEvent& event) override;
void anyEvent(SDL_Event& event) override; void anyEvent(SDL_Event& event) override;
@ -81,11 +90,21 @@ class Application: public Platform::Sdl2Application {
void updateCheckEvent(SDL_Event& event); void updateCheckEvent(SDL_Event& event);
enum FileEventType: std::int32_t {
FileAdded = efsw::Action::Add,
FileDeleted = efsw::Action::Delete,
FileModified = efsw::Action::Modified,
FileMoved = efsw::Action::Moved,
StagedUpdate = 1 << 3
};
void fileUpdateEvent(SDL_Event& event);
// Initialisation methods // Initialisation methods
void initialiseConfiguration(); void initialiseConfiguration();
void initialiseGui(); void initialiseGui();
void initialiseManager(); void initialiseManager();
void initialiseMassManager(); void initialiseMassManager();
void initialiseFileWatcher();
// GUI-related methods // GUI-related methods
void drawImGui(); void drawImGui();
@ -121,9 +140,9 @@ class Application: public Platform::Sdl2Application {
void drawBLAttachment(); void drawBLAttachment();
void drawCustomArmourStyles(); void drawCustomArmourStyles();
void drawWeapons(); void drawWeapons();
void drawWeaponCategory(Containers::StringView name, GameObjects::Weapon::Type category, void drawWeaponCategory(Containers::StringView name, Containers::ArrayView<GameObjects::Weapon> weapons_view,
Containers::ArrayView<GameObjects::Weapon> weapons_view, bool& dirty, Containers::StringView payload_type,
Containers::StringView payload_type, Containers::StringView payload_tooltip); Containers::StringView payload_tooltip);
void drawWeaponEditor(GameObjects::Weapon& weapon); void drawWeaponEditor(GameObjects::Weapon& weapon);
void drawGlobalStyles(); void drawGlobalStyles();
void drawTuning(); void drawTuning();
@ -167,7 +186,6 @@ class Application: public Platform::Sdl2Application {
template<typename Functor, typename... Args> template<typename Functor, typename... Args>
bool drawUnsafeWidget(Functor func, Args... args) { bool drawUnsafeWidget(Functor func, Args... args) {
static_assert(std::is_invocable_r_v<bool, Functor, Args...>);
GameState game_state = _gameState; // Copying the value to reduce the risk of a data race. GameState game_state = _gameState; // Copying the value to reduce the risk of a data race.
ImGui::BeginDisabled(game_state != GameState::NotRunning); ImGui::BeginDisabled(game_state != GameState::NotRunning);
bool result = func(std::forward<Args>(args)...); bool result = func(std::forward<Args>(args)...);
@ -225,6 +243,7 @@ class Application: public Platform::Sdl2Application {
std::uint32_t _initEventId; std::uint32_t _initEventId;
std::uint32_t _updateEventId; std::uint32_t _updateEventId;
std::uint32_t _fileEventId;
Containers::String _lastError; Containers::String _lastError;
@ -246,12 +265,26 @@ class Application: public Platform::Sdl2Application {
GameObjects::Weapon* _currentWeapon = nullptr; GameObjects::Weapon* _currentWeapon = nullptr;
Containers::Pointer<efsw::FileWatcher> _fileWatcher;
enum watchID {
SaveDir = 0,
StagingDir = 1
};
Containers::StaticArray<2, efsw::WatchID> _watchIDs;
Containers::Optional<UpdateChecker> _checker{Containers::NullOpt}; Containers::Optional<UpdateChecker> _checker{Containers::NullOpt};
std::mutex _checkerMutex; std::mutex _checkerMutex;
bool _modifiedBySaveTool = false;
bool _jointsDirty = false; bool _jointsDirty = false;
bool _stylesDirty = false; bool _stylesDirty = false;
bool _eyeFlareDirty = false; bool _eyeFlareDirty = false;
bool _meleeDirty = false;
bool _shieldsDirty = false;
bool _bShootersDirty = false;
bool _eShootersDirty = false;
bool _bLaunchersDirty = false;
bool _eLaunchersDirty = false;
Containers::Optional<std::size_t> _selectedArmourSlot{Containers::NullOpt}; Containers::Optional<std::size_t> _selectedArmourSlot{Containers::NullOpt};
Containers::StaticArray<38, std::int32_t> _selectedArmourDecals{ValueInit}; Containers::StaticArray<38, std::int32_t> _selectedArmourDecals{ValueInit};

View file

@ -0,0 +1,158 @@
// 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 <cstring>
#include <Corrade/Utility/Format.h>
#include <Corrade/Utility/String.h>
#include <Corrade/Utility/Unicode.h>
#include <SDL_events.h>
#include <SDL_messagebox.h>
#include <fileapi.h>
#include <handleapi.h>
#include "Application.h"
namespace mbst {
void
Application::handleFileAction(efsw::WatchID watch_id, const std::string&, const std::string& filename,
efsw::Action action, std::string old_filename)
{
SDL_Event event;
SDL_zero(event);
event.type = _fileEventId;
event.user.data1 = Containers::String{Containers::AllocatedInit, filename.c_str()}.release();
if(watch_id == _watchIDs[StagingDir] && Utility::String::endsWith(filename, ".sav")) {
event.user.code = StagedUpdate | action;
SDL_PushEvent(&event);
return;
}
if(Utility::String::endsWith(filename, "Config.sav")) {
return;
} // TODO: actually do something when config files will finally be handled
if(!Utility::String::endsWith(filename, Utility::format("Profile{}.sav", _currentProfile->account()).data()) &&
filename.find("Unit") == std::string::npos)
{
return;
}
event.user.code = action;
if(action == efsw::Actions::Moved) {
event.user.data2 = Containers::String{Containers::AllocatedInit, old_filename.c_str()}.release();
}
SDL_PushEvent(&event);
}
void
Application::fileUpdateEvent(SDL_Event& event) {
Containers::String filename{static_cast<char*>(event.user.data1),
std::strlen(static_cast<char*>(event.user.data1)), nullptr};
if((event.user.code & StagedUpdate) == StagedUpdate) {
_stagedMassManager->refreshMass(filename);
return;
}
Containers::String old_filename;
std::int32_t index = 0;
std::int32_t old_index = 0;
bool is_current_profile = filename == _currentProfile->filename();
bool is_unit = filename.hasPrefix(_currentProfile->isDemo() ? "DemoUnit"_s : "Unit"_s);
if(is_unit) {
index = ((filename[_currentProfile->isDemo() ? 8 : 4] - 0x30) * 10) +
(filename[_currentProfile->isDemo() ? 9 : 5] - 0x30);
}
if(event.user.code == FileMoved) {
old_filename = Containers::String{static_cast<char*>(event.user.data2),
std::strlen(static_cast<char*>(event.user.data2)), nullptr};
old_index = ((old_filename[_currentProfile->isDemo() ? 8 : 4] - 0x30) * 10) +
(old_filename[_currentProfile->isDemo() ? 9 : 5] - 0x30);
}
switch(event.user.code) {
case FileAdded:
if(is_unit) {
if(!_currentMass || _currentMass != &(_massManager->hangar(index))) {
_massManager->refreshHangar(index);
}
else {
_currentMass->setDirty();
}
}
break;
case FileDeleted:
if(is_current_profile) {
_currentProfile = nullptr;
_uiState = UiState::ProfileManager;
if(!_profileManager->refreshProfiles()) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error",
_profileManager->lastError().data(), window());
exit(EXIT_FAILURE);
}
}
else if(is_unit) {
if(!_currentMass || _currentMass != &(_massManager->hangar(index))) {
_massManager->refreshHangar(index);
}
}
break;
case FileModified:
if(is_current_profile) {
_currentProfile->refreshValues();
}
else if(is_unit) {
if(!_currentMass || _currentMass != &(_massManager->hangar(index))) {
_massManager->refreshHangar(index);
}
else {
if(_modifiedBySaveTool && _currentMass->filename() == filename) {
auto handle = CreateFileW(Utility::Unicode::widen(Containers::StringView{filename}),
GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);
if(handle && handle != INVALID_HANDLE_VALUE) {
CloseHandle(handle);
_modifiedBySaveTool = false;
}
}
else {
_currentMass->setDirty();
}
}
}
break;
case FileMoved:
if(is_unit) {
if(old_filename.hasSuffix(".sav"_s)) {
_massManager->refreshHangar(index);
_massManager->refreshHangar(old_index);
}
}
break;
default:
_queue.addToast(Toast::Type::Warning, "Unknown file action type"_s);
}
}
}

View file

@ -16,8 +16,6 @@
#include <cstring> #include <cstring>
#include <Magnum/Math/Time.h>
#include <SDL_events.h> #include <SDL_events.h>
#include <SDL_messagebox.h> #include <SDL_messagebox.h>
@ -54,8 +52,7 @@ Application::initialiseConfiguration() {
LOG_INFO("Reading configuration file."); LOG_INFO("Reading configuration file.");
setSwapInterval(conf().swapInterval()); setSwapInterval(conf().swapInterval());
using namespace Math::Literals; setMinimalLoopPeriod(0);
setMinimalLoopPeriod(0_nsec);
} }
void void
@ -122,6 +119,8 @@ Application::initialiseManager() {
_backupManager.emplace(); _backupManager.emplace();
_stagedMassManager.emplace();
event.user.code = InitSuccess; event.user.code = InitSuccess;
SDL_PushEvent(&event); SDL_PushEvent(&event);
} }
@ -130,8 +129,15 @@ void
Application::initialiseMassManager() { Application::initialiseMassManager() {
LOG_INFO("Initialising the M.A.S.S. manager."); LOG_INFO("Initialising the M.A.S.S. manager.");
_massManager.emplace(_currentProfile->account(), _currentProfile->isDemo()); _massManager.emplace(_currentProfile->account(), _currentProfile->isDemo());
LOG_INFO("Initialising the staged M.A.S.S. manager."); }
_stagedMassManager.emplace();
void
Application::initialiseFileWatcher() {
LOG_INFO("Initialising the file watcher.");
_fileWatcher.emplace();
_watchIDs[SaveDir] = _fileWatcher->addWatch(conf().directories().gameSaves, this, false);
_watchIDs[StagingDir] = _fileWatcher->addWatch(conf().directories().staging, this, false);
_fileWatcher->watch();
} }
} }

View file

@ -55,20 +55,9 @@ Application::drawManager() {
if(ImGui::Button(ICON_FA_ARROW_LEFT " Back to profile manager")) { if(ImGui::Button(ICON_FA_ARROW_LEFT " Back to profile manager")) {
_currentProfile = nullptr; _currentProfile = nullptr;
_massManager.reset(); _massManager.reset();
_stagedMassManager.reset(); _fileWatcher.reset();
_uiState = UiState::ProfileManager; _uiState = UiState::ProfileManager;
} }
ImGui::SameLine();
if(ImGui::Button(ICON_FA_SYNC_ALT " Refresh external changes")) {
_currentProfile->refreshValues();
if(!_currentProfile->valid()) {
_currentProfile = nullptr;
_profileManager->refreshProfiles();
_queue.addToast(Toast::Type::Error, "The current profile isn't valid anymore."_s);
ImGui::End();
return;
}
}
if(ImGui::BeginChild("##ProfileInfo", if(ImGui::BeginChild("##ProfileInfo",
{ImGui::GetContentRegionAvail().x * 0.60f, 0.0f}, {ImGui::GetContentRegionAvail().x * 0.60f, 0.0f},
@ -104,11 +93,6 @@ Application::drawManager() {
if(ImGui::BeginMenuBar()) { if(ImGui::BeginMenuBar()) {
ImGui::TextUnformatted("M.A.S.S. management"); ImGui::TextUnformatted("M.A.S.S. management");
drawHelpMarker("To move, import, or export builds, drag-and-drop them."); drawHelpMarker("To move, import, or export builds, drag-and-drop them.");
if(ImGui::SmallButton(ICON_FA_SYNC_ALT " Refresh")) {
for(int i = 0; i < 32 ; i++) {
_massManager->refreshHangar(i);
}
}
ImGui::EndMenuBar(); ImGui::EndMenuBar();
} }
@ -543,13 +527,10 @@ Application::drawMassManager() {
ImGui::TableSetColumnIndex(0); ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Staging area"); ImGui::TextUnformatted("Staging area");
ImGui::SameLine();
if(ImGui::SmallButton(ICON_FA_FOLDER_OPEN " Open staging folder")) { if(ImGui::SmallButton(ICON_FA_FOLDER_OPEN " Open staging folder")) {
openUri(Utility::Path::toNativeSeparators(conf().directories().staging)); openUri(Utility::Path::toNativeSeparators(conf().directories().staging));
} }
ImGui::SameLine();
if(ImGui::SmallButton(ICON_FA_SYNC_ALT " Refresh")) {
_stagedMassManager->refresh();
}
for(const auto& mass : _stagedMassManager->stagedMasses()) { for(const auto& mass : _stagedMassManager->stagedMasses()) {
ImGui::TableNextRow(); ImGui::TableNextRow();
@ -594,7 +575,6 @@ Application::drawMassManager() {
if(!_massManager->exportMass(index)) { if(!_massManager->exportMass(index)) {
_queue.addToast(Toast::Type::Error, _massManager->lastError()); _queue.addToast(Toast::Type::Error, _massManager->lastError());
} }
_stagedMassManager->refresh();
} }
ImGui::EndDragDropTarget(); ImGui::EndDragDropTarget();

View file

@ -28,6 +28,7 @@
#include "../GameData/Accessories.h" #include "../GameData/Accessories.h"
#define STYLENAMES_DEFINITION #define STYLENAMES_DEFINITION
#include "../GameData/StyleNames.h" #include "../GameData/StyleNames.h"
#include "../ImportExport/Export.h"
#include "Application.h" #include "Application.h"
@ -72,13 +73,17 @@ Application::drawMassViewer() {
drawTooltip(_currentMass->filename()); drawTooltip(_currentMass->filename());
ImGui::TableSetColumnIndex(2); ImGui::TableSetColumnIndex(2);
if(ImGui::SmallButton(ICON_FA_SYNC_ALT " Refresh external changes")) { if(_currentMass->dirty()) {
ImGui::TextUnformatted("External changes detected");
ImGui::SameLine();
if(ImGui::SmallButton(ICON_FA_SYNC_ALT " Refresh")) {
_currentMass->refreshValues(); _currentMass->refreshValues();
_currentMass->setDirty(false); _currentMass->setDirty(false);
_jointsDirty = false; _jointsDirty = false;
_stylesDirty = false; _stylesDirty = false;
_eyeFlareDirty = false; _eyeFlareDirty = false;
} }
}
ImGui::TableSetColumnIndex(3); ImGui::TableSetColumnIndex(3);
if(ImGui::SmallButton(ICON_FA_TIMES)) { if(ImGui::SmallButton(ICON_FA_TIMES)) {
@ -186,7 +191,9 @@ Application::drawGlobalStyles() {
_currentMass->getGlobalStyles(); _currentMass->getGlobalStyles();
break; break;
case DCS_Save: case DCS_Save:
_modifiedBySaveTool = true;
if(!_currentMass->writeGlobalStyle(i)) { if(!_currentMass->writeGlobalStyle(i)) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError()); _queue.addToast(Toast::Type::Error, _currentMass->lastError());
} }
break; break;

View file

@ -14,8 +14,6 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <string>
#include <imgui_internal.h> #include <imgui_internal.h>
#include "../FontAwesome/IconsFontAwesome5.h" #include "../FontAwesome/IconsFontAwesome5.h"
@ -199,7 +197,9 @@ Application::drawArmour() {
ImGui::EndChild(); ImGui::EndChild();
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) {
_modifiedBySaveTool = true;
if(!_currentMass->writeArmourPart(part.slot)) { if(!_currentMass->writeArmourPart(part.slot)) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError()); _queue.addToast(Toast::Type::Error, _currentMass->lastError());
} }
} }
@ -357,8 +357,11 @@ Application::drawBLAttachment() {
ImGui::EndGroup(); ImGui::EndGroup();
} }
_modifiedBySaveTool = true;
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) {
_modifiedBySaveTool = true;
if(!_currentMass->writeBulletLauncherAttachments()) { if(!_currentMass->writeBulletLauncherAttachments()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError()); _queue.addToast(Toast::Type::Error, _currentMass->lastError());
} }
} }
@ -385,7 +388,9 @@ Application::drawCustomArmourStyles() {
_currentMass->getArmourCustomStyles(); _currentMass->getArmourCustomStyles();
break; break;
case DCS_Save: case DCS_Save:
_modifiedBySaveTool = true;
if(!_currentMass->writeArmourCustomStyle(i)) { if(!_currentMass->writeArmourCustomStyle(i)) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError()); _queue.addToast(Toast::Type::Error, _currentMass->lastError());
} }
break; break;

View file

@ -149,7 +149,9 @@ Application::drawJointSliders() {
} }
else { else {
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) {
_modifiedBySaveTool = true;
if(!_currentMass->writeJointSliders()) { if(!_currentMass->writeJointSliders()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError()); _queue.addToast(Toast::Type::Error, _currentMass->lastError());
} }
_jointsDirty = false; _jointsDirty = false;
@ -204,7 +206,9 @@ Application::drawFrameStyles() {
} }
else { else {
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) {
_modifiedBySaveTool = true;
if(!_currentMass->writeFrameStyles()) { if(!_currentMass->writeFrameStyles()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError()); _queue.addToast(Toast::Type::Error, _currentMass->lastError());
} }
_stylesDirty = false; _stylesDirty = false;
@ -236,7 +240,9 @@ Application::drawEyeColourPicker() {
} }
else { else {
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) { if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) {
_modifiedBySaveTool = true;
if(!_currentMass->writeEyeFlareColour()) { if(!_currentMass->writeEyeFlareColour()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError()); _queue.addToast(Toast::Type::Error, _currentMass->lastError());
} }
_eyeFlareDirty = false; _eyeFlareDirty = false;
@ -270,7 +276,9 @@ Application::drawCustomFrameStyles() {
_currentMass->getFrameCustomStyles(); _currentMass->getFrameCustomStyles();
break; break;
case DCS_Save: case DCS_Save:
_modifiedBySaveTool = true;
if(!_currentMass->writeFrameCustomStyle(i)) { if(!_currentMass->writeFrameCustomStyle(i)) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError()); _queue.addToast(Toast::Type::Error, _currentMass->lastError());
} }
break; break;

View file

@ -22,7 +22,6 @@
#include "../GameData/WeaponParts.h" #include "../GameData/WeaponParts.h"
#include "Application.h" #include "Application.h"
#include "../Configuration/Configuration.h"
namespace mbst { namespace mbst {
@ -33,11 +32,13 @@ Application::drawWeapons() {
return; return;
} }
const float footer_height_to_reserve = ImGui::GetFrameHeightWithSpacing();
ImGui::BeginGroup(); ImGui::BeginGroup();
if(!ImGui::BeginTable("##WeaponsList", 1, if(!ImGui::BeginTable("##WeaponsList", 1,
ImGuiTableFlags_ScrollY|ImGuiTableFlags_BordersOuter|ImGuiTableFlags_BordersInnerH, ImGuiTableFlags_ScrollY|ImGuiTableFlags_BordersOuter|ImGuiTableFlags_BordersInnerH,
{ImGui::GetContentRegionAvail().x * 0.2f, 0.0f})) {ImGui::GetContentRegionAvail().x * 0.2f, -footer_height_to_reserve}))
{ {
ImGui::EndGroup(); ImGui::EndGroup();
return; return;
@ -45,20 +46,118 @@ Application::drawWeapons() {
ImGui::TableSetupColumn("Weapon"); ImGui::TableSetupColumn("Weapon");
drawWeaponCategory("Melee weapons", GameObjects::Weapon::Type::Melee, _currentMass->meleeWeapons(), "MeleeWeapon", drawWeaponCategory("Melee weapons", _currentMass->meleeWeapons(), _meleeDirty, "MeleeWeapon", "Melee weapon");
"Melee weapon"); drawWeaponCategory("Shield", _currentMass->shields(), _shieldsDirty, "Shield", "Shield");
drawWeaponCategory("Shield", GameObjects::Weapon::Type::Shield, _currentMass->shields(), "Shield", "Shield"); drawWeaponCategory("Bullet shooters", _currentMass->bulletShooters(), _bShootersDirty, "BShooter", "Bullet shooter");
drawWeaponCategory("Bullet shooters", GameObjects::Weapon::Type::BulletShooter, _currentMass->bulletShooters(), drawWeaponCategory("Energy shooters", _currentMass->energyShooters(), _eShootersDirty, "EShooter", "Energy shooter");
"BShooter", "Bullet shooter"); drawWeaponCategory("Bullet launchers", _currentMass->bulletLaunchers(), _bLaunchersDirty, "BLauncher", "Bullet launcher");
drawWeaponCategory("Energy shooters", GameObjects::Weapon::Type::EnergyShooter, _currentMass->energyShooters(), drawWeaponCategory("Energy launchers", _currentMass->energyLaunchers(), _eLaunchersDirty, "ELauncher", "Energy launcher");
"EShooter", "Energy shooter");
drawWeaponCategory("Bullet launchers", GameObjects::Weapon::Type::BulletLauncher, _currentMass->bulletLaunchers(),
"BLauncher", "Bullet launcher");
drawWeaponCategory("Energy launchers", GameObjects::Weapon::Type::EnergyLauncher, _currentMass->energyLaunchers(),
"ELauncher", "Energy launcher");
ImGui::EndTable(); ImGui::EndTable();
bool dirty = _meleeDirty || _shieldsDirty || _bShootersDirty || _eShootersDirty || _bLaunchersDirty || _eLaunchersDirty;
ImGui::BeginDisabled(!dirty);
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save order"); })) {
if(_meleeDirty) {
_modifiedBySaveTool = true;
if(!_currentMass->writeMeleeWeapons()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
else {
_meleeDirty = false;
}
}
if(_shieldsDirty) {
_modifiedBySaveTool = true;
if(!_currentMass->writeShields()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
else {
_shieldsDirty = false;
}
}
if(_bShootersDirty) {
_modifiedBySaveTool = true;
if(!_currentMass->writeBulletShooters()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
else {
_bShootersDirty = false;
}
}
if(_eShootersDirty) {
_modifiedBySaveTool = true;
if(_currentMass->writeEnergyShooters()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
else {
_eShootersDirty = false;
}
}
if(_bLaunchersDirty) {
_modifiedBySaveTool = true;
if(_currentMass->writeBulletLaunchers()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
else {
_bLaunchersDirty = false;
}
}
if(_eLaunchersDirty) {
_modifiedBySaveTool = true;
if(_currentMass->writeEnergyLaunchers()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
else {
_eLaunchersDirty = false;
}
}
}
ImGui::SameLine();
if(ImGui::Button(ICON_FA_UNDO_ALT " Reset")) {
if(_meleeDirty) {
_currentMass->getMeleeWeapons();
_meleeDirty = false;
}
if(_shieldsDirty) {
_currentMass->getShields();
_shieldsDirty = false;
}
if(_bShootersDirty) {
_currentMass->getBulletShooters();
_bShootersDirty = false;
}
if(_eShootersDirty) {
_currentMass->getEnergyShooters();
_eShootersDirty = false;
}
if(_bLaunchersDirty) {
_currentMass->getBulletLaunchers();
_bLaunchersDirty = false;
}
if(_eLaunchersDirty) {
_currentMass->getEnergyLaunchers();
_eLaunchersDirty = false;
}
}
ImGui::EndDisabled();
ImGui::EndGroup(); ImGui::EndGroup();
ImGui::SameLine(); ImGui::SameLine();
@ -70,7 +169,7 @@ Application::drawWeapons() {
ImGui::BeginGroup(); ImGui::BeginGroup();
if(!ImGui::BeginChild("##WeaponChild")) { if(!ImGui::BeginChild("##WeaponChild", {0.0f, -footer_height_to_reserve})) {
ImGui::EndChild(); ImGui::EndChild();
return; return;
} }
@ -79,61 +178,55 @@ Application::drawWeapons() {
ImGui::EndChild(); ImGui::EndChild();
ImGui::EndGroup(); if(drawUnsafeWidget([](){ return ImGui::Button(ICON_FA_SAVE " Save changes to weapon category"); })) {
} _modifiedBySaveTool = true;
switch(_currentWeapon->type) {
void
Application::drawWeaponCategory(Containers::StringView name, GameObjects::Weapon::Type category,
Containers::ArrayView<GameObjects::Weapon> weapons_view,
Containers::StringView payload_type, Containers::StringView payload_tooltip)
{
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
ImGui::TableNextColumn();
ImGui::TextUnformatted(name.cbegin(), name.cend());
ImGui::PushID(name.cbegin());
if(drawUnsafeWidget(ImGui::SmallButton, ICON_FA_SAVE " Save")) {
switch(category) {
case GameObjects::Weapon::Type::Melee: case GameObjects::Weapon::Type::Melee:
if(!_currentMass->writeMeleeWeapons()) { if(!_currentMass->writeMeleeWeapons()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError()); _queue.addToast(Toast::Type::Error, _currentMass->lastError());
} }
break; break;
case GameObjects::Weapon::Type::Shield: case GameObjects::Weapon::Type::Shield:
if(!_currentMass->writeShields()) { if(!_currentMass->writeShields()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError()); _queue.addToast(Toast::Type::Error, _currentMass->lastError());
} }
break; break;
case GameObjects::Weapon::Type::BulletShooter: case GameObjects::Weapon::Type::BulletShooter:
if(!_currentMass->writeBulletShooters()) { if(!_currentMass->writeBulletShooters()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError()); _queue.addToast(Toast::Type::Error, _currentMass->lastError());
} }
break; break;
case GameObjects::Weapon::Type::EnergyShooter: case GameObjects::Weapon::Type::EnergyShooter:
if(!_currentMass->writeEnergyShooters()) { if(!_currentMass->writeEnergyShooters()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError()); _queue.addToast(Toast::Type::Error, _currentMass->lastError());
} }
break; break;
case GameObjects::Weapon::Type::BulletLauncher: case GameObjects::Weapon::Type::BulletLauncher:
if(!_currentMass->writeBulletLaunchers()) { if(!_currentMass->writeBulletLaunchers()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError()); _queue.addToast(Toast::Type::Error, _currentMass->lastError());
} }
break; break;
case GameObjects::Weapon::Type::EnergyLauncher: case GameObjects::Weapon::Type::EnergyLauncher:
if(!_currentMass->writeEnergyLaunchers()) { if(!_currentMass->writeEnergyLaunchers()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError()); _queue.addToast(Toast::Type::Error, _currentMass->lastError());
} }
break; break;
default: default:
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, "Unknown weapon type"); _queue.addToast(Toast::Type::Error, "Unknown weapon type");
} }
} }
ImGui::SameLine(); ImGui::SameLine();
if(ImGui::SmallButton(ICON_FA_UNDO_ALT " Reset")) { if(ImGui::Button(ICON_FA_UNDO_ALT " Reset weapon category")) {
switch(category) { switch(_currentWeapon->type) {
case GameObjects::Weapon::Type::Melee: case GameObjects::Weapon::Type::Melee:
_currentMass->getMeleeWeapons(); _currentMass->getMeleeWeapons();
break; break;
@ -157,6 +250,19 @@ Application::drawWeaponCategory(Containers::StringView name, GameObjects::Weapon
} }
} }
ImGui::EndGroup();
}
void
Application::drawWeaponCategory(Containers::StringView name, Containers::ArrayView<GameObjects::Weapon> weapons_view, bool& dirty,
Containers::StringView payload_type, Containers::StringView payload_tooltip)
{
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
ImGui::TableNextColumn();
ImGui::TextUnformatted(name.cbegin(), name.cend());
ImGui::PushID(payload_type.data());
for(std::uint32_t i = 0; i < weapons_view.size(); i++) { for(std::uint32_t i = 0; i < weapons_view.size(); i++) {
auto& weapon = weapons_view[i]; auto& weapon = weapons_view[i];
@ -195,6 +301,7 @@ Application::drawWeaponCategory(Containers::StringView name, GameObjects::Weapon
else { else {
weapons_view[i] = weapons_view[index]; weapons_view[i] = weapons_view[index];
} }
dirty = true;
} }
ImGui::EndDragDropTarget(); ImGui::EndDragDropTarget();
@ -216,7 +323,7 @@ Application::drawWeaponEditor(GameObjects::Weapon& weapon) {
return; return;
} }
static constexpr Containers::StringView labels[] { static Containers::StringView labels[] {
#define c(enumerator, strenum, name) name, #define c(enumerator, strenum, name) name,
#include "../Maps/WeaponTypes.hpp" #include "../Maps/WeaponTypes.hpp"
#undef c #undef c
@ -238,35 +345,9 @@ Application::drawWeaponEditor(GameObjects::Weapon& weapon) {
weapon.name = name_buf.data(); weapon.name = name_buf.data();
} }
if(weapon.type == GameObjects::Weapon::Type::BulletShooter) {
ImGui::Button("Import energy shooter by dragging it here");
if(ImGui::BeginDragDropTarget()) {
if(const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("EShooter")) {
std::uint32_t index = *static_cast<std::uint32_t*>(payload->Data);
weapon = _currentMass->energyShooters()[index];
}
ImGui::EndDragDropTarget();
}
}
else if(weapon.type == GameObjects::Weapon::Type::EnergyShooter) {
ImGui::Button("Import bullet shooter by dragging it here");
if(ImGui::BeginDragDropTarget()) {
if(const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("BShooter")) {
std::uint32_t index = *static_cast<std::uint32_t*>(payload->Data);
weapon = _currentMass->bulletShooters()[index];
}
ImGui::EndDragDropTarget();
}
}
ImGui::BeginGroup(); ImGui::BeginGroup();
drawAlignedText("Equipped:"); drawAlignedText("Equipped:");
if(conf().advancedMode()) {
drawAlignedText("Weapon type:");
drawAlignedText("");
}
if(weapon.type != GameObjects::Weapon::Type::Shield) { if(weapon.type != GameObjects::Weapon::Type::Shield) {
drawAlignedText("Damage type:"); drawAlignedText("Damage type:");
} }
@ -285,47 +366,6 @@ Application::drawWeaponEditor(GameObjects::Weapon& weapon) {
ImGui::BeginGroup(); ImGui::BeginGroup();
ImGui::Checkbox("##EquippedCheckbox", &weapon.attached); ImGui::Checkbox("##EquippedCheckbox", &weapon.attached);
if(conf().advancedMode()) {
if(ImGui::RadioButton("Melee##MeleeType", weapon.type == GameObjects::Weapon::Type::Melee)) {
weapon.type = GameObjects::Weapon::Type::Melee;
if(weapon.damageType == GameObjects::Weapon::DamageType::Piercing || weapon.damageType == GameObjects::Weapon::DamageType::Plasma) {
weapon.damageType = GameObjects::Weapon::DamageType::Physical;
}
}
ImGui::SameLine();
if(ImGui::RadioButton("Shield##ShieldType", weapon.type == GameObjects::Weapon::Type::Shield)) {
weapon.type = GameObjects::Weapon::Type::Shield;
}
ImGui::SameLine();
if(ImGui::RadioButton("Bullet shooter##BShooterType", weapon.type == GameObjects::Weapon::Type::BulletShooter)) {
weapon.type = GameObjects::Weapon::Type::BulletShooter;
if(weapon.damageType == GameObjects::Weapon::DamageType::Physical || weapon.damageType == GameObjects::Weapon::DamageType::Plasma) {
weapon.damageType = GameObjects::Weapon::DamageType::Piercing;
}
}
if(ImGui::RadioButton("Energy shooter##EShooterType", weapon.type == GameObjects::Weapon::Type::EnergyShooter)) {
weapon.type = GameObjects::Weapon::Type::EnergyShooter;
if(weapon.damageType == GameObjects::Weapon::DamageType::Physical || weapon.damageType == GameObjects::Weapon::DamageType::Piercing) {
weapon.damageType = GameObjects::Weapon::DamageType::Plasma;
}
}
ImGui::SameLine();
if(ImGui::RadioButton("Bullet launcher##BLauncherType", weapon.type == GameObjects::Weapon::Type::BulletLauncher)) {
weapon.type = GameObjects::Weapon::Type::BulletLauncher;
if(weapon.damageType == GameObjects::Weapon::DamageType::Physical || weapon.damageType == GameObjects::Weapon::DamageType::Plasma) {
weapon.damageType = GameObjects::Weapon::DamageType::Piercing;
}
}
ImGui::SameLine();
if(ImGui::RadioButton("Energy launcher##ELauncherType", weapon.type == GameObjects::Weapon::Type::EnergyLauncher)) {
weapon.type = GameObjects::Weapon::Type::EnergyLauncher;
if(weapon.damageType == GameObjects::Weapon::DamageType::Physical || weapon.damageType == GameObjects::Weapon::DamageType::Piercing) {
weapon.damageType = GameObjects::Weapon::DamageType::Plasma;
}
}
}
if(weapon.type != GameObjects::Weapon::Type::Shield) { if(weapon.type != GameObjects::Weapon::Type::Shield) {
if(weapon.type == GameObjects::Weapon::Type::Melee && if(weapon.type == GameObjects::Weapon::Type::Melee &&
ImGui::RadioButton("Physical##NoElement", weapon.damageType == GameObjects::Weapon::DamageType::Physical)) ImGui::RadioButton("Physical##NoElement", weapon.damageType == GameObjects::Weapon::DamageType::Physical))
@ -385,7 +425,7 @@ Application::drawWeaponEditor(GameObjects::Weapon& weapon) {
ImGui::Separator(); ImGui::Separator();
if(weapon.parts.isEmpty()) { if(weapon.parts.isEmpty()) {
ImGui::TextUnformatted("This weapon has no parts. This is not normal."); ImGui::TextUnformatted("This weapon has no parts.");
return; return;
} }
@ -427,8 +467,8 @@ Application::drawWeaponEditor(GameObjects::Weapon& weapon) {
if(map->find(part.id) != map->cend()) { if(map->find(part.id) != map->cend()) {
ImGui::TextUnformatted(map->at(part.id).cbegin(), map->at(part.id).cend()); ImGui::TextUnformatted(map->at(part.id).cbegin(), map->at(part.id).cend());
} }
else if(part.id < 0) { else if(part.id == -1) {
ImGui::TextUnformatted("<invalid part ID>"); ImGui::TextUnformatted("<none>");
} }
else{ else{
ImGui::Text("ID: %i", part.id); ImGui::Text("ID: %i", part.id);
@ -456,17 +496,20 @@ Application::drawWeaponEditor(GameObjects::Weapon& weapon) {
} }
} }
if(weapon.type == GameObjects::Weapon::Type::Shield ||
(weapon.type == GameObjects::Weapon::Type::BulletLauncher && _selectedWeaponPart != 0))
{
ImGui::SameLine(); ImGui::SameLine();
if(ImGui::SmallButton("Hide part " ICON_FA_QUESTION_CIRCLE)) { if(ImGui::SmallButton("Unequip")) {
part.id = 96 + part.id >= 0 ? (part.id / 100) * 100 : 0; part.id = -1;
}
if(weapon.type == GameObjects::Weapon::Type::Shield && _selectedWeaponPart == 0) {
drawTooltip("This will make the whole shield and its accessories invisible.");
}
else {
drawTooltip("This will make accessories invisible as well.");
}
} }
drawTooltip(_selectedWeaponPart == 0 ?
"This will hide the selected part, but not its accessories. "
"Parts attached to this one will also be hidden, though their accessories WON'T be visible. "
"For parts that affect weapon stance/functionality, this feature will try to preserve that." :
"This will hide the selected part, but not its accessories. "
"For parts that affect weapon stance/functionality, this feature will try to preserve that.",
static_cast<float>(windowSize().x()) * 0.4f);
if(ImGui::BeginChild("##PartDetails", {}, ImGuiChildFlags_Border)) { if(ImGui::BeginChild("##PartDetails", {}, ImGuiChildFlags_Border)) {
ImGui::TextUnformatted("Styles:"); ImGui::TextUnformatted("Styles:");

View file

@ -91,6 +91,7 @@ Application::drawProfileManager() {
{ {
_currentProfile = _profileManager->getProfile(i); _currentProfile = _profileManager->getProfile(i);
initialiseMassManager(); initialiseMassManager();
initialiseFileWatcher();
_uiState = UiState::MainManager; _uiState = UiState::MainManager;
} }

View file

@ -101,6 +101,8 @@ Application::drawAbout() {
if(ImGui::CollapsingHeader("Third-party components")) { if(ImGui::CollapsingHeader("Third-party components")) {
ImGui::TextWrapped("This application uses the following third-party components:"); ImGui::TextWrapped("This application uses the following third-party components:");
ImGui::PushStyleVar(ImGuiStyleVar_IndentSpacing, 0.0f);
if(ImGui::TreeNodeEx("Corrade", ImGuiTreeNodeFlags_SpanAvailWidth)) { if(ImGui::TreeNodeEx("Corrade", ImGuiTreeNodeFlags_SpanAvailWidth)) {
ImGui::Text("Version used: %s", CORRADE_VERSION_STRING); ImGui::Text("Version used: %s", CORRADE_VERSION_STRING);
auto corrade_website = "https://magnum.graphics/corrade"; auto corrade_website = "https://magnum.graphics/corrade";
@ -193,6 +195,23 @@ Application::drawAbout() {
ImGui::TreePop(); ImGui::TreePop();
} }
if(ImGui::TreeNodeEx("Entropia File System Watcher (efsw)", ImGuiTreeNodeFlags_SpanAvailWidth)) {
auto efsw_repo = "https://github.com/SpartanJ/efsw";
drawAlignedText(ICON_FA_GITHUB " %s", efsw_repo);
ImGui::SameLine();
if(ImGui::Button("Copy to clipboard")) {
ImGui::SetClipboardText(efsw_repo);
}
ImGui::SameLine();
if(ImGui::Button("Open in browser")) {
openUri(efsw_repo);
}
ImGui::TextUnformatted("Licence: MIT");
ImGui::TreePop();
}
if(ImGui::TreeNodeEx("libcurl", ImGuiTreeNodeFlags_SpanAvailWidth)) { if(ImGui::TreeNodeEx("libcurl", ImGuiTreeNodeFlags_SpanAvailWidth)) {
ImGui::Text("Version used: %s", LIBCURL_VERSION); ImGui::Text("Version used: %s", LIBCURL_VERSION);
auto curl_website = "https://curl.se/libcurl"; auto curl_website = "https://curl.se/libcurl";
@ -245,6 +264,8 @@ Application::drawAbout() {
ImGui::TreePop(); ImGui::TreePop();
} }
ImGui::PopStyleVar();
} }
ImGui::EndPopup(); ImGui::EndPopup();

View file

@ -16,8 +16,6 @@
#include <Corrade/Utility/Path.h> #include <Corrade/Utility/Path.h>
#include <Magnum/Math/Time.h>
#include "../Configuration/Configuration.h" #include "../Configuration/Configuration.h"
#include "../FontAwesome/IconsFontAwesome5.h" #include "../FontAwesome/IconsFontAwesome5.h"
#include "../FontAwesome/IconsFontAwesome5Brands.h" #include "../FontAwesome/IconsFontAwesome5Brands.h"
@ -68,6 +66,28 @@ Application::drawMainMenu() {
openUri(Utility::Path::toNativeSeparators(conf().directories().staging)); openUri(Utility::Path::toNativeSeparators(conf().directories().staging));
} }
/*if(ImGui::BeginMenu(ICON_FA_BOXES " Armoury")) {
if(ImGui::MenuItem(ICON_FA_SHIELD_ALT " Armour parts", nullptr, false,
Utility::Path::exists(conf().directories().armours)))
{
openUri(Utility::Path::toNativeSeparators(conf().directories().armours));
}
if(ImGui::MenuItem(ICON_FA_HAMMER " Weapons", nullptr, false,
Utility::Path::exists(conf().directories().weapons)))
{
openUri(Utility::Path::toNativeSeparators(conf().directories().weapons));
}
if(ImGui::MenuItem(ICON_FA_PALETTE " Custom styles", nullptr, false,
Utility::Path::exists(conf().directories().styles)))
{
openUri(Utility::Path::toNativeSeparators(conf().directories().styles));
}
ImGui::EndMenu();
}*/
ImGui::EndMenu(); ImGui::EndMenu();
} }
@ -100,8 +120,7 @@ Application::drawMainMenu() {
conf().setSwapInterval(i); conf().setSwapInterval(i);
setSwapInterval(i); setSwapInterval(i);
if(i == 0) { if(i == 0) {
using namespace Math::Literals; setMinimalLoopPeriod(0);
setMinimalLoopPeriod(0_nsec);
} }
} }
} }
@ -130,13 +149,12 @@ Application::drawMainMenu() {
drawHelpMarker("This gives access to save edition features that can be considered cheats.", drawHelpMarker("This gives access to save edition features that can be considered cheats.",
float(windowSize().x()) * 0.4f); float(windowSize().x()) * 0.4f);
if(drawCheckbox("Advanced fuckery mode", conf().advancedMode())) { if(drawCheckbox("Advanced mode", conf().advancedMode())) {
conf().setAdvancedMode(!conf().advancedMode()); conf().setAdvancedMode(!conf().advancedMode());
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
drawHelpMarker("This gives access to editing values that have unknown purposes or are undocumented. " drawHelpMarker("This gives access to editing values that have unknown purposes or are undocumented.",
"It also grants access to some special editing features allowing things the game is NOT designed for.",
float(windowSize().x()) * 0.4f); float(windowSize().x()) * 0.4f);
if(drawCheckbox("Check for updates on startup", conf().checkUpdatesOnStartup())) { if(drawCheckbox("Check for updates on startup", conf().checkUpdatesOnStartup())) {
@ -205,7 +223,7 @@ Application::drawMainMenu() {
ImGui::EndMenu(); ImGui::EndMenu();
} }
//ImGui::EndDisabled(); ImGui::EndDisabled();
//if(conf().isRunningInWine() && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { //if(conf().isRunningInWine() && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
// ImGui::SetTooltip("Not available when running in Wine."); // ImGui::SetTooltip("Not available when running in Wine.");
//} //}

View file

@ -18,7 +18,7 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_CXX_EXTENSIONS OFF)
set(SAVETOOL_PROJECT_VERSION 1.6.1) set(SAVETOOL_PROJECT_VERSION 1.5.3)
find_package(Corrade REQUIRED Containers Utility) find_package(Corrade REQUIRED Containers Utility)
if(CORRADE_TARGET_WINDOWS) if(CORRADE_TARGET_WINDOWS)
@ -31,6 +31,10 @@ if(SAVETOOL_USE_SYSTEM_LIBZIP)
find_package(libzip REQUIRED) find_package(libzip REQUIRED)
endif(SAVETOOL_USE_SYSTEM_LIBZIP) endif(SAVETOOL_USE_SYSTEM_LIBZIP)
if(SAVETOOL_USE_SYSTEM_EFSW)
find_package(efsw REQUIRED)
endif(SAVETOOL_USE_SYSTEM_EFSW)
if(SAVETOOL_USE_SYSTEM_LIBCURL) if(SAVETOOL_USE_SYSTEM_LIBCURL)
find_package(CURL REQUIRED HTTPS) find_package(CURL REQUIRED HTTPS)
endif(SAVETOOL_USE_SYSTEM_LIBCURL) endif(SAVETOOL_USE_SYSTEM_LIBCURL)
@ -131,6 +135,14 @@ set(Gvas_SOURCES
Gvas/PropertySerialiser.cpp Gvas/PropertySerialiser.cpp
) )
set(ImportExport_SOURCES
ImportExport/Import.h
ImportExport/Import.cpp
ImportExport/Export.h
ImportExport/Export.cpp
ImportExport/Keys.h
)
if(CORRADE_TARGET_WINDOWS) if(CORRADE_TARGET_WINDOWS)
set(SAVETOOL_RC_FILE resource.rc) set(SAVETOOL_RC_FILE resource.rc)
endif() endif()
@ -141,6 +153,7 @@ add_executable(MassBuilderSaveTool
Application/Application.cpp Application/Application.cpp
Application/Application_drawAbout.cpp Application/Application_drawAbout.cpp
Application/Application_drawMainMenu.cpp Application/Application_drawMainMenu.cpp
Application/Application_FileWatcher.cpp
Application/Application_Initialisation.cpp Application/Application_Initialisation.cpp
Application/Application_MainManager.cpp Application/Application_MainManager.cpp
Application/Application_MassViewer.cpp Application/Application_MassViewer.cpp
@ -209,6 +222,7 @@ add_executable(MassBuilderSaveTool
${Logger_SOURCES} ${Logger_SOURCES}
${BinaryIo_SOURCES} ${BinaryIo_SOURCES}
${Gvas_SOURCES} ${Gvas_SOURCES}
${ImportExport_SOURCES}
${SAVETOOL_RC_FILE} ${SAVETOOL_RC_FILE}
${Assets} ${Assets}
) )
@ -220,10 +234,10 @@ endif()
target_compile_definitions(MassBuilderSaveTool PRIVATE target_compile_definitions(MassBuilderSaveTool PRIVATE
SAVETOOL_VERSION_STRING="${SAVETOOL_PROJECT_VERSION}" SAVETOOL_VERSION_STRING="${SAVETOOL_PROJECT_VERSION}"
SAVETOOL_VERSION_MAJOR=1 SAVETOOL_VERSION_MAJOR=1
SAVETOOL_VERSION_MINOR=6 SAVETOOL_VERSION_MINOR=5
SAVETOOL_VERSION_PATCH=1 SAVETOOL_VERSION_PATCH=3
SAVETOOL_VERSION_PRERELEASE=false SAVETOOL_VERSION_PRERELEASE=false
SAVETOOL_CODENAME="Glorious Fuckery" SAVETOOL_CODENAME="Friendly Valkyrie"
SAVETOOL_SUPPORTED_GAME_VERSION="0.12.x" SAVETOOL_SUPPORTED_GAME_VERSION="0.12.x"
) )
@ -257,6 +271,12 @@ else()
target_link_libraries(MassBuilderSaveTool PRIVATE zip) target_link_libraries(MassBuilderSaveTool PRIVATE zip)
endif() endif()
if(SAVETOOL_USE_SYSTEM_EFSW)
target_link_libraries(MassBuilderSaveTool PRIVATE efsw::efsw)
else()
target_link_libraries(MassBuilderSaveTool PRIVATE efsw)
endif()
if(CORRADE_TARGET_WINDOWS) if(CORRADE_TARGET_WINDOWS)
target_link_libraries(MassBuilderSaveTool PRIVATE target_link_libraries(MassBuilderSaveTool PRIVATE
Corrade::Main Corrade::Main

View file

@ -119,6 +119,10 @@ Configuration::Configuration() {
Containers::String executable_location = Utility::Path::split(*Utility::Path::executableLocation()).first(); Containers::String executable_location = Utility::Path::split(*Utility::Path::executableLocation()).first();
_directories.backups = Utility::Path::join(executable_location, "backups"); _directories.backups = Utility::Path::join(executable_location, "backups");
_directories.staging = Utility::Path::join(executable_location, "staging"); _directories.staging = Utility::Path::join(executable_location, "staging");
//auto armoury_dir = Utility::Path::join(executable_location, "armoury");
//_directories.armours = Utility::Path::join(armoury_dir, "armours");
//_directories.weapons = Utility::Path::join(armoury_dir, "weapons");
//_directories.styles = Utility::Path::join(armoury_dir, "styles");
_directories.temp = Utility::Path::join(executable_location, "temp"); _directories.temp = Utility::Path::join(executable_location, "temp");
if(!Utility::Path::exists(_directories.backups)) { if(!Utility::Path::exists(_directories.backups)) {
@ -135,6 +139,27 @@ Configuration::Configuration() {
return; return;
} }
} }
//if(!Utility::Path::exists(_directories.armours)) {
// LOG_WARNING("Armours directory not found, creating...");
// if(!Utility::Path::make(_directories.armours)) {
// LOG_ERROR(_lastError = "Couldn't create the armours directory.");
// return;
// }
//}
//if(!Utility::Path::exists(_directories.weapons)) {
// LOG_WARNING("Weapons directory not found, creating...");
// if(!Utility::Path::make(_directories.weapons)) {
// LOG_ERROR(_lastError = "Couldn't create the weapons directory.");
// return;
// }
//}
//if(!Utility::Path::exists(_directories.styles)) {
// LOG_WARNING("Styles directory not found, creating...");
// if(!Utility::Path::make(_directories.styles)) {
// LOG_ERROR(_lastError = "Couldn't create the styles directory.");
// return;
// }
//}
if(!Utility::Path::exists(_directories.temp)) { if(!Utility::Path::exists(_directories.temp)) {
LOG_WARNING("Temporary directory not found, creating..."); LOG_WARNING("Temporary directory not found, creating...");

View file

@ -70,6 +70,9 @@ class Configuration {
Containers::String gameScreenshots; Containers::String gameScreenshots;
Containers::String backups; Containers::String backups;
Containers::String staging; Containers::String staging;
Containers::String armours;
Containers::String weapons;
Containers::String styles;
Containers::String temp; Containers::String temp;
}; };
auto directories() const -> Directories const&; auto directories() const -> Directories const&;

View file

@ -27,9 +27,6 @@ using namespace Containers::Literals;
namespace mbst::GameData { namespace mbst::GameData {
// As of this writing (2024-12-07T12:08+01:00), the IDs ending in 96 are not valid IDs in the game, but are used by the
// Save Tool to display a name instead of "ID: <number>".
// region Melee // region Melee
static const std::map<std::int32_t, Containers::StringView> melee_grips { static const std::map<std::int32_t, Containers::StringView> melee_grips {
{0, "Combat Grip (1H)"_s}, {0, "Combat Grip (1H)"_s},
@ -41,7 +38,6 @@ static const std::map<std::int32_t, Containers::StringView> melee_grips {
{6, "Knight Guard Grip (1H)"_s}, {6, "Knight Guard Grip (1H)"_s},
{7, "Saber Guard Grip (1H)"_s}, {7, "Saber Guard Grip (1H)"_s},
{8, "Base Grip (1H)"_s}, {8, "Base Grip (1H)"_s},
{96, "<hidden grip> (1H)"_s},
{100, "Combat Side Grip (1H)"_s}, {100, "Combat Side Grip (1H)"_s},
{101, "Hollowed Side Grip (1H)"_s}, {101, "Hollowed Side Grip (1H)"_s},
@ -52,14 +48,12 @@ static const std::map<std::int32_t, Containers::StringView> melee_grips {
{106, "Concave Side Grip (1H)"_s}, {106, "Concave Side Grip (1H)"_s},
{107, "Polehead Side Grip (1H)"_s}, {107, "Polehead Side Grip (1H)"_s},
{108, "Base Side Grip (1H)"_s}, {108, "Base Side Grip (1H)"_s},
{196, "<hidden side grip> (1H)"_s},
{200, "Combat Dual Grip (1H)"_s}, {200, "Combat Dual Grip (1H)"_s},
{201, "Hollowed Dual Grip (1H)"_s}, {201, "Hollowed Dual Grip (1H)"_s},
{202, "Plated Dual Grip (1H)"_s}, {202, "Plated Dual Grip (1H)"_s},
{203, "Concave Dual Grip (1H)"_s}, {203, "Concave Dual Grip (1H)"_s},
{204, "Polehead Dual Grip (1H)"_s}, {204, "Polehead Dual Grip (1H)"_s},
{296, "<hidden dual grip> (1H)"_s},
{400, "Combat Twin Grip (1H)"_s}, {400, "Combat Twin Grip (1H)"_s},
{401, "Sepal Twin Grip (1H)"_s}, {401, "Sepal Twin Grip (1H)"_s},
@ -69,7 +63,6 @@ static const std::map<std::int32_t, Containers::StringView> melee_grips {
{405, "Handguard Twin Grip (1H)"_s}, {405, "Handguard Twin Grip (1H)"_s},
{406, "Fullguard Twin Grip (1H)"_s}, {406, "Fullguard Twin Grip (1H)"_s},
{407, "Base Twin Grip (1H)"_s}, {407, "Base Twin Grip (1H)"_s},
{496, "<hidden twin grip> (1H)"_s},
{1000, "Combat Knuckle (R/L)"_s}, {1000, "Combat Knuckle (R/L)"_s},
{1001, "Battle Fist (R/L)"_s}, {1001, "Battle Fist (R/L)"_s},
@ -77,7 +70,6 @@ static const std::map<std::int32_t, Containers::StringView> melee_grips {
{1003, "Heavy Fist (R/L)"_s}, {1003, "Heavy Fist (R/L)"_s},
{1004, "Thick Fist (R/L)"_s}, {1004, "Thick Fist (R/L)"_s},
{1005, "Base Fist (R/L)"_s}, {1005, "Base Fist (R/L)"_s},
{1096, "<hidden fist> (R/L)"_s},
{2000, "Combat Polearm (2H)"_s}, {2000, "Combat Polearm (2H)"_s},
{2001, "Dual Guard Polearm (2H)"_s}, {2001, "Dual Guard Polearm (2H)"_s},
@ -87,7 +79,6 @@ static const std::map<std::int32_t, Containers::StringView> melee_grips {
{2005, "Sharp Polearm (2H)"_s}, {2005, "Sharp Polearm (2H)"_s},
{2006, "Ring Polearm (2H)"_s}, {2006, "Ring Polearm (2H)"_s},
{2007, "Base Polearm (2H)"_s}, {2007, "Base Polearm (2H)"_s},
{2096, "<hidden polearm> (2H)"_s},
{2100, "Combat Side Polearm (2H)"_s}, {2100, "Combat Side Polearm (2H)"_s},
{2101, "Plated Side Polearm (2H)"_s}, {2101, "Plated Side Polearm (2H)"_s},
@ -95,12 +86,10 @@ static const std::map<std::int32_t, Containers::StringView> melee_grips {
{2103, "Fin Side Polearm (2H)"_s}, {2103, "Fin Side Polearm (2H)"_s},
{2104, "Heavy Side Polearm (2H)"_s}, {2104, "Heavy Side Polearm (2H)"_s},
{2105, "Base Side Polearm (2H)"_s}, {2105, "Base Side Polearm (2H)"_s},
{2196, "<hidden side polearm> (2H)"_s},
{2200, "Combat Dual Polearm (2H)"_s}, {2200, "Combat Dual Polearm (2H)"_s},
{2201, "Studded Dual Polearm (2H)"_s}, {2201, "Studded Dual Polearm (2H)"_s},
{2202, "Circular Dual Polearm (2H)"_s}, {2202, "Circular Dual Polearm (2H)"_s},
{2296, "<hidden dual polearm> (2H)"_s},
{2400, "Combat Twin Blade (2H)"_s}, {2400, "Combat Twin Blade (2H)"_s},
{2401, "Guard Twin Blade (2H)"_s}, {2401, "Guard Twin Blade (2H)"_s},
@ -110,7 +99,6 @@ static const std::map<std::int32_t, Containers::StringView> melee_grips {
{2405, "Holder Twin Blade (2H)"_s}, {2405, "Holder Twin Blade (2H)"_s},
{2406, "Ring Twin Blade (2H)"_s}, {2406, "Ring Twin Blade (2H)"_s},
{2407, "Base Twin Blade (2H)"_s}, {2407, "Base Twin Blade (2H)"_s},
{2496, "<hidden twin blade> (2H)"_s},
}; };
static const std::map<std::int32_t, Containers::StringView> melee_assaulters { static const std::map<std::int32_t, Containers::StringView> melee_assaulters {
@ -127,35 +115,39 @@ static const std::map<std::int32_t, Containers::StringView> melee_assaulters {
{10, "Long Curved Blade"_s}, {10, "Long Curved Blade"_s},
{11, "Long Broad Blade"_s}, {11, "Long Broad Blade"_s},
{12, "Base L Sword"_s}, {12, "Base L Sword"_s},
{20, "Long Combat Edge"_s}, {20, "Long Combat Edge"_s},
{21, "Long Attached Edge"_s}, {21, "Long Attached Edge"_s},
{40, "Katana Blade"_s}, {40, "Katana Blade"_s},
{41, "Custom Katana Blade"_s}, {41, "Custom Katana Blade"_s},
{60, "Energy Blade (Motion)"_s}, {60, "Energy Blade (Motion)"_s},
{61, "Powered Blade"_s}, {61, "Powered Blade"_s},
{96, "<hidden long blade>"_s},
{100, "Short Metal Blade"_s}, {100, "Short Metal Blade"_s},
{101, "Short Assault Blade"_s}, {101, "Short Assault Blade"_s},
{102, "Short Fin Blade"_s}, {102, "Short Fin Blade"_s},
{103, "Base S Sword"_s}, {103, "Base S Sword"_s},
{120, "Short Combat Edge"_s}, {120, "Short Combat Edge"_s},
{160, "Short Energy Blade (Motion)"_s}, {160, "Short Energy Blade (Motion)"_s},
{161, "Short Powered Blade"_s}, {161, "Short Powered Blade"_s},
{180, "Triclaw"_s}, {180, "Triclaw"_s},
{181, "Straight Triclaw"_s}, {181, "Straight Triclaw"_s},
{182, "Griphold Claw"_s}, {182, "Griphold Claw"_s},
{183, "Energy Claw"_s}, {183, "Energy Claw"_s},
{184, "Openhold Claw"_s}, {184, "Openhold Claw"_s},
{185, "Hooktusk Claw"_s}, {185, "Hooktusk Claw"_s},
{196, "<hidden short blade>"_s},
{200, "Bracer"_s}, {200, "Bracer"_s},
{201, "Custom Bracer"_s}, {201, "Custom Bracer"_s},
{202, "Base Hammer"_s}, {202, "Base Hammer"_s},
{210, "Expanded Bracer"_s}, {210, "Expanded Bracer"_s},
{211, "Expanded Custom Bracer"_s}, {211, "Expanded Custom Bracer"_s},
{296, "<hidden bracer>"_s},
{300, "Heavy Smasher"_s}, {300, "Heavy Smasher"_s},
{301, "Heavy Basher"_s}, {301, "Heavy Basher"_s},
@ -164,7 +156,6 @@ static const std::map<std::int32_t, Containers::StringView> melee_assaulters {
{304, "Heavy Diamond Smasher"_s}, {304, "Heavy Diamond Smasher"_s},
{305, "Heavy Spinning Smasher (Motion)"_s}, {305, "Heavy Spinning Smasher (Motion)"_s},
{306, "Base L Mace"_s}, {306, "Base L Mace"_s},
{396, "<hidden heavy mace>"_s},
{400, "Light Smasher"_s}, {400, "Light Smasher"_s},
{401, "Light Basher"_s}, {401, "Light Basher"_s},
@ -173,13 +164,14 @@ static const std::map<std::int32_t, Containers::StringView> melee_assaulters {
{404, "Light Diamond Smasher"_s}, {404, "Light Diamond Smasher"_s},
{405, "Light Spinning Smasher"_s}, {405, "Light Spinning Smasher"_s},
{406, "Base S Mace"_s}, {406, "Base S Mace"_s},
{420, "War Hammer"_s}, {420, "War Hammer"_s},
{421, "Great Hammer"_s}, {421, "Great Hammer"_s},
{422, "Spiked Hammer"_s}, {422, "Spiked Hammer"_s},
{423, "Broadhead Hammer"_s}, {423, "Broadhead Hammer"_s},
{440, "Morning Star"_s}, {440, "Morning Star"_s},
{441, "Spike Ball"_s}, {441, "Spike Ball"_s},
{496, "<hidden light mace>"_s},
{500, "Combat Lance"_s}, {500, "Combat Lance"_s},
{501, "Gouger Lance"_s}, {501, "Gouger Lance"_s},
@ -187,7 +179,6 @@ static const std::map<std::int32_t, Containers::StringView> melee_assaulters {
{503, "Spinning Pointy Lance (Motion)"_s}, {503, "Spinning Pointy Lance (Motion)"_s},
{504, "Crystal Lance"_s}, {504, "Crystal Lance"_s},
{510, "Piercer"_s}, {510, "Piercer"_s},
{596, "<hidden lance>"_s},
{600, "Short Combat Lance"_s}, {600, "Short Combat Lance"_s},
{601, "Short Pointy Lance"_s}, {601, "Short Pointy Lance"_s},
@ -195,7 +186,6 @@ static const std::map<std::int32_t, Containers::StringView> melee_assaulters {
{603, "Short Crystal Lance"_s}, {603, "Short Crystal Lance"_s},
{605, "Short Combat Drill (Motion)"_s}, {605, "Short Combat Drill (Motion)"_s},
{610, "Short Piercer"_s}, {610, "Short Piercer"_s},
{696, "<hidden short lance>"_s},
{700, "Combat Axe"_s}, {700, "Combat Axe"_s},
{701, "Custom Combat Axe"_s}, {701, "Custom Combat Axe"_s},
@ -203,21 +193,18 @@ static const std::map<std::int32_t, Containers::StringView> melee_assaulters {
{703, "Frontbreak Axe"_s}, {703, "Frontbreak Axe"_s},
{704, "Maiming Axe"_s}, {704, "Maiming Axe"_s},
{705, "Delta Axe"_s}, {705, "Delta Axe"_s},
{796, "<hidden axe>"_s},
{800, "Combat Scythe"_s}, {800, "Combat Scythe"_s},
{801, "Reaper Blade"_s}, {801, "Reaper Blade"_s},
{802, "Clawtooth Scythe"_s}, {802, "Clawtooth Scythe"_s},
{803, "Wingpoint Scythe"_s}, {803, "Wingpoint Scythe"_s},
{804, "Snakebone Scythe"_s}, {804, "Snakebone Scythe"_s},
{896, "<hidden scythe>"_s},
{900, "Short Combat Scythe"_s}, {900, "Short Combat Scythe"_s},
{901, "Short Reaper Blade"_s}, {901, "Short Reaper Blade"_s},
{902, "Short Clawtooth Scythe"_s}, {902, "Short Clawtooth Scythe"_s},
{903, "Short Wingpoint Scythe"_s}, {903, "Short Wingpoint Scythe"_s},
{904, "Short Snakebone Scythe"_s}, {904, "Short Snakebone Scythe"_s},
{996, "<hidden short scythe>"_s},
}; };
// endregion // endregion
@ -236,7 +223,7 @@ static const std::map<std::int32_t, Containers::StringView> shield_handles {
{10, "Board Handle"_s}, {10, "Board Handle"_s},
{11, "Knight Handle"_s}, {11, "Knight Handle"_s},
{12, "Cargwall Handle"_s}, {12, "Cargwall Handle"_s},
{96, "<hidden handle>"_s},
{100, "Buckler Handle"_s}, {100, "Buckler Handle"_s},
{101, "Star Handle"_s}, {101, "Star Handle"_s},
}; };
@ -256,7 +243,7 @@ static const std::map<std::int32_t, Containers::StringView> shield_shells {
{11, "Parted Shell"_s}, {11, "Parted Shell"_s},
{12, "Tapst Shell"_s}, {12, "Tapst Shell"_s},
{13, "Sidloc Shell"_s}, {13, "Sidloc Shell"_s},
{96, "<hidden shell>"_s},
{100, "V-Cross Shell"_s}, {100, "V-Cross Shell"_s},
}; };
// endregion // endregion
@ -270,7 +257,6 @@ static const std::map<std::int32_t, Containers::StringView> bshooter_triggers {
{4, "Longhold Trigger (1H)"_s}, {4, "Longhold Trigger (1H)"_s},
{5, "Downhold Trigger (1H)"_s}, {5, "Downhold Trigger (1H)"_s},
{6, "Cellblock Trigger (1H)"_s}, {6, "Cellblock Trigger (1H)"_s},
{96, "<hidden trigger> (1H)"_s},
{99, "Base Trigger (1H)"_s}, {99, "Base Trigger (1H)"_s},
{100, "BL-Machine Trigger (2H)"_s}, {100, "BL-Machine Trigger (2H)"_s},
@ -278,7 +264,6 @@ static const std::map<std::int32_t, Containers::StringView> bshooter_triggers {
{102, "Shielded Trigger (2H)"_s}, {102, "Shielded Trigger (2H)"_s},
{103, "Platedframe Trigger (2H)"_s}, {103, "Platedframe Trigger (2H)"_s},
{104, "Sidebox Trigger (2H) (Motion)"_s}, {104, "Sidebox Trigger (2H) (Motion)"_s},
{196, "<hidden trigger> (2H)"_s},
{199, "2H Base Trigger (2H)"_s}, {199, "2H Base Trigger (2H)"_s},
}; };
@ -288,7 +273,6 @@ static const std::map<std::int32_t, Containers::StringView> bshooter_barrels {
{2, "Muzzlemod Barrel (1 shot)"_s}, {2, "Muzzlemod Barrel (1 shot)"_s},
{3, "Triangular Barrel (1 shot)"_s}, {3, "Triangular Barrel (1 shot)"_s},
{4, "Recoilblock Barrel (1 shot) (Motion)"_s}, {4, "Recoilblock Barrel (1 shot) (Motion)"_s},
{96, "<hidden barrel> (1 shot)"_s},
{97, "Short S Base Barrel (1 shot)"_s}, {97, "Short S Base Barrel (1 shot)"_s},
{98, "Medium S Base Barrel (1 shot)"_s}, {98, "Medium S Base Barrel (1 shot)"_s},
{99, "Long S Base Barrel (1 shot)"_s}, {99, "Long S Base Barrel (1 shot)"_s},
@ -297,7 +281,6 @@ static const std::map<std::int32_t, Containers::StringView> bshooter_barrels {
{101, "Modded Six-Barrel Gatling (Auto) (Motion)"_s}, {101, "Modded Six-Barrel Gatling (Auto) (Motion)"_s},
{102, "Four-Barrel Gatling (Auto) (Motion)"_s}, {102, "Four-Barrel Gatling (Auto) (Motion)"_s},
{103, "Retro Style Gatling (Auto) (Motion)"_s}, {103, "Retro Style Gatling (Auto) (Motion)"_s},
{196, "<hidden barrel> (Auto)"_s},
{197, "Short G Base Barrel (Auto)"_s}, {197, "Short G Base Barrel (Auto)"_s},
{198, "Medium G Base Barrel (Auto)"_s}, {198, "Medium G Base Barrel (Auto)"_s},
{199, "Long G Base Barrel (Auto)"_s}, {199, "Long G Base Barrel (Auto)"_s},
@ -306,7 +289,6 @@ static const std::map<std::int32_t, Containers::StringView> bshooter_barrels {
{201, "Wideblast Barrel (Spread) (Motion)"_s}, {201, "Wideblast Barrel (Spread) (Motion)"_s},
{202, "Pelleter Barrel (Spread) (Motion)"_s}, {202, "Pelleter Barrel (Spread) (Motion)"_s},
{203, "Lockhold Barrel (Spread) (Motion)"_s}, {203, "Lockhold Barrel (Spread) (Motion)"_s},
{296, "<hidden barrel> (Spread)"_s},
{297, "Short B Base Barrel (Spread)"_s}, {297, "Short B Base Barrel (Spread)"_s},
{298, "Medium B Base Barrel (Spread)"_s}, {298, "Medium B Base Barrel (Spread)"_s},
{299, "Long B Base Barrel (Spread)"_s}, {299, "Long B Base Barrel (Spread)"_s},
@ -315,7 +297,6 @@ static const std::map<std::int32_t, Containers::StringView> bshooter_barrels {
{301, "Roundbox Barrel (Detonate)"_s}, {301, "Roundbox Barrel (Detonate)"_s},
{302, "ShieldDet Barrel (Detonate)"_s}, {302, "ShieldDet Barrel (Detonate)"_s},
{303, "RecoilDet Barrel (Detonate) (Motion)"_s}, {303, "RecoilDet Barrel (Detonate) (Motion)"_s},
{396, "<hidden barrel> (Detonate)"_s},
{397, "Short D Base Barrel (Detonate)"_s}, {397, "Short D Base Barrel (Detonate)"_s},
{398, "Medium D Base Barrel (Detonate)"_s}, {398, "Medium D Base Barrel (Detonate)"_s},
{399, "Long D Base Barrel (Detonate)"_s}, {399, "Long D Base Barrel (Detonate)"_s},
@ -323,7 +304,6 @@ static const std::map<std::int32_t, Containers::StringView> bshooter_barrels {
{400, "Heavy Burst Barrel (Pile Bunker) (Motion)"_s}, {400, "Heavy Burst Barrel (Pile Bunker) (Motion)"_s},
{401, "Under Guard Barrel (Pile Bunker) (Motion)"_s}, {401, "Under Guard Barrel (Pile Bunker) (Motion)"_s},
{402, "Facthold Barrel (Pile Bunker) (Motion)"_s}, {402, "Facthold Barrel (Pile Bunker) (Motion)"_s},
{496, "<hidden barrel> (Pile Bunker)"_s},
{499, "Long P Base Barrel (Pile Bunker) (Motion)"_s}, {499, "Long P Base Barrel (Pile Bunker) (Motion)"_s},
}; };
// endregion // endregion
@ -337,7 +317,6 @@ static const std::map<std::int32_t, Containers::StringView> eshooter_triggers {
{4, "EN-Needler Trigger (1H)"_s}, {4, "EN-Needler Trigger (1H)"_s},
{5, "Angular Trigger (1H)"_s}, {5, "Angular Trigger (1H)"_s},
{6, "Exposed Trigger (1H)"_s}, {6, "Exposed Trigger (1H)"_s},
{96, "<hidden trigger> (1H)"_s},
{99, "Base EnTrigger (1H)"_s}, {99, "Base EnTrigger (1H)"_s},
{100, "EN-Combat Trigger (2H)"_s}, {100, "EN-Combat Trigger (2H)"_s},
@ -345,7 +324,6 @@ static const std::map<std::int32_t, Containers::StringView> eshooter_triggers {
{102, "Framed Trigger (2H) (Motion)"_s}, {102, "Framed Trigger (2H) (Motion)"_s},
{103, "Stabilised Trigger (2H)"_s}, {103, "Stabilised Trigger (2H)"_s},
{104, "EN-Heavy Trigger (2H)"_s}, {104, "EN-Heavy Trigger (2H)"_s},
{196, "<hidden trigger> (2H)"_s},
{199, "2H Base EnTrigger (2H)"_s}, {199, "2H Base EnTrigger (2H)"_s},
}; };
@ -354,7 +332,6 @@ static const std::map<std::int32_t, Containers::StringView> eshooter_busters {
{1, "Delta Cycler (1 shot) (Motion)"_s}, {1, "Delta Cycler (1 shot) (Motion)"_s},
{2, "EN-Longbarrel Buster (1 shot)"_s}, {2, "EN-Longbarrel Buster (1 shot)"_s},
{3, "Kinetic Buster (1 shot) (Motion)"_s}, {3, "Kinetic Buster (1 shot) (Motion)"_s},
{96, "<hidden buster> (1 shot)"_s},
{97, "Short S Base Buster (1 shot)"_s}, {97, "Short S Base Buster (1 shot)"_s},
{98, "Medium S Base Buster (1 shot)"_s}, {98, "Medium S Base Buster (1 shot)"_s},
{99, "Long S Base Buster (1 shot)"_s}, {99, "Long S Base Buster (1 shot)"_s},
@ -363,7 +340,6 @@ static const std::map<std::int32_t, Containers::StringView> eshooter_busters {
{101, "EN-Focus Buster (Auto)"_s}, {101, "EN-Focus Buster (Auto)"_s},
{102, "Machinist Buster (Auto)"_s}, {102, "Machinist Buster (Auto)"_s},
{103, "EN-Precision Buster (Auto) (Motion)"_s}, {103, "EN-Precision Buster (Auto) (Motion)"_s},
{196, "<hidden buster> (Auto)"_s},
{197, "Short A Base Buster (Auto)"_s}, {197, "Short A Base Buster (Auto)"_s},
{198, "Medium A Base Buster (Auto)"_s}, {198, "Medium A Base Buster (Auto)"_s},
{199, "Long A Base Buster (Auto)"_s}, {199, "Long A Base Buster (Auto)"_s},
@ -372,7 +348,6 @@ static const std::map<std::int32_t, Containers::StringView> eshooter_busters {
{201, "Clawcharge Buster (Ray)"_s}, {201, "Clawcharge Buster (Ray)"_s},
{202, "Twizelcharge Buster (Ray)"_s}, {202, "Twizelcharge Buster (Ray)"_s},
{203, "Deltacharge Buster (Ray)"_s}, {203, "Deltacharge Buster (Ray)"_s},
{296, "<hidden buster> (Ray)"_s},
{297, "Short R Base Buster (Ray)"_s}, {297, "Short R Base Buster (Ray)"_s},
{298, "Medium R Base Buster (Ray)"_s}, {298, "Medium R Base Buster (Ray)"_s},
{299, "Long R Base Buster (Ray)"_s}, {299, "Long R Base Buster (Ray)"_s},
@ -381,7 +356,6 @@ static const std::map<std::int32_t, Containers::StringView> eshooter_busters {
{301, "Amplifier Buster (Wave) (Motion)"_s}, {301, "Amplifier Buster (Wave) (Motion)"_s},
{302, "Cyclonwave Buster (Wave)"_s}, {302, "Cyclonwave Buster (Wave)"_s},
{303, "Warhorn Buster (Wave) (Motion)"_s}, {303, "Warhorn Buster (Wave) (Motion)"_s},
{396, "<hidden buster> (Wave)"_s},
{397, "Short W Base Buster (Wave)"_s}, {397, "Short W Base Buster (Wave)"_s},
{398, "Medium W Base Buster (Wave)"_s}, {398, "Medium W Base Buster (Wave)"_s},
{399, "Long W Base Buster (Wave)"_s}, {399, "Long W Base Buster (Wave)"_s},
@ -389,7 +363,6 @@ static const std::map<std::int32_t, Containers::StringView> eshooter_busters {
{400, "Wiredcharge Buster (Prism) (Motion)"_s}, {400, "Wiredcharge Buster (Prism) (Motion)"_s},
{402, "Heavyclamp Buster (Prism) (Motion)"_s}, {402, "Heavyclamp Buster (Prism) (Motion)"_s},
{402, "Curlescent Buster (Prism) (Motion)"_s}, {402, "Curlescent Buster (Prism) (Motion)"_s},
{496, "<hidden buster> (Prism)"_s},
{499, "Long P Base Buster (Prism) (Motion)"_s}, {499, "Long P Base Buster (Prism) (Motion)"_s},
}; };
// endregion // endregion
@ -401,7 +374,6 @@ static const std::map<std::int32_t, Containers::StringView> blauncher_pods {
{2, "Detector Launcher (Missile x12)"_s}, {2, "Detector Launcher (Missile x12)"_s},
{3, "BL-Triplet Pack Launcher (Missile x12)"_s}, {3, "BL-Triplet Pack Launcher (Missile x12)"_s},
{4, "Shielded Launcher (Missile x12)"_s}, {4, "Shielded Launcher (Missile x12)"_s},
{96, "<hidden launcher> (Missile x12)"_s},
{99, "H Base Pod (Missile x12)"_s}, {99, "H Base Pod (Missile x12)"_s},
{100, "Warhead Pod (Nuke x2)"_s}, {100, "Warhead Pod (Nuke x2)"_s},
@ -409,7 +381,6 @@ static const std::map<std::int32_t, Containers::StringView> blauncher_pods {
{102, "Triangular Warhead Pod (Nuke x2)"_s}, {102, "Triangular Warhead Pod (Nuke x2)"_s},
{103, "Expanded Warhead Pod (Nuke x2)"_s}, {103, "Expanded Warhead Pod (Nuke x2)"_s},
{104, "Shielded Warhead Pod (Nuke x2)"_s}, {104, "Shielded Warhead Pod (Nuke x2)"_s},
{196, "<hidden launcher> (Nuke x2)"_s},
{199, "N Base Pod (Nuke x2)"_s}, {199, "N Base Pod (Nuke x2)"_s},
{200, "Widepack Launcher (Salvo x24)"_s}, {200, "Widepack Launcher (Salvo x24)"_s},
@ -417,7 +388,6 @@ static const std::map<std::int32_t, Containers::StringView> blauncher_pods {
{202, "Double Delta Launcher (Salvo x24)"_s}, {202, "Double Delta Launcher (Salvo x24)"_s},
{203, "Hexagonal Launcher (Salvo x24)"_s}, {203, "Hexagonal Launcher (Salvo x24)"_s},
{204, "Shielded Six Launcher (Salvo x24)"_s}, {204, "Shielded Six Launcher (Salvo x24)"_s},
{296, "<hidden launcher> (Salvo x24)"_s},
{299, "S Base Pod (Salvo x24)"_s}, {299, "S Base Pod (Salvo x24)"_s},
{300, "Sentinel Cluster Pod (Cluster x40)"_s}, {300, "Sentinel Cluster Pod (Cluster x40)"_s},
@ -425,7 +395,6 @@ static const std::map<std::int32_t, Containers::StringView> blauncher_pods {
{302, "Elliptical Cluster Pod (Cluster x40)"_s}, {302, "Elliptical Cluster Pod (Cluster x40)"_s},
{303, "Sawed Cluster Pod (Cluster x40)"_s}, {303, "Sawed Cluster Pod (Cluster x40)"_s},
{304, "Pentagonal Cluster Pod (Cluster x40)"_s}, {304, "Pentagonal Cluster Pod (Cluster x40)"_s},
{396, "<hidden launcher> (Cluster x40)"_s},
{399, "C Base Pod (Cluster x40)"_s}, {399, "C Base Pod (Cluster x40)"_s},
}; };
@ -435,7 +404,6 @@ static const std::map<std::int32_t, Containers::StringView> blauncher_projectile
{2, "Pointhead Missile"_s}, {2, "Pointhead Missile"_s},
{3, "Marker Missile"_s}, {3, "Marker Missile"_s},
{4, "ArB Missile"_s}, {4, "ArB Missile"_s},
{96, "<hidden missile>"_s},
}; };
// endregion // endregion
@ -469,8 +437,7 @@ static const std::map<std::int32_t, Containers::StringView> elauncher_generators
{25, "Carrier Unit"_s}, {25, "Carrier Unit"_s},
{26, "Compartment Unit"_s}, {26, "Compartment Unit"_s},
{27, "Flatedge Unit"_s}, {27, "Flatedge Unit"_s},
{96, "<hidden generator>"_s}, {99, "Base Generator"},
{99, "Base Generator"_s},
}; };
static const std::map<std::int32_t, Containers::StringView> elauncher_pods { static const std::map<std::int32_t, Containers::StringView> elauncher_pods {
@ -481,7 +448,6 @@ static const std::map<std::int32_t, Containers::StringView> elauncher_pods {
{4, "EN-Needler Launcher (Echo)"_s}, {4, "EN-Needler Launcher (Echo)"_s},
{5, "Spark Launcher (Echo)"_s}, {5, "Spark Launcher (Echo)"_s},
{6, "Pinpoint Launcher (Echo)"_s}, {6, "Pinpoint Launcher (Echo)"_s},
{96, "<hidden pod> (Echo)"_s},
{99, "E Base EPod (Echo)"_s}, {99, "E Base EPod (Echo)"_s},
{100, "Raystream Launcher (Beam)"_s}, {100, "Raystream Launcher (Beam)"_s},
@ -491,7 +457,6 @@ static const std::map<std::int32_t, Containers::StringView> elauncher_pods {
{104, "Crosshair Launcher (Beam)"_s}, {104, "Crosshair Launcher (Beam)"_s},
{105, "Powerlined Launcher (Beam)"_s}, {105, "Powerlined Launcher (Beam)"_s},
{106, "Attached Launcher (Beam)"_s}, {106, "Attached Launcher (Beam)"_s},
{196, "<hidden pod> (Beam)"_s},
{199, "B Base EPod (Beam)"_s}, {199, "B Base EPod (Beam)"_s},
{200, "Hilt Launcher (Slash) (Motion)"_s}, {200, "Hilt Launcher (Slash) (Motion)"_s},
@ -501,7 +466,6 @@ static const std::map<std::int32_t, Containers::StringView> elauncher_pods {
{204, "Spike Launcher (Slash)"_s}, {204, "Spike Launcher (Slash)"_s},
{205, "Tri-Pronged Launcher (Slash) (Motion)"_s}, {205, "Tri-Pronged Launcher (Slash) (Motion)"_s},
{206, "Heavyblade Launcher (Slash)"_s}, {206, "Heavyblade Launcher (Slash)"_s},
{296, "<hidden pod> (Slash)"_s},
{299, "S Base EPod (Slash)"_s}, {299, "S Base EPod (Slash)"_s},
{300, "Covering Launcher (Photon)"_s}, {300, "Covering Launcher (Photon)"_s},
@ -511,7 +475,6 @@ static const std::map<std::int32_t, Containers::StringView> elauncher_pods {
{304, "Shelled Launcher (Photon)"_s}, {304, "Shelled Launcher (Photon)"_s},
{305, "Widearm Launcher (Photon)"_s}, {305, "Widearm Launcher (Photon)"_s},
{306, "Wingspan Launcher (Photon)"_s}, {306, "Wingspan Launcher (Photon)"_s},
{396, "<hidden pod> (Photon)"_s},
{399, "P Base EPod (Photon)"_s}, {399, "P Base EPod (Photon)"_s},
}; };
// endregion // endregion

127
src/ImportExport/Export.cpp Normal file
View file

@ -0,0 +1,127 @@
// 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 <Corrade/Utility/Format.h>
#include <Corrade/Utility/Path.h>
#include "../Logger/Logger.h"
#include "../BinaryIo/Writer.h"
#include "../Configuration/Configuration.h"
#include "../Utilities/Crc32.h"
#include "Keys.h"
#include "Export.h"
namespace mbst::ImportExport {
static Containers::String last_export_error;
Containers::StringView
lastExportError() {
return last_export_error;
}
bool
exportStyle(Containers::StringView mass_name, mbst::GameObjects::CustomStyle& style) {
Containers::String style_type_str;
switch(style.type) {
case GameObjects::CustomStyle::Type::Unknown:
style_type_str = "Style";
break;
case GameObjects::CustomStyle::Type::Frame:
style_type_str = "FrameStyle";
break;
case GameObjects::CustomStyle::Type::Armour:
style_type_str = "ArmourStyle";
break;
case GameObjects::CustomStyle::Type::Weapon:
style_type_str = "WeaponStyle";
break;
case GameObjects::CustomStyle::Type::Global:
style_type_str = "GlobalStyle";
break;
}
auto filename = Utility::format("{}_{}_{}.style.mbst", mass_name, style_type_str, style.name);
for(auto& c : filename) {
if(c == ' ') {
c = '_';
}
}
auto path = Utility::Path::join(conf().directories().styles, filename);
BinaryIo::Writer writer{path};
if(!writer.open()) {
last_export_error = path + " couldn't be opened.";
return false;
}
if(!writer.writeString("MBSTSTYLE")) {
LOG_ERROR(last_export_error = "Couldn't write magic bytes into " + filename);
return false;
}
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::Name);
writer.writeUEStringToArray(style.name);
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::Colour);
writer.writeValueToArray<Color4>(style.colour);
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::Metallic);
writer.writeValueToArray<float>(style.metallic);
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::Gloss);
writer.writeValueToArray<float>(style.gloss);
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::Glow);
writer.writeValueToArray<bool>(style.glow);
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::PatternId);
writer.writeValueToArray<std::int32_t>(style.patternId);
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::PatternOpacity);
writer.writeValueToArray<float>(style.opacity);
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::PatternOffset);
writer.writeValueToArray<Vector2>(style.offset);
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::PatternRotation);
writer.writeValueToArray<float>(style.rotation);
writer.writeValueToArray<Keys::CustomStyle>(Keys::CustomStyle::PatternScale);
writer.writeValueToArray<float>(style.scale);
if(!writer.writeUint64(writer.array().size())) {
LOG_ERROR(last_export_error = "Couldn't write data size into " + filename);
writer.closeFile();
Utility::Path::remove(path);
return false;
}
auto crc = Utilities::crc32(0, writer.array());
if(!writer.writeUint32(crc)) {
LOG_ERROR(last_export_error = "Couldn't write CRC32 checksum into " + filename);
writer.closeFile();
Utility::Path::remove(path);
return false;
}
if(!writer.flushToFile()) {
LOG_ERROR(last_export_error = "Couldn't write data into " + filename);
writer.closeFile();
Utility::Path::remove(path);
return false;
}
return true;
}
}

31
src/ImportExport/Export.h Normal file
View file

@ -0,0 +1,31 @@
#pragma once
// 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 <Corrade/Containers/StringView.h>
#include "../GameObjects/CustomStyle.h"
using namespace Corrade;
namespace mbst::ImportExport {
auto lastExportError() -> Containers::StringView;
bool exportStyle(Containers::StringView mass_name, GameObjects::CustomStyle& style);
}

186
src/ImportExport/Import.cpp Normal file
View file

@ -0,0 +1,186 @@
// 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 <cstring>
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/String.h>
#include <Corrade/Utility/Path.h>
#include "../BinaryIo/Reader.h"
#include "../Configuration/Configuration.h"
#include "../Logger/Logger.h"
#include "../Utilities/Crc32.h"
#include "Keys.h"
#include "Import.h"
namespace mbst::ImportExport {
static Containers::String last_import_error;
Containers::StringView
lastImportError() {
return last_import_error;
}
Containers::Optional<GameObjects::CustomStyle>
importStyle(Containers::StringView filename) {
auto path = Utility::Path::join(conf().directories().styles, filename);
if(!Utility::Path::exists(path)) {
LOG_ERROR(last_import_error = path + " doesn't exist.");
return Containers::NullOpt;
}
BinaryIo::Reader reader{path};
if(!reader.open()) {
last_import_error = path + " couldn't be opened.";
return Containers::NullOpt;
}
Containers::Array<char> magic_bytes;
if(!reader.readArray(magic_bytes, 9)) {
LOG_ERROR(last_import_error = "Couldn't read the magic bytes.");
return Containers::NullOpt;
}
Containers::StringView magic_bytes_view = magic_bytes;
static const auto expected_magic_bytes = "MBSTSTYLE"_s;
if(magic_bytes_view != expected_magic_bytes) {
LOG_ERROR(last_import_error = "Magic bytes are mismatched.");
return Containers::NullOpt;
}
std::size_t data_size;
if(!reader.readUint64(data_size)) {
LOG_ERROR(last_import_error = "Couldn't read data size.");
return Containers::NullOpt;
}
std::uint32_t crc;
if(!reader.readUint32(crc)) {
LOG_ERROR(last_import_error = "Couldn't read CRC-32 checksum.");
return Containers::NullOpt;
}
auto position = reader.position();
{
Containers::Array<char> data;
if(!reader.readArray(data, data_size)) {
LOG_ERROR(last_import_error = "Couldn't read data.");
return Containers::NullOpt;
}
auto computed_crc = Utilities::crc32(0, data);
if(computed_crc != crc) {
LOG_ERROR(last_import_error = Utility::format("CRC-32 checksum doesn't match. "
"Expected {}, got {} instead.",
crc, computed_crc));
return Containers::NullOpt;
}
}
if(!reader.seek(position)) {
LOG_ERROR(last_import_error = Utility::format("Couldn't seek to position {}.", position));
return Containers::NullOpt;
}
GameObjects::CustomStyle style{};
while(!reader.eof()) {
Keys::CustomStyle key;
if(!reader.readValue(key)) {
if(reader.eof()) {
break;
}
LOG_ERROR(last_import_error = "Couldn't read key.");
return Containers::NullOpt;
}
switch(key) {
case Keys::Name:
if(!reader.readUEString(style.name)) {
LOG_ERROR(last_import_error = "Couldn't read style name.");
return Containers::NullOpt;
}
break;
case Keys::Colour:
if(!reader.readValue<Color4>(style.colour)) {
LOG_ERROR(last_import_error = "Couldn't read style colour.");
return Containers::NullOpt;
}
break;
case Keys::Metallic:
if(!reader.readFloat(style.metallic)) {
LOG_ERROR(last_import_error = "Couldn't read style metallic.");
return Containers::NullOpt;
}
break;
case Keys::Gloss:
if(!reader.readFloat(style.gloss)) {
LOG_ERROR(last_import_error = "Couldn't read style gloss.");
return Containers::NullOpt;
}
break;
case Keys::Glow:
if(!reader.readValue(style.glow)) {
LOG_ERROR(last_import_error = "Couldn't read style glow.");
return Containers::NullOpt;
}
break;
case Keys::PatternId:
if(!reader.readInt32(style.patternId)) {
LOG_ERROR(last_import_error = "Couldn't read style pattern ID.");
return Containers::NullOpt;
}
break;
case Keys::PatternOpacity:
if(!reader.readFloat(style.opacity)) {
LOG_ERROR(last_import_error = "Couldn't read style pattern opacity.");
return Containers::NullOpt;
}
break;
case Keys::PatternOffset:
if(!reader.readValue(style.offset)) {
LOG_ERROR(last_import_error = "Couldn't read style pattern offset.");
return Containers::NullOpt;
}
break;
case Keys::PatternRotation:
if(!reader.readFloat(style.rotation)) {
LOG_ERROR(last_import_error = "Couldn't read style pattern rotation.");
return Containers::NullOpt;
}
break;
case Keys::PatternScale:
if(!reader.readFloat(style.scale)) {
LOG_ERROR(last_import_error = "Couldn't read style pattern scale.");
return Containers::NullOpt;
}
break;
default:
LOG_ERROR(last_import_error = Utility::format("Unknown key {}.", key));
return Containers::NullOpt;
}
}
return Utility::move(style);
}
}

31
src/ImportExport/Import.h Normal file
View file

@ -0,0 +1,31 @@
#pragma once
// 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 <Corrade/Containers/StringView.h>
#include "../GameObjects/CustomStyle.h"
using namespace Corrade;
namespace mbst::ImportExport {
auto lastImportError() -> Containers::StringView;
auto importStyle(Containers::StringView filename) -> Containers::Optional<GameObjects::CustomStyle>;
}

36
src/ImportExport/Keys.h Normal file
View file

@ -0,0 +1,36 @@
#pragma once
// 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 <cstdint>
namespace mbst::ImportExport::Keys {
enum CustomStyle: std::uint8_t {
Name = 0, // type: string
Colour = 1, // type: Magnum::Color4
Metallic = 2, // type: float
Gloss = 3, // type: float
Glow = 4, // type: bool
PatternId = 5, // type: std::int32_t
PatternOpacity = 6, // type: float
PatternOffset = 7, // type: Magnum::Vector2
PatternRotation = 8, // type: float
PatternScale = 9, // type: float
};
}

2
third-party/SDL vendored

@ -1 +1 @@
Subproject commit 9791069d78747bbb0aeb9e62b18ecd62e728c466 Subproject commit 1fa6142903b88007c7b77d324ee78fad9966871a

2
third-party/corrade vendored

@ -1 +1 @@
Subproject commit 0b13b42ca0ca437152961b42639615264a2d3a52 Subproject commit f966f918bd7dec95002921227b6bd68ccbdda113

2
third-party/curl vendored

@ -1 +1 @@
Subproject commit aed732acb1252dcd096c295ef28391c99422d2fb Subproject commit 9287563e86a5b2007fb67f68c075a87a93825861

1
third-party/efsw vendored Submodule

@ -0,0 +1 @@
Subproject commit 0c70ed2cd069a18d534989e8ae1c25ee39cba1f1

2
third-party/imgui vendored

@ -1 +1 @@
Subproject commit 1d069cf43527bdf81b42289f7c385efac129c3a0 Subproject commit a8e96ae21a4ec10e5f02b19dd865dfffe8a98e67

2
third-party/libzip vendored

@ -1 +1 @@
Subproject commit 6ba97d4167023c3421e37d3420490b467410d9b8 Subproject commit aa90b70e552709316cd2144837c3dd13b5fa1ec3

2
third-party/magnum vendored

@ -1 +1 @@
Subproject commit 1d2a1c1b3e979ff3b8bd5eacadc780e3c1359945 Subproject commit a40020f3fff91dc9c59148f52adb0d48203799eb

@ -1 +1 @@
Subproject commit 697ddd2cccf17aa03987da9a2213af99cd6d4cd3 Subproject commit bf09698491f2061733dc263f375da1f02f41d8ec