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
url = https://github.com/nih-at/libzip
branch = main
[submodule "efsw"]
path = third-party/efsw
url = https://github.com/SpartanJ/efsw
branch = master
[submodule "libcurl"]
path = third-party/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_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_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)
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)
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)
set(BUILD_CURL_EXE OFF CACHE BOOL "" FORCE)
set(ENABLE_UNICODE ON CACHE BOOL "" FORCE)

View file

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

View file

@ -17,6 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <mutex>
#include <string>
#include <thread>
#include <Corrade/Containers/Pointer.h>
@ -34,6 +35,8 @@
#include <imgui.h>
#include <efsw/efsw.hpp>
#include "../Managers/BackupManager.h"
#include "../Managers/MassManager.h"
#include "../Managers/ProfileManager.h"
@ -51,11 +54,17 @@ using namespace Magnum;
namespace mbst {
class Application: public Platform::Sdl2Application {
class Application: public Platform::Sdl2Application, public efsw::FileWatchListener {
public:
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:
// Events
@ -65,10 +74,10 @@ class Application: public Platform::Sdl2Application {
void keyPressEvent(KeyEvent& event) override;
void keyReleaseEvent(KeyEvent& event) override;
void pointerPressEvent(PointerEvent& event) override;
void pointerReleaseEvent(PointerEvent& event) override;
void pointerMoveEvent(PointerMoveEvent& event) override;
void scrollEvent(ScrollEvent& event) override;
void mousePressEvent(MouseEvent& event) override;
void mouseReleaseEvent(MouseEvent& event) override;
void mouseMoveEvent(MouseMoveEvent& event) override;
void mouseScrollEvent(MouseScrollEvent& event) override;
void textInputEvent(TextInputEvent& event) override;
void anyEvent(SDL_Event& event) override;
@ -81,11 +90,21 @@ class Application: public Platform::Sdl2Application {
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
void initialiseConfiguration();
void initialiseGui();
void initialiseManager();
void initialiseMassManager();
void initialiseFileWatcher();
// GUI-related methods
void drawImGui();
@ -121,9 +140,9 @@ class Application: public Platform::Sdl2Application {
void drawBLAttachment();
void drawCustomArmourStyles();
void drawWeapons();
void drawWeaponCategory(Containers::StringView name, GameObjects::Weapon::Type category,
Containers::ArrayView<GameObjects::Weapon> weapons_view,
Containers::StringView payload_type, Containers::StringView payload_tooltip);
void drawWeaponCategory(Containers::StringView name, Containers::ArrayView<GameObjects::Weapon> weapons_view,
bool& dirty, Containers::StringView payload_type,
Containers::StringView payload_tooltip);
void drawWeaponEditor(GameObjects::Weapon& weapon);
void drawGlobalStyles();
void drawTuning();
@ -167,7 +186,6 @@ class Application: public Platform::Sdl2Application {
template<typename Functor, typename... 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.
ImGui::BeginDisabled(game_state != GameState::NotRunning);
bool result = func(std::forward<Args>(args)...);
@ -225,6 +243,7 @@ class Application: public Platform::Sdl2Application {
std::uint32_t _initEventId;
std::uint32_t _updateEventId;
std::uint32_t _fileEventId;
Containers::String _lastError;
@ -246,12 +265,26 @@ class Application: public Platform::Sdl2Application {
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};
std::mutex _checkerMutex;
bool _modifiedBySaveTool = false;
bool _jointsDirty = false;
bool _stylesDirty = 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::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 <Magnum/Math/Time.h>
#include <SDL_events.h>
#include <SDL_messagebox.h>
@ -54,8 +52,7 @@ Application::initialiseConfiguration() {
LOG_INFO("Reading configuration file.");
setSwapInterval(conf().swapInterval());
using namespace Math::Literals;
setMinimalLoopPeriod(0_nsec);
setMinimalLoopPeriod(0);
}
void
@ -122,6 +119,8 @@ Application::initialiseManager() {
_backupManager.emplace();
_stagedMassManager.emplace();
event.user.code = InitSuccess;
SDL_PushEvent(&event);
}
@ -130,8 +129,15 @@ void
Application::initialiseMassManager() {
LOG_INFO("Initialising the M.A.S.S. manager.");
_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")) {
_currentProfile = nullptr;
_massManager.reset();
_stagedMassManager.reset();
_fileWatcher.reset();
_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",
{ImGui::GetContentRegionAvail().x * 0.60f, 0.0f},
@ -104,11 +93,6 @@ Application::drawManager() {
if(ImGui::BeginMenuBar()) {
ImGui::TextUnformatted("M.A.S.S. management");
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();
}
@ -543,13 +527,10 @@ Application::drawMassManager() {
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted("Staging area");
ImGui::SameLine();
if(ImGui::SmallButton(ICON_FA_FOLDER_OPEN " Open staging folder")) {
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()) {
ImGui::TableNextRow();
@ -594,7 +575,6 @@ Application::drawMassManager() {
if(!_massManager->exportMass(index)) {
_queue.addToast(Toast::Type::Error, _massManager->lastError());
}
_stagedMassManager->refresh();
}
ImGui::EndDragDropTarget();

View file

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

View file

@ -14,8 +14,6 @@
// 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 <string>
#include <imgui_internal.h>
#include "../FontAwesome/IconsFontAwesome5.h"
@ -199,7 +197,9 @@ Application::drawArmour() {
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());
}
}
@ -357,8 +357,11 @@ Application::drawBLAttachment() {
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());
}
}
@ -385,7 +388,9 @@ Application::drawCustomArmourStyles() {
_currentMass->getArmourCustomStyles();
break;
case DCS_Save:
_modifiedBySaveTool = true;
if(!_currentMass->writeArmourCustomStyle(i)) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;

View file

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

View file

@ -22,7 +22,6 @@
#include "../GameData/WeaponParts.h"
#include "Application.h"
#include "../Configuration/Configuration.h"
namespace mbst {
@ -33,11 +32,13 @@ Application::drawWeapons() {
return;
}
const float footer_height_to_reserve = ImGui::GetFrameHeightWithSpacing();
ImGui::BeginGroup();
if(!ImGui::BeginTable("##WeaponsList", 1,
ImGuiTableFlags_ScrollY|ImGuiTableFlags_BordersOuter|ImGuiTableFlags_BordersInnerH,
{ImGui::GetContentRegionAvail().x * 0.2f, 0.0f}))
{ImGui::GetContentRegionAvail().x * 0.2f, -footer_height_to_reserve}))
{
ImGui::EndGroup();
return;
@ -45,20 +46,118 @@ Application::drawWeapons() {
ImGui::TableSetupColumn("Weapon");
drawWeaponCategory("Melee weapons", GameObjects::Weapon::Type::Melee, _currentMass->meleeWeapons(), "MeleeWeapon",
"Melee weapon");
drawWeaponCategory("Shield", GameObjects::Weapon::Type::Shield, _currentMass->shields(), "Shield", "Shield");
drawWeaponCategory("Bullet shooters", GameObjects::Weapon::Type::BulletShooter, _currentMass->bulletShooters(),
"BShooter", "Bullet shooter");
drawWeaponCategory("Energy shooters", GameObjects::Weapon::Type::EnergyShooter, _currentMass->energyShooters(),
"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");
drawWeaponCategory("Melee weapons", _currentMass->meleeWeapons(), _meleeDirty, "MeleeWeapon", "Melee weapon");
drawWeaponCategory("Shield", _currentMass->shields(), _shieldsDirty, "Shield", "Shield");
drawWeaponCategory("Bullet shooters", _currentMass->bulletShooters(), _bShootersDirty, "BShooter", "Bullet shooter");
drawWeaponCategory("Energy shooters", _currentMass->energyShooters(), _eShootersDirty, "EShooter", "Energy shooter");
drawWeaponCategory("Bullet launchers", _currentMass->bulletLaunchers(), _bLaunchersDirty, "BLauncher", "Bullet launcher");
drawWeaponCategory("Energy launchers", _currentMass->energyLaunchers(), _eLaunchersDirty, "ELauncher", "Energy launcher");
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::SameLine();
@ -70,7 +169,7 @@ Application::drawWeapons() {
ImGui::BeginGroup();
if(!ImGui::BeginChild("##WeaponChild")) {
if(!ImGui::BeginChild("##WeaponChild", {0.0f, -footer_height_to_reserve})) {
ImGui::EndChild();
return;
}
@ -79,61 +178,55 @@ Application::drawWeapons() {
ImGui::EndChild();
ImGui::EndGroup();
}
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) {
if(drawUnsafeWidget([](){ return ImGui::Button(ICON_FA_SAVE " Save changes to weapon category"); })) {
_modifiedBySaveTool = true;
switch(_currentWeapon->type) {
case GameObjects::Weapon::Type::Melee:
if(!_currentMass->writeMeleeWeapons()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;
case GameObjects::Weapon::Type::Shield:
if(!_currentMass->writeShields()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;
case GameObjects::Weapon::Type::BulletShooter:
if(!_currentMass->writeBulletShooters()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;
case GameObjects::Weapon::Type::EnergyShooter:
if(!_currentMass->writeEnergyShooters()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;
case GameObjects::Weapon::Type::BulletLauncher:
if(!_currentMass->writeBulletLaunchers()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;
case GameObjects::Weapon::Type::EnergyLauncher:
if(!_currentMass->writeEnergyLaunchers()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;
default:
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, "Unknown weapon type");
}
}
ImGui::SameLine();
if(ImGui::SmallButton(ICON_FA_UNDO_ALT " Reset")) {
switch(category) {
if(ImGui::Button(ICON_FA_UNDO_ALT " Reset weapon category")) {
switch(_currentWeapon->type) {
case GameObjects::Weapon::Type::Melee:
_currentMass->getMeleeWeapons();
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++) {
auto& weapon = weapons_view[i];
@ -195,6 +301,7 @@ Application::drawWeaponCategory(Containers::StringView name, GameObjects::Weapon
else {
weapons_view[i] = weapons_view[index];
}
dirty = true;
}
ImGui::EndDragDropTarget();
@ -216,7 +323,7 @@ Application::drawWeaponEditor(GameObjects::Weapon& weapon) {
return;
}
static constexpr Containers::StringView labels[] {
static Containers::StringView labels[] {
#define c(enumerator, strenum, name) name,
#include "../Maps/WeaponTypes.hpp"
#undef c
@ -238,35 +345,9 @@ Application::drawWeaponEditor(GameObjects::Weapon& weapon) {
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();
drawAlignedText("Equipped:");
if(conf().advancedMode()) {
drawAlignedText("Weapon type:");
drawAlignedText("");
}
if(weapon.type != GameObjects::Weapon::Type::Shield) {
drawAlignedText("Damage type:");
}
@ -285,47 +366,6 @@ Application::drawWeaponEditor(GameObjects::Weapon& weapon) {
ImGui::BeginGroup();
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::Melee &&
ImGui::RadioButton("Physical##NoElement", weapon.damageType == GameObjects::Weapon::DamageType::Physical))
@ -385,7 +425,7 @@ Application::drawWeaponEditor(GameObjects::Weapon& weapon) {
ImGui::Separator();
if(weapon.parts.isEmpty()) {
ImGui::TextUnformatted("This weapon has no parts. This is not normal.");
ImGui::TextUnformatted("This weapon has no parts.");
return;
}
@ -427,8 +467,8 @@ Application::drawWeaponEditor(GameObjects::Weapon& weapon) {
if(map->find(part.id) != map->cend()) {
ImGui::TextUnformatted(map->at(part.id).cbegin(), map->at(part.id).cend());
}
else if(part.id < 0) {
ImGui::TextUnformatted("<invalid part ID>");
else if(part.id == -1) {
ImGui::TextUnformatted("<none>");
}
else{
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();
if(ImGui::SmallButton("Hide part " ICON_FA_QUESTION_CIRCLE)) {
part.id = 96 + part.id >= 0 ? (part.id / 100) * 100 : 0;
if(ImGui::SmallButton("Unequip")) {
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)) {
ImGui::TextUnformatted("Styles:");

View file

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

View file

@ -101,6 +101,8 @@ Application::drawAbout() {
if(ImGui::CollapsingHeader("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)) {
ImGui::Text("Version used: %s", CORRADE_VERSION_STRING);
auto corrade_website = "https://magnum.graphics/corrade";
@ -193,6 +195,23 @@ Application::drawAbout() {
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)) {
ImGui::Text("Version used: %s", LIBCURL_VERSION);
auto curl_website = "https://curl.se/libcurl";
@ -245,6 +264,8 @@ Application::drawAbout() {
ImGui::TreePop();
}
ImGui::PopStyleVar();
}
ImGui::EndPopup();

View file

@ -16,8 +16,6 @@
#include <Corrade/Utility/Path.h>
#include <Magnum/Math/Time.h>
#include "../Configuration/Configuration.h"
#include "../FontAwesome/IconsFontAwesome5.h"
#include "../FontAwesome/IconsFontAwesome5Brands.h"
@ -68,6 +66,28 @@ Application::drawMainMenu() {
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();
}
@ -100,8 +120,7 @@ Application::drawMainMenu() {
conf().setSwapInterval(i);
setSwapInterval(i);
if(i == 0) {
using namespace Math::Literals;
setMinimalLoopPeriod(0_nsec);
setMinimalLoopPeriod(0);
}
}
}
@ -130,13 +149,12 @@ Application::drawMainMenu() {
drawHelpMarker("This gives access to save edition features that can be considered cheats.",
float(windowSize().x()) * 0.4f);
if(drawCheckbox("Advanced fuckery mode", conf().advancedMode())) {
if(drawCheckbox("Advanced mode", conf().advancedMode())) {
conf().setAdvancedMode(!conf().advancedMode());
}
ImGui::SameLine();
ImGui::AlignTextToFramePadding();
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.",
drawHelpMarker("This gives access to editing values that have unknown purposes or are undocumented.",
float(windowSize().x()) * 0.4f);
if(drawCheckbox("Check for updates on startup", conf().checkUpdatesOnStartup())) {
@ -205,7 +223,7 @@ Application::drawMainMenu() {
ImGui::EndMenu();
}
//ImGui::EndDisabled();
ImGui::EndDisabled();
//if(conf().isRunningInWine() && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
// 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_EXTENSIONS OFF)
set(SAVETOOL_PROJECT_VERSION 1.6.1)
set(SAVETOOL_PROJECT_VERSION 1.5.3)
find_package(Corrade REQUIRED Containers Utility)
if(CORRADE_TARGET_WINDOWS)
@ -31,6 +31,10 @@ if(SAVETOOL_USE_SYSTEM_LIBZIP)
find_package(libzip REQUIRED)
endif(SAVETOOL_USE_SYSTEM_LIBZIP)
if(SAVETOOL_USE_SYSTEM_EFSW)
find_package(efsw REQUIRED)
endif(SAVETOOL_USE_SYSTEM_EFSW)
if(SAVETOOL_USE_SYSTEM_LIBCURL)
find_package(CURL REQUIRED HTTPS)
endif(SAVETOOL_USE_SYSTEM_LIBCURL)
@ -131,6 +135,14 @@ set(Gvas_SOURCES
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)
set(SAVETOOL_RC_FILE resource.rc)
endif()
@ -141,6 +153,7 @@ add_executable(MassBuilderSaveTool
Application/Application.cpp
Application/Application_drawAbout.cpp
Application/Application_drawMainMenu.cpp
Application/Application_FileWatcher.cpp
Application/Application_Initialisation.cpp
Application/Application_MainManager.cpp
Application/Application_MassViewer.cpp
@ -209,6 +222,7 @@ add_executable(MassBuilderSaveTool
${Logger_SOURCES}
${BinaryIo_SOURCES}
${Gvas_SOURCES}
${ImportExport_SOURCES}
${SAVETOOL_RC_FILE}
${Assets}
)
@ -220,10 +234,10 @@ endif()
target_compile_definitions(MassBuilderSaveTool PRIVATE
SAVETOOL_VERSION_STRING="${SAVETOOL_PROJECT_VERSION}"
SAVETOOL_VERSION_MAJOR=1
SAVETOOL_VERSION_MINOR=6
SAVETOOL_VERSION_PATCH=1
SAVETOOL_VERSION_MINOR=5
SAVETOOL_VERSION_PATCH=3
SAVETOOL_VERSION_PRERELEASE=false
SAVETOOL_CODENAME="Glorious Fuckery"
SAVETOOL_CODENAME="Friendly Valkyrie"
SAVETOOL_SUPPORTED_GAME_VERSION="0.12.x"
)
@ -257,6 +271,12 @@ else()
target_link_libraries(MassBuilderSaveTool PRIVATE zip)
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)
target_link_libraries(MassBuilderSaveTool PRIVATE
Corrade::Main

View file

@ -119,6 +119,10 @@ Configuration::Configuration() {
Containers::String executable_location = Utility::Path::split(*Utility::Path::executableLocation()).first();
_directories.backups = Utility::Path::join(executable_location, "backups");
_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");
if(!Utility::Path::exists(_directories.backups)) {
@ -135,6 +139,27 @@ Configuration::Configuration() {
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)) {
LOG_WARNING("Temporary directory not found, creating...");

View file

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

View file

@ -27,9 +27,6 @@ using namespace Containers::Literals;
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
static const std::map<std::int32_t, Containers::StringView> melee_grips {
{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},
{7, "Saber Guard Grip (1H)"_s},
{8, "Base Grip (1H)"_s},
{96, "<hidden grip> (1H)"_s},
{100, "Combat 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},
{107, "Polehead Side Grip (1H)"_s},
{108, "Base Side Grip (1H)"_s},
{196, "<hidden side grip> (1H)"_s},
{200, "Combat Dual Grip (1H)"_s},
{201, "Hollowed Dual Grip (1H)"_s},
{202, "Plated Dual Grip (1H)"_s},
{203, "Concave Dual Grip (1H)"_s},
{204, "Polehead Dual Grip (1H)"_s},
{296, "<hidden dual grip> (1H)"_s},
{400, "Combat 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},
{406, "Fullguard Twin Grip (1H)"_s},
{407, "Base Twin Grip (1H)"_s},
{496, "<hidden twin grip> (1H)"_s},
{1000, "Combat Knuckle (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},
{1004, "Thick Fist (R/L)"_s},
{1005, "Base Fist (R/L)"_s},
{1096, "<hidden fist> (R/L)"_s},
{2000, "Combat 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},
{2006, "Ring Polearm (2H)"_s},
{2007, "Base Polearm (2H)"_s},
{2096, "<hidden polearm> (2H)"_s},
{2100, "Combat 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},
{2104, "Heavy Side Polearm (2H)"_s},
{2105, "Base Side Polearm (2H)"_s},
{2196, "<hidden side polearm> (2H)"_s},
{2200, "Combat Dual Polearm (2H)"_s},
{2201, "Studded Dual Polearm (2H)"_s},
{2202, "Circular Dual Polearm (2H)"_s},
{2296, "<hidden dual polearm> (2H)"_s},
{2400, "Combat 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},
{2406, "Ring 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 {
@ -127,35 +115,39 @@ static const std::map<std::int32_t, Containers::StringView> melee_assaulters {
{10, "Long Curved Blade"_s},
{11, "Long Broad Blade"_s},
{12, "Base L Sword"_s},
{20, "Long Combat Edge"_s},
{21, "Long Attached Edge"_s},
{40, "Katana Blade"_s},
{41, "Custom Katana Blade"_s},
{60, "Energy Blade (Motion)"_s},
{61, "Powered Blade"_s},
{96, "<hidden long blade>"_s},
{100, "Short Metal Blade"_s},
{101, "Short Assault Blade"_s},
{102, "Short Fin Blade"_s},
{103, "Base S Sword"_s},
{120, "Short Combat Edge"_s},
{160, "Short Energy Blade (Motion)"_s},
{161, "Short Powered Blade"_s},
{180, "Triclaw"_s},
{181, "Straight Triclaw"_s},
{182, "Griphold Claw"_s},
{183, "Energy Claw"_s},
{184, "Openhold Claw"_s},
{185, "Hooktusk Claw"_s},
{196, "<hidden short blade>"_s},
{200, "Bracer"_s},
{201, "Custom Bracer"_s},
{202, "Base Hammer"_s},
{210, "Expanded Bracer"_s},
{211, "Expanded Custom Bracer"_s},
{296, "<hidden bracer>"_s},
{300, "Heavy Smasher"_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},
{305, "Heavy Spinning Smasher (Motion)"_s},
{306, "Base L Mace"_s},
{396, "<hidden heavy mace>"_s},
{400, "Light Smasher"_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},
{405, "Light Spinning Smasher"_s},
{406, "Base S Mace"_s},
{420, "War Hammer"_s},
{421, "Great Hammer"_s},
{422, "Spiked Hammer"_s},
{423, "Broadhead Hammer"_s},
{440, "Morning Star"_s},
{441, "Spike Ball"_s},
{496, "<hidden light mace>"_s},
{500, "Combat 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},
{504, "Crystal Lance"_s},
{510, "Piercer"_s},
{596, "<hidden lance>"_s},
{600, "Short Combat 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},
{605, "Short Combat Drill (Motion)"_s},
{610, "Short Piercer"_s},
{696, "<hidden short lance>"_s},
{700, "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},
{704, "Maiming Axe"_s},
{705, "Delta Axe"_s},
{796, "<hidden axe>"_s},
{800, "Combat Scythe"_s},
{801, "Reaper Blade"_s},
{802, "Clawtooth Scythe"_s},
{803, "Wingpoint Scythe"_s},
{804, "Snakebone Scythe"_s},
{896, "<hidden scythe>"_s},
{900, "Short Combat Scythe"_s},
{901, "Short Reaper Blade"_s},
{902, "Short Clawtooth Scythe"_s},
{903, "Short Wingpoint Scythe"_s},
{904, "Short Snakebone Scythe"_s},
{996, "<hidden short scythe>"_s},
};
// endregion
@ -236,7 +223,7 @@ static const std::map<std::int32_t, Containers::StringView> shield_handles {
{10, "Board Handle"_s},
{11, "Knight Handle"_s},
{12, "Cargwall Handle"_s},
{96, "<hidden handle>"_s},
{100, "Buckler 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},
{12, "Tapst Shell"_s},
{13, "Sidloc Shell"_s},
{96, "<hidden shell>"_s},
{100, "V-Cross Shell"_s},
};
// endregion
@ -270,7 +257,6 @@ static const std::map<std::int32_t, Containers::StringView> bshooter_triggers {
{4, "Longhold Trigger (1H)"_s},
{5, "Downhold Trigger (1H)"_s},
{6, "Cellblock Trigger (1H)"_s},
{96, "<hidden trigger> (1H)"_s},
{99, "Base Trigger (1H)"_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},
{103, "Platedframe Trigger (2H)"_s},
{104, "Sidebox Trigger (2H) (Motion)"_s},
{196, "<hidden 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},
{3, "Triangular Barrel (1 shot)"_s},
{4, "Recoilblock Barrel (1 shot) (Motion)"_s},
{96, "<hidden barrel> (1 shot)"_s},
{97, "Short S Base Barrel (1 shot)"_s},
{98, "Medium 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},
{102, "Four-Barrel Gatling (Auto) (Motion)"_s},
{103, "Retro Style Gatling (Auto) (Motion)"_s},
{196, "<hidden barrel> (Auto)"_s},
{197, "Short G Base Barrel (Auto)"_s},
{198, "Medium 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},
{202, "Pelleter Barrel (Spread) (Motion)"_s},
{203, "Lockhold Barrel (Spread) (Motion)"_s},
{296, "<hidden barrel> (Spread)"_s},
{297, "Short B Base Barrel (Spread)"_s},
{298, "Medium 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},
{302, "ShieldDet Barrel (Detonate)"_s},
{303, "RecoilDet Barrel (Detonate) (Motion)"_s},
{396, "<hidden barrel> (Detonate)"_s},
{397, "Short D Base Barrel (Detonate)"_s},
{398, "Medium 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},
{401, "Under Guard 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},
};
// endregion
@ -337,7 +317,6 @@ static const std::map<std::int32_t, Containers::StringView> eshooter_triggers {
{4, "EN-Needler Trigger (1H)"_s},
{5, "Angular Trigger (1H)"_s},
{6, "Exposed Trigger (1H)"_s},
{96, "<hidden trigger> (1H)"_s},
{99, "Base EnTrigger (1H)"_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},
{103, "Stabilised Trigger (2H)"_s},
{104, "EN-Heavy Trigger (2H)"_s},
{196, "<hidden trigger> (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},
{2, "EN-Longbarrel Buster (1 shot)"_s},
{3, "Kinetic Buster (1 shot) (Motion)"_s},
{96, "<hidden buster> (1 shot)"_s},
{97, "Short S Base Buster (1 shot)"_s},
{98, "Medium 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},
{102, "Machinist Buster (Auto)"_s},
{103, "EN-Precision Buster (Auto) (Motion)"_s},
{196, "<hidden buster> (Auto)"_s},
{197, "Short A Base Buster (Auto)"_s},
{198, "Medium 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},
{202, "Twizelcharge Buster (Ray)"_s},
{203, "Deltacharge Buster (Ray)"_s},
{296, "<hidden buster> (Ray)"_s},
{297, "Short R Base Buster (Ray)"_s},
{298, "Medium 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},
{302, "Cyclonwave Buster (Wave)"_s},
{303, "Warhorn Buster (Wave) (Motion)"_s},
{396, "<hidden buster> (Wave)"_s},
{397, "Short W Base Buster (Wave)"_s},
{398, "Medium 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},
{402, "Heavyclamp Buster (Prism) (Motion)"_s},
{402, "Curlescent Buster (Prism) (Motion)"_s},
{496, "<hidden buster> (Prism)"_s},
{499, "Long P Base Buster (Prism) (Motion)"_s},
};
// endregion
@ -401,7 +374,6 @@ static const std::map<std::int32_t, Containers::StringView> blauncher_pods {
{2, "Detector Launcher (Missile x12)"_s},
{3, "BL-Triplet Pack Launcher (Missile x12)"_s},
{4, "Shielded Launcher (Missile x12)"_s},
{96, "<hidden launcher> (Missile x12)"_s},
{99, "H Base Pod (Missile x12)"_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},
{103, "Expanded 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},
{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},
{203, "Hexagonal Launcher (Salvo x24)"_s},
{204, "Shielded Six Launcher (Salvo x24)"_s},
{296, "<hidden launcher> (Salvo x24)"_s},
{299, "S Base Pod (Salvo x24)"_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},
{303, "Sawed 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},
};
@ -435,7 +404,6 @@ static const std::map<std::int32_t, Containers::StringView> blauncher_projectile
{2, "Pointhead Missile"_s},
{3, "Marker Missile"_s},
{4, "ArB Missile"_s},
{96, "<hidden missile>"_s},
};
// endregion
@ -469,8 +437,7 @@ static const std::map<std::int32_t, Containers::StringView> elauncher_generators
{25, "Carrier Unit"_s},
{26, "Compartment Unit"_s},
{27, "Flatedge Unit"_s},
{96, "<hidden generator>"_s},
{99, "Base Generator"_s},
{99, "Base Generator"},
};
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},
{5, "Spark Launcher (Echo)"_s},
{6, "Pinpoint Launcher (Echo)"_s},
{96, "<hidden pod> (Echo)"_s},
{99, "E Base EPod (Echo)"_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},
{105, "Powerlined Launcher (Beam)"_s},
{106, "Attached Launcher (Beam)"_s},
{196, "<hidden pod> (Beam)"_s},
{199, "B Base EPod (Beam)"_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},
{205, "Tri-Pronged Launcher (Slash) (Motion)"_s},
{206, "Heavyblade Launcher (Slash)"_s},
{296, "<hidden pod> (Slash)"_s},
{299, "S Base EPod (Slash)"_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},
{305, "Widearm Launcher (Photon)"_s},
{306, "Wingspan Launcher (Photon)"_s},
{396, "<hidden pod> (Photon)"_s},
{399, "P Base EPod (Photon)"_s},
};
// 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