Compare commits

..

No commits in common. "40f2d884336f08538e6d1d722e01c9c510bd174d" and "7b64d78b5b0e358a3a5061ebe43b7153d2762ae6" have entirely different histories.

198 changed files with 7097 additions and 9704 deletions

4
.gitmodules vendored
View file

@ -17,11 +17,11 @@
[submodule "SDL2"]
path = third-party/SDL
url = https://github.com/libsdl-org/SDL
branch = SDL2
branch = main
[submodule "libzip"]
path = third-party/libzip
url = https://github.com/nih-at/libzip
branch = main
branch = master
[submodule "efsw"]
path = third-party/efsw
url = https://github.com/SpartanJ/efsw

View file

@ -1,5 +1,5 @@
# MassBuilderSaveTool
# Copyright (C) 2021-2024 Guillaume Jacquemin
# Copyright (C) 2021-2022 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
@ -14,127 +14,105 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
cmake_minimum_required(VERSION 3.24)
cmake_minimum_required(VERSION 3.5)
project(MassBuilderSaveTool)
set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/modules/" ${CMAKE_MODULE_PATH})
set(ZLIB_USE_STATIC_LIBS ON CACHE BOOL "" FORCE) # Required on setups where zlib is available as both dynamic and static libs. Which is pretty much everywhere, actually.
SET(CMAKE_FIND_LIBRARY_SUFFIXES .a ${CMAKE_FIND_LIBRARY_SUFFIXES})
option(SAVETOOL_USE_SYSTEM_LIBS "Use system-wide versions of the dependencies instead of the versions provided by submodules." OFF)
set(CORRADE_BUILD_DEPRECATED OFF CACHE BOOL "" FORCE)
set(CORRADE_BUILD_STATIC ON CACHE BOOL "" FORCE)
set(CORRADE_BUILD_STATIC_PIC ON CACHE BOOL "" FORCE)
set(CORRADE_BUILD_STATIC_UNIQUE_GLOBALS OFF CACHE BOOL "" FORCE)
set(CORRADE_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(CORRADE_WITH_INTERCONNECT OFF CACHE BOOL "" FORCE)
set(CORRADE_WITH_PLUGINMANAGER OFF CACHE BOOL "" FORCE)
set(CORRADE_WITH_TESTSUITE OFF CACHE BOOL "" FORCE)
set(CORRADE_WITH_MAIN ON CACHE BOOL "" FORCE)
set(CORRADE_UTILITY_USE_ANSI_COLORS ON CACHE BOOL "" FORCE)
add_subdirectory(third-party/corrade EXCLUDE_FROM_ALL)
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)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(DIRECTX OFF CACHE BOOL "" FORCE) # We use OpenGL.
set(SDL_ATOMIC OFF CACHE BOOL "" FORCE)
set(SDL_CPUINFO OFF CACHE BOOL "" FORCE)
set(SDL_EVENTS ON CACHE BOOL "" FORCE)
set(SDL_FILE OFF CACHE BOOL "" FORCE)
set(SDL_FILESYSTEM OFF CACHE BOOL "" FORCE)
set(SDL_HAPTIC OFF CACHE BOOL "" FORCE)
set(SDL_LOCALE OFF CACHE BOOL "" FORCE)
set(SDL_POWER OFF CACHE BOOL "" FORCE)
set(SDL_RENDER OFF CACHE BOOL "" FORCE)
set(SDL_SENSOR OFF CACHE BOOL "" FORCE)
set(SDL_THREADS ON CACHE BOOL "" FORCE)
set(SDL_TIMERS ON CACHE BOOL "" FORCE)
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
add_subdirectory(third-party/SDL EXCLUDE_FROM_ALL)
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))
# Generic variables shared by multiple libs that don't provide their own.
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(BUILD_STATIC_LIBS ON CACHE BOOL "" FORCE)
set(BUILD_TESTING OFF CACHE BOOL "" FORCE)
endif()
set(MAGNUM_BUILD_STATIC ON CACHE BOOL "" FORCE)
set(MAGNUM_BUILD_STATIC_PIC ON CACHE BOOL "" FORCE)
set(MAGNUM_BUILD_STATIC_UNIQUE_GLOBALS OFF CACHE BOOL "" FORCE)
set(MAGNUM_BUILD_DEPRECATED OFF CACHE BOOL "" FORCE)
set(MAGNUM_BUILD_TESTS OFF CACHE BOOL "" FORCE)
if(NOT SAVETOOL_USE_SYSTEM_SDL2)
set(DIRECTX OFF CACHE BOOL "" FORCE) # We use OpenGL.
set(SDL_ATOMIC OFF CACHE BOOL "" FORCE)
set(SDL_CPUINFO OFF CACHE BOOL "" FORCE)
set(SDL_EVENTS ON CACHE BOOL "" FORCE)
set(SDL_FILE OFF CACHE BOOL "" FORCE)
set(SDL_FILESYSTEM OFF CACHE BOOL "" FORCE)
set(SDL_HAPTIC OFF CACHE BOOL "" FORCE)
set(SDL_LOCALE OFF CACHE BOOL "" FORCE)
set(SDL_POWER OFF CACHE BOOL "" FORCE)
set(SDL_RENDER OFF CACHE BOOL "" FORCE)
set(SDL_SENSOR OFF CACHE BOOL "" FORCE)
set(SDL_THREADS ON CACHE BOOL "" FORCE)
set(SDL_TIMERS ON CACHE BOOL "" FORCE)
set(SDL_SHARED OFF CACHE BOOL "" FORCE)
add_subdirectory(third-party/SDL EXCLUDE_FROM_ALL)
endif(NOT SAVETOOL_USE_SYSTEM_SDL2)
set(MAGNUM_TARGET_GL ON CACHE BOOL "" FORCE)
set(MAGNUM_TARGET_GLES OFF CACHE BOOL "" FORCE)
set(MAGNUM_TARGET_VK OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_AUDIO OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_DEBUGTOOLS OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_GL ON CACHE BOOL "" FORCE)
set(MAGNUM_WITH_MATERIALTOOLS OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_MESHTOOLS OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_PRIMITIVES OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_SCENEGRAPH OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_SCENETOOLS OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_SHADERS ON CACHE BOOL "" FORCE)
set(MAGNUM_WITH_SHADERTOOLS OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_TEXT OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_TEXTURETOOLS OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_TRADE OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_VK OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_SDL2APPLICATION ON CACHE BOOL "" FORCE)
add_subdirectory(third-party/magnum EXCLUDE_FROM_ALL)
if(NOT SAVETOOL_USE_SYSTEM_CORRADE_MAGNUM)
set(CORRADE_BUILD_DEPRECATED OFF CACHE BOOL "" FORCE)
set(CORRADE_BUILD_STATIC ON CACHE BOOL "" FORCE)
set(CORRADE_BUILD_STATIC_PIC ON CACHE BOOL "" FORCE)
set(CORRADE_BUILD_STATIC_UNIQUE_GLOBALS OFF CACHE BOOL "" FORCE)
set(CORRADE_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(CORRADE_WITH_INTERCONNECT OFF CACHE BOOL "" FORCE)
set(CORRADE_WITH_PLUGINMANAGER OFF CACHE BOOL "" FORCE)
set(CORRADE_WITH_TESTSUITE OFF CACHE BOOL "" FORCE)
set(CORRADE_WITH_MAIN ON CACHE BOOL "" FORCE)
add_subdirectory(third-party/corrade EXCLUDE_FROM_ALL)
set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third-party/imgui)
set(MAGNUM_WITH_IMGUI ON CACHE BOOL "" FORCE)
add_subdirectory(third-party/magnum-integration EXCLUDE_FROM_ALL)
set(MAGNUM_BUILD_STATIC ON CACHE BOOL "" FORCE)
set(MAGNUM_BUILD_STATIC_PIC ON CACHE BOOL "" FORCE)
set(MAGNUM_BUILD_STATIC_UNIQUE_GLOBALS OFF CACHE BOOL "" FORCE)
set(MAGNUM_BUILD_DEPRECATED OFF CACHE BOOL "" FORCE)
set(MAGNUM_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(ENABLE_COMMONCRYPTO OFF CACHE BOOL "" FORCE)
set(ENABLE_GNUTLS OFF CACHE BOOL "" FORCE)
set(ENABLE_MBEDTLS OFF CACHE BOOL "" FORCE)
set(ENABLE_OPENSSL OFF CACHE BOOL "" FORCE)
set(ENABLE_WINDOWS_CRYPTO OFF CACHE BOOL "" FORCE)
set(ENABLE_BZIP2 OFF CACHE BOOL "" FORCE)
set(ENABLE_LZMA OFF CACHE BOOL "" FORCE)
set(ENABLE_ZSTD OFF CACHE BOOL "" FORCE)
set(BUILD_TOOLS OFF CACHE BOOL "" FORCE)
set(BUILD_REGRESS OFF CACHE BOOL "" FORCE)
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(BUILD_DOC OFF CACHE BOOL "" FORCE)
add_subdirectory(third-party/libzip EXCLUDE_FROM_ALL)
set(MAGNUM_TARGET_GL ON CACHE BOOL "" FORCE)
set(MAGNUM_TARGET_GLES OFF CACHE BOOL "" FORCE)
set(MAGNUM_TARGET_VK OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_AUDIO OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_DEBUGTOOLS OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_GL ON CACHE BOOL "" FORCE)
set(MAGNUM_WITH_MATERIALTOOLS OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_MESHTOOLS OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_PRIMITIVES OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_SCENEGRAPH OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_SCENETOOLS OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_SHADERS ON CACHE BOOL "" FORCE)
set(MAGNUM_WITH_SHADERTOOLS OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_TEXT OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_TEXTURETOOLS OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_TRADE OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_VK OFF CACHE BOOL "" FORCE)
set(MAGNUM_WITH_SDL2APPLICATION ON CACHE BOOL "" FORCE)
add_subdirectory(third-party/magnum EXCLUDE_FROM_ALL)
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)
set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third-party/imgui)
set(MAGNUM_WITH_IMGUI ON CACHE BOOL "" FORCE)
add_subdirectory(third-party/magnum-integration EXCLUDE_FROM_ALL)
endif(NOT SAVETOOL_USE_SYSTEM_CORRADE_MAGNUM)
if(NOT SAVETOOL_USE_SYSTEM_LIBZIP)
set(ENABLE_COMMONCRYPTO OFF CACHE BOOL "" FORCE)
set(ENABLE_GNUTLS OFF CACHE BOOL "" FORCE)
set(ENABLE_MBEDTLS OFF CACHE BOOL "" FORCE)
set(ENABLE_OPENSSL OFF CACHE BOOL "" FORCE)
set(ENABLE_WINDOWS_CRYPTO OFF CACHE BOOL "" FORCE)
set(ENABLE_BZIP2 OFF CACHE BOOL "" FORCE)
set(ENABLE_LZMA OFF CACHE BOOL "" FORCE)
set(ENABLE_ZSTD OFF CACHE BOOL "" FORCE)
set(BUILD_TOOLS OFF CACHE BOOL "" FORCE)
set(BUILD_REGRESS OFF CACHE BOOL "" FORCE)
set(BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(BUILD_DOC OFF CACHE BOOL "" FORCE)
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)
set(ENABLE_INET_PTON OFF CACHE BOOL "" FORCE)
set(ENABLE_DEBUG OFF CACHE BOOL "" FORCE)
set(ENABLE_THREADED_RESOLVER OFF CACHE BOOL "" FORCE)
set(HTTP_ONLY ON CACHE BOOL "" FORCE)
set(USE_LIBIDN2 OFF CACHE BOOL "" FORCE)
set(USE_WIN32_IDN ON CACHE BOOL "" FORCE)
set(CURL_USE_LIBPSL OFF CACHE BOOL "" FORCE)
set(CURL_STATIC_CRT OFF CACHE BOOL "" FORCE)
set(CURL_USE_SCHANNEL ON CACHE BOOL "" FORCE)
set(CURL_USE_LIBSSH2 OFF CACHE BOOL "" FORCE) # For some reason, even when HTTP_ONLY is set to ON, libcurl will try to link to libssh2.
add_subdirectory(third-party/curl EXCLUDE_FROM_ALL)
endif(NOT SAVETOOL_USE_SYSTEM_LIBCURL)
set(BUILD_TESTING OFF CACHE BOOL "" FORCE)
set(BUILD_CURL_EXE OFF CACHE BOOL "" FORCE)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(ENABLE_UNICODE ON CACHE BOOL "" FORCE)
set(ENABLE_INET_PTON OFF CACHE BOOL "" FORCE)
set(ENABLE_DEBUG OFF CACHE BOOL "" FORCE)
set(ENABLE_THREADED_RESOLVER OFF CACHE BOOL "" FORCE)
set(HTTP_ONLY ON CACHE BOOL "" FORCE)
set(USE_LIBIDN2 OFF CACHE BOOL "" FORCE)
set(USE_WIN32_IDN ON CACHE BOOL "" FORCE)
set(CURL_USE_LIBPSL OFF CACHE BOOL "" FORCE)
set(CURL_STATIC_CRT OFF CACHE BOOL "" FORCE)
set(CURL_USE_SCHANNEL ON CACHE BOOL "" FORCE)
set(CURL_USE_LIBSSH2 OFF CACHE BOOL "" FORCE) # For some reason, even when HTTP_ONLY is set to ON, libcurl will try to link to libssh2.
add_subdirectory(third-party/curl EXCLUDE_FROM_ALL)
add_subdirectory(src)

View file

@ -13,7 +13,7 @@ launch `MassBuilderSaveTool-<version>.exe`.
1. Install the 64-bit (`x86_64`) version of [MSYS2](https://www.msys2.org/) in its default path (`C:\msys64`), and
update it fully.
2. Run `pacman -S git mingw-w64-ucrt-x86_64-toolchain mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-ninja mingw-w64-ucrt-x86_64-zlib`.
2. Run `pacman -S git mingw-w64-ucrt-x86_64-toolchain mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-ninja`.
3. In a `URCT64` shell, type `git clone https://github.com/williamjcm/MassBuilderSaveTool`.
4. Type `cd MassBuilderSaveTool && git submodule init && git submodule update && mkdir build && cd build`.
5. Type `cmake -GNinja -DCMAKE_BUILD_TYPE=Release ..`

View file

@ -16,8 +16,6 @@
# components, which are:
#
# Containers - Containers library
# Interconnect - Interconnect library
# Main - Main library
# PluginManager - PluginManager library
# TestSuite - TestSuite library
# Utility - Utility library
@ -70,7 +68,7 @@
# mode for MSVC 2017
# CORRADE_MSVC2015_COMPATIBILITY - Defined if compiled with compatibility
# mode for MSVC 2015
# CORRADE_BUILD_DEPRECATED - Defined if compiled with deprecated features
# CORRADE_BUILD_DEPRECATED - Defined if compiled with deprecated APIs
# included
# CORRADE_BUILD_STATIC - Defined if compiled as static libraries.
# Default are shared libraries.
@ -80,9 +78,6 @@
# CORRADE_BUILD_MULTITHREADED - Defined if compiled in a way that makes it
# possible to safely use certain Corrade features simultaneously in multiple
# threads
# CORRADE_BUILD_CPU_RUNTIME_DISPATCH - Defined if built with code paths
# optimized for multiple architectres with the best matching variant selected
# at runtime based on detected CPU features
# CORRADE_TARGET_UNIX - Defined if compiled for some Unix flavor
# (Linux, BSD, macOS)
# CORRADE_TARGET_APPLE - Defined if compiled for Apple platforms
@ -103,8 +98,6 @@
# CORRADE_TARGET_MSVC - Defined if compiling with MSVC or Clang with
# a MSVC frontend
# CORRADE_TARGET_MINGW - Defined if compiling under MinGW
# CORRADE_CPU_USE_IFUNC - Defined if GNU IFUNC is allowed to be used
# for runtime dispatch in the Cpu library
# CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT - Defined if PluginManager
# doesn't support dynamic plugin loading due to platform limitations
# CORRADE_TESTSUITE_TARGET_XCTEST - Defined if TestSuite is targeting Xcode
@ -122,7 +115,6 @@
# automatically)
# CORRADE_TESTSUITE_XCTEST_RUNNER - Path to XCTestRunner.mm.in file
# CORRADE_TESTSUITE_ADB_RUNNER - Path to AdbRunner.sh file
# CORRADE_UTILITY_JS - Path to CorradeUtility.js file
# CORRADE_PEDANTIC_COMPILER_OPTIONS - List of pedantic compiler options used
# for targets with :prop_tgt:`CORRADE_USE_PEDANTIC_FLAGS` enabled
# CORRADE_PEDANTIC_COMPILER_DEFINITIONS - List of pedantic compiler
@ -216,7 +208,7 @@
# <metadata file>
# <sources>...)
#
# Unlike the above version this puts everything into ``<debug install dir>`` on
# Unline the above version this puts everything into ``<debug install dir>`` on
# both DLL and non-DLL platforms. If ``<debug install dir>`` is set to
# :variable:`CMAKE_CURRENT_BINARY_DIR` (e.g. for testing purposes), the files
# are copied directly, without the need to perform install step. Note that the
@ -272,7 +264,7 @@
# This file is part of Corrade.
#
# Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016,
# 2017, 2018, 2019, 2020, 2021, 2022, 2023
# 2017, 2018, 2019, 2020, 2021, 2022
# Vladimír Vondruš <mosra@centrum.cz>
#
# Permission is hereby granted, free of charge, to any person obtaining a
@ -325,7 +317,6 @@ set(_corradeFlags
BUILD_STATIC
BUILD_STATIC_UNIQUE_GLOBALS
BUILD_MULTITHREADED
BUILD_CPU_RUNTIME_DISPATCH
TARGET_UNIX
TARGET_APPLE
TARGET_IOS
@ -334,12 +325,10 @@ set(_corradeFlags
TARGET_WINDOWS_RT
TARGET_EMSCRIPTEN
TARGET_ANDROID
# TARGET_X86 etc, TARGET_32BIT, TARGET_BIG_ENDIAN and TARGET_LIBCXX etc.
# are not exposed to CMake as the meaning is unclear on platforms with
# multi-arch binaries or when mixing different STL implementations.
# TARGET_GCC etc are figured out via UseCorrade.cmake, as the compiler can
# be different when compiling the lib & when using it.
CPU_USE_IFUNC
# TARGET_X86 etc and TARGET_LIBCXX are not exposed to CMake as the meaning
# is unclear on platforms with multi-arch binaries or when mixing different
# STL implementations. TARGET_GCC etc are figured out via UseCorrade.cmake,
# as the compiler can be different when compiling the lib & when using it.
PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT
TESTSUITE_TARGET_XCTEST
UTILITY_USE_ANSI_COLORS)
@ -417,8 +406,6 @@ foreach(_component ${Corrade_FIND_COMPONENTS})
if(TARGET Corrade::${_component})
set(Corrade_${_component}_FOUND TRUE)
else()
unset(Corrade_${_component}_FOUND)
# Library (and not header-only) components
if(_component IN_LIST _CORRADE_LIBRARY_COMPONENTS AND NOT _component IN_LIST _CORRADE_HEADER_ONLY_COMPONENTS)
add_library(Corrade::${_component} UNKNOWN IMPORTED)
@ -474,9 +461,8 @@ foreach(_component ${Corrade_FIND_COMPONENTS})
# Interconnect library
if(_component STREQUAL Interconnect)
# Disable /OPT:ICF on MSVC, which merges functions with identical
# contents and thus breaks signal comparison. Same case is for
# clang-cl which uses the MSVC linker by default.
if(CORRADE_TARGET_WINDOWS AND (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" OR CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC"))
# contents and thus breaks signal comparison
if(CORRADE_TARGET_WINDOWS AND CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
if(CMAKE_VERSION VERSION_LESS 3.13)
set_property(TARGET Corrade::${_component} PROPERTY
INTERFACE_LINK_LIBRARIES "-OPT:NOICF,REF")
@ -510,33 +496,25 @@ foreach(_component ${Corrade_FIND_COMPONENTS})
elseif(_component STREQUAL PluginManager)
# -ldl is handled by Utility now
# TestSuite library has some additional files. If those are not found,
# set the component _FOUND variable to false so it works properly both
# when the component is required and when it's optional.
# TestSuite library has some additional files
elseif(_component STREQUAL TestSuite)
# XCTest runner file
if(CORRADE_TESTSUITE_TARGET_XCTEST)
find_file(CORRADE_TESTSUITE_XCTEST_RUNNER XCTestRunner.mm.in
PATH_SUFFIXES share/corrade/TestSuite)
if(NOT CORRADE_TESTSUITE_XCTEST_RUNNER)
set(Corrade_${_component}_FOUND FALSE)
endif()
set(CORRADE_TESTSUITE_XCTEST_RUNNER_NEEDED CORRADE_TESTSUITE_XCTEST_RUNNER)
# ADB runner file
elseif(CORRADE_TARGET_ANDROID)
find_file(CORRADE_TESTSUITE_ADB_RUNNER AdbRunner.sh
PATH_SUFFIXES share/corrade/TestSuite)
if(NOT CORRADE_TESTSUITE_ADB_RUNNER)
set(Corrade_${_component}_FOUND FALSE)
endif()
set(CORRADE_TESTSUITE_ADB_RUNNER_NEEDED CORRADE_TESTSUITE_ADB_RUNNER)
# Emscripten runner file
elseif(CORRADE_TARGET_EMSCRIPTEN)
find_file(CORRADE_TESTSUITE_EMSCRIPTEN_RUNNER EmscriptenRunner.html.in
PATH_SUFFIXES share/corrade/TestSuite)
if(NOT CORRADE_TESTSUITE_EMSCRIPTEN_RUNNER)
set(Corrade_${_component}_FOUND FALSE)
endif()
set(CORRADE_TESTSUITE_EMSCRIPTEN_RUNNER_NEEDED CORRADE_TESTSUITE_EMSCRIPTEN_RUNNER)
endif()
# Utility library (contains all setup that is used by others)
@ -561,15 +539,6 @@ foreach(_component ${Corrade_FIND_COMPONENTS})
set_property(TARGET Corrade::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES "log")
endif()
# Emscripten has various stuff implemented in JS
if(CORRADE_TARGET_EMSCRIPTEN)
find_file(CORRADE_UTILITY_JS CorradeUtility.js
PATH_SUFFIXES lib)
set_property(TARGET Corrade::${_component} APPEND PROPERTY
# TODO switch to INTERFACE_LINK_OPTIONS and SHELL: once we
# require CMake 3.13 unconditionally
INTERFACE_LINK_LIBRARIES "--js-library ${CORRADE_UTILITY_JS}")
endif()
endif()
# Find library includes
@ -590,14 +559,11 @@ foreach(_component ${Corrade_FIND_COMPONENTS})
endforeach()
endif()
# Decide if the component was found, unless the _FOUND is already set
# by something above.
if(NOT DEFINED Corrade_${_component}_FOUND)
if((_component IN_LIST _CORRADE_LIBRARY_COMPONENTS AND _CORRADE_${_COMPONENT}_INCLUDE_DIR AND (_component IN_LIST _CORRADE_HEADER_ONLY_COMPONENTS OR CORRADE_${_COMPONENT}_LIBRARY_RELEASE OR CORRADE_${_COMPONENT}_LIBRARY_DEBUG)) OR (_component IN_LIST _CORRADE_EXECUTABLE_COMPONENTS AND CORRADE_${_COMPONENT}_EXECUTABLE))
set(Corrade_${_component}_FOUND TRUE)
else()
set(Corrade_${_component}_FOUND FALSE)
endif()
# Decide if the component was found
if((_component IN_LIST _CORRADE_LIBRARY_COMPONENTS AND _CORRADE_${_COMPONENT}_INCLUDE_DIR AND (_component IN_LIST _CORRADE_HEADER_ONLY_COMPONENTS OR CORRADE_${_COMPONENT}_LIBRARY_RELEASE OR CORRADE_${_COMPONENT}_LIBRARY_DEBUG)) OR (_component IN_LIST _CORRADE_EXECUTABLE_COMPONENTS AND CORRADE_${_COMPONENT}_EXECUTABLE))
set(Corrade_${_component}_FOUND TRUE)
else()
set(Corrade_${_component}_FOUND FALSE)
endif()
endif()
endforeach()
@ -624,7 +590,7 @@ if(NOT CMAKE_VERSION VERSION_LESS 3.16)
# misleading messages.
elseif(NOT _component IN_LIST _CORRADE_IMPLICITLY_ENABLED_COMPONENTS)
string(TOUPPER ${_component} _COMPONENT)
list(APPEND _CORRADE_REASON_FAILURE_MESSAGE "${_component} is not built by default. Make sure you enabled CORRADE_WITH_${_COMPONENT} when building Corrade.")
list(APPEND _CORRADE_REASON_FAILURE_MESSAGE "${_component} is not built by default. Make sure you enabled WITH_${_COMPONENT} when building Corrade.")
# Otherwise we have no idea. Better be silent than to print something
# misleading.
else()
@ -640,6 +606,9 @@ find_package_handle_standard_args(Corrade REQUIRED_VARS
CORRADE_INCLUDE_DIR
_CORRADE_MODULE_DIR
_CORRADE_CONFIGURE_FILE
${CORRADE_TESTSUITE_XCTEST_RUNNER_NEEDED}
${CORRADE_TESTSUITE_ADB_RUNNER_NEEDED}
${CORRADE_TESTSUITE_EMSCRIPTEN_RUNNER_NEEDED}
HANDLE_COMPONENTS
${_CORRADE_REASON_FAILURE_MESSAGE})

View file

@ -36,7 +36,7 @@
# This file is part of Magnum.
#
# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
# 2020, 2021, 2022, 2023 Vladimír Vondruš <mosra@centrum.cz>
# 2020, 2021, 2022 Vladimír Vondruš <mosra@centrum.cz>
# Copyright © 2018 Jonathan Hale <squareys@googlemail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a

View file

@ -57,7 +57,6 @@
# Audio - Audio library
# DebugTools - DebugTools library
# GL - GL library
# MaterialTools - MaterialTools library
# MeshTools - MeshTools library
# Primitives - Primitives library
# SceneGraph - SceneGraph library
@ -79,6 +78,7 @@
# WindowlessGlxApplication - Windowless GLX application
# WindowlessIosApplication - Windowless iOS application
# WindowlessWglApplication - Windowless WGL application
# WindowlessWindowsEglApplication - Windowless Windows/EGL application
# CglContext - CGL context
# EglContext - EGL context
# GlxContext - GLX context
@ -128,18 +128,19 @@
#
# Features of found Magnum library are exposed in these variables:
#
# MAGNUM_BUILD_DEPRECATED - Defined if compiled with deprecated features
# MAGNUM_BUILD_DEPRECATED - Defined if compiled with deprecated APIs
# included
# MAGNUM_BUILD_STATIC - Defined if compiled as static libraries
# MAGNUM_BUILD_STATIC_UNIQUE_GLOBALS - Defined if static libraries keep the
# globals unique even across different shared libraries
# MAGNUM_TARGET_GL - Defined if compiled with OpenGL interop
# MAGNUM_TARGET_GLES - Defined if compiled for OpenGL ES
# MAGNUM_TARGET_GLES2 - Defined if compiled for OpenGL ES 2.0
# MAGNUM_TARGET_GLES3 - Defined if compiled for OpenGL ES 3.0
# MAGNUM_TARGET_DESKTOP_GLES - Defined if compiled with OpenGL ES
# emulation on desktop OpenGL
# MAGNUM_TARGET_WEBGL - Defined if compiled for WebGL
# MAGNUM_TARGET_GLES2 - Defined if compiled for OpenGL ES 2.0 / WebGL
# 1 instead of OpenGL ES 3.0+ / WebGL 2
# MAGNUM_TARGET_EGL - Defined if compiled for EGL instead of a
# platform-specific OpenGL support library like CGL, EAGL, GLX or WGL
# MAGNUM_TARGET_HEADLESS - Defined if compiled for headless machines
# MAGNUM_TARGET_VK - Defined if compiled with Vulkan interop
#
# The following variables are provided for backwards compatibility purposes
@ -148,12 +149,6 @@
#
# MAGNUM_BUILD_MULTITHREADED - Alias to CORRADE_BUILD_MULTITHREADED. Use
# CORRADE_BUILD_MULTITHREADED instead.
# MAGNUM_TARGET_HEADLESS - Alias to MAGNUM_TARGET_EGL, unless on iOS,
# Android, Emscripten or Windows RT. Use MAGNUM_TARGET_EGL instead.
# MAGNUM_TARGET_DESKTOP_GLES` - Defined if compiled for OpenGL ES but
# GLX / WGL is used instead of EGL. Use MAGNUM_TARGET_EGL instead.
# MAGNUM_TARGET_GLES3 - Defined if compiled for OpenGL ES 3.0+ /
# WebGL 2. Use an inverse of the MAGNUM_TARGET_GLES2 variable instead.
#
# Additionally these variables are defined for internal usage:
#
@ -164,7 +159,6 @@
# MAGNUM_*_LIBRARY - Component libraries (w/o dependencies)
# MAGNUM_*_LIBRARY_DEBUG - Debug version of given library, if found
# MAGNUM_*_LIBRARY_RELEASE - Release version of given library, if found
# MAGNUM_PLATFORM_JS - Path to MagnumPlatform.js file
# MAGNUM_BINARY_INSTALL_DIR - Binary installation directory
# MAGNUM_LIBRARY_INSTALL_DIR - Library installation directory
# MAGNUM_DATA_INSTALL_DIR - Data installation directory
@ -208,7 +202,7 @@
# This file is part of Magnum.
#
# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
# 2020, 2021, 2022, 2023 Vladimír Vondruš <mosra@centrum.cz>
# 2020, 2021, 2022 Vladimír Vondruš <mosra@centrum.cz>
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
@ -229,37 +223,18 @@
# DEALINGS IN THE SOFTWARE.
#
# CMake policies used by FindMagnum are popped again at the end.
cmake_policy(PUSH)
# Prefer GLVND when finding OpenGL. If this causes problems (known to fail with
# NVidia drivers in Debian Buster, reported on 2019-04-09), users can override
# this by setting OpenGL_GL_PREFERENCE to LEGACY.
if(POLICY CMP0072)
cmake_policy(SET CMP0072 NEW)
endif()
# Corrade library dependencies
set(_MAGNUM_CORRADE_DEPENDENCIES )
foreach(_magnum_component ${Magnum_FIND_COMPONENTS})
set(_MAGNUM_${_magnum_component}_CORRADE_DEPENDENCIES )
foreach(_component ${Magnum_FIND_COMPONENTS})
string(TOUPPER ${_component} _COMPONENT)
# Unrolling the transitive dependencies here so this doesn't need to be
# after resolving inter-component dependencies. Listing also all plugins.
if(_magnum_component MATCHES "^(Audio|DebugTools|MeshTools|Primitives|SceneTools|ShaderTools|Text|TextureTools|Trade|.+Importer|.+ImageConverter|.+Font|.+ShaderConverter)$")
list(APPEND _MAGNUM_${_magnum_component}_CORRADE_DEPENDENCIES PluginManager)
endif()
if(_magnum_component STREQUAL DebugTools)
# DebugTools depends on TestSuite optionally, so if it's not there
# assume it wasn't compiled against it. Also, all variables from the
# FindCorrade module overwrite the local variables here (in particular
# _component, _COMPONENT and such), so we need to prefix extensively.
find_package(Corrade QUIET COMPONENTS TestSuite)
if(Corrade_TestSuite_FOUND)
list(APPEND _MAGNUM_${_magnum_component}_CORRADE_DEPENDENCIES TestSuite)
endif()
if(_component MATCHES "^(Audio|DebugTools|MeshTools|Primitives|SceneTools|ShaderTools|Text|TextureTools|Trade|.+Importer|.+ImageConverter|.+Font|.+ShaderConverter)$")
set(_MAGNUM_${_COMPONENT}_CORRADE_DEPENDENCIES PluginManager)
endif()
list(APPEND _MAGNUM_CORRADE_DEPENDENCIES ${_MAGNUM_${_magnum_component}_CORRADE_DEPENDENCIES})
list(APPEND _MAGNUM_CORRADE_DEPENDENCIES ${_MAGNUM_${_COMPONENT}_CORRADE_DEPENDENCIES})
endforeach()
find_package(Corrade REQUIRED Utility ${_MAGNUM_CORRADE_DEPENDENCIES})
@ -293,8 +268,10 @@ set(_magnumFlags
TARGET_GL
TARGET_GLES
TARGET_GLES2
TARGET_GLES3
TARGET_DESKTOP_GLES
TARGET_WEBGL
TARGET_EGL
TARGET_HEADLESS
TARGET_VK)
foreach(_magnumFlag ${_magnumFlags})
list(FIND _magnumConfigure "#define MAGNUM_${_magnumFlag}" _magnum_${_magnumFlag})
@ -303,23 +280,17 @@ foreach(_magnumFlag ${_magnumFlags})
endif()
endforeach()
# For compatibility only, to be removed at some point. Refer to
# src/Magnum/configure.h.cmake for the decision logic here.
if(MAGNUM_BUILD_DEPRECATED)
if(CORRADE_BUILD_MULTITHREADED)
set(MAGNUM_BUILD_MULTITHREADED 1)
endif()
if(NOT CORRADE_TARGET_IOS AND NOT CORRADE_TARGET_ANDROID AND NOT CORRADE_TARGET_EMSCRIPTEN AND NOT CORRADE_TARGET_WINDOWS_RT)
if(NOT MAGNUM_TARGET_GLES AND MAGNUM_TARGET_EGL)
set(MAGNUM_TARGET_HEADLESS 1)
endif()
if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_EGL)
set(MAGNUM_TARGET_DESKTOP_GLES 1)
endif()
endif()
if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_GLES2)
set(MAGNUM_TARGET_GLES3 1)
endif()
# For compatibility only, to be removed at some point
if(MAGNUM_BUILD_DEPRECATED AND CORRADE_BUILD_MULTITHREADED)
set(MAGNUM_BUILD_MULTITHREADED 1)
endif()
# OpenGL library preference. Prefer to use GLVND, since that's the better
# approach nowadays, but allow the users to override it from outside in case
# it is broken for some reason (Nvidia drivers in Debian's testing (Buster) --
# reported on 2019-04-09).
if(NOT CMAKE_VERSION VERSION_LESS 3.10 AND NOT OpenGL_GL_PREFERENCE)
set(OpenGL_GL_PREFERENCE GLVND)
endif()
# Base Magnum library
@ -384,8 +355,8 @@ endif()
# Component distinction (listing them explicitly to avoid mistakes with finding
# components from other repositories)
set(_MAGNUM_LIBRARY_COMPONENTS
Audio DebugTools GL MaterialTools MeshTools Primitives SceneGraph
SceneTools Shaders ShaderTools Text TextureTools Trade
Audio DebugTools GL MeshTools Primitives SceneGraph SceneTools Shaders
ShaderTools Text TextureTools Trade
WindowlessEglApplication EglContext OpenGLTester)
set(_MAGNUM_PLUGIN_COMPONENTS
AnyAudioImporter AnyImageConverter AnyImageImporter AnySceneConverter
@ -424,7 +395,7 @@ if(CORRADE_TARGET_UNIX AND NOT CORRADE_TARGET_APPLE)
list(APPEND _MAGNUM_LIBRARY_COMPONENTS GlxApplication XEglApplication WindowlessGlxApplication GlxContext)
endif()
if(CORRADE_TARGET_WINDOWS)
list(APPEND _MAGNUM_LIBRARY_COMPONENTS WindowlessWglApplication WglContext)
list(APPEND _MAGNUM_LIBRARY_COMPONENTS WindowlessWglApplication WglContext WindowlessWindowsEglApplication)
endif()
if(CORRADE_TARGET_UNIX OR CORRADE_TARGET_WINDOWS)
list(APPEND _MAGNUM_EXECUTABLE_COMPONENTS fontconverter distancefieldconverter)
@ -449,24 +420,30 @@ if(MAGNUM_TARGET_GL)
set(_MAGNUM_DebugTools_GL_DEPENDENCY_IS_OPTIONAL ON)
endif()
set(_MAGNUM_MaterialTools_DEPENDENCIES Trade)
set(_MAGNUM_MeshTools_DEPENDENCIES Trade)
if(MAGNUM_TARGET_GL)
list(APPEND _MAGNUM_MeshTools_DEPENDENCIES GL)
endif()
set(_MAGNUM_OpenGLTester_DEPENDENCIES GL)
if(MAGNUM_TARGET_EGL)
if(MAGNUM_TARGET_HEADLESS OR CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID)
list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessEglApplication)
elseif(CORRADE_TARGET_IOS)
list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessIosApplication)
elseif(CORRADE_TARGET_APPLE)
elseif(CORRADE_TARGET_APPLE AND NOT MAGNUM_TARGET_GLES)
list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessCglApplication)
elseif(CORRADE_TARGET_UNIX)
list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessGlxApplication)
if(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES)
list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessEglApplication)
else()
list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessGlxApplication)
endif()
elseif(CORRADE_TARGET_WINDOWS)
list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessWglApplication)
if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES)
list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessWglApplication)
else()
list(APPEND _MAGNUM_OpenGLTester_DEPENDENCIES WindowlessWindowsEglApplication)
endif()
endif()
set(_MAGNUM_Primitives_DEPENDENCIES MeshTools Trade)
@ -515,6 +492,7 @@ set(_MAGNUM_WindowlessEglApplication_DEPENDENCIES GL)
set(_MAGNUM_WindowlessGlxApplication_DEPENDENCIES GL)
set(_MAGNUM_WindowlessIosApplication_DEPENDENCIES GL)
set(_MAGNUM_WindowlessWglApplication_DEPENDENCIES GL)
set(_MAGNUM_WindowlessWindowsEglApplication_DEPENDENCIES GL)
set(_MAGNUM_XEglApplication_DEPENDENCIES GL)
set(_MAGNUM_CglContext_DEPENDENCIES GL)
set(_MAGNUM_EglContext_DEPENDENCIES GL)
@ -702,17 +680,7 @@ foreach(_component ${Magnum_FIND_COMPONENTS})
set_property(TARGET Magnum::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES android EGL::EGL)
# Emscripten application dependencies
elseif(_component STREQUAL EmscriptenApplication)
# Emscripten has various stuff implemented in JS
if(CORRADE_TARGET_EMSCRIPTEN)
find_file(MAGNUM_PLATFORM_JS MagnumPlatform.js
PATH_SUFFIXES lib)
set_property(TARGET Magnum::${_component} APPEND PROPERTY
# TODO switch to INTERFACE_LINK_OPTIONS and SHELL: once
# we require CMake 3.13 unconditionally
INTERFACE_LINK_LIBRARIES "--js-library ${MAGNUM_PLATFORM_JS}")
endif()
# EmscriptenApplication has no additional dependencies
# GLFW application dependencies
elseif(_component STREQUAL GlfwApplication)
@ -731,7 +699,7 @@ foreach(_component ${Magnum_FIND_COMPONENTS})
INTERFACE_LINK_LIBRARIES ${CMAKE_DL_LIBS})
endif()
# With GLVND (since CMake 3.10) we need to explicitly link to
# With GLVND (since CMake 3.11) we need to explicitly link to
# GLX/EGL because libOpenGL doesn't provide it. For EGL we have
# our own EGL find module, which makes things simpler. The
# upstream FindOpenGL is anything but simple. Also can't use
@ -740,16 +708,16 @@ foreach(_component ${Magnum_FIND_COMPONENTS})
# OPENGL_opengl_LIBRARY because that's set even if
# OpenGL_GL_PREFERENCE is explicitly set to LEGACY.
if(MAGNUM_TARGET_GL)
if(MAGNUM_TARGET_EGL)
find_package(EGL)
set_property(TARGET Magnum::${_component} APPEND
PROPERTY INTERFACE_LINK_LIBRARIES EGL::EGL)
elseif(CORRADE_TARGET_UNIX AND NOT CORRADE_TARGET_APPLE)
if(CORRADE_TARGET_UNIX AND NOT CORRADE_TARGET_APPLE AND (NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES))
find_package(OpenGL)
if(OPENGL_opengl_LIBRARY AND OpenGL_GL_PREFERENCE STREQUAL GLVND)
set_property(TARGET Magnum::${_component} APPEND
PROPERTY INTERFACE_LINK_LIBRARIES OpenGL::GLX)
endif()
elseif(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES AND NOT CORRADE_TARGET_EMSCRIPTEN)
find_package(EGL)
set_property(TARGET Magnum::${_component} APPEND
PROPERTY INTERFACE_LINK_LIBRARIES EGL::EGL)
endif()
endif()
@ -768,17 +736,9 @@ foreach(_component ${Magnum_FIND_COMPONENTS})
elseif(CORRADE_TARGET_UNIX)
set_property(TARGET Magnum::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES ${CMAKE_DL_LIBS})
# Emscripten has various stuff implemented in JS
elseif(CORRADE_TARGET_EMSCRIPTEN)
find_file(MAGNUM_PLATFORM_JS MagnumPlatform.js
PATH_SUFFIXES lib)
set_property(TARGET Magnum::${_component} APPEND PROPERTY
# TODO switch to INTERFACE_LINK_OPTIONS and SHELL: once
# we require CMake 3.13 unconditionally
INTERFACE_LINK_LIBRARIES "--js-library ${MAGNUM_PLATFORM_JS}")
endif()
# With GLVND (since CMake 3.10) we need to explicitly link to
# With GLVND (since CMake 3.11) we need to explicitly link to
# GLX/EGL because libOpenGL doesn't provide it. For EGL we have
# our own EGL find module, which makes things simpler. The
# upstream FindOpenGL is anything but simple. Also can't use
@ -787,16 +747,16 @@ foreach(_component ${Magnum_FIND_COMPONENTS})
# OPENGL_opengl_LIBRARY because that's set even if
# OpenGL_GL_PREFERENCE is explicitly set to LEGACY.
if(MAGNUM_TARGET_GL)
if(MAGNUM_TARGET_EGL)
find_package(EGL)
set_property(TARGET Magnum::${_component} APPEND
PROPERTY INTERFACE_LINK_LIBRARIES EGL::EGL)
elseif(CORRADE_TARGET_UNIX AND NOT CORRADE_TARGET_APPLE)
if(CORRADE_TARGET_UNIX AND NOT CORRADE_TARGET_APPLE AND (NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES))
find_package(OpenGL)
if(OPENGL_opengl_LIBRARY AND OpenGL_GL_PREFERENCE STREQUAL GLVND)
set_property(TARGET Magnum::${_component} APPEND
PROPERTY INTERFACE_LINK_LIBRARIES OpenGL::GLX)
endif()
elseif(MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_DESKTOP_GLES AND NOT CORRADE_TARGET_EMSCRIPTEN)
find_package(EGL)
set_property(TARGET Magnum::${_component} APPEND
PROPERTY INTERFACE_LINK_LIBRARIES EGL::EGL)
endif()
endif()
@ -808,19 +768,12 @@ foreach(_component ${Magnum_FIND_COMPONENTS})
set_property(TARGET Magnum::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES ${X11_LIBRARIES})
# With GLVND (since CMake 3.10) we need to explicitly link to
# With GLVND (since CMake 3.11) we need to explicitly link to
# GLX because libOpenGL doesn't provide it. Also can't use
# OpenGL_OpenGL_FOUND, because that one is set also if GLVND is
# *not* found. WTF. Also can't just check for
# OPENGL_opengl_LIBRARY because that's set even if
# OpenGL_GL_PREFERENCE is explicitly set to LEGACY.
#
# If MAGNUM_TARGET_GLES and MAGNUM_TARGET_EGL is set, these
# applications can be built only if GLVND is available as
# otherwise there would be a conflict between libGL and
# libGLES. Thus, if GLVND is not available, it won't link
# libGLX here, but that shouldn't be a problem since the
# application library won't exist either.
find_package(OpenGL)
if(OPENGL_opengl_LIBRARY AND OpenGL_GL_PREFERENCE STREQUAL GLVND)
set_property(TARGET Magnum::${_component} APPEND PROPERTY
@ -846,6 +799,12 @@ foreach(_component ${Magnum_FIND_COMPONENTS})
# Windowless WGL application has no additional dependencies
# Windowless Windows/EGL application dependencies
elseif(_component STREQUAL WindowlessWindowsEglApplication)
find_package(EGL)
set_property(TARGET Magnum::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES EGL::EGL)
# X/EGL application dependencies
elseif(_component STREQUAL XEglApplication)
find_package(EGL)
@ -863,7 +822,7 @@ foreach(_component ${Magnum_FIND_COMPONENTS})
# GLX context dependencies
if(_component STREQUAL GlxContext)
# With GLVND (since CMake 3.10) we need to explicitly link to
# With GLVND (since CMake 3.11) we need to explicitly link to
# GLX because libOpenGL doesn't provide it. Also can't use
# OpenGL_OpenGL_FOUND, because that one is set also if GLVND is
# *not* found. If GLVND is not used, link to X11 instead. Also
@ -895,14 +854,14 @@ foreach(_component ${Magnum_FIND_COMPONENTS})
elseif(_component STREQUAL Audio)
find_package(OpenAL)
set_property(TARGET Magnum::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES OpenAL::OpenAL)
INTERFACE_LINK_LIBRARIES Corrade::PluginManager OpenAL::OpenAL)
# No special setup for DebugTools library
# GL library
elseif(_component STREQUAL GL)
if(NOT MAGNUM_TARGET_GLES OR (MAGNUM_TARGET_GLES AND NOT MAGNUM_TARGET_EGL AND NOT CORRADE_TARGET_IOS))
# If the GLVND library (CMake 3.10+) was found, link to the
if(NOT MAGNUM_TARGET_GLES OR MAGNUM_TARGET_DESKTOP_GLES)
# If the GLVND library (CMake 3.11+) was found, link to the
# imported target. Otherwise (and also on all systems except
# Linux) link to the classic libGL. Can't use
# OpenGL_OpenGL_FOUND, because that one is set also if GLVND is
@ -921,16 +880,12 @@ foreach(_component ${Magnum_FIND_COMPONENTS})
find_package(OpenGLES2 REQUIRED)
set_property(TARGET Magnum::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES OpenGLES2::OpenGLES2)
else()
elseif(MAGNUM_TARGET_GLES3)
find_package(OpenGLES3 REQUIRED)
set_property(TARGET Magnum::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES OpenGLES3::OpenGLES3)
endif()
# MaterialTools library
elseif(_component STREQUAL MaterialTools)
set(_MAGNUM_${_COMPONENT}_INCLUDE_PATH_NAMES PhongToPbrMetallicRoughness.h)
# MeshTools library
elseif(_component STREQUAL MeshTools)
set(_MAGNUM_${_COMPONENT}_INCLUDE_PATH_NAMES CompressIndices.h)
@ -949,19 +904,26 @@ foreach(_component ${Magnum_FIND_COMPONENTS})
# No special setup for SceneGraph library
# SceneTools library
elseif(_component STREQUAL SceneTools)
set(_MAGNUM_${_COMPONENT}_INCLUDE_PATH_NAMES Hierarchy.h)
# ShaderTools library
elseif(_component STREQUAL ShaderTools)
set_property(TARGET Magnum::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES Corrade::PluginManager)
# No special setup for ShaderTools library
# No special setup for Shaders library
# No special setup for Text library
# Text library
elseif(_component STREQUAL Text)
set_property(TARGET Magnum::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES Corrade::PluginManager)
# TextureTools library
elseif(_component STREQUAL TextureTools)
set(_MAGNUM_${_COMPONENT}_INCLUDE_PATH_NAMES Atlas.h)
# No special setup for Trade library
# Trade library
elseif(_component STREQUAL Trade)
set_property(TARGET Magnum::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES Corrade::PluginManager)
# Vk library
elseif(_component STREQUAL Vk)
@ -990,13 +952,8 @@ foreach(_component ${Magnum_FIND_COMPONENTS})
endif()
# Automatic import of static plugins. Skip in case the include dir was
# not found -- that'll fail later with a proper message. Skip it also
# if the include dir doesn't contain the generated configure.h, which
# is the case with Magnum as a subproject and given plugin not enabled
# -- there it finds just the sources, where's just configure.h.cmake,
# and that's not useful for anything. The assumption here is that it
# will fail later anyway on the binary not being found.
if(_component IN_LIST _MAGNUM_PLUGIN_COMPONENTS AND _MAGNUM_${_COMPONENT}_INCLUDE_DIR AND EXISTS ${_MAGNUM_${_COMPONENT}_INCLUDE_DIR}/configure.h)
# not found -- that'll fail later with a proper message.
if(_component IN_LIST _MAGNUM_PLUGIN_COMPONENTS AND _MAGNUM_${_COMPONENT}_INCLUDE_DIR)
# Automatic import of static plugins
file(READ ${_MAGNUM_${_COMPONENT}_INCLUDE_DIR}/configure.h _magnum${_component}Configure)
string(FIND "${_magnum${_component}Configure}" "#define MAGNUM_${_COMPONENT}_BUILD_STATIC" _magnum${_component}_BUILD_STATIC)
@ -1010,13 +967,9 @@ foreach(_component ${Magnum_FIND_COMPONENTS})
# are optional dependencies, defer adding them to later once we know if
# they were found or not.
if(_component IN_LIST _MAGNUM_LIBRARY_COMPONENTS OR _component IN_LIST _MAGNUM_PLUGIN_COMPONENTS)
foreach(_dependency ${_MAGNUM_${_component}_CORRADE_DEPENDENCIES})
set_property(TARGET Magnum::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES Corrade::${_dependency})
endforeach()
set_property(TARGET Magnum::${_component} APPEND PROPERTY
INTERFACE_LINK_LIBRARIES Magnum::Magnum)
set(_MAGNUM_${_component}_OPTIONAL_DEPENDENCIES_TO_ADD )
set(_MAGNUM_${component}_OPTIONAL_DEPENDENCIES_TO_ADD )
foreach(_dependency ${_MAGNUM_${_component}_DEPENDENCIES})
if(NOT _MAGNUM_${_component}_${_dependency}_DEPENDENCY_IS_OPTIONAL)
set_property(TARGET Magnum::${_component} APPEND PROPERTY
@ -1078,6 +1031,20 @@ if(CORRADE_TARGET_EMSCRIPTEN)
MAGNUM_EMSCRIPTENAPPLICATION_JS
MAGNUM_WINDOWLESSEMSCRIPTENAPPLICATION_JS
MAGNUM_WEBAPPLICATION_CSS)
# If we are on CMake 3.13 and up, `-s USE_WEBGL2=1` linker option is
# propagated from FindOpenGLES3.cmake already. If not (and the GL library
# is used), we need to modify the global CMAKE_EXE_LINKER_FLAGS. Do it here
# instead of in FindOpenGLES3.cmake so it works also for CMake subprojects
# (in which case find_package(OpenGLES3) is called in (and so
# CMAKE_EXE_LINKER_FLAGS would be modified in) Magnum's root CMakeLists.txt
# and thus can't affect the variable in the outer project). CMake supports
# IN_LIST as an operator since 3.1 (Emscripten needs at least 3.7), but
# it's behind a policy, so enable that one as well.
cmake_policy(SET CMP0057 NEW)
if(CMAKE_VERSION VERSION_LESS 3.13 AND GL IN_LIST Magnum_FIND_COMPONENTS AND NOT MAGNUM_TARGET_GLES2 AND NOT CMAKE_EXE_LINKER_FLAGS MATCHES "-s USE_WEBGL2=1")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s USE_WEBGL2=1")
endif()
endif()
# For CMake 3.16+ with REASON_FAILURE_MESSAGE, provide additional potentially
@ -1102,7 +1069,7 @@ if(NOT CMAKE_VERSION VERSION_LESS 3.16)
# misleading messages.
elseif(NOT _component IN_LIST _MAGNUM_IMPLICITLY_ENABLED_COMPONENTS)
string(TOUPPER ${_component} _COMPONENT)
list(APPEND _MAGNUM_REASON_FAILURE_MESSAGE "${_component} is not built by default. Make sure you enabled MAGNUM_WITH_${_COMPONENT} when building Magnum.")
list(APPEND _MAGNUM_REASON_FAILURE_MESSAGE "${_component} is not built by default. Make sure you enabled WITH_${_COMPONENT} when building Magnum.")
# Otherwise we have no idea. Better be silent than to print something
# misleading.
else()
@ -1304,6 +1271,3 @@ if(MAGNUM_PLUGINS_RELEASE_DIR)
set(MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/sceneconverters)
set(MAGNUM_PLUGINS_AUDIOIMPORTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/audioimporters)
endif()
# Resets CMake policies set at the top of the file to not affect other code.
cmake_policy(POP)

View file

@ -48,7 +48,7 @@
# This file is part of Magnum.
#
# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
# 2020, 2021, 2022, 2023 Vladimír Vondruš <mosra@centrum.cz>
# 2020, 2021, 2022 Vladimír Vondruš <mosra@centrum.cz>
# Copyright © 2018 Konstantinos Chatzilygeroudis <costashatz@gmail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a
@ -89,14 +89,8 @@ if(_MAGNUMINTEGRATION_OPTIONAL_DEPENDENCIES)
find_package(Magnum OPTIONAL_COMPONENTS ${_MAGNUMINTEGRATION_OPTIONAL_DEPENDENCIES})
endif()
# Global include dir that's unique to Magnum Integration. Often it will be
# installed alongside Magnum, which is why the hint, but if not, it shouldn't
# just pick MAGNUM_INCLUDE_DIR because then _MAGNUMINTEGRATION_*_INCLUDE_DIR
# will fail to be found. In case of CMake subprojects the versionIntegration.h
# is generated inside the build dir so this won't find it, instead
# src/CMakeLists.txt forcibly sets MAGNUMINTEGRATION_INCLUDE_DIR as an internal
# cache value to make that work.
find_path(MAGNUMINTEGRATION_INCLUDE_DIR Magnum/versionIntegration.h
# Global integration include dir
find_path(MAGNUMINTEGRATION_INCLUDE_DIR Magnum
HINTS ${MAGNUM_INCLUDE_DIR})
mark_as_advanced(MAGNUMINTEGRATION_INCLUDE_DIR)
@ -320,7 +314,7 @@ if(NOT CMAKE_VERSION VERSION_LESS 3.16)
# misleading messages.
elseif(NOT _component IN_LIST _MAGNUMINTEGRATION_IMPLICITLY_ENABLED_COMPONENTS)
string(TOUPPER ${_component} _COMPONENT)
list(APPEND _MAGNUMINTEGRATION_REASON_FAILURE_MESSAGE "${_component} is not built by default. Make sure you enabled MAGNUM_WITH_${_COMPONENT} when building Magnum Integration.")
list(APPEND _MAGNUMINTEGRATION_REASON_FAILURE_MESSAGE "${_component} is not built by default. Make sure you enabled WITH_${_COMPONENT} when building Magnum Integration.")
# Otherwise we have no idea. Better be silent than to print something
# misleading.
else()

View file

@ -20,7 +20,7 @@
# This file is part of Magnum.
#
# Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
# 2020, 2021, 2022, 2023 Vladimír Vondruš <mosra@centrum.cz>
# 2020, 2021, 2022 Vladimír Vondruš <mosra@centrum.cz>
# Copyright © 2018 Jonathan Hale <squareys@googlemail.com>
#
# Permission is hereby granted, free of charge, to any person obtaining a
@ -138,10 +138,10 @@ else()
# which CMake somehow prefers before the SDL2-2.0.dylib file. Making
# the dylib first so it is preferred. Not sure how this maps to debug
# config though :/
NAMES SDL2-2.0 SDL2 SDL2-static
NAMES SDL2-2.0 SDL2
PATH_SUFFIXES ${_SDL2_LIBRARY_PATH_SUFFIX})
find_library(SDL2_LIBRARY_DEBUG
NAMES SDL2d SDL2-staticd
NAMES SDL2d
PATH_SUFFIXES ${_SDL2_LIBRARY_PATH_SUFFIX})
# FPHSA needs one of the _DEBUG/_RELEASE variables to check that the
# library was found -- using SDL_LIBRARY, which will get populated by

View file

@ -1,141 +0,0 @@
// 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 <SDL_events.h>
#include <SDL_messagebox.h>
#include "../Configuration/Configuration.h"
#include "../FontAwesome/IconsFontAwesome5.h"
#include "../FontAwesome/IconsFontAwesome5Brands.h"
#include "../Logger/Logger.h"
#include "Application.h"
namespace mbst {
void
Application::initEvent(SDL_Event& event) {
_initThread.join();
switch(event.user.code) {
case InitSuccess:
_uiState = UiState::ProfileManager;
ImGui::CloseCurrentPopup();
break;
case ProfileManagerFailure:
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error ",
_profileManager->lastError().data(), window());
exit(EXIT_FAILURE);
break;
default:
break;
}
}
void
Application::initialiseConfiguration() {
LOG_INFO("Reading configuration file.");
setSwapInterval(conf().swapInterval());
setMinimalLoopPeriod(0);
}
void
Application::initialiseGui() {
LOG_INFO("Initialising Dear ImGui.");
auto ctx = ImGui::CreateContext();
auto& io = ImGui::GetIO();
const auto size = Vector2{windowSize()}/dpiScaling();
auto reg_font = _rs.getRaw("SourceSansPro-Regular.ttf"_s);
ImFontConfig font_config;
font_config.FontDataOwnedByAtlas = false;
std::strcpy(font_config.Name, "Source Sans Pro");
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(reg_font.data()), int(reg_font.size()),
20.0f * float(framebufferSize().x()) / size.x(), &font_config);
auto icon_font = _rs.getRaw(FONT_ICON_FILE_NAME_FAS);
static const ImWchar icon_range[] = { ICON_MIN_FA, ICON_MAX_FA, 0 };
ImFontConfig icon_config;
icon_config.FontDataOwnedByAtlas = false;
icon_config.MergeMode = true;
icon_config.PixelSnapH = true;
icon_config.OversampleH = icon_config.OversampleV = 1;
icon_config.GlyphMinAdvanceX = 18.0f;
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(icon_font.data()), int(icon_font.size()),
16.0f * float(framebufferSize().x()) / size.x(), &icon_config, icon_range);
auto brand_font = _rs.getRaw(FONT_ICON_FILE_NAME_FAB);
static const ImWchar brand_range[] = { ICON_MIN_FAB, ICON_MAX_FAB, 0 };
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(brand_font.data()), int(brand_font.size()),
16.0f * float(framebufferSize().x()) / size.x(), &icon_config, brand_range);
auto mono_font = _rs.getRaw("SourceCodePro-Regular.ttf"_s);
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(mono_font.data()), int(mono_font.size()),
18.0f * float(framebufferSize().x()) / size.x(), &font_config);
_imgui = ImGuiIntegration::Context(*ctx, Vector2{windowSize()}/dpiScaling(), windowSize(), framebufferSize());
io.IniFilename = nullptr;
auto& style = ImGui::GetStyle();
style.WindowTitleAlign = {0.5f, 0.5f};
style.FrameRounding = 3.2f;
style.Colors[ImGuiCol_WindowBg] = ImColor(0xff1f1f1f);
}
void
Application::initialiseManager() {
LOG_INFO("Initialising the profile manager.");
SDL_Event event;
SDL_zero(event);
event.type = _initEventId;
_profileManager.emplace();
if(!_profileManager->ready()) {
event.user.code = ProfileManagerFailure;
SDL_PushEvent(&event);
return;
}
_backupManager.emplace();
_stagedMassManager.emplace();
event.user.code = InitSuccess;
SDL_PushEvent(&event);
}
void
Application::initialiseMassManager() {
LOG_INFO("Initialising the M.A.S.S. manager.");
_massManager.emplace(_currentProfile->account(), _currentProfile->isDemo());
}
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

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

View file

@ -1,294 +0,0 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include <imgui_internal.h>
#include "../FontAwesome/IconsFontAwesome5.h"
#include "../GameData/StyleNames.h"
#include "Application.h"
namespace mbst {
void
Application::drawFrameInfo() {
if(!_currentMass || _currentMass->state() != GameObjects::Mass::State::Valid) {
return;
}
if(!ImGui::BeginChild("##FrameInfo")) {
ImGui::EndChild();
return;
}
ImGui::BeginGroup();
if(ImGui::BeginChild("##JointSliders",
{(ImGui::GetContentRegionAvail().x / 2.0f) - (ImGui::GetStyle().WindowPadding.x / 2.0f),
0.0f},
ImGuiChildFlags_Border|ImGuiChildFlags_AutoResizeY, ImGuiWindowFlags_MenuBar))
{
if(ImGui::BeginMenuBar()) {
ImGui::TextUnformatted("Joint sliders");
drawHelpMarker("In-game values are multiplied by 100. For example, 0.500 here is equal to 50 in-game.",
static_cast<float>(windowSize().x()) / 4.0f);
ImGui::EndMenuBar();
}
drawJointSliders();
}
ImGui::EndChild();
if(ImGui::BeginChild("##FrameStyles",
{(ImGui::GetContentRegionAvail().x / 2.0f) - (ImGui::GetStyle().WindowPadding.x / 2.0f), 0.0f},
ImGuiChildFlags_Border, ImGuiWindowFlags_MenuBar))
{
if(ImGui::BeginMenuBar()) {
ImGui::TextUnformatted("Frame styles");
ImGui::EndMenuBar();
}
drawFrameStyles();
}
ImGui::EndChild();
ImGui::EndGroup();
ImGui::SameLine();
if(ImGui::BeginChild("##EyeFlare", {}, ImGuiChildFlags_Border, ImGuiWindowFlags_MenuBar)) {
if(ImGui::BeginMenuBar()) {
ImGui::TextUnformatted("Eye flare colour");
drawHelpMarker("Right-click the picker for more options.", 250.0f);
ImGui::EndMenuBar();
}
drawEyeColourPicker();
}
ImGui::EndChild();
ImGui::EndChild();
}
void
Application::drawJointSliders() {
if(!_currentMass || _currentMass->state() != GameObjects::Mass::State::Valid) {
return;
}
ImGui::BeginGroup();
drawAlignedText("Neck:");
drawAlignedText("Body:");
drawAlignedText("Shoulders:");
drawAlignedText("Hips:");
drawAlignedText("Arms:");
drawAlignedText("Legs:");
ImGui::EndGroup();
ImGui::SameLine();
ImGui::BeginGroup();
ImGui::PushItemWidth(-1.0f);
if(ImGui::SliderFloat("##NeckSlider", &_currentMass->jointSliders().neck, 0.0f, 1.0f)) {
_jointsDirty = true;
}
if(ImGui::SliderFloat("##BodySlider", &_currentMass->jointSliders().body, 0.0f, 1.0f)) {
_jointsDirty = true;
}
if(ImGui::SliderFloat("##ShouldersSlider", &_currentMass->jointSliders().shoulders, 0.0f, 1.0f)) {
_jointsDirty = true;
}
if(ImGui::SliderFloat("##HipsSlider", &_currentMass->jointSliders().hips, 0.0f, 1.0f)) {
_jointsDirty = true;
}
ImGui::PushMultiItemsWidths(2, ImGui::CalcItemWidth());
if(ImGui::SliderFloat("##UpperArmsSlider", &_currentMass->jointSliders().upperArms, 0.0f, 1.0f, "Upper: %.3f")) {
_jointsDirty = true;
}
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
if(ImGui::SliderFloat("##LowerArmsSlider", &_currentMass->jointSliders().lowerArms, 0.0f, 1.0f, "Lower: %.3f")) {
_jointsDirty = true;
}
ImGui::PopItemWidth();
ImGui::PushMultiItemsWidths(2, ImGui::CalcItemWidth());
if(ImGui::SliderFloat("##UpperLegsSlider", &_currentMass->jointSliders().upperLegs, 0.0f, 1.0f, "Upper: %.3f")) {
_jointsDirty = true;
}
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
if(ImGui::SliderFloat("##LowerLegsSlider", &_currentMass->jointSliders().lowerLegs, 0.0f, 1.0f, "Lower: %.3f")) {
_jointsDirty = true;
}
ImGui::PopItemWidth();
ImGui::PopItemWidth();
ImGui::EndGroup();
if(!_jointsDirty) {
ImGui::BeginDisabled();
ImGui::Button(ICON_FA_SAVE " Save");
ImGui::SameLine();
ImGui::Button(ICON_FA_UNDO " Reset");
ImGui::EndDisabled();
}
else {
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) {
_modifiedBySaveTool = true;
if(!_currentMass->writeJointSliders()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
_jointsDirty = false;
}
ImGui::SameLine();
if(ImGui::Button(ICON_FA_UNDO " Reset")) {
_currentMass->getJointSliders();
_jointsDirty = false;
}
}
}
void
Application::drawFrameStyles() {
if(!_currentMass || _currentMass->state() != GameObjects::Mass::State::Valid) {
return;
}
for(std::int32_t i = 0; i < 4; i++) {
drawAlignedText("Slot %d:", i + 1);
ImGui::SameLine();
ImGui::PushID(i);
ImGui::PushItemWidth(-1.0f);
if(ImGui::BeginCombo("##Style",
getStyleName(_currentMass->frameStyles()[i], _currentMass->frameCustomStyles()).data()))
{
for(const auto& style : GameData::builtin_style_names) {
if(ImGui::Selectable(getStyleName(style.first, _currentMass->frameCustomStyles()).data(),
_currentMass->frameStyles()[i] == style.first))
{
_currentMass->frameStyles()[i] = style.first;
_stylesDirty = true;
}
}
ImGui::EndCombo();
}
ImGui::PopItemWidth();
ImGui::PopID();
}
if(!_stylesDirty) {
ImGui::BeginDisabled();
ImGui::Button(ICON_FA_SAVE " Save");
ImGui::SameLine();
ImGui::Button(ICON_FA_UNDO " Reset");
ImGui::EndDisabled();
}
else {
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) {
_modifiedBySaveTool = true;
if(!_currentMass->writeFrameStyles()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
_stylesDirty = false;
}
ImGui::SameLine();
if(ImGui::Button(ICON_FA_UNDO " Reset")) {
_currentMass->getFrameStyles();
_stylesDirty = false;
}
}
}
void
Application::drawEyeColourPicker() {
if(!_currentMass || _currentMass->state() != GameObjects::Mass::State::Valid) {
return;
}
if(ImGui::ColorPicker3("##EyeFlarePicker", &_currentMass->eyeFlareColour().x())) {
_eyeFlareDirty = true;
}
if(!_eyeFlareDirty) {
ImGui::BeginDisabled();
ImGui::Button(ICON_FA_SAVE " Save");
ImGui::SameLine();
ImGui::Button(ICON_FA_UNDO " Reset");
ImGui::EndDisabled();
}
else {
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) {
_modifiedBySaveTool = true;
if(!_currentMass->writeEyeFlareColour()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
_eyeFlareDirty = false;
}
ImGui::SameLine();
if(ImGui::Button(ICON_FA_UNDO " Reset")) {
_currentMass->getEyeFlareColour();
_eyeFlareDirty = false;
}
}
}
void
Application::drawCustomFrameStyles() {
if(!_currentMass || _currentMass->state() != GameObjects::Mass::State::Valid) {
return;
}
if(!ImGui::BeginChild("##FrameStyles")) {
ImGui::EndChild();
return;
}
ImGui::TextWrapped("In-game values are multiplied by 100. For example, 0.500 here is equal to 50 in-game.");
for(std::uint32_t i = 0; i < _currentMass->frameCustomStyles().size(); i++) {
ImGui::PushID(int(i));
auto result = drawCustomStyle(_currentMass->frameCustomStyles()[i]);
switch(result) {
case DCS_ResetStyle:
_currentMass->getFrameCustomStyles();
break;
case DCS_Save:
_modifiedBySaveTool = true;
if(!_currentMass->writeFrameCustomStyle(i)) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;
default:
break;
}
ImGui::PopID();
}
ImGui::EndChild();
}
}

View file

@ -1,95 +0,0 @@
// 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 <SDL_events.h>
#include "../Logger/Logger.h"
#include "Application.h"
namespace mbst {
void
Application::updateCheckEvent(SDL_Event& event) {
_updateThread.join();
switch(static_cast<UpdateChecker::Result>(event.user.code)) {
case UpdateChecker::Success:
_checkerMutex.lock();
if(_checker->updateAvailable()) {
using namespace std::chrono_literals;
_queue.addToast(Toast::Type::Warning,
"Your version is out of date and thus unsupported.\nCheck the settings for more information."_s, 5s);
}
else {
if(_checker->version() == current_version || (current_version > _checker->version() && current_version.prerelease)) {
_queue.addToast(Toast::Type::Success, "The application is already up to date."_s);
}
else if(_checker->version() > current_version && !current_version.prerelease) {
_queue.addToast(Toast::Type::Warning,
"Your version is more recent than the latest one in the repo. How???"_s);
}
}
_checkerMutex.unlock();
break;
case UpdateChecker::HttpError:
_checkerMutex.lock();
_queue.addToast(Toast::Type::Error, _checker->error());
LOG_ERROR(_checker->error());
_checkerMutex.unlock();
break;
case UpdateChecker::CurlInitFailed:
_queue.addToast(Toast::Type::Error, "Couldn't initialise libcurl. Update check aborted."_s);
LOG_ERROR("Couldn't initialise libcurl. Update check aborted.");
break;
case UpdateChecker::CurlError:
{
using namespace std::chrono_literals;
_checkerMutex.lock();
_queue.addToast(Toast::Type::Error, _checker->error(), 10s);
LOG_ERROR(_checker->error());
_checkerMutex.unlock();
}
break;
case UpdateChecker::CurlTimeout:
_queue.addToast(Toast::Type::Error, "The request timed out."_s);
LOG_ERROR("The request timed out.");
break;
}
}
void
Application::checkForUpdates() {
SDL_Event event;
SDL_zero(event);
event.type = _updateEventId;
_checkerMutex.lock();
if(!_checker) {
_checker.emplace();
}
event.user.code = _checker->check();
_checkerMutex.unlock();
SDL_PushEvent(&event);
}
}

View file

@ -1,277 +0,0 @@
// 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/Path.h>
#include "../Configuration/Configuration.h"
#include "../FontAwesome/IconsFontAwesome5.h"
#include "../FontAwesome/IconsFontAwesome5Brands.h"
#include "Application.h"
namespace mbst {
void
Application::drawMainMenu() {
if(!ImGui::BeginMainMenuBar()) {
return;
}
if(ImGui::BeginMenu("Save Tool##SaveToolMenu")) {
if(ImGui::BeginMenu(ICON_FA_FOLDER_OPEN " Open game data directory")) {
if(ImGui::MenuItem(ICON_FA_COG " Configuration", nullptr, false,
Utility::Path::exists(conf().directories().gameConfig)))
{
openUri(Utility::Path::toNativeSeparators(conf().directories().gameConfig));
}
if(ImGui::MenuItem(ICON_FA_SAVE " Saves", nullptr, false,
Utility::Path::exists(conf().directories().gameSaves)))
{
openUri(Utility::Path::toNativeSeparators(conf().directories().gameSaves));
}
if(ImGui::MenuItem(ICON_FA_IMAGE " Screenshots", nullptr, false,
Utility::Path::exists(conf().directories().gameScreenshots)))
{
openUri(Utility::Path::toNativeSeparators(conf().directories().gameScreenshots));
}
ImGui::EndMenu();
}
if(ImGui::BeginMenu(ICON_FA_FOLDER_OPEN " Open manager directory")) {
if(ImGui::MenuItem(ICON_FA_FILE_ARCHIVE " Profile backups", nullptr, false,
Utility::Path::exists(conf().directories().backups)))
{
openUri(Utility::Path::toNativeSeparators(conf().directories().backups));
}
if(ImGui::MenuItem(ICON_FA_EXCHANGE_ALT " Staging area", nullptr, false,
Utility::Path::exists(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::Separator();
if(ImGui::BeginMenu(ICON_FA_COG " Settings")) {
ImGui::BeginGroup();
drawAlignedText("Vertical sync:");
if(conf().swapInterval() == 0) {
drawAlignedText("FPS cap:");
}
ImGui::EndGroup();
ImGui::SameLine();
ImGui::BeginGroup();
static const char* framelimit_labels[] = {
"Off",
"Every VBLANK",
"Every second VBLANK",
"Every third VBLANK",
};
ImGui::PushItemWidth(300.0f);
if(ImGui::BeginCombo("##FrameLimit", framelimit_labels[conf().swapInterval()])) {
for(int i = 0; i <= 3; i++) {
if(ImGui::Selectable(framelimit_labels[i], conf().swapInterval() == i)) {
conf().setSwapInterval(i);
setSwapInterval(i);
if(i == 0) {
setMinimalLoopPeriod(0);
}
}
}
ImGui::EndCombo();
}
if(conf().swapInterval() == 0) {
static float fps_cap = conf().fpsCap();
if(ImGui::SliderFloat("##FpsCapSlider", &fps_cap, 15.0f, 301.0f,
conf().fpsCap() != 301.0f ? "%.0f" : "Uncapped", ImGuiSliderFlags_AlwaysClamp))
{
conf().setFpsCap(fps_cap);
}
}
ImGui::PopItemWidth();
ImGui::EndGroup();
if(drawCheckbox("Cheat mode", conf().cheatMode())) {
conf().setCheatMode(!conf().cheatMode());
}
ImGui::SameLine();
ImGui::AlignTextToFramePadding();
drawHelpMarker("This gives access to save edition features that can be considered cheats.",
float(windowSize().x()) * 0.4f);
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.",
float(windowSize().x()) * 0.4f);
if(drawCheckbox("Check for updates on startup", conf().checkUpdatesOnStartup())) {
conf().setCheckUpdatesOnStartup(!conf().checkUpdatesOnStartup());
}
ImGui::SameLine();
if(ImGui::Button(ICON_FA_SYNC_ALT " Check now")) {
_queue.addToast(Toast::Type::Default, "Checking for updates...");
_updateThread = std::thread{[this]{ checkForUpdates(); }};
}
if(_checker && _checkerMutex.try_lock()) {
if(_checker->updateAvailable()) {
drawAlignedText("Version %s is available.", Containers::String{_checker->version()}.data());
if(ImGui::Button(ICON_FA_FILE_SIGNATURE " Release notes")) {
openUri("https://williamjcm.ovh/mbst");
}
ImGui::SameLine();
if(ImGui::Button(ICON_FA_DOWNLOAD " Download now")) {
openUri(_checker->downloadLink());
}
}
_checkerMutex.unlock();
}
if(drawCheckbox("Skip disclaimer", conf().skipDisclaimer())) {
conf().setSkipDisclaimer(!conf().skipDisclaimer());
}
ImGui::EndMenu();
}
ImGui::Separator();
if(ImGui::MenuItem(ICON_FA_SIGN_OUT_ALT " Quit##QuitMenuItem")) {
exit(EXIT_SUCCESS);
}
ImGui::EndMenu();
}
ImGui::BeginDisabled(conf().isRunningInWine());
if(ImGui::BeginMenu("Game##GameMenu")) {
if(ImGui::MenuItem(ICON_FA_PLAY " Run demo##RunDemoMenuItem")) {
openUri("steam://run/1048390");
}
drawTooltip("Will not work if you have the full game.");
if(ImGui::MenuItem(ICON_FA_PLAY " Run full game##RunFullGameMenuItem")) {
openUri("steam://run/956680");
}
ImGui::Separator();
if(ImGui::BeginMenu(ICON_FA_DISCORD " Discord communities")) {
if(ImGui::MenuItem("Official server")) {
openUri("https://discord.gg/sekai-project");
}
if(ImGui::MenuItem("Community server")) {
openUri("https://discord.gg/massbuildercommunity");
}
ImGui::EndMenu();
}
ImGui::EndMenu();
}
ImGui::EndDisabled();
if(conf().isRunningInWine() && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
ImGui::SetTooltip("Not available when running in Wine.");
}
#ifdef SAVETOOL_DEBUG_BUILD
if(ImGui::BeginMenu("Debug tools")) {
ImGui::MenuItem("ImGui demo window", nullptr, &_demoWindow);
ImGui::MenuItem("ImGui style editor", nullptr, &_styleEditor);
ImGui::MenuItem("ImGui metrics window", nullptr, &_metricsWindow);
ImGui::EndMenu();
}
#endif
if(ImGui::BeginMenu("Help")) {
if(ImGui::BeginMenu(ICON_FA_KEYBOARD " Keyboard shortcuts")) {
ImGui::BulletText("CTRL+Click on a slider or drag box to input value as text.");
ImGui::BulletText("TAB/SHIFT+TAB to cycle through keyboard editable fields.");
ImGui::BulletText("While inputing text:\n");
ImGui::Indent();
ImGui::BulletText("CTRL+Left/Right to word jump.");
ImGui::BulletText("CTRL+A or double-click to select all.");
ImGui::BulletText("CTRL+X/C/V to use clipboard cut/copy/paste.");
ImGui::BulletText("CTRL+Z,CTRL+Y to undo/redo.");
ImGui::BulletText("ESCAPE to revert.");
ImGui::BulletText("You can apply arithmetic operators +,*,/ on numerical values.\nUse +- to subtract.");
ImGui::Unindent();
ImGui::EndMenu();
}
ImGui::MenuItem(ICON_FA_INFO_CIRCLE " About", nullptr, &_aboutPopup);
ImGui::EndMenu();
}
if(_gameCheckTimerId != 0) {
if(ImGui::BeginTable("##MainMenuLayout", 2)) {
ImGui::TableSetupColumn("##Dummy", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("##GameState", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(1);
drawGameState();
ImGui::EndTable();
}
}
ImGui::EndMainMenuBar();
}
}

View file

@ -1,24 +0,0 @@
#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/>.
namespace BinaryIo {
class Reader;
class Writer;
}

View file

@ -1,150 +0,0 @@
// 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/String.h>
#include "../Logger/Logger.h"
#include "Reader.h"
namespace BinaryIo {
Reader::Reader(Containers::StringView filename) {
_file = std::fopen(filename.data(), "rb");
if(!_file) {
LOG_ERROR_FORMAT("Couldn't open {} for reading: {}", filename, std::strerror(errno));
}
}
Reader::~Reader() {
closeFile();
}
bool
Reader::open() {
return _file;
}
bool
Reader::eof() {
return std::feof(_file) != 0;
}
std::int64_t
Reader::position() {
return _ftelli64(_file);
}
bool
Reader::seek(std::int64_t position) {
return _fseeki64(_file, position, SEEK_SET) == 0;
}
void
Reader::closeFile() {
std::fclose(_file);
_file = nullptr;
}
bool
Reader::readChar(char& value) {
return std::fread(&value, sizeof(char), 1, _file) == 1;
}
bool
Reader::readInt8(std::int8_t& value) {
return std::fread(&value, sizeof(std::int8_t), 1, _file) == 1;
}
bool
Reader::readUint8(std::uint8_t& value) {
return std::fread(&value, sizeof(std::uint8_t), 1, _file) == 1;
}
bool
Reader::readInt16(std::int16_t& value) {
return std::fread(&value, sizeof(std::int16_t), 1, _file) == 1;
}
bool
Reader::readUint16(std::uint16_t& value) {
return std::fread(&value, sizeof(std::uint16_t), 1, _file) == 1;
}
bool
Reader::readInt32(std::int32_t& value) {
return std::fread(&value, sizeof(std::int32_t), 1, _file) == 1;
}
bool
Reader::readUint32(std::uint32_t& value) {
return std::fread(&value, sizeof(std::uint32_t), 1, _file) == 1;
}
bool
Reader::readInt64(std::int64_t& value) {
return std::fread(&value, sizeof(std::int64_t), 1, _file) == 1;
}
bool
Reader::readUint64(std::uint64_t& value) {
return std::fread(&value, sizeof(std::uint64_t), 1, _file) == 1;
}
bool
Reader::readFloat(float& value) {
return std::fread(&value, sizeof(float), 1, _file) == 1;
}
bool
Reader::readDouble(double& value) {
return std::fread(&value, sizeof(double), 1, _file) == 1;
}
bool
Reader::readArray(Containers::Array<char>& array, std::size_t count) {
if(array.size() < count) {
array = Containers::Array<char>{ValueInit, count};
}
return std::fread(array.data(), sizeof(char), count, _file) == count;
}
bool
Reader::readUEString(Containers::String& str) {
std::uint32_t length = 0;
if(!readUint32(length) || length == 0) {
return false;
}
str = Containers::String{ValueInit, length - 1};
return std::fread(str.data(), sizeof(char), length, _file) == length;
}
std::int32_t
Reader::peekChar() {
std::int32_t c;
c = std::fgetc(_file);
std::ungetc(c, _file);
return c;
}
}

View file

@ -1,80 +0,0 @@
#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>
#include <cstdio>
#include <Corrade/Containers/Containers.h>
#include <Corrade/Containers/StaticArray.h>
#include <Corrade/Containers/StringView.h>
using namespace Corrade;
namespace BinaryIo {
class Reader {
public:
explicit Reader(Containers::StringView filename);
~Reader();
Reader(const Reader& other) = delete;
Reader& operator=(const Reader& other) = delete;
Reader(Reader&& other) = default;
Reader& operator=(Reader&& other) = default;
bool open();
bool eof();
auto position() -> std::int64_t;
bool seek(std::int64_t position);
void closeFile();
bool readChar(char& value);
bool readInt8(std::int8_t& value);
bool readUint8(std::uint8_t& value);
bool readInt16(std::int16_t& value);
bool readUint16(std::uint16_t& value);
bool readInt32(std::int32_t& value);
bool readUint32(std::uint32_t& value);
bool readInt64(std::int64_t& value);
bool readUint64(std::uint64_t& value);
bool readFloat(float& value);
bool readDouble(double& value);
bool readArray(Containers::Array<char>& array, std::size_t count);
template<typename T>
bool readValue(T& value) {
return fread(&value, sizeof(T), 1, _file) == 1;
}
template<std::size_t S>
bool readStaticArray(Containers::StaticArray<S, char>& array) {
return std::fread(array.data(), sizeof(char), S, _file) == S;
}
bool readUEString(Containers::String& str);
auto peekChar() -> std::int32_t;
private:
std::FILE* _file = nullptr;
};
}

View file

@ -1,163 +0,0 @@
// 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 "../Logger/Logger.h"
#include "Writer.h"
using namespace Containers::Literals;
namespace BinaryIo {
Writer::Writer(Containers::StringView filename) {
_file = std::fopen(filename.data(), "wb");
if(!_file) {
LOG_ERROR_FORMAT("Couldn't open {} for reading: {}", filename, std::strerror(errno));
}
}
Writer::~Writer() {
closeFile();
}
bool
Writer::open() {
return _file;
}
void
Writer::closeFile() {
std::fflush(_file);
std::fclose(_file);
_file = nullptr;
}
std::int64_t
Writer::position() {
return _ftelli64(_file);
}
Containers::ArrayView<const char>
Writer::array() const {
return _data;
}
std::size_t
Writer::arrayPosition() const {
return _index;
}
bool
Writer::flushToFile() {
bool ret = writeArray(_data);
std::fflush(_file);
_data = Containers::Array<char>{};
_index = 0;
return ret;
}
bool
Writer::writeChar(char value) {
return std::fwrite(&value, sizeof(char), 1, _file) == 1;
}
bool
Writer::writeInt8(std::int8_t value) {
return std::fwrite(&value, sizeof(std::int8_t), 1, _file) == 1;
}
bool
Writer::writeUint8(std::uint8_t value) {
return std::fwrite(&value, sizeof(std::uint8_t), 1, _file) == 1;
}
bool
Writer::writeInt16(std::int16_t value) {
return std::fwrite(&value, sizeof(std::int16_t), 1, _file) == 1;
}
bool
Writer::writeUint16(std::uint16_t value) {
return std::fwrite(&value, sizeof(std::uint16_t), 1, _file) == 1;
}
bool
Writer::writeInt32(std::int32_t value) {
return std::fwrite(&value, sizeof(std::int32_t), 1, _file) == 1;
}
bool
Writer::writeUint32(std::uint32_t value) {
return std::fwrite(&value, sizeof(std::uint32_t), 1, _file) == 1;
}
bool
Writer::writeInt64(std::int64_t value) {
return std::fwrite(&value, sizeof(std::int64_t), 1, _file) == 1;
}
bool
Writer::writeUint64(std::uint64_t value) {
return std::fwrite(&value, sizeof(std::uint64_t), 1, _file) == 1;
}
bool
Writer::writeFloat(float value) {
return std::fwrite(&value, sizeof(float), 1, _file) == 1;
}
bool
Writer::writeDouble(double value) {
return std::fwrite(&value, sizeof(double), 1, _file) == 1;
}
bool
Writer::writeArray(Containers::ArrayView<const char> array) {
if(array.isEmpty()) {
return false;
}
return std::fwrite(array.data(), sizeof(char), array.size(), _file) == array.size();
}
bool
Writer::writeUEString(Containers::StringView str) {
if(str.size() > UINT32_MAX) {
LOG_ERROR_FORMAT("String is too big. Expected size() < UINT32_MAX, got {} instead.", str.size());
return false;
}
writeUint32(static_cast<std::uint32_t>(str.size()) + 1);
if(str.size() > 0) {
std::size_t count = std::fwrite(str.data(), sizeof(char), str.size(), _file);
if(count != str.size()) {
return false;
}
}
return writeChar('\0');
}
std::size_t
Writer::writeUEStringToArray(Containers::StringView value) {
return writeValueToArray<std::uint32_t>(std::uint32_t(value.size()) + 1u) +
writeDataToArray(Containers::ArrayView<const char>{value}) +
writeValueToArray<char>('\0');
}
}

View file

@ -1,5 +1,5 @@
# MassBuilderSaveTool
# Copyright (C) 2021-2024 Guillaume Jacquemin
# Copyright (C) 2021-2022 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
@ -14,36 +14,21 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(SAVETOOL_PROJECT_VERSION 1.5.0)
set(SAVETOOL_PROJECT_VERSION 1.4.3)
find_package(Corrade REQUIRED Containers Utility)
if(CORRADE_TARGET_WINDOWS)
find_package(Corrade REQUIRED Main)
endif()
find_package(Corrade REQUIRED Main Containers Utility)
find_package(Magnum REQUIRED GL Sdl2Application)
find_package(MagnumIntegration REQUIRED ImGui)
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)
set_directory_properties(PROPERTIES CORRADE_USE_PEDANTIC_FLAGS ON)
corrade_add_resource(Assets assets.conf)
set(Logger_SOURCES
add_library(Logger STATIC EXCLUDE_FROM_ALL
Logger/Logger.h
Logger/Logger.cpp
Logger/EntryType.h
@ -51,199 +36,163 @@ set(Logger_SOURCES
Logger/MagnumLogBuffer.cpp
)
set(BinaryIo_SOURCES
BinaryIo/BinaryIo.h
BinaryIo/Reader.h
BinaryIo/Reader.cpp
BinaryIo/Writer.h
BinaryIo/Writer.cpp
target_link_libraries(Logger PRIVATE
Corrade::Utility
Magnum::Magnum
)
set(Gvas_SOURCES
Gvas/Serialisers/Serialisers.h
Gvas/Serialisers/AbstractUnrealCollectionProperty.h
Gvas/Serialisers/AbstractUnrealProperty.h
Gvas/Serialisers/AbstractUnrealStruct.h
Gvas/Serialisers/ArrayProperty.h
Gvas/Serialisers/ArrayProperty.cpp
Gvas/Serialisers/BoolProperty.h
Gvas/Serialisers/BoolProperty.cpp
Gvas/Serialisers/ByteProperty.h
Gvas/Serialisers/ByteProperty.cpp
Gvas/Serialisers/ColourProperty.h
Gvas/Serialisers/ColourProperty.cpp
Gvas/Serialisers/DateTimeProperty.h
Gvas/Serialisers/DateTimeProperty.cpp
Gvas/Serialisers/EnumProperty.h
Gvas/Serialisers/EnumProperty.cpp
Gvas/Serialisers/FloatProperty.h
Gvas/Serialisers/FloatProperty.cpp
Gvas/Serialisers/GuidProperty.h
Gvas/Serialisers/GuidProperty.cpp
Gvas/Serialisers/IntProperty.h
Gvas/Serialisers/IntProperty.cpp
Gvas/Serialisers/MapProperty.h
Gvas/Serialisers/MapProperty.cpp
Gvas/Serialisers/ResourceProperty.h
Gvas/Serialisers/ResourceProperty.cpp
Gvas/Serialisers/RotatorProperty.h
Gvas/Serialisers/RotatorProperty.cpp
Gvas/Serialisers/StringProperty.h
Gvas/Serialisers/StringProperty.cpp
Gvas/Serialisers/SetProperty.h
Gvas/Serialisers/SetProperty.cpp
Gvas/Serialisers/Struct.h
Gvas/Serialisers/Struct.cpp
Gvas/Serialisers/TextProperty.h
Gvas/Serialisers/TextProperty.cpp
Gvas/Serialisers/UnrealProperty.h
Gvas/Serialisers/VectorProperty.h
Gvas/Serialisers/VectorProperty.cpp
Gvas/Serialisers/Vector2DProperty.h
Gvas/Serialisers/Vector2DProperty.cpp
add_library(UESaveFile STATIC EXCLUDE_FROM_ALL
UESaveFile/Serialisers/AbstractUnrealCollectionPropertySerialiser.h
UESaveFile/Serialisers/AbstractUnrealPropertySerialiser.h
UESaveFile/Serialisers/AbstractUnrealStructSerialiser.h
UESaveFile/Serialisers/ArrayPropertySerialiser.h
UESaveFile/Serialisers/ArrayPropertySerialiser.cpp
UESaveFile/Serialisers/BoolPropertySerialiser.h
UESaveFile/Serialisers/BoolPropertySerialiser.cpp
UESaveFile/Serialisers/BytePropertySerialiser.h
UESaveFile/Serialisers/BytePropertySerialiser.cpp
UESaveFile/Serialisers/ColourPropertySerialiser.h
UESaveFile/Serialisers/ColourPropertySerialiser.cpp
UESaveFile/Serialisers/DateTimePropertySerialiser.h
UESaveFile/Serialisers/DateTimePropertySerialiser.cpp
UESaveFile/Serialisers/EnumPropertySerialiser.h
UESaveFile/Serialisers/EnumPropertySerialiser.cpp
UESaveFile/Serialisers/FloatPropertySerialiser.h
UESaveFile/Serialisers/FloatPropertySerialiser.cpp
UESaveFile/Serialisers/GuidPropertySerialiser.h
UESaveFile/Serialisers/GuidPropertySerialiser.cpp
UESaveFile/Serialisers/IntPropertySerialiser.h
UESaveFile/Serialisers/IntPropertySerialiser.cpp
UESaveFile/Serialisers/MapPropertySerialiser.h
UESaveFile/Serialisers/MapPropertySerialiser.cpp
UESaveFile/Serialisers/ResourcePropertySerialiser.h
UESaveFile/Serialisers/ResourcePropertySerialiser.cpp
UESaveFile/Serialisers/RotatorPropertySerialiser.h
UESaveFile/Serialisers/RotatorPropertySerialiser.cpp
UESaveFile/Serialisers/StringPropertySerialiser.h
UESaveFile/Serialisers/StringPropertySerialiser.cpp
UESaveFile/Serialisers/SetPropertySerialiser.h
UESaveFile/Serialisers/SetPropertySerialiser.cpp
UESaveFile/Serialisers/StructSerialiser.h
UESaveFile/Serialisers/StructSerialiser.cpp
UESaveFile/Serialisers/TextPropertySerialiser.h
UESaveFile/Serialisers/TextPropertySerialiser.cpp
UESaveFile/Serialisers/UnrealPropertySerialiser.h
UESaveFile/Serialisers/VectorPropertySerialiser.h
UESaveFile/Serialisers/VectorPropertySerialiser.cpp
UESaveFile/Serialisers/Vector2DPropertySerialiser.h
UESaveFile/Serialisers/Vector2DPropertySerialiser.cpp
Gvas/Types/Types.h
Gvas/Types/ArrayProperty.h
Gvas/Types/BoolProperty.h
Gvas/Types/ByteProperty.h
Gvas/Types/ColourStructProperty.h
Gvas/Types/DateTimeStructProperty.h
Gvas/Types/EnumProperty.h
Gvas/Types/FloatProperty.h
Gvas/Types/GenericStructProperty.h
Gvas/Types/GuidStructProperty.h
Gvas/Types/IntProperty.h
Gvas/Types/MapProperty.h
Gvas/Types/NoneProperty.h
Gvas/Types/RotatorStructProperty.h
Gvas/Types/SetProperty.h
Gvas/Types/StringProperty.h
Gvas/Types/StructProperty.h
Gvas/Types/ResourceItemValue.h
Gvas/Types/TextProperty.h
Gvas/Types/UnrealProperty.h
Gvas/Types/UnrealPropertyBase.h
Gvas/Types/Vector2DStructProperty.h
Gvas/Types/VectorStructProperty.h
UESaveFile/Types/ArrayProperty.h
UESaveFile/Types/BoolProperty.h
UESaveFile/Types/ByteProperty.h
UESaveFile/Types/ColourStructProperty.h
UESaveFile/Types/DateTimeStructProperty.h
UESaveFile/Types/EnumProperty.h
UESaveFile/Types/FloatProperty.h
UESaveFile/Types/GenericStructProperty.h
UESaveFile/Types/GuidStructProperty.h
UESaveFile/Types/IntProperty.h
UESaveFile/Types/MapProperty.h
UESaveFile/Types/NoneProperty.h
UESaveFile/Types/RotatorStructProperty.h
UESaveFile/Types/SetProperty.h
UESaveFile/Types/StringProperty.h
UESaveFile/Types/StructProperty.h
UESaveFile/Types/ResourceItemValue.h
UESaveFile/Types/TextProperty.h
UESaveFile/Types/UnrealProperty.h
UESaveFile/Types/UnrealPropertyBase.h
UESaveFile/Types/VectorStructProperty.h
Gvas/Gvas.h
Gvas/Debug.h
Gvas/Debug.cpp
Gvas/File.h
Gvas/File.cpp
Gvas/PropertySerialiser.h
Gvas/PropertySerialiser.cpp
UESaveFile/Debug.h
UESaveFile/Debug.cpp
UESaveFile/UESaveFile.h
UESaveFile/UESaveFile.cpp
UESaveFile/BinaryReader.h
UESaveFile/BinaryReader.cpp
UESaveFile/BinaryWriter.h
UESaveFile/BinaryWriter.cpp
UESaveFile/PropertySerialiser.h
UESaveFile/PropertySerialiser.cpp
)
set(ImportExport_SOURCES
ImportExport/Import.h
ImportExport/Import.cpp
ImportExport/Export.h
ImportExport/Export.cpp
ImportExport/Keys.h
target_link_libraries(UESaveFile PRIVATE
Corrade::Containers
Corrade::Utility
Magnum::Magnum
Logger
)
if(CORRADE_TARGET_WINDOWS)
set(SAVETOOL_RC_FILE resource.rc)
endif()
add_executable(MassBuilderSaveTool
add_executable(MassBuilderSaveTool WIN32
main.cpp
Application/Application.h
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
Application/Application_MassViewer_Frame.cpp
Application/Application_MassViewer_Armour.cpp
Application/Application_MassViewer_Weapons.cpp
Application/Application_ProfileManager.cpp
Application/Application_UpdateChecker.cpp
Configuration/Configuration.h
Configuration/Configuration.cpp
FontAwesome/IconsFontAwesome5.h
FontAwesome/IconsFontAwesome5Brands.h
GameData/Accessories.h
GameData/ArmourSets.h
GameData/LastMissionId.h
GameData/ResourceIDs.h
GameData/StoryProgress.h
GameData/StyleNames.h
GameData/WeaponParts.h
GameObjects/Accessory.h
GameObjects/ArmourPart.h
GameObjects/BulletLauncherAttachment.h
GameObjects/CustomStyle.h
GameObjects/Decal.h
GameObjects/Joints.h
GameObjects/Mass.h
GameObjects/Mass.cpp
GameObjects/Mass_Frame.cpp
GameObjects/Mass_Armour.cpp
GameObjects/Mass_Weapons.cpp
GameObjects/Mass_Styles.cpp
GameObjects/Mass_DecalsAccessories.cpp
GameObjects/Profile.h
GameObjects/Profile.cpp
GameObjects/PropertyNames.h
GameObjects/Weapon.h
GameObjects/Weapon.cpp
GameObjects/WeaponPart.h
Managers/Backup.h
Managers/BackupManager.h
Managers/BackupManager.cpp
Managers/MassManager.h
Managers/MassManager.cpp
Managers/ProfileManager.h
Managers/ProfileManager.cpp
Managers/StagedMass.h
Managers/StagedMassManager.h
Managers/StagedMassManager.cpp
Managers/Vfs/Directory.h
SaveTool/SaveTool.h
SaveTool/SaveTool.cpp
SaveTool/SaveTool_drawAbout.cpp
SaveTool/SaveTool_drawMainMenu.cpp
SaveTool/SaveTool_FileWatcher.cpp
SaveTool/SaveTool_Initialisation.cpp
SaveTool/SaveTool_MainManager.cpp
SaveTool/SaveTool_MassViewer.cpp
SaveTool/SaveTool_MassViewer_Frame.cpp
SaveTool/SaveTool_MassViewer_Armour.cpp
SaveTool/SaveTool_MassViewer_Weapons.cpp
SaveTool/SaveTool_ProfileManager.cpp
SaveTool/SaveTool_UpdateChecker.cpp
ProfileManager/ProfileManager.h
ProfileManager/ProfileManager.cpp
Profile/Profile.h
Profile/Profile.cpp
Profile/PropertyNames.h
Profile/ResourceIDs.h
MassManager/MassManager.h
MassManager/MassManager.cpp
Mass/Accessory.h
Mass/ArmourPart.h
Mass/BulletLauncherAttachment.h
Mass/CustomStyle.h
Mass/Decal.h
Mass/Joints.h
Mass/Mass.h
Mass/Mass.cpp
Mass/Mass_Frame.cpp
Mass/Mass_Armour.cpp
Mass/Mass_Weapons.cpp
Mass/Mass_Styles.cpp
Mass/Mass_DecalsAccessories.cpp
Mass/PropertyNames.h
Mass/Weapon.h
Mass/Weapon.cpp
Mass/WeaponPart.h
Maps/Accessories.h
Maps/ArmourSets.h
Maps/ArmourSlots.hpp
Maps/BulletLauncherAttachmentStyles.hpp
Maps/BulletLauncherSockets.hpp
Maps/DamageTypes.hpp
Maps/EffectColourModes.hpp
Maps/LastMissionId.h
Maps/StoryProgress.h
Maps/StyleNames.h
Maps/WeaponParts.h
Maps/WeaponTypes.hpp
ToastQueue/ToastQueue.h
ToastQueue/ToastQueue.cpp
UpdateChecker/UpdateChecker.h
UpdateChecker/UpdateChecker.cpp
Utilities/Crc32.h
Utilities/Crc32.cpp
Utilities/Temp.h
Utilities/Temp.cpp
Version/Version.h
Version/Version.cpp
${Logger_SOURCES}
${BinaryIo_SOURCES}
${Gvas_SOURCES}
${ImportExport_SOURCES}
${SAVETOOL_RC_FILE}
FontAwesome/IconsFontAwesome5.h
FontAwesome/IconsFontAwesome5Brands.h
resource.rc
${Assets}
)
if(CORRADE_TARGET_WINDOWS)
set_target_properties(${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE $<CONFIG:Release>)
endif()
target_compile_definitions(MassBuilderSaveTool PRIVATE
SAVETOOL_VERSION_STRING="${SAVETOOL_PROJECT_VERSION}"
SAVETOOL_VERSION_MAJOR=1
SAVETOOL_VERSION_MINOR=5
SAVETOOL_VERSION_PATCH=0
SAVETOOL_VERSION_PRERELEASE=false
SAVETOOL_CODENAME="Fuckin' UE5..."
SAVETOOL_SUPPORTED_GAME_VERSION="0.11.x"
)
if(CMAKE_BUILD_TYPE STREQUAL Debug)
target_compile_definitions(MassBuilderSaveTool PRIVATE SAVETOOL_DEBUG_BUILD)
add_compile_definitions(SAVETOOL_DEBUG_BUILD)
endif()
add_compile_definitions(
SAVETOOL_VERSION="${SAVETOOL_PROJECT_VERSION}"
SAVETOOL_CODENAME="Enigmatic Ellenier"
SUPPORTED_GAME_VERSION="0.9.x"
)
if(CMAKE_BUILD_TYPE STREQUAL Release)
set_target_properties(MassBuilderSaveTool PROPERTIES OUTPUT_NAME MassBuilderSaveTool-${SAVETOOL_PROJECT_VERSION})
@ -258,29 +207,16 @@ endif()
target_link_libraries(MassBuilderSaveTool PRIVATE
Corrade::Containers
Corrade::Utility
Corrade::Main
Magnum::Magnum
Magnum::GL
Magnum::Sdl2Application
MagnumIntegration::ImGui
CURL::libcurl_static
Logger
UESaveFile
efsw
zip
libcurl
imm32
wtsapi32
)
if(SAVETOOL_USE_SYSTEM_LIBZIP)
target_link_libraries(MassBuilderSaveTool PRIVATE libzip::zip)
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
imm32
wtsapi32
)
endif()

View file

@ -1,292 +0,0 @@
// 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/Optional.h>
#include <Corrade/Containers/Pair.h>
#include <Corrade/Containers/ScopeGuard.h>
#include <Corrade/Utility/Path.h>
#include <Corrade/Utility/Unicode.h>
#include <combaseapi.h>
#include <knownfolders.h>
#include <shlobj.h>
#include "../Logger/Logger.h"
#include "Configuration.h"
namespace mbst {
Configuration::Configuration() {
Containers::String exe_path = Utility::Path::split(*Utility::Path::executableLocation()).first();
_conf = Utility::Configuration{Utility::Path::join(exe_path, "MassBuilderSaveTool.ini")};
if(_conf.hasValue("swap_interval")) {
_swapInterval = _conf.value<int>("swap_interval");
}
else {
_conf.setValue("swap_interval", 1);
}
if(_conf.hasValue("frame_limit")) {
std::string frame_limit = _conf.value("frame_limit");
if(frame_limit == "half_vsync") {
_swapInterval = 2;
}
_conf.removeValue("frame_limit");
}
if(_conf.hasValue("fps_cap")) {
_fpsCap = _conf.value<float>("fps_cap");
}
else {
_conf.setValue("fps_cap", 60.0f);
}
if(_conf.hasValue("cheat_mode")) {
_cheatMode = _conf.value<bool>("cheat_mode");
}
else {
_conf.setValue("cheat_mode", _cheatMode);
}
if(_conf.hasValue("advanced_mode")) {
_advancedMode = _conf.value<bool>("advanced_mode");
}
else {
_conf.setValue("advanced_mode", _advancedMode);
}
if(_conf.hasValue("startup_update_check")) {
_checkUpdatesOnStartup = _conf.value<bool>("startup_update_check");
}
else {
_conf.setValue("startup_update_check", _checkUpdatesOnStartup);
}
if(_conf.hasValue("skip_disclaimer")) {
_skipDisclaimer = _conf.value<bool>("skip_disclaimer");
}
else {
_conf.setValue("skip_disclaimer", _skipDisclaimer);
}
LOG_INFO("Searching for the game's save directory.");
wchar_t* localappdata_path = nullptr;
Containers::ScopeGuard guard{localappdata_path, CoTaskMemFree};
auto result = SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_DEFAULT, nullptr, &localappdata_path);
if(result != S_OK)
{
char* message_buffer = nullptr;
auto size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, result, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
reinterpret_cast<char*>(&message_buffer), 0, nullptr);
String message{message_buffer, size};
LocalFree(message_buffer);
_lastError = Utility::format("SHGetKnownFolderPath() failed with error code {}: {}", result, message);
LOG_ERROR(_lastError);
return;
}
auto game_data_dir = Utility::Path::join(
Utility::Path::fromNativeSeparators(Utility::Unicode::narrow(localappdata_path)),
"MASS_Builder/Saved"_s
);
if(!Utility::Path::exists(game_data_dir)) {
LOG_ERROR(_lastError = game_data_dir + " wasn't found. Make sure to play the game at least once."_s);
return;
}
_directories.gameConfig = Utility::Path::join(game_data_dir, "Config/WindowsNoEditor"_s);
_directories.gameSaves = Utility::Path::join(game_data_dir, "SaveGames"_s);
_directories.gameScreenshots = Utility::Path::join(game_data_dir, "Screenshots/WindowsNoEditor"_s);
LOG_INFO("Initialising Save Tool directories.");
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)) {
LOG_WARNING("Backups directory not found, creating...");
if(!Utility::Path::make(_directories.backups)) {
LOG_ERROR(_lastError = "Couldn't create the backups directory.");
return;
}
}
if(!Utility::Path::exists(_directories.staging)) {
LOG_WARNING("Staging directory not found, creating...");
if(!Utility::Path::make(_directories.staging)) {
LOG_ERROR(_lastError = "Couldn't create the staging directory.");
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...");
if(!Utility::Path::make(_directories.temp)) {
LOG_ERROR(_lastError = "Couldn't create the temporary directory.");
return;
}
}
_valid = true;
}
Configuration::~Configuration() {
save();
}
bool
Configuration::valid() const {
return _valid;
}
Containers::StringView
Configuration::lastError() const {
return _lastError;
}
void
Configuration::save() {
_conf.save();
}
int
Configuration::swapInterval() const {
return _swapInterval;
}
void
Configuration::setSwapInterval(int interval) {
_swapInterval = interval;
_conf.setValue("swap_interval", _swapInterval);
_conf.save();
}
float
Configuration::fpsCap() const {
return _fpsCap;
}
void
Configuration::setFpsCap(float cap) {
_fpsCap = cap;
_conf.setValue("fps_cap", _fpsCap);
_conf.save();
}
bool
Configuration::cheatMode() const {
return _cheatMode;
}
void
Configuration::setCheatMode(bool enabled) {
_cheatMode = enabled;
_conf.setValue("cheat_mode", _cheatMode);
_conf.save();
}
bool
Configuration::advancedMode() const {
return _advancedMode;
}
void
Configuration::setAdvancedMode(bool enabled) {
_advancedMode = enabled;
_conf.setValue("advanced_mode", _advancedMode);
_conf.save();
}
bool
Configuration::checkUpdatesOnStartup() const {
return _checkUpdatesOnStartup;
}
void
Configuration::setCheckUpdatesOnStartup(bool mode) {
_checkUpdatesOnStartup = mode;
_conf.setValue("startup_update_check", _checkUpdatesOnStartup);
_conf.save();
}
bool
Configuration::skipDisclaimer() const {
return _skipDisclaimer;
}
void
Configuration::setSkipDisclaimer(bool mode) {
_skipDisclaimer = mode;
_conf.setValue("skip_disclaimer", _skipDisclaimer);
_conf.save();
}
bool
Configuration::isRunningInWine() const {
return _isRunningInWine;
}
void
Configuration::setRunningInWine(bool wine) {
_isRunningInWine = wine;
}
Configuration::Directories const&
Configuration::directories() const {
return _directories;
}
Configuration&
Configuration::instance() {
static Configuration conf{};
return conf;
}
Configuration&
conf() {
return Configuration::instance();
}
}

View file

@ -1,94 +0,0 @@
#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/Utility/Configuration.h>
using namespace Corrade;
namespace mbst {
class Configuration {
public:
static auto instance() -> Configuration&;
~Configuration();
bool valid() const;
auto lastError() const -> Containers::StringView;
void save();
auto swapInterval() const -> int;
void setSwapInterval(int interval);
auto fpsCap() const -> float;
void setFpsCap(float cap);
bool cheatMode() const;
void setCheatMode(bool enabled);
bool advancedMode() const;
void setAdvancedMode(bool enabled);
bool checkUpdatesOnStartup() const;
void setCheckUpdatesOnStartup(bool mode);
bool skipDisclaimer() const;
void setSkipDisclaimer(bool mode);
bool isRunningInWine() const;
void setRunningInWine(bool wine);
struct Directories {
Containers::String gameSaves;
Containers::String gameConfig;
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&;
private:
explicit Configuration();
Utility::Configuration _conf;
Containers::String _lastError;
int _swapInterval = 1;
float _fpsCap = 60.0f;
bool _cheatMode = false;
bool _advancedMode = false;
bool _checkUpdatesOnStartup = true;
bool _skipDisclaimer = false;
bool _isRunningInWine = false;
bool _valid = false;
Directories _directories;
};
Configuration& conf();
}

View file

@ -1,783 +0,0 @@
#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>
#include <map>
#include <Corrade/Containers/StringView.h>
using namespace Corrade;
using namespace Containers::Literals;
namespace mbst::GameData {
struct Accessory {
Containers::StringView name;
enum Size {
S,
M,
L,
XL
};
Size size{};
};
static const std::map<std::int32_t, Accessory> accessories{
// region Primitives
{1, {"Cube"_s, Accessory::Size::S}},
{2, {"Pentagon"_s, Accessory::Size::S}},
{3, {"Hexagon"_s, Accessory::Size::S}},
{4, {"Cylinder"_s, Accessory::Size::S}},
{5, {"Sphere"_s, Accessory::Size::S}},
{6, {"TriPyramid"_s, Accessory::Size::S}},
{7, {"SquPyramid"_s, Accessory::Size::S}},
{8, {"PenPyramid"_s, Accessory::Size::S}},
{9, {"HexPyramid"_s, Accessory::Size::S}},
{10, {"Cone"_s, Accessory::Size::S}},
{11, {"SquStick"_s, Accessory::Size::S}},
{12, {"PenStick"_s, Accessory::Size::S}},
{13, {"HexStick"_s, Accessory::Size::S}},
{14, {"CycStick"_s, Accessory::Size::S}},
{15, {"Capsule"_s, Accessory::Size::S}},
{16, {"Decal Pad 01"_s, Accessory::Size::S}},
{17, {"Decal Pad 02"_s, Accessory::Size::S}},
{18, {"Decal Pad 03"_s, Accessory::Size::S}},
{19, {"Decal Pad 04"_s, Accessory::Size::S}},
{20, {"Decal Pad 05"_s, Accessory::Size::S}},
{21, {"Triangle"_s, Accessory::Size::S}},
{22, {"ThinStar"_s, Accessory::Size::S}},
{23, {"Star"_s, Accessory::Size::S}},
{24, {"SixSideStar"_s, Accessory::Size::S}},
{25, {"Asterisk"_s, Accessory::Size::S}},
{26, {"Ring"_s, Accessory::Size::S}},
{27, {"SawedRing"_s, Accessory::Size::S}},
{28, {"HalfRing"_s, Accessory::Size::S}},
{29, {"Cresent"_s, Accessory::Size::S}},
{30, {"Donut"_s, Accessory::Size::S}},
{31, {"FiveCogWheel"_s, Accessory::Size::S}},
{32, {"SixCogWheel"_s, Accessory::Size::S}},
{33, {"SevenCogWheel"_s, Accessory::Size::S}},
{34, {"EightCogWheel"_s, Accessory::Size::S}},
{35, {"TwelveCogWheel"_s, Accessory::Size::S}},
{51, {"SquBevel"_s, Accessory::Size::S}},
{52, {"TriBevel"_s, Accessory::Size::S}},
{53, {"PenBevel"_s, Accessory::Size::S}},
{54, {"HexBevel"_s, Accessory::Size::S}},
{55, {"CycBevel"_s, Accessory::Size::S}},
{56, {"RecBevel"_s, Accessory::Size::S}},
{57, {"DaiBevel"_s, Accessory::Size::S}},
{58, {"MonBevel"_s, Accessory::Size::S}},
{59, {"CofBevel"_s, Accessory::Size::S}},
{60, {"JevBevel"_s, Accessory::Size::S}},
{61, {"SquEmboss"_s, Accessory::Size::S}},
{62, {"TriEmboss"_s, Accessory::Size::S}},
{63, {"PenEmboss"_s, Accessory::Size::S}},
{64, {"HexEmboss"_s, Accessory::Size::S}},
{65, {"CycEmboss"_s, Accessory::Size::S}},
{66, {"RecEmboss"_s, Accessory::Size::S}},
{67, {"DaiEmboss"_s, Accessory::Size::S}},
{68, {"MonEmboss"_s, Accessory::Size::S}},
{69, {"CofEmboss"_s, Accessory::Size::S}},
{70, {"JevEmboss"_s, Accessory::Size::S}},
{101, {"Flat Hex Pin"_s, Accessory::Size::S}},
{102, {"Cross Circle Pin"_s, Accessory::Size::S}},
{103, {"Flat Circle Pin"_s, Accessory::Size::S}},
{104, {"Hex Circle Pin"_s, Accessory::Size::S}},
{105, {"Circle Button Pin"_s, Accessory::Size::S}},
{106, {"Hexagon Pin"_s, Accessory::Size::S}},
{107, {"Cross Square Pin"_s, Accessory::Size::S}},
{108, {"Flat Square Pin"_s, Accessory::Size::S}},
{109, {"Quad Corner Pin"_s, Accessory::Size::S}},
{110, {"Bi Corner Pin"_s, Accessory::Size::S}},
{111, {"Circle Pin"_s, Accessory::Size::S}},
{112, {"Flat End Pin"_s, Accessory::Size::S}},
{113, {"Flat Cut Pin"_s, Accessory::Size::S}},
{114, {"Radial Pin"_s, Accessory::Size::S}},
{115, {"Diamiter Pin"_s, Accessory::Size::S}},
{151, {"TriPoint"_s, Accessory::Size::S}},
{152, {"SquPoint"_s, Accessory::Size::S}},
{153, {"PenPoint"_s, Accessory::Size::S}},
{154, {"HexPoint"_s, Accessory::Size::S}},
{155, {"CycPoint"_s, Accessory::Size::S}},
{156, {"Bevel SquCutPoint"_s, Accessory::Size::S}},
{157, {"Bevel HexCutPoint"_s, Accessory::Size::S}},
{158, {"Bevel HexPoint"_s, Accessory::Size::S}},
{159, {"Bevel CycCutPoint"_s, Accessory::Size::S}},
{160, {"Bevel CycPoint"_s, Accessory::Size::S}},
{201, {"Shaped Edge 01"_s, Accessory::Size::M}},
{202, {"Shaped Edge 02"_s, Accessory::Size::M}},
{203, {"Shaped Edge 03"_s, Accessory::Size::M}},
{204, {"Shaped Edge 04"_s, Accessory::Size::M}},
{205, {"Shaped Edge 05"_s, Accessory::Size::M}},
{206, {"Shaped Edge 06"_s, Accessory::Size::M}},
{207, {"Shaped Edge 07"_s, Accessory::Size::M}},
{208, {"Shaped Edge 08"_s, Accessory::Size::M}},
{209, {"Shaped Edge 09"_s, Accessory::Size::M}},
{210, {"Shaped Edge 10"_s, Accessory::Size::M}},
{211, {"Shaped Edge 11"_s, Accessory::Size::M}},
{212, {"Shaped Edge 12"_s, Accessory::Size::M}},
{213, {"Shaped Edge 13"_s, Accessory::Size::M}},
{214, {"Shaped Edge 14"_s, Accessory::Size::M}},
{215, {"Shaped Edge 15"_s, Accessory::Size::M}},
{216, {"Shaped Edge 16"_s, Accessory::Size::M}},
{217, {"Shaped Edge 17"_s, Accessory::Size::M}},
{218, {"Shaped Edge 18"_s, Accessory::Size::M}},
{219, {"Shaped Edge 19"_s, Accessory::Size::M}},
{220, {"Shaped Edge 20"_s, Accessory::Size::M}},
{251, {"Fish Tail 01"_s, Accessory::Size::M}},
{252, {"Fish Tail 02"_s, Accessory::Size::M}},
{253, {"Fish Tail 03"_s, Accessory::Size::M}},
{254, {"Fish Tail 04"_s, Accessory::Size::M}},
{255, {"Fish Tail 05"_s, Accessory::Size::M}},
{256, {"Based Separator 01"_s, Accessory::Size::M}},
{257, {"Based Separator 02"_s, Accessory::Size::M}},
{258, {"Based Separator 03"_s, Accessory::Size::M}},
{259, {"Based Separator 04"_s, Accessory::Size::M}},
{260, {"Based Separator 05"_s, Accessory::Size::M}},
{261, {"Based Separator 06"_s, Accessory::Size::M}},
{262, {"Based Separator 07"_s, Accessory::Size::M}},
{263, {"Based Separator 08"_s, Accessory::Size::M}},
{264, {"Based Separator 09"_s, Accessory::Size::M}},
{265, {"Based Separator 10"_s, Accessory::Size::M}},
{301, {"Rectangular Box 01"_s, Accessory::Size::M}},
{302, {"Rectangular Box 02"_s, Accessory::Size::M}},
{303, {"Rectangular Box 03"_s, Accessory::Size::M}},
{304, {"Rectangular Box 04"_s, Accessory::Size::M}},
{305, {"Rectangular Box 05"_s, Accessory::Size::M}},
{306, {"CofBox 01"_s, Accessory::Size::M}},
{307, {"CofBox 02"_s, Accessory::Size::M}},
{308, {"CofBox 03"_s, Accessory::Size::M}},
{309, {"CofBox 04"_s, Accessory::Size::M}},
{310, {"CofBox 05"_s, Accessory::Size::M}},
{311, {"Triangular Box 01"_s, Accessory::Size::M}},
{312, {"Triangular Box 02"_s, Accessory::Size::M}},
{313, {"Triangular Box 03"_s, Accessory::Size::M}},
{314, {"Triangular Box 04"_s, Accessory::Size::M}},
{315, {"Triangular Box 05"_s, Accessory::Size::M}},
{316, {"Diagonal Box A01"_s, Accessory::Size::M}},
{317, {"Diagonal Box A02"_s, Accessory::Size::M}},
{318, {"Diagonal Box A03"_s, Accessory::Size::M}},
{319, {"Diagonal Box A04"_s, Accessory::Size::M}},
{320, {"Diagonal Box A05"_s, Accessory::Size::M}},
{321, {"Diagonal Box B01"_s, Accessory::Size::M}},
{322, {"Diagonal Box B02"_s, Accessory::Size::M}},
{323, {"Diagonal Box B03"_s, Accessory::Size::M}},
{324, {"Diagonal Box B04"_s, Accessory::Size::M}},
{325, {"Diagonal Box B05"_s, Accessory::Size::M}},
// endregion
// region Armours
{1001, {"Short Layer 01"_s, Accessory::Size::M}},
{1002, {"Short Layer 02"_s, Accessory::Size::M}},
{1003, {"Short Layer 03"_s, Accessory::Size::M}},
{1004, {"Short Layer 04"_s, Accessory::Size::M}},
{1005, {"Short Layer 05"_s, Accessory::Size::M}},
{1006, {"Long Layer 01"_s, Accessory::Size::M}},
{1007, {"Long Layer 02"_s, Accessory::Size::M}},
{1008, {"Long Layer 03"_s, Accessory::Size::M}},
{1009, {"Long Layer 04"_s, Accessory::Size::M}},
{1010, {"Long Layer 05"_s, Accessory::Size::M}},
{1011, {"Diagonal Long Layer 01"_s, Accessory::Size::M}},
{1012, {"Diagonal Long Layer 02"_s, Accessory::Size::M}},
{1013, {"Diagonal Long Layer 03"_s, Accessory::Size::M}},
{1014, {"Diagonal Long Layer 04"_s, Accessory::Size::M}},
{1015, {"Diagonal Long Layer 05"_s, Accessory::Size::M}},
{1051, {"Sloped Layer 01"_s, Accessory::Size::M}},
{1052, {"Sloped Layer 02"_s, Accessory::Size::M}},
{1053, {"Sloped Layer 03"_s, Accessory::Size::M}},
{1054, {"Sloped Layer 04"_s, Accessory::Size::M}},
{1055, {"Sloped Layer 05"_s, Accessory::Size::M}},
{1056, {"Sloped Layer 06"_s, Accessory::Size::M}},
{1057, {"Sloped Layer 07"_s, Accessory::Size::M}},
{1058, {"Sloped Layer 08"_s, Accessory::Size::M}},
{1059, {"Sloped Layer 09"_s, Accessory::Size::M}},
{1060, {"Sloped Layer 10"_s, Accessory::Size::M}},
{1061, {"Sloped Layer 11"_s, Accessory::Size::M}},
{1062, {"Sloped Layer 12"_s, Accessory::Size::M}},
{1063, {"Sloped Layer 13"_s, Accessory::Size::M}},
{1064, {"Sloped Layer 14"_s, Accessory::Size::M}},
{1065, {"Sloped Layer 15"_s, Accessory::Size::M}},
{1101, {"Raised Center 01"_s, Accessory::Size::M}},
{1102, {"Raised Center 02"_s, Accessory::Size::M}},
{1103, {"Raised Center 03"_s, Accessory::Size::M}},
{1104, {"Raised Center 04"_s, Accessory::Size::M}},
{1105, {"Raised Center 05"_s, Accessory::Size::M}},
{1106, {"Raised Block 01"_s, Accessory::Size::M}},
{1107, {"Raised Block 02"_s, Accessory::Size::M}},
{1108, {"Raised Block 03"_s, Accessory::Size::M}},
{1109, {"Raised Pointed"_s, Accessory::Size::M}},
{1110, {"Raised Cover"_s, Accessory::Size::M}},
{1111, {"Raised Slant 01"_s, Accessory::Size::M}},
{1112, {"Raised Slant 02"_s, Accessory::Size::M}},
{1113, {"Raised Slant 03"_s, Accessory::Size::M}},
{1114, {"Raised Slant 04"_s, Accessory::Size::M}},
{1115, {"Raised Slant 05"_s, Accessory::Size::M}},
{1151, {"Wide Patch 01"_s, Accessory::Size::L}},
{1152, {"Wide Patch 02"_s, Accessory::Size::L}},
{1153, {"Wide Patch 03"_s, Accessory::Size::L}},
{1154, {"Wide Patch 04"_s, Accessory::Size::L}},
{1155, {"Wide Patch 05"_s, Accessory::Size::L}},
{1201, {"Pointed Armour 01"_s, Accessory::Size::L}},
{1202, {"Pointed Armour 02"_s, Accessory::Size::L}},
{1203, {"Pointed Armour 03"_s, Accessory::Size::L}},
{1204, {"Pointed Armour 04"_s, Accessory::Size::L}},
{1205, {"Pointed Armour 05"_s, Accessory::Size::L}},
{1206, {"Pointed Armour 06"_s, Accessory::Size::L}},
{1207, {"Pointed Armour 07"_s, Accessory::Size::L}},
{1208, {"Pointed Armour 08"_s, Accessory::Size::L}},
{1209, {"Pointed Armour 09"_s, Accessory::Size::L}},
{1210, {"Pointed Armour 10"_s, Accessory::Size::L}},
{1211, {"Pointed Armour 11"_s, Accessory::Size::L}},
{1212, {"Pointed Armour 12"_s, Accessory::Size::L}},
{1213, {"Pointed Armour 13"_s, Accessory::Size::L}},
{1214, {"Pointed Armour 14"_s, Accessory::Size::L}},
{1215, {"Pointed Armour 15"_s, Accessory::Size::L}},
{1216, {"Pointed Armour 16"_s, Accessory::Size::L}},
{1217, {"Pointed Armour 17"_s, Accessory::Size::L}},
{1218, {"Pointed Armour 18"_s, Accessory::Size::L}},
{1219, {"Pointed Armour 19"_s, Accessory::Size::L}},
{1220, {"Pointed Armour 20"_s, Accessory::Size::L}},
{1251, {"E Limb Cover 01"_s, Accessory::Size::L}},
{1252, {"E Limb Cover 02"_s, Accessory::Size::L}},
{1253, {"E Limb Cover 03"_s, Accessory::Size::L}},
{1254, {"E Limb Cover 04"_s, Accessory::Size::L}},
{1255, {"E Limb Cover 05"_s, Accessory::Size::L}},
{1256, {"E Limb Cover 06"_s, Accessory::Size::L}},
{1257, {"E Limb Cover 07"_s, Accessory::Size::L}},
{1258, {"E Limb Cover 08"_s, Accessory::Size::L}},
{1259, {"E Limb Cover 09"_s, Accessory::Size::L}},
{1260, {"E Limb Cover 10"_s, Accessory::Size::L}},
{1301, {"C Limb Cover 01"_s, Accessory::Size::L}},
{1302, {"C Limb Cover 02"_s, Accessory::Size::L}},
{1303, {"C Limb Cover 03"_s, Accessory::Size::L}},
{1304, {"C Limb Cover 04"_s, Accessory::Size::L}},
{1305, {"C Limb Cover 05"_s, Accessory::Size::L}},
{1306, {"C Limb Cover 06"_s, Accessory::Size::L}},
{1307, {"C Limb Cover 07"_s, Accessory::Size::L}},
{1308, {"C Limb Cover 08"_s, Accessory::Size::L}},
{1309, {"C Limb Cover 09"_s, Accessory::Size::L}},
{1310, {"C Limb Cover 10"_s, Accessory::Size::L}},
{1311, {"C Limb Cover 11"_s, Accessory::Size::L}},
{1312, {"C Limb Cover 12"_s, Accessory::Size::L}},
{1313, {"C Limb Cover 13"_s, Accessory::Size::L}},
{1314, {"C Limb Cover 14"_s, Accessory::Size::L}},
{1315, {"C Limb Cover 15"_s, Accessory::Size::L}},
{1316, {"C Limb Cover 16"_s, Accessory::Size::L}},
{1317, {"C Limb Cover 17"_s, Accessory::Size::L}},
{1318, {"C Limb Cover 18"_s, Accessory::Size::L}},
{1319, {"C Limb Cover 19"_s, Accessory::Size::L}},
{1320, {"C Limb Cover 20"_s, Accessory::Size::L}},
{1351, {"P Limb Cover 01"_s, Accessory::Size::XL}},
{1352, {"P Limb Cover 02"_s, Accessory::Size::XL}},
{1353, {"P Limb Cover 03"_s, Accessory::Size::XL}},
{1354, {"P Limb Cover 04"_s, Accessory::Size::XL}},
{1355, {"P Limb Cover 05"_s, Accessory::Size::XL}},
{1401, {"Flat Cover 01"_s, Accessory::Size::XL}},
{1402, {"Flat Cover 02"_s, Accessory::Size::XL}},
{1403, {"Flat Cover 03"_s, Accessory::Size::XL}},
{1404, {"Flat Cover 04"_s, Accessory::Size::XL}},
{1405, {"Flat Cover 05"_s, Accessory::Size::XL}},
{1406, {"Flat Cover 06"_s, Accessory::Size::XL}},
{1407, {"Flat Cover 07"_s, Accessory::Size::XL}},
{1408, {"Flat Cover 08"_s, Accessory::Size::XL}},
{1409, {"Flat Cover 09"_s, Accessory::Size::XL}},
{1410, {"Flat Cover 10"_s, Accessory::Size::XL}},
{1451, {"L Side Opening 01"_s, Accessory::Size::XL}},
{1452, {"L Side Opening 02"_s, Accessory::Size::XL}},
{1453, {"L Side Opening 03"_s, Accessory::Size::XL}},
{1454, {"L Side Opening 04"_s, Accessory::Size::XL}},
{1455, {"L Side Opening 05"_s, Accessory::Size::XL}},
{1456, {"L Side Opening 06"_s, Accessory::Size::XL}},
{1457, {"L Side Opening 07"_s, Accessory::Size::XL}},
{1458, {"L Side Opening 08"_s, Accessory::Size::XL}},
{1459, {"L Side Opening 09"_s, Accessory::Size::XL}},
{1460, {"L Side Opening 10"_s, Accessory::Size::XL}},
// endregion
// region Components
{2001, {"Disc Padding 01"_s, Accessory::Size::M}},
{2002, {"Disc Padding 02"_s, Accessory::Size::M}},
{2003, {"Disc Padding 03"_s, Accessory::Size::M}},
{2004, {"Disc Padding 04"_s, Accessory::Size::M}},
{2005, {"Disc Padding 05"_s, Accessory::Size::M}},
{2006, {"Thin Padding 01"_s, Accessory::Size::M}},
{2007, {"Thin Padding 02"_s, Accessory::Size::M}},
{2008, {"Thin Padding 03"_s, Accessory::Size::M}},
{2009, {"Thin Padding 04"_s, Accessory::Size::M}},
{2010, {"Thin Padding 05"_s, Accessory::Size::M}},
{2011, {"Thick Padding 01"_s, Accessory::Size::M}},
{2012, {"Thick Padding 02"_s, Accessory::Size::M}},
{2013, {"Thick Padding 03"_s, Accessory::Size::M}},
{2014, {"Thick Padding 04"_s, Accessory::Size::M}},
{2015, {"Thick Padding 05"_s, Accessory::Size::M}},
{2016, {"Thick Padding 06"_s, Accessory::Size::M}},
{2017, {"Thick Padding 07"_s, Accessory::Size::M}},
{2018, {"Thick Padding 08"_s, Accessory::Size::M}},
{2019, {"Thick Padding 09"_s, Accessory::Size::M}},
{2020, {"Thick Padding 10"_s, Accessory::Size::M}},
{2021, {"CSide Padding 01"_s, Accessory::Size::M}},
{2022, {"CSide Padding 02"_s, Accessory::Size::M}},
{2023, {"CSide Padding 03"_s, Accessory::Size::M}},
{2024, {"CSide Padding 04"_s, Accessory::Size::M}},
{2025, {"CSide Padding 05"_s, Accessory::Size::M}},
{2051, {"Container 01"_s, Accessory::Size::L}},
{2052, {"Container 02"_s, Accessory::Size::L}},
{2053, {"Container 03"_s, Accessory::Size::L}},
{2054, {"Container 04"_s, Accessory::Size::L}},
{2055, {"Container 05"_s, Accessory::Size::L}},
{2101, {"Plating 01"_s, Accessory::Size::L}},
{2102, {"Plating 02"_s, Accessory::Size::L}},
{2103, {"Plating 03"_s, Accessory::Size::L}},
{2104, {"Plating 04"_s, Accessory::Size::L}},
{2105, {"Plating 05"_s, Accessory::Size::L}},
{2126, {"Curved Plating 01"_s, Accessory::Size::L}},
{2127, {"Curved Plating 02"_s, Accessory::Size::L}},
{2128, {"Curved Plating 03"_s, Accessory::Size::L}},
{2129, {"Curved Plating 04"_s, Accessory::Size::L}},
{2130, {"Curved Plating 05"_s, Accessory::Size::L}},
{2151, {"Complex Base 01"_s, Accessory::Size::L}},
{2152, {"Complex Base 02"_s, Accessory::Size::L}},
{2153, {"Complex Base 03"_s, Accessory::Size::L}},
{2154, {"Complex Base 04"_s, Accessory::Size::L}},
{2155, {"Complex Base 05"_s, Accessory::Size::L}},
{2156, {"Complex Base 06"_s, Accessory::Size::L}},
{2157, {"Complex Base 07"_s, Accessory::Size::L}},
{2158, {"Complex Base 08"_s, Accessory::Size::L}},
{2159, {"Complex Base 09"_s, Accessory::Size::L}},
{2160, {"Complex Base 10"_s, Accessory::Size::L}},
{2201, {"Long Base 01"_s, Accessory::Size::XL}},
{2202, {"Long Base 02"_s, Accessory::Size::XL}},
{2203, {"Long Base 03"_s, Accessory::Size::XL}},
{2204, {"Long Base 04"_s, Accessory::Size::XL}},
{2205, {"Long Base 05"_s, Accessory::Size::XL}},
{2251, {"Straight Wing 01"_s, Accessory::Size::XL}},
{2252, {"Straight Wing 02"_s, Accessory::Size::XL}},
{2253, {"Straight Wing 03"_s, Accessory::Size::XL}},
{2254, {"Straight Wing 04"_s, Accessory::Size::XL}},
{2255, {"Straight Wing 05"_s, Accessory::Size::XL}},
{2256, {"Straight Wing 06"_s, Accessory::Size::XL}},
{2257, {"Straight Wing 07"_s, Accessory::Size::XL}},
{2258, {"Straight Wing 08"_s, Accessory::Size::XL}},
{2259, {"Straight Wing 09"_s, Accessory::Size::XL}},
{2260, {"Straight Wing 10"_s, Accessory::Size::XL}},
{2301, {"Triangular Wing 01"_s, Accessory::Size::XL}},
{2302, {"Triangular Wing 02"_s, Accessory::Size::XL}},
{2303, {"Triangular Wing 03"_s, Accessory::Size::XL}},
{2304, {"Triangular Wing 04"_s, Accessory::Size::XL}},
{2305, {"Triangular Wing 05"_s, Accessory::Size::XL}},
{2306, {"Triangular Wing 06"_s, Accessory::Size::XL}},
{2307, {"Triangular Wing 07"_s, Accessory::Size::XL}},
{2308, {"Triangular Wing 08"_s, Accessory::Size::XL}},
{2309, {"Triangular Wing 09"_s, Accessory::Size::XL}},
{2310, {"Triangular Wing 10"_s, Accessory::Size::XL}},
{2311, {"Triangular Wing 11"_s, Accessory::Size::L}},
{2312, {"Triangular Wing 12"_s, Accessory::Size::L}},
{2313, {"Triangular Wing 13"_s, Accessory::Size::L}},
{2314, {"Triangular Wing 14"_s, Accessory::Size::L}},
{2315, {"Triangular Wing 15"_s, Accessory::Size::L}},
{2351, {"Complex Wing 01"_s, Accessory::Size::XL}},
{2352, {"Complex Wing 02"_s, Accessory::Size::XL}},
{2353, {"Complex Wing 03"_s, Accessory::Size::XL}},
{2354, {"Complex Wing 04"_s, Accessory::Size::XL}},
{2355, {"Complex Wing 05"_s, Accessory::Size::XL}},
{2356, {"Complex Wing 06"_s, Accessory::Size::L}},
{2357, {"Complex Wing 07"_s, Accessory::Size::L}},
{2358, {"Complex Wing 08"_s, Accessory::Size::L}},
{2359, {"Complex Wing 09"_s, Accessory::Size::L}},
{2360, {"Complex Wing 10"_s, Accessory::Size::L}},
{2401, {"Blade 01"_s, Accessory::Size::XL}},
{2402, {"Blade 02"_s, Accessory::Size::XL}},
{2403, {"Blade 03"_s, Accessory::Size::XL}},
{2404, {"Blade 04"_s, Accessory::Size::XL}},
{2405, {"Blade 05"_s, Accessory::Size::XL}},
{2406, {"Blade 06"_s, Accessory::Size::XL}},
{2407, {"Blade 07"_s, Accessory::Size::XL}},
{2408, {"Blade 08"_s, Accessory::Size::XL}},
{2409, {"Blade 09"_s, Accessory::Size::XL}},
{2410, {"Blade 10"_s, Accessory::Size::XL}},
{2411, {"Blade 11"_s, Accessory::Size::XL}},
{2412, {"Blade 12"_s, Accessory::Size::XL}},
{2413, {"Blade 13"_s, Accessory::Size::XL}},
{2414, {"Blade 14"_s, Accessory::Size::XL}},
{2415, {"Blade 15"_s, Accessory::Size::XL}},
{2426, {"Curved Blade 01"_s, Accessory::Size::XL}},
{2427, {"Curved Blade 02"_s, Accessory::Size::XL}},
{2428, {"Curved Blade 03"_s, Accessory::Size::XL}},
{2429, {"Curved Blade 04"_s, Accessory::Size::XL}},
{2430, {"Curved Blade 05"_s, Accessory::Size::XL}},
{2431, {"Axe Head 01"_s, Accessory::Size::XL}},
{2432, {"Axe Head 02"_s, Accessory::Size::XL}},
{2433, {"Axe Head 03"_s, Accessory::Size::XL}},
{2434, {"Axe Head 04"_s, Accessory::Size::XL}},
{2435, {"Axe Head 05"_s, Accessory::Size::XL}},
{2451, {"Horn 01"_s, Accessory::Size::M}},
{2452, {"Horn 02"_s, Accessory::Size::M}},
{2453, {"Horn 03"_s, Accessory::Size::M}},
{2454, {"Horn 04"_s, Accessory::Size::M}},
{2455, {"Horn 05"_s, Accessory::Size::M}},
{2456, {"Horn 06"_s, Accessory::Size::M}},
{2457, {"Horn 07"_s, Accessory::Size::M}},
{2458, {"Horn 08"_s, Accessory::Size::M}},
{2459, {"Horn 09"_s, Accessory::Size::M}},
{2460, {"Horn 10"_s, Accessory::Size::M}},
{2461, {"Horn 11"_s, Accessory::Size::M}},
{2462, {"Horn 12"_s, Accessory::Size::M}},
{2463, {"Horn 13"_s, Accessory::Size::M}},
{2464, {"Horn 14"_s, Accessory::Size::M}},
{2465, {"Horn 15"_s, Accessory::Size::M}},
{2471, {"Mask"_s, Accessory::Size::M}},
{2472, {"Droplet"_s, Accessory::Size::M}},
{2473, {"Thigh"_s, Accessory::Size::M}},
{2474, {"LegS"_s, Accessory::Size::M}},
{2475, {"LegTH"_s, Accessory::Size::M}},
{2476, {"Plume 01"_s, Accessory::Size::M}},
{2477, {"Plume 02"_s, Accessory::Size::M}},
{2478, {"Plume 03"_s, Accessory::Size::M}},
{2479, {"Plume 04"_s, Accessory::Size::M}},
{2480, {"Plume 05"_s, Accessory::Size::M}},
{2491, {"Tail 01"_s, Accessory::Size::XL}},
{2492, {"Tail 02"_s, Accessory::Size::XL}},
{2493, {"Tail 03"_s, Accessory::Size::XL}},
{2494, {"Tail 04"_s, Accessory::Size::XL}},
{2495, {"Tail 05"_s, Accessory::Size::XL}},
{2501, {"Finger 01"_s, Accessory::Size::M}},
{2502, {"Finger 02"_s, Accessory::Size::M}},
{2503, {"Finger 03"_s, Accessory::Size::M}},
{2504, {"Finger 04"_s, Accessory::Size::M}},
{2505, {"Finger 05"_s, Accessory::Size::M}},
{2510, {"Clamp 01"_s, Accessory::Size::M}},
{2511, {"Clamp 02"_s, Accessory::Size::M}},
{2512, {"Clamp 03"_s, Accessory::Size::M}},
{2513, {"Clamp 04"_s, Accessory::Size::M}},
{2514, {"Clamp 05"_s, Accessory::Size::M}},
{2521, {"Fabric 01"_s, Accessory::Size::XL}},
{2522, {"Fabric 02"_s, Accessory::Size::XL}},
{2523, {"Fabric 03"_s, Accessory::Size::XL}},
{2524, {"Fabric 04"_s, Accessory::Size::XL}},
{2525, {"Fabric 05"_s, Accessory::Size::XL}},
{2551, {"Energy Barrel 01"_s, Accessory::Size::XL}},
{2552, {"Energy Barrel 02"_s, Accessory::Size::XL}},
{2553, {"Energy Barrel 03"_s, Accessory::Size::XL}},
{2554, {"Energy Barrel 04"_s, Accessory::Size::XL}},
{2555, {"Energy Barrel 05"_s, Accessory::Size::XL}},
{2560, {"Wire Head 01"_s, Accessory::Size::M}},
{2561, {"Wire Head 02"_s, Accessory::Size::M}},
{2562, {"Wire Head 03"_s, Accessory::Size::M}},
{2563, {"Wire Head 04"_s, Accessory::Size::M}},
{2564, {"Wire Head 05"_s, Accessory::Size::M}},
{2565, {"Wire Head 06"_s, Accessory::Size::M}},
{2566, {"Wire Head 07"_s, Accessory::Size::M}},
{2567, {"Wire Head 08"_s, Accessory::Size::M}},
{2568, {"Wire Head 09"_s, Accessory::Size::M}},
{2569, {"Wire Head 10"_s, Accessory::Size::M}},
{2601, {"L Bullet Barrel 01"_s, Accessory::Size::XL}},
{2602, {"L Bullet Barrel 02"_s, Accessory::Size::XL}},
{2603, {"L Bullet Barrel 03"_s, Accessory::Size::XL}},
{2604, {"L Bullet Barrel 04"_s, Accessory::Size::XL}},
{2605, {"L Bullet Barrel 05"_s, Accessory::Size::XL}},
{2606, {"S Bullet Barrel 01"_s, Accessory::Size::XL}},
{2607, {"S Bullet Barrel 02"_s, Accessory::Size::XL}},
{2608, {"S Bullet Barrel 03"_s, Accessory::Size::XL}},
{2609, {"S Bullet Barrel 04"_s, Accessory::Size::XL}},
{2610, {"S Bullet Barrel 05"_s, Accessory::Size::XL}},
{2611, {"B Bullet Barrel 01"_s, Accessory::Size::XL}},
{2612, {"B Bullet Barrel 02"_s, Accessory::Size::XL}},
{2613, {"B Bullet Barrel 03"_s, Accessory::Size::XL}},
{2614, {"B Bullet Barrel 04"_s, Accessory::Size::XL}},
{2615, {"B Bullet Barrel 05"_s, Accessory::Size::XL}},
{2616, {"B Bullet Barrel 06"_s, Accessory::Size::XL}},
{2617, {"B Bullet Barrel 07"_s, Accessory::Size::XL}},
{2618, {"B Bullet Barrel 08"_s, Accessory::Size::XL}},
{2619, {"B Bullet Barrel 09"_s, Accessory::Size::XL}},
{2620, {"B Bullet Barrel 10"_s, Accessory::Size::XL}},
{2651, {"Cylinder Scope 01"_s, Accessory::Size::M}},
{2652, {"Cylinder Scope 02"_s, Accessory::Size::M}},
{2653, {"Cylinder Scope 03"_s, Accessory::Size::M}},
{2654, {"Cylinder Scope 04"_s, Accessory::Size::M}},
{2655, {"Cylinder Scope 05"_s, Accessory::Size::M}},
{2656, {"Elec Scope 01"_s, Accessory::Size::M}},
{2657, {"Elec Scope 02"_s, Accessory::Size::M}},
{2658, {"Elec Scope 03"_s, Accessory::Size::M}},
{2659, {"Elec Scope 04"_s, Accessory::Size::M}},
{2660, {"Elec Scope 05"_s, Accessory::Size::M}},
{2661, {"Mark Scope 01"_s, Accessory::Size::S}},
{2662, {"Mark Scope 02"_s, Accessory::Size::S}},
{2663, {"Mark Scope 03"_s, Accessory::Size::S}},
{2664, {"Mark Scope 04"_s, Accessory::Size::S}},
{2665, {"Mark Scope 05"_s, Accessory::Size::S}},
{2701, {"S Single Weaponry"_s, Accessory::Size::M}},
{2702, {"S Packed Weaponry 01"_s, Accessory::Size::M}},
{2703, {"S Packed Weaponry 02"_s, Accessory::Size::M}},
{2704, {"S Packed Weaponry 03"_s, Accessory::Size::M}},
{2705, {"S Packed Weaponry 04"_s, Accessory::Size::M}},
{2706, {"L Single Weaponry"_s, Accessory::Size::XL}},
{2707, {"L Packed Weaponry 01"_s, Accessory::Size::XL}},
{2708, {"L Packed Weaponry 02"_s, Accessory::Size::XL}},
{2709, {"L Packed Weaponry 03"_s, Accessory::Size::XL}},
{2710, {"L Packed Weaponry 04"_s, Accessory::Size::XL}},
{2711, {"Atk Single Weaponry"_s, Accessory::Size::XL}},
{2712, {"Atk Packed Weaponry 01"_s, Accessory::Size::XL}},
{2713, {"Atk Packed Weaponry 02"_s, Accessory::Size::XL}},
{2714, {"Atk Packed Weaponry 03"_s, Accessory::Size::XL}},
{2715, {"Atk Packed Weaponry 04"_s, Accessory::Size::XL}},
{2721, {"Double Pod"_s, Accessory::Size::L}},
{2722, {"Triple Pod"_s, Accessory::Size::L}},
{2723, {"Vertical T Pod"_s, Accessory::Size::L}},
{2724, {"Quadruple Pod"_s, Accessory::Size::L}},
{2725, {"Vertical Q Pod"_s, Accessory::Size::L}},
{2741, {"Grenade 01"_s, Accessory::Size::M}},
{2742, {"Grenade 02"_s, Accessory::Size::M}},
{2743, {"Grenade 03"_s, Accessory::Size::M}},
{2744, {"Grenade 04"_s, Accessory::Size::M}},
{2745, {"Grenade 05"_s, Accessory::Size::M}},
{2751, {"Vent 01"_s, Accessory::Size::M}},
{2752, {"Vent 02"_s, Accessory::Size::M}},
{2753, {"Vent 03"_s, Accessory::Size::M}},
{2754, {"Vent 04"_s, Accessory::Size::M}},
{2755, {"Vent 05"_s, Accessory::Size::M}},
{2756, {"Vent 06"_s, Accessory::Size::M}},
{2757, {"Vent 07"_s, Accessory::Size::M}},
{2758, {"Vent 08"_s, Accessory::Size::M}},
{2759, {"Vent 09"_s, Accessory::Size::M}},
{2760, {"Vent 10"_s, Accessory::Size::M}},
{2761, {"Cooling Tile 01"_s, Accessory::Size::L}},
{2762, {"Cooling Tile 02"_s, Accessory::Size::L}},
{2763, {"Cooling Tile 03"_s, Accessory::Size::L}},
{2764, {"Cooling Tile 04"_s, Accessory::Size::L}},
{2765, {"Cooling Tile 05"_s, Accessory::Size::L}},
{2901, {"Complex Construct 01"_s, Accessory::Size::L}},
{2902, {"Complex Construct 02"_s, Accessory::Size::L}},
{2903, {"Complex Construct 03"_s, Accessory::Size::L}},
{2904, {"Complex Construct 04"_s, Accessory::Size::L}},
{2905, {"Complex Construct 05"_s, Accessory::Size::L}},
{2906, {"Grating 01"_s, Accessory::Size::M}},
{2907, {"Grating 02"_s, Accessory::Size::M}},
{2908, {"Grating 03"_s, Accessory::Size::M}},
{2909, {"Grating 04"_s, Accessory::Size::M}},
{2910, {"Grating 05"_s, Accessory::Size::M}},
{2911, {"Wireframe 01"_s, Accessory::Size::L}},
{2912, {"Wireframe 02"_s, Accessory::Size::L}},
{2913, {"Wireframe 03"_s, Accessory::Size::L}},
{2914, {"Wireframe 04"_s, Accessory::Size::L}},
{2915, {"Wireframe 05"_s, Accessory::Size::L}},
{2926, {"Complex Armour 01"_s, Accessory::Size::L}},
{2927, {"Complex Armour 02"_s, Accessory::Size::L}},
{2928, {"Complex Armour 03"_s, Accessory::Size::L}},
{2929, {"Complex Armour 04"_s, Accessory::Size::L}},
{2930, {"Complex Armour 05"_s, Accessory::Size::L}},
{2950, {"Q Mask 01"_s, Accessory::Size::M}},
{2951, {"Q Mask 02"_s, Accessory::Size::M}},
{2952, {"Q Mask 03"_s, Accessory::Size::M}},
{2953, {"Q Mask 04"_s, Accessory::Size::M}},
{2954, {"Q Mask 05"_s, Accessory::Size::M}},
// endregion
// region Connectors
{3001, {"Circular Vent 01"_s, Accessory::Size::M}},
{3002, {"Circular Vent 02"_s, Accessory::Size::M}},
{3003, {"Circular Vent 03"_s, Accessory::Size::M}},
{3004, {"Circular Vent 04"_s, Accessory::Size::M}},
{3005, {"Circular Vent 05"_s, Accessory::Size::M}},
{3006, {"Circular Vent 06"_s, Accessory::Size::M}},
{3007, {"Circular Vent 07"_s, Accessory::Size::M}},
{3008, {"Circular Vent 08"_s, Accessory::Size::M}},
{3009, {"Circular Vent 09"_s, Accessory::Size::M}},
{3010, {"Circular Vent 10"_s, Accessory::Size::M}},
{3011, {"Circular Vent 11"_s, Accessory::Size::M}},
{3012, {"Circular Vent 12"_s, Accessory::Size::M}},
{3013, {"Circular Vent 13"_s, Accessory::Size::M}},
{3014, {"Circular Vent 14"_s, Accessory::Size::M}},
{3015, {"Circular Vent 15"_s, Accessory::Size::M}},
{3051, {"Reactor 01"_s, Accessory::Size::L}},
{3052, {"Reactor 02"_s, Accessory::Size::L}},
{3053, {"Reactor 03"_s, Accessory::Size::L}},
{3054, {"Reactor 04"_s, Accessory::Size::L}},
{3055, {"Reactor 05"_s, Accessory::Size::L}},
{3101, {"Connecting Tube 01"_s, Accessory::Size::XL}},
{3102, {"Connecting Tube 02"_s, Accessory::Size::XL}},
{3103, {"Connecting Tube 03"_s, Accessory::Size::XL}},
{3104, {"Connecting Tube 04"_s, Accessory::Size::XL}},
{3105, {"Connecting Tube 05"_s, Accessory::Size::XL}},
{3151, {"Latch 01"_s, Accessory::Size::M}},
{3152, {"Latch 02"_s, Accessory::Size::M}},
{3153, {"Latch 03"_s, Accessory::Size::M}},
{3154, {"Latch 04"_s, Accessory::Size::M}},
{3155, {"Latch 05"_s, Accessory::Size::M}},
{3156, {"Latch 06"_s, Accessory::Size::M}},
{3157, {"Latch 07"_s, Accessory::Size::M}},
{3158, {"Latch 08"_s, Accessory::Size::M}},
{3159, {"Latch 09"_s, Accessory::Size::M}},
{3160, {"Latch 10"_s, Accessory::Size::M}},
{3161, {"Latch 11"_s, Accessory::Size::M}},
{3162, {"Latch 12"_s, Accessory::Size::M}},
{3163, {"Latch 13"_s, Accessory::Size::M}},
{3164, {"Latch 14"_s, Accessory::Size::M}},
{3165, {"Latch 15"_s, Accessory::Size::M}},
{3201, {"Short Connector 01"_s, Accessory::Size::M}},
{3202, {"Short Connector 02"_s, Accessory::Size::M}},
{3203, {"Short Connector 03"_s, Accessory::Size::M}},
{3204, {"Short Connector 04"_s, Accessory::Size::M}},
{3205, {"Short Connector 05"_s, Accessory::Size::M}},
{3206, {"Antenna 01"_s, Accessory::Size::S}},
{3207, {"Antenna 02"_s, Accessory::Size::S}},
{3208, {"Antenna 03"_s, Accessory::Size::S}},
{3209, {"Antenna 04"_s, Accessory::Size::S}},
{3210, {"Antenna 05"_s, Accessory::Size::S}},
{3226, {"Long Connector 01"_s, Accessory::Size::XL}},
{3227, {"Long Connector 02"_s, Accessory::Size::XL}},
{3228, {"Long Connector 03"_s, Accessory::Size::XL}},
{3229, {"Long Connector 04"_s, Accessory::Size::XL}},
{3230, {"Long Connector 05"_s, Accessory::Size::XL}},
{3231, {"Long Connector 06"_s, Accessory::Size::XL}},
{3232, {"Long Connector 07"_s, Accessory::Size::XL}},
{3233, {"Long Connector 08"_s, Accessory::Size::XL}},
{3234, {"Long Connector 09"_s, Accessory::Size::XL}},
{3235, {"Long Connector 10"_s, Accessory::Size::XL}},
{3251, {"Complex Connector 01"_s, Accessory::Size::XL}},
{3252, {"Complex Connector 02"_s, Accessory::Size::XL}},
{3253, {"Complex Connector 03"_s, Accessory::Size::XL}},
{3254, {"Complex Connector 04"_s, Accessory::Size::XL}},
{3255, {"Complex Connector 05"_s, Accessory::Size::XL}},
{3301, {"Tube Line 01"_s, Accessory::Size::L}},
{3302, {"Tube Line 02"_s, Accessory::Size::L}},
{3303, {"Tube Line 03"_s, Accessory::Size::L}},
{3304, {"Tube Line 04"_s, Accessory::Size::XL}},
{3305, {"Tube Line 05"_s, Accessory::Size::XL}},
{3306, {"Tube Line 06"_s, Accessory::Size::M}},
{3307, {"Tube Line 07"_s, Accessory::Size::M}},
{3308, {"Tube Line 08"_s, Accessory::Size::M}},
{3309, {"Tube Line 09"_s, Accessory::Size::L}},
{3310, {"Tube Line 10"_s, Accessory::Size::L}},
{3351, {"Radar Plate 01"_s, Accessory::Size::M}},
{3352, {"Radar Plate 02"_s, Accessory::Size::M}},
{3353, {"Radar Plate 03"_s, Accessory::Size::M}},
{3354, {"Radar Plate 04"_s, Accessory::Size::M}},
{3355, {"Radar Plate 05"_s, Accessory::Size::M}},
{3356, {"Radar Pod 01"_s, Accessory::Size::M}},
{3357, {"Radar Pod 02"_s, Accessory::Size::M}},
{3358, {"Radar Pod 03"_s, Accessory::Size::M}},
{3359, {"Radar Pod 04"_s, Accessory::Size::M}},
{3360, {"Radar Pod 05"_s, Accessory::Size::M}},
{3361, {"Radar Ring 01"_s, Accessory::Size::XL}},
{3362, {"Radar Ring 02"_s, Accessory::Size::XL}},
{3363, {"Radar Ring 03"_s, Accessory::Size::XL}},
{3364, {"Radar Ring 04"_s, Accessory::Size::XL}},
{3365, {"Radar Ring 05"_s, Accessory::Size::XL}},
{3401, {"Tri Pod 01"_s, Accessory::Size::M}},
{3402, {"Tri Pod 02"_s, Accessory::Size::M}},
{3403, {"Tri Pod 03"_s, Accessory::Size::M}},
{3404, {"Tri Pod 04"_s, Accessory::Size::M}},
{3405, {"Tri Pod 05"_s, Accessory::Size::M}},
{3406, {"Signal Pod 01"_s, Accessory::Size::M}},
{3407, {"Signal Pod 02"_s, Accessory::Size::M}},
{3408, {"Signal Pod 03"_s, Accessory::Size::M}},
{3409, {"Signal Pod 04"_s, Accessory::Size::M}},
{3410, {"Signal Pod 05"_s, Accessory::Size::M}},
{3451, {"Track Wheel 01"_s, Accessory::Size::XL}},
{3452, {"Track Wheel 02"_s, Accessory::Size::XL}},
{3453, {"Track Wheel 03"_s, Accessory::Size::XL}},
{3454, {"Track Wheel 04"_s, Accessory::Size::XL}},
{3455, {"Track Wheel 05"_s, Accessory::Size::XL}},
{3456, {"Track Chain 01"_s, Accessory::Size::XL}},
{3457, {"Track Chain 02"_s, Accessory::Size::XL}},
{3458, {"Track Chain 03"_s, Accessory::Size::XL}},
{3459, {"Track Chain 04"_s, Accessory::Size::XL}},
{3460, {"Track Chain 05"_s, Accessory::Size::XL}},
{3461, {"Track Chain 06"_s, Accessory::Size::XL}},
{3462, {"Track Chain 07"_s, Accessory::Size::XL}},
{3463, {"Track Chain 08"_s, Accessory::Size::XL}},
{3464, {"Track Chain 09"_s, Accessory::Size::XL}},
{3465, {"Track Chain 10"_s, Accessory::Size::XL}},
{3466, {"Track Chain 11"_s, Accessory::Size::XL}},
{3467, {"Track Chain 12"_s, Accessory::Size::XL}},
{3468, {"Track Chain 13"_s, Accessory::Size::XL}},
{3469, {"Track Chain 14"_s, Accessory::Size::XL}},
{3470, {"Track Chain 15"_s, Accessory::Size::XL}},
{3500, {"Wire Module 05"_s, Accessory::Size::XL}},
{3501, {"Wire Module 06"_s, Accessory::Size::XL}},
{3502, {"Wire Module 07"_s, Accessory::Size::XL}},
{3503, {"Wire Module 08"_s, Accessory::Size::XL}},
{3504, {"Wire Module 09"_s, Accessory::Size::XL}},
{3505, {"Wire Module 10"_s, Accessory::Size::XL}},
{3506, {"Wire Module 11"_s, Accessory::Size::XL}},
{3507, {"Wire Module 12"_s, Accessory::Size::XL}},
{3508, {"Wire Module 13"_s, Accessory::Size::XL}},
{3509, {"Wire Module 14"_s, Accessory::Size::XL}},
{3510, {"Wire Module 15"_s, Accessory::Size::XL}},
{3511, {"Wire Module 06"_s, Accessory::Size::XL}},
{3512, {"Wire Module 07"_s, Accessory::Size::XL}},
{3513, {"Wire Module 08"_s, Accessory::Size::XL}},
{3514, {"Wire Module 09"_s, Accessory::Size::XL}},
// endregion
};
}

View file

@ -1,70 +0,0 @@
#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 <map>
#include <Corrade/Containers/StringView.h>
using namespace Corrade;
using namespace Containers::Literals;
namespace mbst::GameData {
static const std::map<std::int32_t, Containers::StringView> mission_id_map {{
// Story missions
{100, "Mission 1 - Training"_s},
{101, "Mission 2 - Patrol Operation"_s},
{102, "Mission 3 - Fusion Cells in the Snow"_s},
{103, "Mission 4 - Earning Changes"_s},
{104, "Mission 5 - Unexpected Coordination"_s},
{105, "Mission 6 - Empowering Void"_s},
{106, "Mission 7 - Logisitics Obstacles"_s},
{107, "Mission 8 - Wrath of the Wastelands"_s},
{108, "Mission 9 - Suspicious Originator"_s},
{109, "Mission 10 - Researchers Data Recovery"_s},
{110, "Mission 11 - Tempestuous Sector"_s},
{111, "Mission 12 - Clashes of Metal"_s},
{112, "Mission 13 - The Sandstorm Glutton"_s},
{113, "Mission 14 - An Icy Investigation"_s},
{114, "Mission 15 - Outposts Line of Defense"_s},
{115, "Mission 16 - Hidden in the Pass"_s},
{116, "Mission 17 - Homebase Security"_s},
{117, "Mission 18 - Molewarp Protection Deal"_s},
{118, "Mission 19 - Behind the Walls of Ice"_s},
{119, "Mission 20 - Odin in the Sea of Flames"_s},
{120, "Mission 21 - Retracing Ruined Shelter"_s},
{121, "Mission 22 - The Traitor"_s},
{122, "Mission 23 - Duel of Aces"_s},
// Hunting grounds
{200, "Hunt 1 - Desert Pathway Safety"_s},
{201, "Hunt 2 - Snowfield Custodian"_s},
{202, "Hunt 3 - Abandoned Valley Raid"_s},
{203, "Hunt 4 - Depths of the Machineries"_s},
{204, "Hunt 5 - Crater Crashers"_s},
{205, "Hunt 6 - Prototype Performance Tests"_s},
{206, "Hunt 7 - A Mess in Manufacturing"_s},
{207, "Hunt 8 - Visitors in Volcanic Fissures"_s},
// Challenges
{300, "Challenge 1 - Redline Battlefront"_s},
{320, "Challenge 2 - Void Convergence"_s},
{400, "Challenge 3 - Gates of Ascension"_s}
}};
}

View file

@ -1,139 +0,0 @@
// 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 "PropertyNames.h"
#include "../Gvas/Types/ArrayProperty.h"
#include "../Gvas/Types/BoolProperty.h"
#include "../Gvas/Types/ColourStructProperty.h"
#include "../Gvas/Types/FloatProperty.h"
#include "../Gvas/Types/GenericStructProperty.h"
#include "../Gvas/Types/RotatorStructProperty.h"
#include "../Gvas/Types/VectorStructProperty.h"
#include "../Gvas/Types/Vector2DStructProperty.h"
#include "../Gvas/Types/IntProperty.h"
#include "Mass.h"
using namespace Containers::Literals;
namespace mbst::GameObjects {
void
Mass::getDecals(Containers::ArrayView<Decal> decals, Gvas::Types::ArrayProperty* decal_array) {
for(std::uint32_t i = 0; i < decal_array->items.size(); i++) {
auto decal_prop = decal_array->at<Gvas::Types::GenericStructProperty>(i);
CORRADE_INTERNAL_ASSERT(decal_prop);
auto& decal = decals[i];
decal.id = decal_prop->at<Gvas::Types::IntProperty>(MASS_DECAL_ID)->value;
auto colour_prop = decal_prop->at<Gvas::Types::ColourStructProperty>(MASS_DECAL_COLOUR);
decal.colour = Color4{colour_prop->r, colour_prop->g, colour_prop->b, colour_prop->a};
auto pos_prop = decal_prop->at<Gvas::Types::VectorStructProperty>(MASS_DECAL_POSITION);
decal.position = pos_prop->vector;
auto u_prop = decal_prop->at<Gvas::Types::VectorStructProperty>(MASS_DECAL_UAXIS);
decal.uAxis = u_prop->vector;
auto v_prop = decal_prop->at<Gvas::Types::VectorStructProperty>(MASS_DECAL_VAXIS);
decal.vAxis = v_prop->vector;
auto offset_prop = decal_prop->at<Gvas::Types::Vector2DStructProperty>(MASS_DECAL_OFFSET);
decal.offset = offset_prop->vector;
decal.scale = decal_prop->at<Gvas::Types::FloatProperty>(MASS_DECAL_SCALE)->value;
decal.rotation = decal_prop->at<Gvas::Types::FloatProperty>(MASS_DECAL_ROTATION)->value;
decal.flip = decal_prop->at<Gvas::Types::BoolProperty>(MASS_DECAL_FLIP)->value;
decal.wrap = decal_prop->at<Gvas::Types::BoolProperty>(MASS_DECAL_WRAP)->value;
}
}
void
Mass::writeDecals(Containers::ArrayView<Decal> decals, Gvas::Types::ArrayProperty* decal_array) {
for(std::uint32_t i = 0; i < decal_array->items.size(); i++) {
auto decal_prop = decal_array->at<Gvas::Types::GenericStructProperty>(i);
CORRADE_INTERNAL_ASSERT(decal_prop);
auto& decal = decals[i];
decal_prop->at<Gvas::Types::IntProperty>(MASS_DECAL_ID)->value = decal.id;
auto colour_prop = decal_prop->at<Gvas::Types::ColourStructProperty>(MASS_DECAL_COLOUR);
colour_prop->r = decal.colour.r();
colour_prop->g = decal.colour.g();
colour_prop->b = decal.colour.b();
colour_prop->a = decal.colour.a();
auto pos_prop = decal_prop->at<Gvas::Types::VectorStructProperty>(MASS_DECAL_POSITION);
pos_prop->vector = decal.position;
auto u_prop = decal_prop->at<Gvas::Types::VectorStructProperty>(MASS_DECAL_UAXIS);
u_prop->vector = decal.uAxis;
auto v_prop = decal_prop->at<Gvas::Types::VectorStructProperty>(MASS_DECAL_VAXIS);
v_prop->vector = decal.vAxis;
auto offset_prop = decal_prop->at<Gvas::Types::Vector2DStructProperty>(MASS_DECAL_OFFSET);
offset_prop->vector = decal.offset;
decal_prop->at<Gvas::Types::FloatProperty>(MASS_DECAL_SCALE)->value = decal.scale;
decal_prop->at<Gvas::Types::FloatProperty>(MASS_DECAL_ROTATION)->value = decal.rotation;
decal_prop->at<Gvas::Types::BoolProperty>(MASS_DECAL_FLIP)->value = decal.flip;
decal_prop->at<Gvas::Types::BoolProperty>(MASS_DECAL_WRAP)->value = decal.wrap;
}
}
void
Mass::getAccessories(Containers::ArrayView<Accessory> accessories, Gvas::Types::ArrayProperty* accessory_array) {
for(std::uint32_t i = 0; i < accessory_array->items.size(); i++) {
auto acc_prop = accessory_array->at<Gvas::Types::GenericStructProperty>(i);
CORRADE_INTERNAL_ASSERT(acc_prop);
auto& accessory = accessories[i];
accessory.attachIndex = acc_prop->at<Gvas::Types::IntProperty>(MASS_ACCESSORY_ATTACH_INDEX)->value;
accessory.id = acc_prop->at<Gvas::Types::IntProperty>(MASS_ACCESSORY_ID)->value;
auto acc_styles = acc_prop->at<Gvas::Types::ArrayProperty>(MASS_ACCESSORY_STYLES);
for(std::uint32_t j = 0; j < acc_styles->items.size(); j++) {
accessory.styles[j] = acc_styles->at<Gvas::Types::IntProperty>(j)->value;
}
auto rel_pos_prop = acc_prop->at<Gvas::Types::VectorStructProperty>(MASS_ACCESSORY_RELPOS);
accessory.relativePosition = rel_pos_prop->vector;
auto rel_pos_offset_prop = acc_prop->at<Gvas::Types::VectorStructProperty>(MASS_ACCESSORY_OFFPOS);
accessory.relativePositionOffset = rel_pos_offset_prop->vector;
auto rel_rot_prop = acc_prop->at<Gvas::Types::RotatorStructProperty>(MASS_ACCESSORY_RELROT);
accessory.relativeRotation = rel_rot_prop->vector;
auto rel_rot_offset_prop = acc_prop->at<Gvas::Types::RotatorStructProperty>(MASS_ACCESSORY_OFFROT);
accessory.relativeRotationOffset = rel_rot_offset_prop->vector;
auto local_scale_prop = acc_prop->at<Gvas::Types::VectorStructProperty>(MASS_ACCESSORY_SCALE);
accessory.localScale = local_scale_prop->vector;
}
}
void
Mass::writeAccessories(Containers::ArrayView<Accessory> accessories, Gvas::Types::ArrayProperty* accs_array) {
for(std::uint32_t i = 0; i < accs_array->items.size(); i++) {
auto acc_prop = accs_array->at<Gvas::Types::GenericStructProperty>(i);
CORRADE_INTERNAL_ASSERT(acc_prop);
auto& accessory = accessories[i];
acc_prop->at<Gvas::Types::IntProperty>(MASS_ACCESSORY_ATTACH_INDEX)->value = accessory.attachIndex;
acc_prop->at<Gvas::Types::IntProperty>(MASS_ACCESSORY_ID)->value = accessory.id;
auto acc_styles = acc_prop->at<Gvas::Types::ArrayProperty>(MASS_ACCESSORY_STYLES);
for(std::uint32_t j = 0; j < acc_styles->items.size(); j++) {
acc_styles->at<Gvas::Types::IntProperty>(j)->value = accessory.styles[j];
}
auto rel_pos_prop = acc_prop->at<Gvas::Types::VectorStructProperty>(MASS_ACCESSORY_RELPOS);
rel_pos_prop->vector = accessory.relativePosition;
auto rel_pos_offset_prop = acc_prop->at<Gvas::Types::VectorStructProperty>(MASS_ACCESSORY_OFFPOS);
rel_pos_offset_prop->vector = accessory.relativePositionOffset;
auto rel_rot_prop = acc_prop->at<Gvas::Types::RotatorStructProperty>(MASS_ACCESSORY_RELROT);
rel_rot_prop->vector = accessory.relativeRotation;
auto rel_rot_offset_prop = acc_prop->at<Gvas::Types::RotatorStructProperty>(MASS_ACCESSORY_OFFROT);
rel_rot_offset_prop->vector = accessory.relativeRotationOffset;
auto local_scale_prop = acc_prop->at<Gvas::Types::VectorStructProperty>(MASS_ACCESSORY_SCALE);
local_scale_prop->vector = accessory.localScale;
}
}
}

View file

@ -1,158 +0,0 @@
// 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 "PropertyNames.h"
#include "../Logger/Logger.h"
#include "../Gvas/Types/ArrayProperty.h"
#include "../Gvas/Types/ColourStructProperty.h"
#include "../Gvas/Types/FloatProperty.h"
#include "../Gvas/Types/GenericStructProperty.h"
#include "../Gvas/Types/IntProperty.h"
#include "../Gvas/Types/StringProperty.h"
#include "Mass.h"
using namespace Containers::Literals;
namespace mbst::GameObjects {
Containers::ArrayView<CustomStyle>
Mass::globalStyles() {
return _globalStyles;
}
void
Mass::getGlobalStyles() {
LOG_INFO("Getting global styles.");
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto global_styles = unit_data->at<Gvas::Types::ArrayProperty>(MASS_GLOBAL_STYLES);
if(!global_styles) {
LOG_WARNING_FORMAT("Couldn't find global styles in {}.", _filename);
_globalStyles = Containers::Array<CustomStyle>{0};
return;
}
if(global_styles->items.size() != _globalStyles.size()) {
_globalStyles = Containers::Array<CustomStyle>{global_styles->items.size()};
}
getCustomStyles(_globalStyles, global_styles);
for(auto& style : _globalStyles) {
style.type = CustomStyle::Type::Global;
}
}
bool
Mass::writeGlobalStyle(std::size_t index) {
LOG_INFO_FORMAT("Writing global style number {}.", index);
if(index > _globalStyles.size()) {
_lastError = "Global style index out of range"_s;
LOG_ERROR(_lastError);
return false;
}
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_lastError = "No unit data found in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
auto global_styles = unit_data->at<Gvas::Types::ArrayProperty>(MASS_GLOBAL_STYLES);
if(!global_styles) {
_lastError = "No global styles found in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
return writeCustomStyle(_globalStyles[index], index, global_styles);
}
void
Mass::getCustomStyles(Containers::ArrayView<CustomStyle> styles, Gvas::Types::ArrayProperty* style_array) {
for(std::uint32_t i = 0; i < style_array->items.size(); i++) {
auto style_prop = style_array->at<Gvas::Types::GenericStructProperty>(i);
auto& style = styles[i];
style.name = style_prop->at<Gvas::Types::StringProperty>(MASS_STYLE_NAME)->value;
auto colour_prop = style_prop->at<Gvas::Types::ColourStructProperty>(MASS_STYLE_COLOUR);
style.colour = Color4{colour_prop->r, colour_prop->g, colour_prop->b, colour_prop->a};
style.metallic = style_prop->at<Gvas::Types::FloatProperty>(MASS_STYLE_METALLIC)->value;
style.gloss = style_prop->at<Gvas::Types::FloatProperty>(MASS_STYLE_GLOSS)->value;
style.glow = colour_prop->a != 0.0f;
style.patternId = style_prop->at<Gvas::Types::IntProperty>(MASS_STYLE_PATTERN_ID)->value;
style.opacity = style_prop->at<Gvas::Types::FloatProperty>(MASS_STYLE_PATTERN_OPACITY)->value;
style.offset = Vector2{
style_prop->at<Gvas::Types::FloatProperty>(MASS_STYLE_PATTERN_OFFSETX)->value,
style_prop->at<Gvas::Types::FloatProperty>(MASS_STYLE_PATTERN_OFFSETY)->value
};
style.rotation = style_prop->at<Gvas::Types::FloatProperty>(MASS_STYLE_PATTERN_ROTATION)->value;
style.scale = style_prop->at<Gvas::Types::FloatProperty>(MASS_STYLE_PATTERN_SCALE)->value;
}
}
bool
Mass::writeCustomStyle(const CustomStyle& style, std::size_t index, Gvas::Types::ArrayProperty* style_array) {
if(!style_array) {
_lastError = "style_array is null."_s;
LOG_ERROR(_lastError);
return false;
}
auto style_prop = style_array->at<Gvas::Types::GenericStructProperty>(index);
if(!style_prop) {
_lastError = "Style index is out of range in "_s + _filename;
LOG_ERROR(_lastError);
return false;
}
style_prop->at<Gvas::Types::StringProperty>(MASS_STYLE_NAME)->value = style.name;
auto colour_prop = style_prop->at<Gvas::Types::ColourStructProperty>(MASS_STYLE_COLOUR);
colour_prop->r = style.colour.r();
colour_prop->g = style.colour.g();
colour_prop->b = style.colour.b();
colour_prop->a = style.glow ? 1.0f : 0.0f;
style_prop->at<Gvas::Types::FloatProperty>(MASS_STYLE_METALLIC)->value = style.metallic;
style_prop->at<Gvas::Types::FloatProperty>(MASS_STYLE_GLOSS)->value = style.gloss;
style_prop->at<Gvas::Types::IntProperty>(MASS_STYLE_PATTERN_ID)->value = style.patternId;
style_prop->at<Gvas::Types::FloatProperty>(MASS_STYLE_PATTERN_OPACITY)->value = style.opacity;
style_prop->at<Gvas::Types::FloatProperty>(MASS_STYLE_PATTERN_OFFSETX)->value = style.offset.x();
style_prop->at<Gvas::Types::FloatProperty>(MASS_STYLE_PATTERN_OFFSETY)->value = style.offset.y();
style_prop->at<Gvas::Types::FloatProperty>(MASS_STYLE_PATTERN_ROTATION)->value = style.rotation;
style_prop->at<Gvas::Types::FloatProperty>(MASS_STYLE_PATTERN_SCALE)->value = style.scale;
if(!_mass->saveToFile()) {
_lastError = _mass->lastError();
return false;
}
return true;
}
}

View file

@ -1,325 +0,0 @@
// 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 <algorithm>
#include <Corrade/Containers/Pair.h>
#include <Corrade/Utility/Path.h>
#include "PropertyNames.h"
#include "../Logger/Logger.h"
#include "../Gvas/Types/ArrayProperty.h"
#include "../Gvas/Types/ResourceItemValue.h"
#include "../Gvas/Types/IntProperty.h"
#include "../Gvas/Types/StringProperty.h"
#include "Profile.h"
using namespace Corrade;
using namespace Containers::Literals;
namespace mbst::GameObjects {
Profile::Profile(Containers::StringView path):
_profile(path)
{
LOG_INFO_FORMAT("Reading profile at {}.", path);
if(!_profile.valid()) {
_lastError = _profile.lastError();
_valid = false;
return;
}
_filename = Utility::Path::split(path).second();
if(_filename.hasPrefix("Demo"_s)) {
_type = Type::Demo;
}
else {
_type = Type::FullGame;
}
auto account_prop = _profile.at<Gvas::Types::StringProperty>(PROFILE_ACCOUNT);
if(!account_prop) {
_lastError = "Couldn't find an account ID in "_s + _filename;
_valid = false;
return;
}
_account = account_prop->value;
refreshValues();
}
bool
Profile::valid() const {
return _valid;
}
Containers::StringView
Profile::lastError() const {
return _lastError;
}
Containers::StringView
Profile::filename() const {
return _filename;
}
Profile::Type
Profile::type() const {
return _type;
}
bool
Profile::isDemo() const {
return _type == Type::Demo;
}
Containers::StringView
Profile::account() const {
return _account;
}
void
Profile::refreshValues() {
if(!_profile.reloadData()) {
LOG_ERROR(_profile.lastError());
_valid = false;
return;
}
if(_profile.saveType() != "/Game/Core/Save/bpSaveGameProfile.bpSaveGameProfile_C"_s) {
LOG_ERROR_FORMAT("{} is not a valid profile save.", _filename);
_valid = false;
return;
}
LOG_INFO("Getting the company name.");
auto name_prop = _profile.at<Gvas::Types::StringProperty>(PROFILE_NAME);
if(!name_prop) {
_lastError = "No company name in "_s + _filename;
LOG_ERROR(_lastError);
_valid = false;
return;
}
_name = name_prop->value;
LOG_INFO("Getting the active frame slot.");
auto prop = _profile.at<Gvas::Types::IntProperty>(PROFILE_ACTIVE_FRAME_SLOT);
_activeFrameSlot = prop ? prop->value : 0;
LOG_INFO("Getting the credits.");
prop = _profile.at<Gvas::Types::IntProperty>(PROFILE_CREDITS);
_credits = prop ? prop->value : 0;
LOG_INFO("Getting the story progress.");
prop = _profile.at<Gvas::Types::IntProperty>(PROFILE_STORY_PROGRESS);
_storyProgress = prop ? prop->value : 0;
LOG_INFO("Getting the last mission ID.");
prop = _profile.at<Gvas::Types::IntProperty>(PROFILE_LAST_MISSION_ID);
_lastMissionId = prop ? prop->value : 0;
LOG_INFO("Getting the materials.");
_materials[GameData::MaterialID::VerseSteel] = getResource(PROFILE_MATERIAL, GameData::MaterialID::VerseSteel);
_materials[GameData::MaterialID::Undinium] = getResource(PROFILE_MATERIAL, GameData::MaterialID::Undinium);
_materials[GameData::MaterialID::NecriumAlloy] = getResource(PROFILE_MATERIAL, GameData::MaterialID::NecriumAlloy);
_materials[GameData::MaterialID::Lunarite] = getResource(PROFILE_MATERIAL, GameData::MaterialID::Lunarite);
_materials[GameData::MaterialID::Asterite] = getResource(PROFILE_MATERIAL, GameData::MaterialID::Asterite);
_materials[GameData::MaterialID::HalliteFragma] = getResource(PROFILE_MATERIAL, GameData::MaterialID::HalliteFragma);
_materials[GameData::MaterialID::Unnoctinium] = getResource(PROFILE_MATERIAL, GameData::MaterialID::Unnoctinium);
_materials[GameData::MaterialID::Ednil] = getResource(PROFILE_MATERIAL, GameData::MaterialID::Ednil);
_materials[GameData::MaterialID::Nuflalt] = getResource(PROFILE_MATERIAL, GameData::MaterialID::Nuflalt);
_materials[GameData::MaterialID::Aurelene] = getResource(PROFILE_MATERIAL, GameData::MaterialID::Aurelene);
_materials[GameData::MaterialID::Soldus] = getResource(PROFILE_MATERIAL, GameData::MaterialID::Soldus);
_materials[GameData::MaterialID::SynthesisedN] = getResource(PROFILE_MATERIAL, GameData::MaterialID::SynthesisedN);
_materials[GameData::MaterialID::Nanoc] = getResource(PROFILE_MATERIAL, GameData::MaterialID::Nanoc);
_materials[GameData::MaterialID::Abyssillite] = getResource(PROFILE_MATERIAL, GameData::MaterialID::Abyssillite);
_materials[GameData::MaterialID::Alcarbonite] = getResource(PROFILE_MATERIAL, GameData::MaterialID::Alcarbonite);
_materials[GameData::MaterialID::Keriphene] = getResource(PROFILE_MATERIAL, GameData::MaterialID::Keriphene);
_materials[GameData::MaterialID::NitinolCM] = getResource(PROFILE_MATERIAL, GameData::MaterialID::NitinolCM);
_materials[GameData::MaterialID::Quarkium] = getResource(PROFILE_MATERIAL, GameData::MaterialID::Quarkium);
_materials[GameData::MaterialID::Alterene] = getResource(PROFILE_MATERIAL, GameData::MaterialID::Alterene);
_materials[GameData::MaterialID::Cosmium] = getResource(PROFILE_MATERIAL, GameData::MaterialID::Cosmium);
_materials[GameData::MaterialID::PurifiedQuarkium] = getResource(PROFILE_MATERIAL, GameData::MaterialID::PurifiedQuarkium);
_materials[GameData::MaterialID::MixedComposition] = getResource(PROFILE_QUARK_DATA, GameData::MaterialID::MixedComposition);
_materials[GameData::MaterialID::VoidResidue] = getResource(PROFILE_QUARK_DATA, GameData::MaterialID::VoidResidue);
_materials[GameData::MaterialID::MuscularConstruction] = getResource(PROFILE_QUARK_DATA, GameData::MaterialID::MuscularConstruction);
_materials[GameData::MaterialID::MineralExoskeletology] = getResource(PROFILE_QUARK_DATA, GameData::MaterialID::MineralExoskeletology);
_materials[GameData::MaterialID::CarbonisedSkin] = getResource(PROFILE_QUARK_DATA, GameData::MaterialID::CarbonisedSkin);
_materials[GameData::MaterialID::IsolatedVoidParticle] = getResource(PROFILE_QUARK_DATA, GameData::MaterialID::IsolatedVoidParticle);
_materials[GameData::MaterialID::WeaponisedPhysiology] = getResource(PROFILE_QUARK_DATA, GameData::MaterialID::WeaponisedPhysiology);
_valid = true;
}
Containers::StringView
Profile::companyName() const {
return _name;
}
bool
Profile::renameCompany(Containers::StringView new_name) {
auto name_prop = _profile.at<Gvas::Types::StringProperty>(PROFILE_NAME);
if(!name_prop) {
_lastError = "No company name in "_s + _filename;
LOG_ERROR(_lastError);
_valid = false;
return false;
}
name_prop->value = new_name;
if(!_profile.saveToFile()) {
_lastError = _profile.lastError();
return false;
}
return true;
}
std::int32_t
Profile::activeFrameSlot() const {
return _activeFrameSlot;
}
std::int32_t
Profile::credits() const {
return _credits;
}
bool
Profile::setCredits(std::int32_t amount) {
auto credits_prop = _profile.at<Gvas::Types::IntProperty>(PROFILE_CREDITS);
if(!credits_prop) {
credits_prop = new Gvas::Types::IntProperty;
credits_prop->name.emplace("Credit"_s);
credits_prop->valueLength = sizeof(std::int32_t);
_profile.appendProperty(Gvas::Types::IntProperty::ptr{credits_prop});
}
credits_prop->value = amount;
if(!_profile.saveToFile()) {
_lastError = _profile.lastError();
return false;
}
return true;
}
std::int32_t
Profile::storyProgress() const {
return _storyProgress;
}
bool
Profile::setStoryProgress(std::int32_t progress) {
auto story_progress_prop = _profile.at<Gvas::Types::IntProperty>("StoryProgress"_s);
if(!story_progress_prop) {
story_progress_prop = new Gvas::Types::IntProperty;
story_progress_prop->name.emplace("StoryProgress"_s);
story_progress_prop->valueLength = sizeof(std::int32_t);
_profile.appendProperty(Gvas::Types::IntProperty::ptr{story_progress_prop});
}
story_progress_prop->value = progress;
if(!_profile.saveToFile()) {
_lastError = _profile.lastError();
return false;
}
return true;
}
std::int32_t
Profile::lastMissionId() const {
return _lastMissionId;
}
std::int32_t
Profile::material(GameData::MaterialID id) const {
return _materials.at(id);
}
bool
Profile::setMaterial(GameData::MaterialID id, std::int32_t amount) {
Containers::StringView container = id >= GameData::MaterialID::MixedComposition ? PROFILE_QUARK_DATA : PROFILE_MATERIAL;
auto mats_prop = _profile.at<Gvas::Types::ArrayProperty>(container);
if(!mats_prop) {
mats_prop = new Gvas::Types::ArrayProperty;
mats_prop->name.emplace(container);
mats_prop->itemType = "StructProperty";
_profile.appendProperty(Gvas::Types::ArrayProperty::ptr{mats_prop});
}
auto predicate = [&id](Gvas::Types::UnrealPropertyBase::ptr& prop){
auto res_prop = dynamic_cast<Gvas::Types::ResourceItemValue*>(prop.get());
return res_prop->id == id;
};
auto it = std::find_if(mats_prop->items.begin(), mats_prop->items.end(), predicate);
Gvas::Types::ResourceItemValue* res_prop;
if(it == mats_prop->items.end()) {
res_prop = new Gvas::Types::ResourceItemValue;
if(mats_prop->items.isEmpty()) {
res_prop->name.emplace(container);
}
res_prop->id = id;
Gvas::Types::ResourceItemValue::ptr prop{res_prop};
arrayAppend(mats_prop->items, Utility::move(prop));
}
else {
res_prop = dynamic_cast<Gvas::Types::ResourceItemValue*>(it->get());
}
res_prop->quantity = amount;
if(!_profile.saveToFile()) {
_lastError = _profile.lastError();
return false;
}
return true;
}
std::int32_t
Profile::getResource(Containers::StringView container, GameData::MaterialID id) {
auto mats_prop = _profile.at<Gvas::Types::ArrayProperty>(container);
if(!mats_prop) {
return 0;
}
auto predicate = [&id](Gvas::Types::UnrealPropertyBase::ptr& prop){
auto res_prop = dynamic_cast<Gvas::Types::ResourceItemValue*>(prop.get());
return res_prop->id == id;
};
auto it = std::find_if(mats_prop->items.begin(), mats_prop->items.end(), predicate);
return it != mats_prop->items.end() ? dynamic_cast<Gvas::Types::ResourceItemValue*>(it->get())->quantity : 0;
}
}

View file

@ -1,92 +0,0 @@
#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 <map>
#include <Corrade/Containers/String.h>
#include <Corrade/Containers/StringView.h>
#include "../GameData/ResourceIDs.h"
#include "../Gvas/File.h"
using namespace Corrade;
namespace mbst::GameObjects {
class Profile {
public:
explicit Profile(Containers::StringView path);
bool valid() const;
auto lastError() const -> Containers::StringView;
auto filename() const -> Containers::StringView;
enum class Type: std::uint8_t {
Demo,
FullGame
};
auto type() const -> Type;
bool isDemo() const;
auto account() const -> Containers::StringView;
void refreshValues();
auto companyName() const -> Containers::StringView;
bool renameCompany(Containers::StringView new_name);
auto activeFrameSlot() const -> std::int32_t;
auto credits() const -> std::int32_t;
bool setCredits(std::int32_t credits);
auto storyProgress() const -> std::int32_t;
bool setStoryProgress(std::int32_t progress);
auto lastMissionId() const -> std::int32_t;
auto material(GameData::MaterialID id) const -> std::int32_t;
bool setMaterial(GameData::MaterialID id, std::int32_t amount);
private:
auto getResource(Containers::StringView container, GameData::MaterialID id) -> std::int32_t;
Containers::String _filename;
Type _type;
Gvas::File _profile;
Containers::String _name;
std::int32_t _activeFrameSlot = 0;
std::int32_t _credits = 0;
std::int32_t _storyProgress = 0;
std::int32_t _lastMissionId = 0;
std::map<GameData::MaterialID, std::int32_t> _materials;
Containers::String _account;
bool _valid = false;
Containers::String _lastError;
};
}

View file

@ -1,29 +0,0 @@
#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/Utility/Debug.h>
#include "Types/Types.h"
using namespace Corrade;
Utility::Debug& operator<<(Utility::Debug& debug, const Gvas::Types::ArrayProperty* prop);
Utility::Debug& operator<<(Utility::Debug& debug, const Gvas::Types::SetProperty* prop);
Utility::Debug& operator<<(Utility::Debug& debug, const Gvas::Types::GenericStructProperty* prop);
Utility::Debug& operator<<(Utility::Debug& debug, const Gvas::Types::StructProperty* prop);
Utility::Debug& operator<<(Utility::Debug& debug, const Gvas::Types::UnrealPropertyBase* prop);

View file

@ -1,24 +0,0 @@
#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/>.
namespace Gvas {
class File;
class PropertySerialiser;
}

View file

@ -1,276 +0,0 @@
// 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 <algorithm>
#include "../Logger/Logger.h"
#include "Serialisers/ArrayProperty.h"
#include "Serialisers/BoolProperty.h"
#include "Serialisers/ByteProperty.h"
#include "Serialisers/ColourProperty.h"
#include "Serialisers/DateTimeProperty.h"
#include "Serialisers/EnumProperty.h"
#include "Serialisers/FloatProperty.h"
#include "Serialisers/GuidProperty.h"
#include "Serialisers/IntProperty.h"
#include "Serialisers/MapProperty.h"
#include "Serialisers/ResourceProperty.h"
#include "Serialisers/RotatorProperty.h"
#include "Serialisers/StringProperty.h"
#include "Serialisers/SetProperty.h"
#include "Serialisers/Struct.h"
#include "Serialisers/TextProperty.h"
#include "Serialisers/VectorProperty.h"
#include "Serialisers/Vector2DProperty.h"
#include "Types/NoneProperty.h"
#include "../BinaryIo/Reader.h"
#include "../BinaryIo/Writer.h"
#include "PropertySerialiser.h"
namespace Gvas {
PropertySerialiser::PropertySerialiser() {
arrayAppend(_serialisers, Containers::pointer<Serialisers::ArrayProperty>());
arrayAppend(_serialisers, Containers::pointer<Serialisers::BoolProperty>());
arrayAppend(_serialisers, Containers::pointer<Serialisers::ByteProperty>());
arrayAppend(_serialisers, Containers::pointer<Serialisers::ColourProperty>());
arrayAppend(_serialisers, Containers::pointer<Serialisers::DateTimeProperty>());
arrayAppend(_serialisers, Containers::pointer<Serialisers::EnumProperty>());
arrayAppend(_serialisers, Containers::pointer<Serialisers::FloatProperty>());
arrayAppend(_serialisers, Containers::pointer<Serialisers::GuidProperty>());
arrayAppend(_serialisers, Containers::pointer<Serialisers::IntProperty>());
arrayAppend(_serialisers, Containers::pointer<Serialisers::MapProperty>());
arrayAppend(_serialisers, Containers::pointer<Serialisers::ResourceProperty>());
arrayAppend(_serialisers, Containers::pointer<Serialisers::RotatorProperty>());
arrayAppend(_serialisers, Containers::pointer<Serialisers::StringProperty>());
arrayAppend(_serialisers, Containers::pointer<Serialisers::SetProperty>());
arrayAppend(_serialisers, Containers::pointer<Serialisers::TextProperty>());
arrayAppend(_serialisers, Containers::pointer<Serialisers::VectorProperty>());
arrayAppend(_serialisers, Containers::pointer<Serialisers::Vector2DProperty>());
arrayAppend(_serialisers, Containers::pointer<Serialisers::Struct>());
arrayAppend(_collectionSerialisers, Containers::pointer<Serialisers::Struct>());
}
PropertySerialiser&
PropertySerialiser::instance() {
static PropertySerialiser serialiser;
return serialiser;
}
Types::UnrealPropertyBase::ptr
PropertySerialiser::read(BinaryIo::Reader& reader) {
if(reader.peekChar() < 0 || reader.eof()) {
return nullptr;
}
Containers::String name;
if(!reader.readUEString(name)) {
return nullptr;
}
if(name == "None") {
return Containers::pointer<Types::NoneProperty>();
}
Containers::String type;
if(!reader.readUEString(type)) {
return nullptr;
}
std::size_t value_length;
if(!reader.readUint64(value_length)) {
return nullptr;
}
return deserialise(Utility::move(name), Utility::move(type), value_length, reader);
}
Types::UnrealPropertyBase::ptr
PropertySerialiser::readItem(BinaryIo::Reader& reader, Containers::String type, std::size_t value_length,
Containers::String name)
{
if(reader.peekChar() < 0 || reader.eof()) {
return nullptr;
}
return deserialise(Utility::move(name), Utility::move(type), value_length, reader);
}
Containers::Array<Types::UnrealPropertyBase::ptr>
PropertySerialiser::readSet(BinaryIo::Reader& reader, Containers::StringView item_type, std::uint32_t count) {
if(reader.peekChar() < 0 || reader.eof()) {
return nullptr;
}
auto serialiser = getCollectionSerialiser(item_type);
Containers::Array<Types::UnrealPropertyBase::ptr> array;
if(serialiser) {
Containers::String name;
if(!reader.readUEString(name)) {
return nullptr;
}
Containers::String type;
if(!reader.readUEString(type)) {
return nullptr;
}
std::size_t value_length;
if(!reader.readUint64(value_length)) {
return nullptr;
}
array = serialiser->deserialise(name, type, value_length, count, reader, *this);
for(auto& item : array) {
if(item->name == Containers::NullOpt) {
item->name.emplace(name);
}
}
}
else {
for(std::uint32_t i = 0; i < count; i++) {
auto item = readItem(reader, item_type, std::size_t(-1), "");
arrayAppend(array, Utility::move(item));
}
}
return array;
}
Types::UnrealPropertyBase::ptr
PropertySerialiser::deserialise(Containers::String name, Containers::String type, std::size_t value_length,
BinaryIo::Reader& reader)
{
Types::UnrealPropertyBase::ptr prop;
auto serialiser = getSerialiser(type);
if(serialiser == nullptr) {
return nullptr;
}
prop = serialiser->deserialise(name, type, value_length, reader, *this);
if(!prop) {
LOG_ERROR("No property.");
return nullptr;
}
prop->name = Utility::move(name);
prop->propertyType = Utility::move(type);
return prop;
}
bool PropertySerialiser::serialise(Types::UnrealPropertyBase::ptr& prop, Containers::StringView item_type,
std::size_t& bytes_written, BinaryIo::Writer& writer)
{
auto serialiser = getSerialiser(item_type);
if(!serialiser) {
return false;
}
return serialiser->serialise(prop, bytes_written, writer, *this);
}
bool
PropertySerialiser::write(Types::UnrealPropertyBase::ptr& prop, std::size_t& bytes_written, BinaryIo::Writer& writer) {
if(prop->name == "None" && prop->propertyType == "NoneProperty" && dynamic_cast<Types::NoneProperty*>(prop.get())) {
bytes_written += writer.writeUEStringToArray(*prop->name);
return true;
}
bytes_written += writer.writeUEStringToArray(*prop->name);
bytes_written += writer.writeUEStringToArray(prop->propertyType);
std::size_t value_length = 0;
std::size_t vl_position = writer.arrayPosition();
bytes_written += writer.writeValueToArray<std::size_t>(value_length);
bool ret = serialise(prop, prop->propertyType, value_length, writer);
writer.writeValueToArrayAt(value_length, vl_position);
bytes_written += value_length;
return ret;
}
bool
PropertySerialiser::writeItem(Types::UnrealPropertyBase::ptr& prop, Containers::StringView item_type,
std::size_t& bytes_written, BinaryIo::Writer& writer)
{
if(prop->name == "None" && prop->propertyType == "NoneProperty" && dynamic_cast<Types::NoneProperty*>(prop.get())) {
bytes_written += writer.writeUEStringToArray(*prop->name);
return true;
}
return serialise(prop, item_type, bytes_written, writer);
}
bool PropertySerialiser::writeSet(Containers::ArrayView<Types::UnrealPropertyBase::ptr> props,
Containers::StringView item_type, std::size_t& bytes_written,
BinaryIo::Writer& writer)
{
auto serialiser = getCollectionSerialiser(item_type);
if(serialiser) {
return serialiser->serialise(props, item_type, bytes_written, writer, *this);
}
else {
for(auto& prop : props) {
if(!writeItem(prop, item_type, bytes_written, writer)) {
return false;
}
}
return true;
}
}
Serialisers::AbstractUnrealProperty*
PropertySerialiser::getSerialiser(Containers::StringView item_type) {
for(auto& item : _serialisers) {
for(auto serialiser_type : item->types()) {
if(item_type == serialiser_type) {
return item.get();
}
}
}
return nullptr;
}
Serialisers::AbstractUnrealCollectionProperty*
PropertySerialiser::getCollectionSerialiser(Containers::StringView item_type) {
for(auto& item : _collectionSerialisers) {
for(Containers::StringView serialiser_type : item->types()) {
if(item_type == serialiser_type) {
return item.get();
}
}
}
return nullptr;
}
}

View file

@ -1,64 +0,0 @@
#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/GrowableArray.h>
#include <Corrade/Containers/String.h>
#include <Corrade/Containers/StringView.h>
#include "Serialisers/AbstractUnrealProperty.h"
#include "Serialisers/AbstractUnrealCollectionProperty.h"
#include "Types/UnrealPropertyBase.h"
#include "../BinaryIo/BinaryIo.h"
using namespace Corrade;
namespace Gvas {
class PropertySerialiser {
public:
static auto instance() -> PropertySerialiser&;
auto read(BinaryIo::Reader& reader) -> Types::UnrealPropertyBase::ptr;
auto readItem(BinaryIo::Reader& reader, Containers::String type, std::size_t value_length, Containers::String name)
-> Types::UnrealPropertyBase::ptr;
auto readSet(BinaryIo::Reader& reader, Containers::StringView item_type, std::uint32_t count)
-> Containers::Array<Types::UnrealPropertyBase::ptr>;
auto deserialise(Containers::String name, Containers::String type, std::size_t value_length,
BinaryIo::Reader& reader) -> Types::UnrealPropertyBase::ptr;
bool serialise(Types::UnrealPropertyBase::ptr& prop, Containers::StringView item_type,
std::size_t& bytes_written, BinaryIo::Writer& writer);
bool write(Types::UnrealPropertyBase::ptr& prop, std::size_t& bytes_written, BinaryIo::Writer& writer);
bool writeItem(Types::UnrealPropertyBase::ptr& prop, Containers::StringView item_type,
std::size_t& bytes_written, BinaryIo::Writer& writer);
bool writeSet(Containers::ArrayView<Types::UnrealPropertyBase::ptr> props, Containers::StringView item_type,
std::size_t& bytes_written, BinaryIo::Writer& writer);
private:
PropertySerialiser();
auto getSerialiser(Containers::StringView item_type) -> Serialisers::AbstractUnrealProperty*;
auto getCollectionSerialiser(Containers::StringView item_type) -> Serialisers::AbstractUnrealCollectionProperty*;
Containers::Array<Serialisers::AbstractUnrealProperty::ptr> _serialisers;
Containers::Array<Serialisers::AbstractUnrealCollectionProperty::ptr> _collectionSerialisers;
};
}

View file

@ -1,84 +0,0 @@
// 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 "../../BinaryIo/Reader.h"
#include "../../BinaryIo/Writer.h"
#include "../../Logger/Logger.h"
#include "RotatorProperty.h"
namespace Gvas::Serialisers {
Types::UnrealPropertyBase::ptr
RotatorProperty::deserialiseProperty(Containers::StringView name, Containers::StringView type, std::size_t value_length,
BinaryIo::Reader& reader, PropertySerialiser& serialiser)
{
auto prop = Containers::pointer<Types::RotatorStructProperty>();
if(value_length == 12) { // UE4
float x, y, z;
if(!reader.readFloat(x) || !reader.readFloat(y) || !reader.readFloat(z)) {
LOG_ERROR_FORMAT("Couldn't read rotator property {}'s value.", name);
return nullptr;
}
prop->vector = Vector3{x, y, z};
}
else if(value_length == 24) { // UE5
double x, y, z;
if(!reader.readDouble(x) || !reader.readDouble(y) || !reader.readDouble(z)) {
LOG_ERROR_FORMAT("Couldn't read rotator property {}'s value.", name);
return nullptr;
}
prop->vector = Vector3d{x, y, z};
}
return prop;
}
bool
RotatorProperty::serialiseProperty(Types::UnrealPropertyBase::ptr& prop, std::size_t& bytes_written,
BinaryIo::Writer& writer, PropertySerialiser& serialiser)
{
auto rotator = dynamic_cast<Types::RotatorStructProperty*>(prop.get());
if(!rotator) {
LOG_ERROR("The property is not a valid rotator property.");
return false;
}
std::visit(
[&bytes_written, &writer](auto& arg){
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Vector3>) {
bytes_written += writer.writeValueToArray<float>(arg.x()) +
writer.writeValueToArray<float>(arg.y()) +
writer.writeValueToArray<float>(arg.z());
}
else if constexpr (std::is_same_v<T, Vector3d>) {
bytes_written += writer.writeValueToArray<double>(arg.x()) +
writer.writeValueToArray<double>(arg.y()) +
writer.writeValueToArray<double>(arg.z());
}
}, rotator->vector
);
return true;
}
}

View file

@ -1,44 +0,0 @@
#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/>.
namespace Gvas::Serialisers {
class AbstractUnrealCollectionProperty;
class AbstractUnrealProperty;
class AbstractUnrealStruct;
class ArrayProperty;
class BoolProperty;
class ByteProperty;
class ColourProperty;
class DateTimeProperty;
class EnumProperty;
class FloatProperty;
class GuidProperty;
class IntProperty;
class MapProperty;
class ResourceProperty;
class RotatorProperty;
class SetProperty;
class StringProperty;
class Struct;
class TextProperty;
class UnrealProperty;
class Vector2DProperty;
class VectorProperty;
}

View file

@ -1,55 +0,0 @@
#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/ArrayView.h>
#include <Corrade/Containers/StringView.h>
#include "AbstractUnrealCollectionProperty.h"
#include "AbstractUnrealProperty.h"
#include "AbstractUnrealStruct.h"
#include "../Types/StructProperty.h"
namespace Gvas::Serialisers {
class Struct : public AbstractUnrealProperty, public AbstractUnrealCollectionProperty {
public:
using ptr = Containers::Pointer<Struct>;
auto types() -> StringArrayView override;
auto deserialise(Containers::StringView name, Containers::StringView type, std::size_t value_length,
std::uint32_t count, BinaryIo::Reader& reader, PropertySerialiser& serialiser)
-> PropertyArray override;
auto deserialise(Containers::StringView name, Containers::StringView type, std::size_t value_length,
BinaryIo::Reader& reader, PropertySerialiser& serialiser)
-> Types::UnrealPropertyBase::ptr override;
bool serialise(PropertyArrayView props, Containers::StringView item_type, std::size_t& bytes_written,
BinaryIo::Writer& writer, PropertySerialiser& serialiser) override;
bool serialise(Types::UnrealPropertyBase::ptr& prop, std::size_t& bytes_written, BinaryIo::Writer& writer,
PropertySerialiser& serialiser) override;
private:
auto readStructValue(Containers::StringView name, Containers::StringView type, std::size_t value_length,
BinaryIo::Reader& reader, PropertySerialiser& serialiser) -> Types::StructProperty::ptr;
bool writeStructValue(Types::StructProperty* prop, std::size_t& bytes_written, BinaryIo::Writer& writer,
PropertySerialiser& serialiser);
};
}

View file

@ -1,83 +0,0 @@
// 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 "../../BinaryIo/Reader.h"
#include "../../BinaryIo/Writer.h"
#include "../../Logger/Logger.h"
#include "Vector2DProperty.h"
namespace Gvas::Serialisers {
Types::UnrealPropertyBase::ptr
Vector2DProperty::deserialiseProperty(Containers::StringView name, Containers::StringView type,
std::size_t value_length, BinaryIo::Reader& reader,
PropertySerialiser& serialiser)
{
auto prop = Containers::pointer<Types::Vector2DStructProperty>();
if(value_length == 8) { // UE4
float x, y;
if(!reader.readFloat(x) || !reader.readFloat(y)) {
LOG_ERROR_FORMAT("Couldn't read 2D vector property {}'s value.", name);
return nullptr;
}
prop->vector = Vector2{x, y};
}
else if(value_length == 16) { // UE5
double x, y;
if(!reader.readDouble(x) || !reader.readDouble(y)) {
LOG_ERROR_FORMAT("Couldn't read 2D vector property {}'s value.", name);
return nullptr;
}
prop->vector = Vector2d{x, y};
}
return prop;
}
bool
Vector2DProperty::serialiseProperty(Types::UnrealPropertyBase::ptr& prop, std::size_t& bytes_written,
BinaryIo::Writer& writer, PropertySerialiser& serialiser)
{
auto vector = dynamic_cast<Types::Vector2DStructProperty*>(prop.get());
if(!vector) {
LOG_ERROR("The property is not a valid 2D vector property.");
return false;
}
std::visit(
[&bytes_written, &writer](auto& arg){
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Vector2>) {
bytes_written += writer.writeValueToArray<float>(arg.x()) +
writer.writeValueToArray<float>(arg.y());
}
else if constexpr (std::is_same_v<T, Vector2d>) {
bytes_written += writer.writeValueToArray<double>(arg.x()) +
writer.writeValueToArray<double>(arg.y());
}
}, vector->vector
);
return true;
}
}

View file

@ -1,85 +0,0 @@
// 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 "../../Logger/Logger.h"
#include "../../BinaryIo/Reader.h"
#include "../../BinaryIo/Writer.h"
#include "VectorProperty.h"
namespace Gvas::Serialisers {
Types::UnrealPropertyBase::ptr
VectorProperty::deserialiseProperty(Containers::StringView name, Containers::StringView type, std::size_t value_length,
BinaryIo::Reader& reader, PropertySerialiser& serialiser)
{
auto prop = Containers::pointer<Types::VectorStructProperty>();
if(value_length == 12) { // UE4
float x, y, z;
if(!reader.readFloat(x) || !reader.readFloat(y) || !reader.readFloat(z)) {
LOG_ERROR_FORMAT("Couldn't read vector property {}'s value.", name);
return nullptr;
}
prop->vector = Vector3{x, y, z};
}
else if(value_length == 24) { // UE5
double x, y, z;
if(!reader.readDouble(x) || !reader.readDouble(y) || !reader.readDouble(z)) {
LOG_ERROR_FORMAT("Couldn't read vector property {}'s value.", name);
return nullptr;
}
prop->vector = Vector3d{x, y, z};
}
return prop;
}
bool
VectorProperty::serialiseProperty(Types::UnrealPropertyBase::ptr& prop, std::size_t& bytes_written,
BinaryIo::Writer& writer, PropertySerialiser& serialiser)
{
auto vector = dynamic_cast<Types::VectorStructProperty*>(prop.get());
if(!vector) {
LOG_ERROR("The property is not a valid vector property.");
return false;
}
std::visit(
[&bytes_written, &writer](auto& arg){
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Vector3>) {
bytes_written += writer.writeValueToArray<float>(arg.x()) +
writer.writeValueToArray<float>(arg.y()) +
writer.writeValueToArray<float>(arg.z());
}
else if constexpr (std::is_same_v<T, Vector3d>) {
bytes_written += writer.writeValueToArray<double>(arg.x()) +
writer.writeValueToArray<double>(arg.y()) +
writer.writeValueToArray<double>(arg.z());
}
}, vector->vector
);
return true;
}
}

View file

@ -1,127 +0,0 @@
// 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;
}
}

View file

@ -1,31 +0,0 @@
#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);
}

View file

@ -1,186 +0,0 @@
// 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);
}
}

View file

@ -1,31 +0,0 @@
#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>;
}

View file

@ -1,36 +0,0 @@
#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
};
}

View file

@ -1,7 +1,7 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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

View file

@ -1,5 +1,5 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -16,11 +16,6 @@
#include <iostream>
#ifndef SAVETOOL_DEBUG_BUILD
#include <Corrade/Containers/Optional.h>
#include <Corrade/Containers/Pair.h>
#include <Corrade/Utility/Path.h>
#endif
#include <Corrade/Utility/Debug.h>
#include "Logger.h"
@ -30,6 +25,8 @@ using Utility::Debug;
using Utility::Warning;
using Utility::Error;
using namespace Magnum;
Logger&
Logger::instance() {
static Logger logger;
@ -38,17 +35,16 @@ Logger::instance() {
void
Logger::initialise() {
#ifndef SAVETOOL_DEBUG_BUILD
auto exe_path = Utility::Path::split(*Utility::Path::executableLocation()).first();
_logFile.open(Utility::Path::join(exe_path, "SaveToolLog.txt").cbegin(), std::ios::trunc);
#ifndef SAVETOOL_DEBUG_BUILD
_logFile.open("SaveToolLog.txt", std::ios::trunc);
_logFile << "In case you encounter a bug:\n" <<
"1. Do not run the Save Tool again, as this log will be cleared.\n" <<
"2. Go to either the official Sekai Project Discord guild, or the community M.A.S.S. Builder one.\n" <<
"3. Mention me (@williamjcm) to get my attention, with a description of the bug.\n"
"3. Mention me (William JCM#2301) to get my attention, with a description of the bug.\n"
" Please include as many details as possible, I don't want to play \"20 questions\", and neither do you.\n" <<
"4. Send me this file _when I ask for it_, preferably in DMs.\n" <<
std::endl;
#endif
#endif
}
void
@ -66,36 +62,38 @@ Logger::unindent() {
void
Logger::log(EntryType type, StringView location, StringView message) {
Debug d{
#ifdef SAVETOOL_DEBUG_BUILD
&std::cout
#else
#ifndef SAVETOOL_DEBUG_BUILD
&_logFile
#else
&std::cout
#endif
};
#ifdef SAVETOOL_DEBUG_BUILD
#define COLOURED_TEXT(colour, text) Debug::color(Debug::Color::colour) << (text) << Debug::resetColor
#else
#define COLOURED_TEXT(colour, text) (text)
#endif
switch(type) {
case EntryType::Info:
d << "[ INFO]"_s;
d << COLOURED_TEXT(Default, "[ INFO]"_s);
break;
case EntryType::Warning:
d << "[WARNING]"_s;
d << COLOURED_TEXT(Yellow, "[WARNING]"_s);
break;
case EntryType::Error:
d << "[ ERROR]"_s;
d << COLOURED_TEXT(Red, "[ ERROR]"_s);
break;
}
#undef COLOURED_TEXT
d << "["_s << Debug::nospace << location << Debug::nospace << "]"_s;
d << "["_s << Debug::nospace << location << Debug::nospace << "]";
for(auto i = 0u; i < _indentLevel; i++) {
for(UnsignedInt i = 0; i < _indentLevel; i++) {
d << Debug::nospace << " "_s << Debug::nospace;
}
d << ((message.back() == '\n') ? message.exceptSuffix(1) : message);
#ifndef SAVETOOL_DEBUG_BUILD
_logFile.flush();
#endif
}
void

View file

@ -1,7 +1,7 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -16,15 +16,19 @@
// 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 <ctime>
#include <mutex>
#ifndef SAVETOOL_DEBUG_BUILD
#include <fstream>
#endif
#include <mutex>
#include <Corrade/Containers/String.h>
#include <Corrade/Containers/ArrayView.h>
#include <Corrade/Utility/Format.h>
#include <Magnum/Types.h>
#include "EntryType.h"
using namespace Corrade;
@ -33,6 +37,8 @@ using Containers::ArrayView;
using Containers::String;
using Containers::StringView;
using namespace Magnum;
using namespace Containers::Literals;
class Logger {
@ -62,7 +68,7 @@ class Logger {
std::ofstream _logFile;
#endif
std::uint32_t _indentLevel = 0;
UnsignedInt _indentLevel = 0;
std::mutex _logMutex{};
};

View file

@ -1,5 +1,5 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -16,12 +16,7 @@
#include "MagnumLogBuffer.h"
MagnumLogBuffer::MagnumLogBuffer(EntryType type):
std::stringbuf(std::ios_base::out),
_type{type}
{
//ctor
}
MagnumLogBuffer::MagnumLogBuffer(EntryType type): std::stringbuf(std::ios_base::out), _type{type} {}
MagnumLogBuffer::~MagnumLogBuffer() = default;

View file

@ -1,7 +1,7 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -25,10 +25,10 @@
class MagnumLogBuffer : public std::stringbuf {
public:
explicit MagnumLogBuffer(EntryType type);
~MagnumLogBuffer() override;
~MagnumLogBuffer();
private:
auto sync() -> int override;
int sync() override;
EntryType _type;
};

View file

@ -1,43 +0,0 @@
#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>
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/String.h>
using namespace Corrade;
namespace mbst::Managers {
struct Backup {
Containers::String filename;
Containers::String company;
bool demo;
struct {
std::int32_t year;
std::int32_t month;
std::int32_t day;
std::int32_t hour;
std::int32_t minute;
std::int32_t second;
} timestamp;
Containers::Array<Containers::String> includedFiles;
};
}

View file

@ -1,338 +0,0 @@
// 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 <ctime>
#include <algorithm>
#include <chrono>
#include <Corrade/Containers/GrowableArray.h>
#include <Corrade/Containers/Pair.h>
#include <Corrade/Containers/ScopeGuard.h>
#include <Corrade/Utility/Path.h>
#include <Corrade/Utility/String.h>
#include <zip.h>
#include "../Configuration/Configuration.h"
#include "../Logger/Logger.h"
#include "../Utilities/Temp.h"
#include "BackupManager.h"
namespace mbst::Managers {
BackupManager::BackupManager():
_root{""}
{
refresh();
}
Containers::StringView
BackupManager::lastError() {
return _lastError;
}
void
BackupManager::refresh() {
_backups = Containers::Array<Backup>{};
scanSubdir(""_s);
_root.clear();
_root.build(_backups);
}
Containers::ArrayView<const Backup>
BackupManager::backups() const {
return _backups;
}
const Vfs::Directory<Backup>&
BackupManager::vfs() const
{
return _root;
}
bool
BackupManager::create(const GameObjects::Profile& profile) {
if(!profile.valid()) {
LOG_ERROR(_lastError = "Profile is not valid.");
return false;
}
const auto timestamp = []{
std::time_t timestamp = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
return *std::localtime(&timestamp);
}();
auto filename = Utility::format("{}_{}{:.2d}{:.2d}_{:.2d}{:.2d}{:.2d}.backup.mbst",
Utility::String::replaceAll(profile.companyName(), ' ', '_').data(),
timestamp.tm_year + 1900, timestamp.tm_mon + 1, timestamp.tm_mday,
timestamp.tm_hour, timestamp.tm_min, timestamp.tm_sec);
auto temp_path = Utilities::getTempPath(filename);
int error_code = 0;
auto zip = zip_open(temp_path.data(), ZIP_CREATE|ZIP_TRUNCATE, &error_code);
if(zip == nullptr) {
zip_error_t error;
zip_error_init_with_code(&error, error_code);
LOG_ERROR(_lastError = zip_error_strerror(&error));
zip_error_fini(&error);
return false;
}
Containers::ScopeGuard guard{&filename, [](Containers::String* str){
Utilities::deleteTempFile(*str);
}};
Containers::StringView save_dir = conf().directories().gameSaves;
auto profile_source = zip_source_file(zip, Utility::Path::join(save_dir, profile.filename()).data(), 0, 0);
if(!profile_source) {
LOG_ERROR(_lastError = zip_strerror(zip));
zip_source_free(profile_source);
return false;
}
if(zip_file_add(zip, profile.filename().data(), profile_source, ZIP_FL_ENC_UTF_8) == -1) {
LOG_ERROR(_lastError = zip_strerror(zip));
zip_source_free(profile_source);
return false;
}
auto comment = Utility::format("{}|{}|{}-{:.2d}-{:.2d}-{:.2d}-{:.2d}-{:.2d}",
profile.companyName(), profile.isDemo() ? "demo"_s : "full"_s,
timestamp.tm_year + 1900, timestamp.tm_mon + 1, timestamp.tm_mday,
timestamp.tm_hour, timestamp.tm_min, timestamp.tm_sec);
zip_set_archive_comment(zip, comment.data(), comment.size());
for(std::uint8_t i = 0; i < 32; ++i) {
auto build_filename = Utility::format("{}Unit{:.2d}{}.sav",
profile.isDemo() ? "Demo"_s : ""_s, i,
profile.account());
if(!Utility::Path::exists(Utility::Path::join(save_dir, build_filename))) {
continue;
}
auto build_source = zip_source_file(zip, Utility::Path::join(save_dir, build_filename).data(), 0, 0);
if(!build_source) {
LOG_ERROR(_lastError = zip_strerror(zip));
zip_source_free(build_source);
return false;
}
if(zip_file_add(zip, build_filename.data(), build_source, ZIP_FL_ENC_UTF_8) == -1) {
LOG_ERROR(_lastError = zip_strerror(zip));
zip_source_free(build_source);
return false;
}
}
if(zip_close(zip) == -1) {
LOG_ERROR(_lastError = zip_strerror(zip));
return false;
}
if(!Utilities::moveFromTemp(filename, conf().directories().backups)) {
_lastError = Utility::format("Couldn't move {} to {}.", filename, conf().directories().backups);
return false;
}
guard.release();
return true;
}
bool
BackupManager::remove(std::size_t index) {
CORRADE_INTERNAL_ASSERT(index < _backups.size());
if(!Utility::Path::remove(Utility::Path::join(conf().directories().backups, _backups[index].filename))) {
LOG_ERROR(_lastError = "Couldn't delete " + _backups[index].filename);
return false;
}
return true;
}
bool
BackupManager::restore(std::size_t index) {
CORRADE_INTERNAL_ASSERT(index < _backups.size());
const auto& backup = _backups[index];
int error_code = 0;
auto zip = zip_open(Utility::Path::join(conf().directories().backups, backup.filename).data(), ZIP_RDONLY,
&error_code);
if(zip == nullptr) {
zip_error_t error;
zip_error_init_with_code(&error, error_code);
LOG_ERROR(_lastError = zip_error_strerror(&error));
zip_error_fini(&error);
return false;
}
Containers::ScopeGuard zip_guard{zip, zip_close};
auto error_format = "Extraction of file {} failed: {}"_s;
for(Containers::StringView file : backup.includedFiles) {
auto temp_file = Utilities::getTempPath(file);
auto out = std::fopen(temp_file.cbegin(), "wb");
if(out == nullptr) {
LOG_ERROR(_lastError = Utility::format(error_format.data(), file, std::strerror(errno)));
return false;
}
Containers::ScopeGuard out_guard{out, std::fclose};
auto zf = zip_fopen(zip, file.data(), ZIP_FL_ENC_UTF_8);
if(zf == nullptr) {
LOG_ERROR(_lastError = Utility::format(error_format.data(), file, zip_strerror(zip)));
return false;
}
Containers::ScopeGuard zf_guard{zf, zip_fclose};
Containers::StaticArray<8192, char> buf{ValueInit};
std::int64_t bytes_read;
while((bytes_read = zip_fread(zf, buf.data(), buf.size())) > 0ll) {
if(std::fwrite(buf.data(), sizeof(char), bytes_read, out) < static_cast<std::size_t>(bytes_read)) {
LOG_ERROR(_lastError = Utility::format(error_format.data(), file, "not enough bytes written."));
return false;
}
}
if(bytes_read == -1) {
LOG_ERROR(_lastError = Utility::format(error_format.data(), file, "couldn't read bytes from archive."));
return false;
}
if(!Utilities::moveFromTemp(file, conf().directories().gameSaves)) {
_lastError = Utility::format("Couldn't move {} to {}.", file, conf().directories().gameSaves);
return false;
}
}
return true;
}
void
BackupManager::scanSubdir(Containers::StringView subdir) {
static std::uint8_t depth = 0;
auto full_subdir = Utility::Path::join(conf().directories().backups, subdir);
using Flag = Utility::Path::ListFlag;
auto files = Utility::Path::list(full_subdir, Flag::SkipDirectories|Flag::SkipSpecial);
if(!files) {
LOG_ERROR_FORMAT("Couldn't list contents of {}.", full_subdir);
}
auto predicate = [](Containers::StringView file)->bool{
return !(file.hasSuffix(".mbprofbackup"_s) || file.hasSuffix(".backup.mbst"_s));
};
auto files_view = files->exceptSuffix(files->end() - std::remove_if(files->begin(), files->end(), predicate));
int error_code = 0;
zip_t* zip;
for(Containers::StringView file : files_view) {
Backup backup;
backup.filename = Utility::Path::join(subdir, file);
auto full_path = Utility::Path::join(full_subdir, file);
zip = zip_open(full_path.cbegin(), ZIP_RDONLY, &error_code);
if(zip == nullptr) {
continue;
}
Containers::ScopeGuard guard{zip, zip_close};
auto num_entries = zip_get_num_entries(zip, ZIP_FL_UNCHANGED);
if(num_entries == 0) {
continue;
}
int comment_length;
Containers::StringView comment = zip_get_archive_comment(zip, &comment_length, ZIP_FL_UNCHANGED);
if(comment == nullptr) {
continue;
}
auto info = comment.split('|');
if(info.size() != 3) {
continue;
}
backup.company = info[0];
if(info[1].hasPrefix("full")) {
backup.demo = false;
}
else if(info[1].hasPrefix("demo")) {
backup.demo = true;
}
else {
continue;
}
auto ts = info[2].split('-');
if(ts.size() != 6) {
continue;
}
backup.timestamp.year = std::strtol(ts[0].data(), nullptr, 10);
backup.timestamp.month = std::strtol(ts[1].data(), nullptr, 10);
backup.timestamp.day = std::strtol(ts[2].data(), nullptr, 10);
backup.timestamp.hour = std::strtol(ts[3].data(), nullptr, 10);
backup.timestamp.minute = std::strtol(ts[4].data(), nullptr, 10);
backup.timestamp.second = std::strtol(ts[5].data(), nullptr, 10);
arrayReserve(backup.includedFiles, num_entries);
for(auto i = 0; i < num_entries; i++) {
arrayAppend(backup.includedFiles, InPlaceInit, zip_get_name(zip, i, ZIP_FL_UNCHANGED));
}
arrayAppend(_backups, Utility::move(backup));
}
if(depth == 5) {
return;
}
auto subdirs = Utility::Path::list(full_subdir, Flag::SkipFiles|Flag::SkipSpecial|Flag::SkipDotAndDotDot);
if(!subdirs) {
LOG_ERROR_FORMAT("Couldn't list contents of {}.", full_subdir);
}
depth++;
for(auto& dir : *subdirs) {
scanSubdir(Utility::Path::join(subdir, dir));
}
depth--;
}
}

View file

@ -1,63 +0,0 @@
#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/Array.h>
#include <Corrade/Containers/ArrayView.h>
#include <Corrade/Containers/String.h>
#include <Corrade/Containers/StringView.h>
#include <efsw/efsw.hpp>
#include "Backup.h"
#include "../GameObjects/Profile.h"
#include "Vfs/Directory.h"
using namespace Corrade;
namespace mbst::Managers {
class BackupManager {
public:
explicit BackupManager();
auto lastError() -> Containers::StringView;
void refresh();
auto backups() const -> Containers::ArrayView<const Backup>;
auto vfs() const -> const Vfs::Directory<Backup>&;
bool create(const GameObjects::Profile& profile);
bool remove(std::size_t index);
bool remove(Containers::StringView filename);
bool restore(std::size_t index);
private:
void scanSubdir(Containers::StringView subdir);
Containers::String _lastError;
Containers::Array<Backup> _backups;
Vfs::Directory<Backup> _root;
};
}

View file

@ -1,150 +0,0 @@
// 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 <algorithm>
#include <Corrade/Utility/Format.h>
#include <Corrade/Utility/Path.h>
#include "../Configuration/Configuration.h"
#include "../Logger/Logger.h"
#include "MassManager.h"
using namespace Containers::Literals;
namespace mbst::Managers {
MassManager::MassManager(Containers::StringView account, bool demo):
_account{account}, _demo{demo}
{
Containers::String mass_filename = "";
for(std::uint32_t i = 0; i < _hangars.size(); i++) {
mass_filename = Utility::Path::join(conf().directories().gameSaves,
Utility::format("{}Unit{:.2d}{}.sav", demo ? "Demo"_s : ""_s, i, _account));
new(&_hangars[i]) GameObjects::Mass{mass_filename};
}
}
Containers::StringView
MassManager::lastError() {
return _lastError;
}
GameObjects::Mass&
MassManager::hangar(std::int32_t hangar) {
return _hangars[hangar];
}
void
MassManager::refreshHangar(std::int32_t hangar) {
if(hangar < 0 || hangar >= 32) {
_lastError = "Hangar index out of range.";
LOG_ERROR(_lastError);
return;
}
Containers::String mass_filename =
Utility::Path::join(conf().directories().gameSaves,
Utility::format("{}Unit{:.2d}{}.sav", _demo ? "Demo" : "", hangar, _account));
_hangars[hangar] = GameObjects::Mass{mass_filename};
}
bool
MassManager::exportMass(std::int32_t hangar) {
if(hangar < 0 || hangar >= 32) {
_lastError = "Hangar index out of range."_s;
LOG_ERROR(_lastError);
return false;
}
if(_hangars[hangar].state() != GameObjects::Mass::State::Valid) {
_lastError = Utility::format("There is no valid data to export in hangar {:.2d}", hangar + 1);
LOG_ERROR(_lastError);
return false;
}
Containers::String source = Utility::Path::join(conf().directories().gameSaves, _hangars[hangar].filename());
Containers::String dest = Utility::Path::join(conf().directories().staging,
Utility::format("{}_{}.sav", _hangars[hangar].name(), _account));
if(!Utility::Path::copy(source, dest)) {
_lastError = Utility::format("Couldn't export data from hangar {:.2d} to {}", hangar, dest);
LOG_ERROR(_lastError);
return false;
}
return true;
}
bool
MassManager::moveMass(std::int32_t source, std::int32_t destination) {
if(source < 0 || source >= 32) {
_lastError = "Source hangar index out of range."_s;
LOG_ERROR(_lastError);
return false;
}
if(destination < 0 || destination >= 32) {
_lastError = "Destination hangar index out of range."_s;
LOG_ERROR(_lastError);
return false;
}
Containers::String source_file = Utility::Path::join(conf().directories().gameSaves,
_hangars[source].filename());
Containers::String dest_file = Utility::Path::join(conf().directories().gameSaves,
_hangars[destination].filename());
GameObjects::Mass::State dest_state = _hangars[destination].state();
switch(dest_state) {
case GameObjects::Mass::State::Empty:
break;
case GameObjects::Mass::State::Invalid:
Utility::Path::remove(dest_file);
break;
case GameObjects::Mass::State::Valid:
Utility::Path::move(dest_file, dest_file + ".tmp"_s);
break;
}
Utility::Path::move(source_file, dest_file);
if(dest_state == GameObjects::Mass::State::Valid) {
Utility::Path::move(dest_file + ".tmp"_s, source_file);
}
return true;
}
bool
MassManager::deleteMass(std::int32_t hangar) {
if(hangar < 0 || hangar >= 32) {
_lastError = "Hangar index out of range."_s;
LOG_ERROR(_lastError);
return false;
}
if(!Utility::Path::remove(Utility::Path::join(conf().directories().gameSaves, _hangars[hangar].filename()))) {
_lastError = Utility::format("Deletion failed: {}", std::strerror(errno));
LOG_ERROR(_lastError);
return false;
}
return true;
}
}

View file

@ -1,132 +0,0 @@
// 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 <algorithm>
#include <Corrade/Containers/ScopeGuard.h>
#include <Corrade/Containers/StaticArray.h>
#include <Corrade/Utility/Format.h>
#include <Corrade/Utility/Path.h>
#include <Corrade/Utility/String.h>
#include "../Configuration/Configuration.h"
#include "../Logger/Logger.h"
#include "ProfileManager.h"
using namespace Containers::Literals;
namespace mbst::Managers {
ProfileManager::ProfileManager() {
_ready = refreshProfiles();
}
bool
ProfileManager::ready() const {
return _ready;
}
Containers::StringView
ProfileManager::lastError() {
return _lastError;
}
Containers::ArrayView<GameObjects::Profile>
ProfileManager::profiles() {
return _profiles;
}
bool
ProfileManager::refreshProfiles() {
LOG_INFO("Refreshing profiles.");
_profiles = Containers::Array<GameObjects::Profile>{};
using Utility::Path::ListFlag;
auto files = Utility::Path::list(conf().directories().gameSaves,
ListFlag::SkipSpecial|ListFlag::SkipDirectories|ListFlag::SkipDotAndDotDot);
if(!files) {
LOG_ERROR(_lastError = conf().directories().gameSaves + " can't be opened.");
return false;
}
auto predicate = [](Containers::StringView file)->bool{
return !((file.hasPrefix("DemoProfile") || file.hasPrefix("Profile")) && file.hasSuffix(".sav"));
};
auto files_view = files->exceptSuffix(files->end() - std::remove_if(files->begin(), files->end(), predicate));
for(const auto& file : files_view) {
GameObjects::Profile profile{Utility::Path::join(conf().directories().gameSaves, file)};
if(!profile.valid()) {
LOG_WARNING_FORMAT("Profile {} is invalid: {}", file, profile.lastError());
continue;
}
arrayAppend(_profiles, Utility::move(profile));
}
if(_profiles.isEmpty()) {
_lastError = "No valid profiles were found."_s;
LOG_ERROR(_lastError);
return false;
}
return true;
}
GameObjects::Profile*
ProfileManager::getProfile(std::size_t index) {
return index <= _profiles.size() ? &(_profiles[index]) : nullptr;
}
bool
ProfileManager::deleteProfile(std::size_t index, bool delete_builds) {
CORRADE_INTERNAL_ASSERT(index < _profiles.size());
if(!Utility::Path::remove(Utility::Path::join(conf().directories().gameSaves, _profiles[index].filename()))) {
_lastError = Utility::format("Couldn't delete {} (filename: {}).",
_profiles[index].companyName(),
_profiles[index].filename());
LOG_ERROR(_lastError);
refreshProfiles();
return false;
}
if(delete_builds) {
for(std::uint8_t i = 0; i < 32; ++i) {
auto filename = Utility::format("{}Unit{:.2d}{}.sav",
_profiles[index].type() == GameObjects::Profile::Type::Demo ? "Demo": "",
i, _profiles[index].account());
Utility::Path::remove(Utility::Path::join(conf().directories().gameSaves, filename));
}
}
auto file = _profiles[index].filename();
auto it = std::remove_if(_profiles.begin(), _profiles.end(),
[&file](GameObjects::Profile& profile){ return profile.filename() == file; });
if(it != _profiles.end()) {
arrayRemoveSuffix(_profiles, 1);
}
return true;
}
}

View file

@ -1,48 +0,0 @@
#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/Array.h>
#include <Corrade/Containers/String.h>
#include "../GameObjects/Profile.h"
using namespace Corrade;
namespace mbst::Managers {
class ProfileManager {
public:
explicit ProfileManager();
auto ready() const -> bool;
auto lastError() -> Containers::StringView;
auto profiles() -> Containers::ArrayView<GameObjects::Profile>;
bool refreshProfiles();
auto getProfile(std::size_t index) -> GameObjects::Profile*;
bool deleteProfile(std::size_t index, bool delete_builds);
private:
bool _ready = false;
Containers::String _lastError;
Containers::Array<GameObjects::Profile> _profiles;
};
}

View file

@ -1,30 +0,0 @@
#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/String.h>
using namespace Corrade;
namespace mbst::Managers {
struct StagedMass {
Containers::String filename;
Containers::String name;
};
}

View file

@ -1,215 +0,0 @@
// 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 <algorithm>
#include <Corrade/Containers/Optional.h>
#include <Corrade/Utility/Path.h>
#include "../Configuration/Configuration.h"
#include "../GameObjects/Mass.h"
#include "../Logger/Logger.h"
#include "../Utilities/Temp.h"
#include "StagedMassManager.h"
namespace mbst::Managers {
StagedMassManager::StagedMassManager() {
refresh();
}
Containers::StringView
StagedMassManager::lastError() {
return _lastError;
}
Containers::ArrayView<const StagedMass>
StagedMassManager::stagedMasses() const {
return _stagedMasses;
}
const StagedMass&
StagedMassManager::at(Containers::StringView filename) const {
for(const auto& mass : _stagedMasses) {
if(mass.filename == filename) {
return mass;
}
}
CORRADE_ASSERT_UNREACHABLE("Invalid staged M.A.S.S.!", StagedMass{});
}
void
StagedMassManager::refresh() {
_stagedMasses = Containers::Array<StagedMass>{};
LOG_INFO("Scanning for staged M.A.S.S.es...");
scanSubdir(""_s);
}
void
StagedMassManager::refreshMass(Containers::StringView filename) {
LOG_INFO_FORMAT("Refreshing staged unit with filename {}.", filename);
bool file_exists = Utility::Path::exists(Utility::Path::join(conf().directories().staging, filename));
auto index = _stagedMasses.size();
for(std::size_t i = 0; i < _stagedMasses.size(); ++i) {
if(_stagedMasses[i].filename == filename) {
index = i;
break;
}
}
if(file_exists) {
if(auto name = GameObjects::Mass::getNameFromFile(Utility::Path::join(conf().directories().staging, filename))) {
arrayAppend(_stagedMasses, StagedMass{filename, *name});
std::sort(_stagedMasses.begin(), _stagedMasses.end(),
[](const StagedMass& a, const StagedMass& b)->bool{
if(a.filename.contains('/') && !b.filename.contains('/')) {
return true;
}
return a.filename < b.filename;
}
);
}
else if(index != _stagedMasses.size()) {
arrayRemove(_stagedMasses, index);
}
}
else if(index != _stagedMasses.size()) {
arrayRemove(_stagedMasses, index);
}
}
bool
StagedMassManager::import(Containers::StringView filename, int index, Containers::StringView new_account, bool demo) {
if(index < 0 || index >= 32) {
LOG_ERROR(_lastError = "Hangar index out of range."_s);
return false;
}
bool found = false;
for(auto& mass : _stagedMasses) {
if(mass.filename == filename) {
found = true;
break;
}
}
if(!found) {
LOG_ERROR(_lastError = "Couldn't find "_s + filename + " in the staged M.A.S.S.es."_s);
return false;
}
auto source = Utility::Path::join(conf().directories().staging, filename);
auto temp_path = Utilities::getTempPath(Utility::Path::split(filename).second());
Utility::Path::copy(source, temp_path);
{
GameObjects::Mass mass{temp_path};
if(!mass.updateAccount(new_account)) {
LOG_ERROR(_lastError = mass.lastError());
Utility::Path::remove(temp_path);
return false;
}
}
auto dest = Utility::Path::join(conf().directories().gameSaves,
Utility::format("{}Unit{}{}.sav", demo ? "Demo" : "", index, new_account));
if(Utility::Path::exists(dest)) {
Utility::Path::remove(dest);
}
if(!Utility::Path::move(temp_path, dest)) {
_lastError = Utility::format("Couldn't move {} to hangar {:.2d}", filename, index + 1);
LOG_ERROR(_lastError);
return false;
}
return true;
}
bool
StagedMassManager::remove(Containers::StringView filename) {
bool found = false;
for(auto& mass : _stagedMasses) {
if(mass.filename == filename) {
found = true;
break;
}
}
if(!found || !Utility::Path::remove(Utility::Path::join(conf().directories().staging, filename))) {
LOG_ERROR(_lastError = Utility::format("{} couldn't be found.", filename));
return false;
}
return true;
}
void
StagedMassManager::scanSubdir(Containers::StringView subdir) {
static std::uint8_t depth = 0;
auto full_subdir = Utility::Path::join(conf().directories().staging, subdir);
using Flag = Utility::Path::ListFlag;
auto files = Utility::Path::list(full_subdir, Flag::SkipSpecial|Flag::SkipDirectories);
if(!files) {
LOG_ERROR_FORMAT("{} couldn't be opened.", full_subdir);
return;
}
auto iter = std::remove_if(files->begin(), files->end(), [](Containers::StringView file){
return !file.hasSuffix(".sav"_s);
});
auto files_view = files->exceptSuffix(files->end() - iter);
for(Containers::StringView file : files_view) {
if(auto name = GameObjects::Mass::getNameFromFile(Utility::Path::join(full_subdir, file))) {
LOG_INFO_FORMAT("Found staged M.A.S.S.: {}", *name);
arrayAppend(_stagedMasses, InPlaceInit, file, *name);
}
else {
LOG_WARNING_FORMAT("Skipped {}.", file);
}
}
if(depth == 5) {
return;
}
auto subdirs = Utility::Path::list(full_subdir, Flag::SkipFiles|Flag::SkipSpecial|Flag::SkipDotAndDotDot);
if(!subdirs) {
LOG_ERROR_FORMAT("Couldn't list contents of {}.", full_subdir);
}
depth++;
for(auto& dir : *subdirs) {
scanSubdir(Utility::Path::join(subdir, dir));
}
depth--;
}
}

View file

@ -1,52 +0,0 @@
#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/Array.h>
#include <Corrade/Containers/ArrayView.h>
#include <Corrade/Containers/String.h>
#include <Corrade/Containers/StringView.h>
#include "StagedMass.h"
using namespace Corrade;
namespace mbst::Managers {
class StagedMassManager {
public:
explicit StagedMassManager();
auto lastError() -> Containers::StringView;
auto stagedMasses() const -> Containers::ArrayView<const StagedMass>;
auto at(Containers::StringView filename) const -> const StagedMass&;
void refresh();
void refreshMass(Containers::StringView filename);
bool import(Containers::StringView filename, int index, Containers::StringView new_account, bool demo);
bool remove(Containers::StringView filename);
private:
void scanSubdir(Containers::StringView subdir);
Containers::String _lastError;
Containers::Array<StagedMass> _stagedMasses;
};
}

View file

@ -1,102 +0,0 @@
#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/Array.h>
#include <Corrade/Containers/String.h>
#include <Corrade/Containers/StringView.h>
using namespace Corrade;
namespace mbst::Managers::Vfs {
CORRADE_HAS_TYPE(HasFilename, decltype(T::filename));
template<typename FileType>
class Directory {
public:
static_assert(HasFilename<FileType>::value && (std::is_same<decltype(FileType::filename), Containers::String>::value || std::is_same<decltype(FileType::filename), Containers::StringView>::value),
"FileType has no string-like member named filename.");
using DirType = Directory<FileType>;
explicit Directory(Containers::StringView name): _name{name} {
// ctor
}
Directory(const Directory<FileType>& other) = delete;
Directory& operator=(const Directory<FileType>& other) = delete;
Directory(Directory<FileType>&& other) = default;
Directory& operator=(Directory<FileType>&& other) = default;
auto name() const -> Containers::StringView {
return _name;
}
auto subdirs() const -> Containers::ArrayView<const DirType> {
return _subdirs;
}
auto files() const -> Containers::ArrayView<FileType* const> {
return _files;
}
void clear() {
_subdirs = Containers::Array<DirType>{};
_files = Containers::Array<FileType*>{};
}
void build(Containers::ArrayView<FileType> files) {
for(auto& file : files) {
auto components = file.filename.split('/');
CORRADE_INTERNAL_ASSERT(components.size() != 0);
buildNestedPath(*this, components, file);
}
}
private:
void buildNestedPath(Directory<FileType>& root, Containers::ArrayView<const Containers::MutableStringView> components,
FileType& file)
{
if(components.size() > 1) {
bool found = false;
for(auto& subdir : root._subdirs) {
if(subdir._name == components[0]) {
found = true;
buildNestedPath(subdir, components.exceptPrefix(1), file);
break;
}
}
if(!found) {
arrayAppend(root._subdirs, InPlaceInit, components[0]);
buildNestedPath(root._subdirs.back(), components.exceptPrefix(1), file);
}
}
else if(components.size() == 1) {
arrayAppend(root._files, &file);
}
}
Containers::String _name;
Containers::Array<DirType> _subdirs;
Containers::Array<FileType*> _files;
};
}

684
src/Maps/Accessories.h Normal file
View file

@ -0,0 +1,684 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2022 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 <map>
#include <Corrade/Containers/StringView.h>
#include <Magnum/Types.h>
using namespace Corrade;
using namespace Containers::Literals;
using namespace Magnum;
enum AccessorySize {
S,
M,
L,
XL
};
struct AccessoryData{
Containers::StringView name;
AccessorySize size = AccessorySize::S;
};
static const std::map<Int, AccessoryData> accessories {
// region Primitives
{1, {"Cube"_s, AccessorySize::S}},
{2, {"Pentagon"_s, AccessorySize::S}},
{3, {"Hexagon"_s, AccessorySize::S}},
{4, {"Cylinder"_s, AccessorySize::S}},
{5, {"Sphere"_s, AccessorySize::S}},
{6, {"TriPyramid"_s, AccessorySize::S}},
{7, {"SquPyramid"_s, AccessorySize::S}},
{8, {"PenPyramid"_s, AccessorySize::S}},
{9, {"HexPyramid"_s, AccessorySize::S}},
{10, {"Cone"_s, AccessorySize::S}},
{11, {"SquStick"_s, AccessorySize::S}},
{12, {"PenStick"_s, AccessorySize::S}},
{13, {"HexStick"_s, AccessorySize::S}},
{14, {"CycStick"_s, AccessorySize::S}},
{15, {"Capsule"_s, AccessorySize::S}},
{16, {"Decal Pad 01"_s, AccessorySize::S}},
{17, {"Decal Pad 02"_s, AccessorySize::S}},
{18, {"Decal Pad 03"_s, AccessorySize::S}},
{19, {"Decal Pad 04"_s, AccessorySize::S}},
{20, {"Decal Pad 05"_s, AccessorySize::S}},
{21, {"Triangle"_s, AccessorySize::S}},
{22, {"ThinStar"_s, AccessorySize::S}},
{23, {"Star"_s, AccessorySize::S}},
{24, {"SixSideStar"_s, AccessorySize::S}},
{25, {"Asterisk"_s, AccessorySize::S}},
{26, {"Ring"_s, AccessorySize::S}},
{27, {"SawedRing"_s, AccessorySize::S}},
{28, {"HalfRing"_s, AccessorySize::S}},
{29, {"Cresent"_s, AccessorySize::S}},
{30, {"Donut"_s, AccessorySize::S}},
{31, {"FiveCogWheel"_s, AccessorySize::S}},
{32, {"SixCogWheel"_s, AccessorySize::S}},
{33, {"SevenCogWheel"_s, AccessorySize::S}},
{34, {"EightCogWheel"_s, AccessorySize::S}},
{35, {"TwelveCogWheel"_s, AccessorySize::S}},
{51, {"SquBevel"_s, AccessorySize::S}},
{52, {"TriBevel"_s, AccessorySize::S}},
{53, {"PenBevel"_s, AccessorySize::S}},
{54, {"HexBevel"_s, AccessorySize::S}},
{55, {"CycBevel"_s, AccessorySize::S}},
{56, {"RecBevel"_s, AccessorySize::S}},
{57, {"DaiBevel"_s, AccessorySize::S}},
{58, {"MonBevel"_s, AccessorySize::S}},
{59, {"CofBevel"_s, AccessorySize::S}},
{60, {"JevBevel"_s, AccessorySize::S}},
{61, {"SquEmboss"_s, AccessorySize::S}},
{62, {"TriEmboss"_s, AccessorySize::S}},
{63, {"PenEmboss"_s, AccessorySize::S}},
{64, {"HexEmboss"_s, AccessorySize::S}},
{65, {"CycEmboss"_s, AccessorySize::S}},
{66, {"RecEmboss"_s, AccessorySize::S}},
{67, {"DaiEmboss"_s, AccessorySize::S}},
{68, {"MonEmboss"_s, AccessorySize::S}},
{69, {"CofEmboss"_s, AccessorySize::S}},
{70, {"JevEmboss"_s, AccessorySize::S}},
{101, {"Flat Hex Pin"_s, AccessorySize::S}},
{102, {"Cross Circle Pin"_s, AccessorySize::S}},
{103, {"Flat Circle Pin"_s, AccessorySize::S}},
{104, {"Hex Circle Pin"_s, AccessorySize::S}},
{105, {"Circle Button Pin"_s, AccessorySize::S}},
{106, {"Hexagon Pin"_s, AccessorySize::S}},
{107, {"Cross Square Pin"_s, AccessorySize::S}},
{108, {"Flat Square Pin"_s, AccessorySize::S}},
{109, {"Quad Corner Pin"_s, AccessorySize::S}},
{110, {"Bi Corner Pin"_s, AccessorySize::S}},
{111, {"Circle Pin"_s, AccessorySize::S}},
{112, {"Flat End Pin"_s, AccessorySize::S}},
{113, {"Flat Cut Pin"_s, AccessorySize::S}},
{114, {"Radial Pin"_s, AccessorySize::S}},
{115, {"Diamiter Pin"_s, AccessorySize::S}},
{151, {"TriPoint"_s, AccessorySize::S}},
{152, {"SquPoint"_s, AccessorySize::S}},
{153, {"PenPoint"_s, AccessorySize::S}},
{154, {"HexPoint"_s, AccessorySize::S}},
{155, {"CycPoint"_s, AccessorySize::S}},
{156, {"Bevel SquCutPoint"_s, AccessorySize::S}},
{157, {"Bevel HexCutPoint"_s, AccessorySize::S}},
{158, {"Bevel HexPoint"_s, AccessorySize::S}},
{159, {"Bevel CycCutPoint"_s, AccessorySize::S}},
{160, {"Bevel CycPoint"_s, AccessorySize::S}},
{201, {"Shaped Edge 01"_s, AccessorySize::M}},
{202, {"Shaped Edge 02"_s, AccessorySize::M}},
{203, {"Shaped Edge 03"_s, AccessorySize::M}},
{204, {"Shaped Edge 04"_s, AccessorySize::M}},
{205, {"Shaped Edge 05"_s, AccessorySize::M}},
{206, {"Shaped Edge 06"_s, AccessorySize::M}},
{207, {"Shaped Edge 07"_s, AccessorySize::M}},
{208, {"Shaped Edge 08"_s, AccessorySize::M}},
{209, {"Shaped Edge 09"_s, AccessorySize::M}},
{210, {"Shaped Edge 10"_s, AccessorySize::M}},
{211, {"Shaped Edge 11"_s, AccessorySize::M}},
{212, {"Shaped Edge 12"_s, AccessorySize::M}},
{213, {"Shaped Edge 13"_s, AccessorySize::M}},
{214, {"Shaped Edge 14"_s, AccessorySize::M}},
{215, {"Shaped Edge 15"_s, AccessorySize::M}},
{216, {"Shaped Edge 16"_s, AccessorySize::M}},
{217, {"Shaped Edge 17"_s, AccessorySize::M}},
{218, {"Shaped Edge 18"_s, AccessorySize::M}},
{219, {"Shaped Edge 19"_s, AccessorySize::M}},
{220, {"Shaped Edge 20"_s, AccessorySize::M}},
{251, {"Fish Tail 01"_s, AccessorySize::M}},
{252, {"Fish Tail 02"_s, AccessorySize::M}},
{253, {"Fish Tail 03"_s, AccessorySize::M}},
{254, {"Fish Tail 04"_s, AccessorySize::M}},
{255, {"Fish Tail 05"_s, AccessorySize::M}},
{256, {"Based Separator 01"_s, AccessorySize::M}},
{257, {"Based Separator 02"_s, AccessorySize::M}},
{258, {"Based Separator 03"_s, AccessorySize::M}},
{259, {"Based Separator 04"_s, AccessorySize::M}},
{260, {"Based Separator 05"_s, AccessorySize::M}},
{261, {"Based Separator 06"_s, AccessorySize::M}},
{262, {"Based Separator 07"_s, AccessorySize::M}},
{263, {"Based Separator 08"_s, AccessorySize::M}},
{264, {"Based Separator 09"_s, AccessorySize::M}},
{265, {"Based Separator 10"_s, AccessorySize::M}},
{301, {"Rectangular Box 01"_s, AccessorySize::M}},
{302, {"Rectangular Box 02"_s, AccessorySize::M}},
{303, {"Rectangular Box 03"_s, AccessorySize::M}},
{304, {"Rectangular Box 04"_s, AccessorySize::M}},
{305, {"Rectangular Box 05"_s, AccessorySize::M}},
{306, {"CofBox 01"_s, AccessorySize::M}},
{307, {"CofBox 02"_s, AccessorySize::M}},
{308, {"CofBox 03"_s, AccessorySize::M}},
{309, {"CofBox 04"_s, AccessorySize::M}},
{310, {"CofBox 05"_s, AccessorySize::M}},
{311, {"Triangular Box 01"_s, AccessorySize::M}},
{312, {"Triangular Box 02"_s, AccessorySize::M}},
{313, {"Triangular Box 03"_s, AccessorySize::M}},
{314, {"Triangular Box 04"_s, AccessorySize::M}},
{315, {"Triangular Box 05"_s, AccessorySize::M}},
{316, {"Diagonal Box A01"_s, AccessorySize::M}},
{317, {"Diagonal Box A02"_s, AccessorySize::M}},
{318, {"Diagonal Box A03"_s, AccessorySize::M}},
{319, {"Diagonal Box A04"_s, AccessorySize::M}},
{320, {"Diagonal Box A05"_s, AccessorySize::M}},
{321, {"Diagonal Box B01"_s, AccessorySize::M}},
{322, {"Diagonal Box B02"_s, AccessorySize::M}},
{323, {"Diagonal Box B03"_s, AccessorySize::M}},
{324, {"Diagonal Box B04"_s, AccessorySize::M}},
{325, {"Diagonal Box B05"_s, AccessorySize::M}},
// endregion
// region Armours
{1001, {"Short Layer 01"_s, AccessorySize::M}},
{1002, {"Short Layer 02"_s, AccessorySize::M}},
{1003, {"Short Layer 03"_s, AccessorySize::M}},
{1004, {"Short Layer 04"_s, AccessorySize::M}},
{1005, {"Short Layer 05"_s, AccessorySize::M}},
{1006, {"Long Layer 01"_s, AccessorySize::M}},
{1007, {"Long Layer 02"_s, AccessorySize::M}},
{1008, {"Long Layer 03"_s, AccessorySize::M}},
{1009, {"Long Layer 04"_s, AccessorySize::M}},
{1010, {"Long Layer 05"_s, AccessorySize::M}},
{1011, {"Diagonal Long Layer 01"_s, AccessorySize::M}},
{1012, {"Diagonal Long Layer 02"_s, AccessorySize::M}},
{1013, {"Diagonal Long Layer 03"_s, AccessorySize::M}},
{1014, {"Diagonal Long Layer 04"_s, AccessorySize::M}},
{1015, {"Diagonal Long Layer 05"_s, AccessorySize::M}},
{1051, {"Sloped Layer 01"_s, AccessorySize::M}},
{1052, {"Sloped Layer 02"_s, AccessorySize::M}},
{1053, {"Sloped Layer 03"_s, AccessorySize::M}},
{1054, {"Sloped Layer 04"_s, AccessorySize::M}},
{1055, {"Sloped Layer 05"_s, AccessorySize::M}},
{1056, {"Sloped Layer 06"_s, AccessorySize::M}},
{1057, {"Sloped Layer 07"_s, AccessorySize::M}},
{1058, {"Sloped Layer 08"_s, AccessorySize::M}},
{1059, {"Sloped Layer 09"_s, AccessorySize::M}},
{1060, {"Sloped Layer 10"_s, AccessorySize::M}},
{1061, {"Sloped Layer 11"_s, AccessorySize::M}},
{1062, {"Sloped Layer 12"_s, AccessorySize::M}},
{1063, {"Sloped Layer 13"_s, AccessorySize::M}},
{1064, {"Sloped Layer 14"_s, AccessorySize::M}},
{1065, {"Sloped Layer 15"_s, AccessorySize::M}},
{1101, {"Raised Center 01"_s, AccessorySize::M}},
{1102, {"Raised Center 02"_s, AccessorySize::M}},
{1103, {"Raised Center 03"_s, AccessorySize::M}},
{1104, {"Raised Center 04"_s, AccessorySize::M}},
{1105, {"Raised Center 05"_s, AccessorySize::M}},
{1106, {"Raised Block 01"_s, AccessorySize::M}},
{1107, {"Raised Block 02"_s, AccessorySize::M}},
{1108, {"Raised Block 03"_s, AccessorySize::M}},
{1109, {"Raised Pointed"_s, AccessorySize::M}},
{1110, {"Raised Cover"_s, AccessorySize::M}},
{1111, {"Raised Slant 01"_s, AccessorySize::M}},
{1112, {"Raised Slant 02"_s, AccessorySize::M}},
{1113, {"Raised Slant 03"_s, AccessorySize::M}},
{1114, {"Raised Slant 04"_s, AccessorySize::M}},
{1115, {"Raised Slant 05"_s, AccessorySize::M}},
{1151, {"Wide Patch 01"_s, AccessorySize::L}},
{1152, {"Wide Patch 02"_s, AccessorySize::L}},
{1153, {"Wide Patch 03"_s, AccessorySize::L}},
{1154, {"Wide Patch 04"_s, AccessorySize::L}},
{1155, {"Wide Patch 05"_s, AccessorySize::L}},
{1201, {"Pointed Armour 01"_s, AccessorySize::L}},
{1202, {"Pointed Armour 02"_s, AccessorySize::L}},
{1203, {"Pointed Armour 03"_s, AccessorySize::L}},
{1204, {"Pointed Armour 04"_s, AccessorySize::L}},
{1205, {"Pointed Armour 05"_s, AccessorySize::L}},
{1206, {"Pointed Armour 06"_s, AccessorySize::L}},
{1207, {"Pointed Armour 07"_s, AccessorySize::L}},
{1208, {"Pointed Armour 08"_s, AccessorySize::L}},
{1209, {"Pointed Armour 09"_s, AccessorySize::L}},
{1210, {"Pointed Armour 10"_s, AccessorySize::L}},
{1211, {"Pointed Armour 11"_s, AccessorySize::L}},
{1212, {"Pointed Armour 12"_s, AccessorySize::L}},
{1213, {"Pointed Armour 13"_s, AccessorySize::L}},
{1214, {"Pointed Armour 14"_s, AccessorySize::L}},
{1215, {"Pointed Armour 15"_s, AccessorySize::L}},
{1251, {"E Limb Cover 01"_s, AccessorySize::L}},
{1252, {"E Limb Cover 02"_s, AccessorySize::L}},
{1253, {"E Limb Cover 03"_s, AccessorySize::L}},
{1254, {"E Limb Cover 04"_s, AccessorySize::L}},
{1255, {"E Limb Cover 05"_s, AccessorySize::L}},
{1256, {"E Limb Cover 06"_s, AccessorySize::L}},
{1257, {"E Limb Cover 07"_s, AccessorySize::L}},
{1258, {"E Limb Cover 08"_s, AccessorySize::L}},
{1259, {"E Limb Cover 09"_s, AccessorySize::L}},
{1260, {"E Limb Cover 10"_s, AccessorySize::L}},
{1301, {"C Limb Cover 01"_s, AccessorySize::L}},
{1302, {"C Limb Cover 02"_s, AccessorySize::L}},
{1303, {"C Limb Cover 03"_s, AccessorySize::L}},
{1304, {"C Limb Cover 04"_s, AccessorySize::L}},
{1305, {"C Limb Cover 05"_s, AccessorySize::L}},
{1306, {"C Limb Cover 06"_s, AccessorySize::L}},
{1307, {"C Limb Cover 07"_s, AccessorySize::L}},
{1308, {"C Limb Cover 08"_s, AccessorySize::L}},
{1309, {"C Limb Cover 09"_s, AccessorySize::L}},
{1310, {"C Limb Cover 10"_s, AccessorySize::L}},
{1311, {"C Limb Cover 11"_s, AccessorySize::L}},
{1312, {"C Limb Cover 12"_s, AccessorySize::L}},
{1313, {"C Limb Cover 13"_s, AccessorySize::L}},
{1314, {"C Limb Cover 14"_s, AccessorySize::L}},
{1315, {"C Limb Cover 15"_s, AccessorySize::L}},
{1316, {"C Limb Cover 16"_s, AccessorySize::L}},
{1317, {"C Limb Cover 17"_s, AccessorySize::L}},
{1318, {"C Limb Cover 18"_s, AccessorySize::L}},
{1319, {"C Limb Cover 19"_s, AccessorySize::L}},
{1320, {"C Limb Cover 20"_s, AccessorySize::L}},
{1351, {"P Limb Cover 01"_s, AccessorySize::XL}},
{1352, {"P Limb Cover 02"_s, AccessorySize::XL}},
{1353, {"P Limb Cover 03"_s, AccessorySize::XL}},
{1354, {"P Limb Cover 04"_s, AccessorySize::XL}},
{1355, {"P Limb Cover 05"_s, AccessorySize::XL}},
{1401, {"Flat Cover 01"_s, AccessorySize::XL}},
{1402, {"Flat Cover 02"_s, AccessorySize::XL}},
{1403, {"Flat Cover 03"_s, AccessorySize::XL}},
{1404, {"Flat Cover 04"_s, AccessorySize::XL}},
{1405, {"Flat Cover 05"_s, AccessorySize::XL}},
{1406, {"Flat Cover 06"_s, AccessorySize::XL}},
{1407, {"Flat Cover 07"_s, AccessorySize::XL}},
{1408, {"Flat Cover 08"_s, AccessorySize::XL}},
{1409, {"Flat Cover 09"_s, AccessorySize::XL}},
{1410, {"Flat Cover 10"_s, AccessorySize::XL}},
{1451, {"L Side Opening 01"_s, AccessorySize::XL}},
{1452, {"L Side Opening 02"_s, AccessorySize::XL}},
{1453, {"L Side Opening 03"_s, AccessorySize::XL}},
{1454, {"L Side Opening 04"_s, AccessorySize::XL}},
{1455, {"L Side Opening 05"_s, AccessorySize::XL}},
{1456, {"L Side Opening 06"_s, AccessorySize::XL}},
{1457, {"L Side Opening 07"_s, AccessorySize::XL}},
{1458, {"L Side Opening 08"_s, AccessorySize::XL}},
{1459, {"L Side Opening 09"_s, AccessorySize::XL}},
{1460, {"L Side Opening 10"_s, AccessorySize::XL}},
// endregion
// region Components
{2001, {"Disc Padding 01"_s, AccessorySize::M}},
{2002, {"Disc Padding 02"_s, AccessorySize::M}},
{2003, {"Disc Padding 03"_s, AccessorySize::M}},
{2004, {"Disc Padding 04"_s, AccessorySize::M}},
{2005, {"Disc Padding 05"_s, AccessorySize::M}},
{2006, {"Thin Padding 01"_s, AccessorySize::M}},
{2007, {"Thin Padding 02"_s, AccessorySize::M}},
{2008, {"Thin Padding 03"_s, AccessorySize::M}},
{2009, {"Thin Padding 04"_s, AccessorySize::M}},
{2010, {"Thin Padding 05"_s, AccessorySize::M}},
{2011, {"Thick Padding 01"_s, AccessorySize::M}},
{2012, {"Thick Padding 02"_s, AccessorySize::M}},
{2013, {"Thick Padding 03"_s, AccessorySize::M}},
{2014, {"Thick Padding 04"_s, AccessorySize::M}},
{2015, {"Thick Padding 05"_s, AccessorySize::M}},
{2016, {"Thick Padding 06"_s, AccessorySize::M}},
{2017, {"Thick Padding 07"_s, AccessorySize::M}},
{2018, {"Thick Padding 08"_s, AccessorySize::M}},
{2019, {"Thick Padding 09"_s, AccessorySize::M}},
{2020, {"Thick Padding 10"_s, AccessorySize::M}},
{2021, {"CSide Padding 01"_s, AccessorySize::M}},
{2022, {"CSide Padding 02"_s, AccessorySize::M}},
{2023, {"CSide Padding 03"_s, AccessorySize::M}},
{2024, {"CSide Padding 04"_s, AccessorySize::M}},
{2025, {"CSide Padding 05"_s, AccessorySize::M}},
{2051, {"Container 01"_s, AccessorySize::L}},
{2052, {"Container 02"_s, AccessorySize::L}},
{2053, {"Container 03"_s, AccessorySize::L}},
{2054, {"Container 04"_s, AccessorySize::L}},
{2055, {"Container 05"_s, AccessorySize::L}},
{2101, {"Plating 01"_s, AccessorySize::L}},
{2102, {"Plating 02"_s, AccessorySize::L}},
{2103, {"Plating 03"_s, AccessorySize::L}},
{2104, {"Plating 04"_s, AccessorySize::L}},
{2105, {"Plating 05"_s, AccessorySize::L}},
{2151, {"Complex Base 01"_s, AccessorySize::L}},
{2152, {"Complex Base 02"_s, AccessorySize::L}},
{2153, {"Complex Base 03"_s, AccessorySize::L}},
{2154, {"Complex Base 04"_s, AccessorySize::L}},
{2155, {"Complex Base 05"_s, AccessorySize::L}},
{2156, {"Complex Base 06"_s, AccessorySize::L}},
{2157, {"Complex Base 07"_s, AccessorySize::L}},
{2158, {"Complex Base 08"_s, AccessorySize::L}},
{2159, {"Complex Base 09"_s, AccessorySize::L}},
{2160, {"Complex Base 10"_s, AccessorySize::L}},
{2201, {"Long Base 01"_s, AccessorySize::XL}},
{2202, {"Long Base 02"_s, AccessorySize::XL}},
{2203, {"Long Base 03"_s, AccessorySize::XL}},
{2204, {"Long Base 04"_s, AccessorySize::XL}},
{2205, {"Long Base 05"_s, AccessorySize::XL}},
{2251, {"Straight Wing 01"_s, AccessorySize::XL}},
{2252, {"Straight Wing 02"_s, AccessorySize::XL}},
{2253, {"Straight Wing 03"_s, AccessorySize::XL}},
{2254, {"Straight Wing 04"_s, AccessorySize::XL}},
{2255, {"Straight Wing 05"_s, AccessorySize::XL}},
{2256, {"Straight Wing 06"_s, AccessorySize::XL}},
{2257, {"Straight Wing 07"_s, AccessorySize::XL}},
{2258, {"Straight Wing 08"_s, AccessorySize::XL}},
{2259, {"Straight Wing 09"_s, AccessorySize::XL}},
{2260, {"Straight Wing 10"_s, AccessorySize::XL}},
{2301, {"Triangular Wing 01"_s, AccessorySize::XL}},
{2302, {"Triangular Wing 02"_s, AccessorySize::XL}},
{2303, {"Triangular Wing 03"_s, AccessorySize::XL}},
{2304, {"Triangular Wing 04"_s, AccessorySize::XL}},
{2305, {"Triangular Wing 05"_s, AccessorySize::XL}},
{2306, {"Triangular Wing 06"_s, AccessorySize::XL}},
{2307, {"Triangular Wing 07"_s, AccessorySize::XL}},
{2308, {"Triangular Wing 08"_s, AccessorySize::XL}},
{2309, {"Triangular Wing 09"_s, AccessorySize::XL}},
{2310, {"Triangular Wing 10"_s, AccessorySize::XL}},
{2311, {"Triangular Wing 11"_s, AccessorySize::L}},
{2312, {"Triangular Wing 12"_s, AccessorySize::L}},
{2313, {"Triangular Wing 13"_s, AccessorySize::L}},
{2314, {"Triangular Wing 14"_s, AccessorySize::L}},
{2315, {"Triangular Wing 15"_s, AccessorySize::L}},
{2351, {"Complex Wing 01"_s, AccessorySize::XL}},
{2352, {"Complex Wing 02"_s, AccessorySize::XL}},
{2353, {"Complex Wing 03"_s, AccessorySize::XL}},
{2354, {"Complex Wing 04"_s, AccessorySize::XL}},
{2355, {"Complex Wing 05"_s, AccessorySize::XL}},
{2356, {"Complex Wing 06"_s, AccessorySize::L}},
{2357, {"Complex Wing 07"_s, AccessorySize::L}},
{2358, {"Complex Wing 08"_s, AccessorySize::L}},
{2359, {"Complex Wing 09"_s, AccessorySize::L}},
{2360, {"Complex Wing 10"_s, AccessorySize::L}},
{2401, {"Blade 01"_s, AccessorySize::XL}},
{2402, {"Blade 02"_s, AccessorySize::XL}},
{2403, {"Blade 03"_s, AccessorySize::XL}},
{2404, {"Blade 04"_s, AccessorySize::XL}},
{2405, {"Blade 05"_s, AccessorySize::XL}},
{2406, {"Blade 06"_s, AccessorySize::XL}},
{2407, {"Blade 07"_s, AccessorySize::XL}},
{2408, {"Blade 08"_s, AccessorySize::XL}},
{2409, {"Blade 09"_s, AccessorySize::XL}},
{2410, {"Blade 10"_s, AccessorySize::XL}},
{2411, {"Blade 11"_s, AccessorySize::XL}},
{2412, {"Blade 12"_s, AccessorySize::XL}},
{2413, {"Blade 13"_s, AccessorySize::XL}},
{2414, {"Blade 14"_s, AccessorySize::XL}},
{2415, {"Blade 15"_s, AccessorySize::XL}},
{2426, {"Curved Blade 01"_s, AccessorySize::XL}},
{2427, {"Curved Blade 02"_s, AccessorySize::XL}},
{2428, {"Curved Blade 03"_s, AccessorySize::XL}},
{2429, {"Curved Blade 04"_s, AccessorySize::XL}},
{2430, {"Curved Blade 05"_s, AccessorySize::XL}},
{2431, {"Axe Head 01"_s, AccessorySize::XL}},
{2432, {"Axe Head 02"_s, AccessorySize::XL}},
{2433, {"Axe Head 03"_s, AccessorySize::XL}},
{2434, {"Axe Head 04"_s, AccessorySize::XL}},
{2435, {"Axe Head 05"_s, AccessorySize::XL}},
{2451, {"Horn 01"_s, AccessorySize::M}},
{2452, {"Horn 02"_s, AccessorySize::M}},
{2453, {"Horn 03"_s, AccessorySize::M}},
{2454, {"Horn 04"_s, AccessorySize::M}},
{2455, {"Horn 05"_s, AccessorySize::M}},
{2456, {"Horn 06"_s, AccessorySize::M}},
{2457, {"Horn 07"_s, AccessorySize::M}},
{2458, {"Horn 08"_s, AccessorySize::M}},
{2459, {"Horn 09"_s, AccessorySize::M}},
{2460, {"Horn 10"_s, AccessorySize::M}},
{2461, {"Horn 11"_s, AccessorySize::M}},
{2462, {"Horn 12"_s, AccessorySize::M}},
{2463, {"Horn 13"_s, AccessorySize::M}},
{2464, {"Horn 14"_s, AccessorySize::M}},
{2465, {"Horn 15"_s, AccessorySize::M}},
{2471, {"Mask"_s, AccessorySize::M}},
{2472, {"Droplet"_s, AccessorySize::M}},
{2473, {"Thigh"_s, AccessorySize::M}},
{2474, {"LegS"_s, AccessorySize::M}},
{2475, {"LegTH"_s, AccessorySize::M}},
{2476, {"Plume 01"_s, AccessorySize::M}},
{2477, {"Plume 02"_s, AccessorySize::M}},
{2478, {"Plume 03"_s, AccessorySize::M}},
{2479, {"Plume 04"_s, AccessorySize::M}},
{2480, {"Plume 05"_s, AccessorySize::M}},
{2491, {"Tail 01"_s, AccessorySize::XL}},
{2492, {"Tail 02"_s, AccessorySize::XL}},
{2493, {"Tail 03"_s, AccessorySize::XL}},
{2494, {"Tail 04"_s, AccessorySize::XL}},
{2495, {"Tail 05"_s, AccessorySize::XL}},
{2501, {"Finger 01"_s, AccessorySize::M}},
{2502, {"Finger 02"_s, AccessorySize::M}},
{2503, {"Finger 03"_s, AccessorySize::M}},
{2504, {"Finger 04"_s, AccessorySize::M}},
{2505, {"Finger 05"_s, AccessorySize::M}},
{2510, {"Clamp 01"_s, AccessorySize::M}},
{2511, {"Clamp 02"_s, AccessorySize::M}},
{2512, {"Clamp 03"_s, AccessorySize::M}},
{2513, {"Clamp 04"_s, AccessorySize::M}},
{2514, {"Clamp 05"_s, AccessorySize::M}},
{2521, {"Fabric 01"_s, AccessorySize::XL}},
{2522, {"Fabric 02"_s, AccessorySize::XL}},
{2523, {"Fabric 03"_s, AccessorySize::XL}},
{2524, {"Fabric 04"_s, AccessorySize::XL}},
{2525, {"Fabric 05"_s, AccessorySize::XL}},
{2551, {"Energy Barrel 01"_s, AccessorySize::XL}},
{2552, {"Energy Barrel 02"_s, AccessorySize::XL}},
{2553, {"Energy Barrel 03"_s, AccessorySize::XL}},
{2554, {"Energy Barrel 04"_s, AccessorySize::XL}},
{2555, {"Energy Barrel 05"_s, AccessorySize::XL}},
{2601, {"L Bullet Barrel 01"_s, AccessorySize::XL}},
{2602, {"L Bullet Barrel 02"_s, AccessorySize::XL}},
{2603, {"L Bullet Barrel 03"_s, AccessorySize::XL}},
{2604, {"L Bullet Barrel 04"_s, AccessorySize::XL}},
{2605, {"L Bullet Barrel 05"_s, AccessorySize::XL}},
{2606, {"S Bullet Barrel 01"_s, AccessorySize::XL}},
{2607, {"S Bullet Barrel 02"_s, AccessorySize::XL}},
{2608, {"S Bullet Barrel 03"_s, AccessorySize::XL}},
{2609, {"S Bullet Barrel 04"_s, AccessorySize::XL}},
{2610, {"S Bullet Barrel 05"_s, AccessorySize::XL}},
{2611, {"B Bullet Barrel 01"_s, AccessorySize::XL}},
{2612, {"B Bullet Barrel 02"_s, AccessorySize::XL}},
{2613, {"B Bullet Barrel 03"_s, AccessorySize::XL}},
{2614, {"B Bullet Barrel 04"_s, AccessorySize::XL}},
{2615, {"B Bullet Barrel 05"_s, AccessorySize::XL}},
{2616, {"B Bullet Barrel 06"_s, AccessorySize::XL}},
{2617, {"B Bullet Barrel 07"_s, AccessorySize::XL}},
{2618, {"B Bullet Barrel 08"_s, AccessorySize::XL}},
{2619, {"B Bullet Barrel 09"_s, AccessorySize::XL}},
{2620, {"B Bullet Barrel 10"_s, AccessorySize::XL}},
{2651, {"Cylinder Scope 01"_s, AccessorySize::M}},
{2652, {"Cylinder Scope 02"_s, AccessorySize::M}},
{2653, {"Cylinder Scope 03"_s, AccessorySize::M}},
{2654, {"Cylinder Scope 04"_s, AccessorySize::M}},
{2655, {"Cylinder Scope 05"_s, AccessorySize::M}},
{2656, {"Elec Scope 01"_s, AccessorySize::M}},
{2657, {"Elec Scope 02"_s, AccessorySize::M}},
{2658, {"Elec Scope 03"_s, AccessorySize::M}},
{2659, {"Elec Scope 04"_s, AccessorySize::M}},
{2660, {"Elec Scope 05"_s, AccessorySize::M}},
{2661, {"Mark Scope 01"_s, AccessorySize::S}},
{2662, {"Mark Scope 02"_s, AccessorySize::S}},
{2663, {"Mark Scope 03"_s, AccessorySize::S}},
{2664, {"Mark Scope 04"_s, AccessorySize::S}},
{2665, {"Mark Scope 05"_s, AccessorySize::S}},
{2701, {"S Single Weaponry"_s, AccessorySize::M}},
{2702, {"S Packed Weaponry 01"_s, AccessorySize::M}},
{2703, {"S Packed Weaponry 02"_s, AccessorySize::M}},
{2704, {"S Packed Weaponry 03"_s, AccessorySize::M}},
{2705, {"S Packed Weaponry 04"_s, AccessorySize::M}},
{2706, {"L Single Weaponry"_s, AccessorySize::XL}},
{2707, {"L Packed Weaponry 01"_s, AccessorySize::XL}},
{2708, {"L Packed Weaponry 02"_s, AccessorySize::XL}},
{2709, {"L Packed Weaponry 03"_s, AccessorySize::XL}},
{2710, {"L Packed Weaponry 04"_s, AccessorySize::XL}},
{2711, {"Atk Single Weaponry"_s, AccessorySize::XL}},
{2712, {"Atk Packed Weaponry 01"_s, AccessorySize::XL}},
{2713, {"Atk Packed Weaponry 02"_s, AccessorySize::XL}},
{2714, {"Atk Packed Weaponry 03"_s, AccessorySize::XL}},
{2715, {"Atk Packed Weaponry 04"_s, AccessorySize::XL}},
{2751, {"Vent 01"_s, AccessorySize::M}},
{2752, {"Vent 02"_s, AccessorySize::M}},
{2753, {"Vent 03"_s, AccessorySize::M}},
{2754, {"Vent 04"_s, AccessorySize::M}},
{2755, {"Vent 05"_s, AccessorySize::M}},
{2756, {"Vent 06"_s, AccessorySize::M}},
{2757, {"Vent 07"_s, AccessorySize::M}},
{2758, {"Vent 08"_s, AccessorySize::M}},
{2759, {"Vent 09"_s, AccessorySize::M}},
{2760, {"Vent 10"_s, AccessorySize::M}},
{2901, {"Complex Construct 01"_s, AccessorySize::L}},
{2902, {"Complex Construct 02"_s, AccessorySize::L}},
{2903, {"Complex Construct 03"_s, AccessorySize::L}},
{2904, {"Complex Construct 04"_s, AccessorySize::L}},
{2905, {"Complex Construct 05"_s, AccessorySize::L}},
{2950, {"Q Mask 01"_s, AccessorySize::M}},
{2951, {"Q Mask 02"_s, AccessorySize::M}},
{2952, {"Q Mask 03"_s, AccessorySize::M}},
{2953, {"Q Mask 04"_s, AccessorySize::M}},
{2954, {"Q Mask 05"_s, AccessorySize::M}},
// endregion
// region Connectors
{3001, {"Circular Vent 01"_s, AccessorySize::M}},
{3002, {"Circular Vent 02"_s, AccessorySize::M}},
{3003, {"Circular Vent 03"_s, AccessorySize::M}},
{3004, {"Circular Vent 04"_s, AccessorySize::M}},
{3005, {"Circular Vent 05"_s, AccessorySize::M}},
{3006, {"Circular Vent 06"_s, AccessorySize::M}},
{3007, {"Circular Vent 07"_s, AccessorySize::M}},
{3008, {"Circular Vent 08"_s, AccessorySize::M}},
{3009, {"Circular Vent 09"_s, AccessorySize::M}},
{3010, {"Circular Vent 10"_s, AccessorySize::M}},
{3011, {"Circular Vent 11"_s, AccessorySize::M}},
{3012, {"Circular Vent 12"_s, AccessorySize::M}},
{3013, {"Circular Vent 13"_s, AccessorySize::M}},
{3014, {"Circular Vent 14"_s, AccessorySize::M}},
{3015, {"Circular Vent 15"_s, AccessorySize::M}},
{3051, {"Reactor 01"_s, AccessorySize::L}},
{3052, {"Reactor 02"_s, AccessorySize::L}},
{3053, {"Reactor 03"_s, AccessorySize::L}},
{3054, {"Reactor 04"_s, AccessorySize::L}},
{3055, {"Reactor 05"_s, AccessorySize::L}},
{3101, {"Connecting Tube 01"_s, AccessorySize::XL}},
{3102, {"Connecting Tube 02"_s, AccessorySize::XL}},
{3103, {"Connecting Tube 03"_s, AccessorySize::XL}},
{3104, {"Connecting Tube 04"_s, AccessorySize::XL}},
{3105, {"Connecting Tube 05"_s, AccessorySize::XL}},
{3151, {"Latch 01"_s, AccessorySize::M}},
{3152, {"Latch 02"_s, AccessorySize::M}},
{3153, {"Latch 03"_s, AccessorySize::M}},
{3154, {"Latch 04"_s, AccessorySize::M}},
{3155, {"Latch 05"_s, AccessorySize::M}},
{3156, {"Latch 06"_s, AccessorySize::M}},
{3157, {"Latch 07"_s, AccessorySize::M}},
{3158, {"Latch 08"_s, AccessorySize::M}},
{3159, {"Latch 09"_s, AccessorySize::M}},
{3160, {"Latch 10"_s, AccessorySize::M}},
{3161, {"Latch 11"_s, AccessorySize::M}},
{3162, {"Latch 12"_s, AccessorySize::M}},
{3163, {"Latch 13"_s, AccessorySize::M}},
{3164, {"Latch 14"_s, AccessorySize::M}},
{3165, {"Latch 15"_s, AccessorySize::M}},
{3201, {"Short Connector 01"_s, AccessorySize::M}},
{3202, {"Short Connector 02"_s, AccessorySize::M}},
{3203, {"Short Connector 03"_s, AccessorySize::M}},
{3204, {"Short Connector 04"_s, AccessorySize::M}},
{3205, {"Short Connector 05"_s, AccessorySize::M}},
{3206, {"Antenna 01"_s, AccessorySize::S}},
{3207, {"Antenna 02"_s, AccessorySize::S}},
{3208, {"Antenna 03"_s, AccessorySize::S}},
{3209, {"Antenna 04"_s, AccessorySize::S}},
{3210, {"Antenna 05"_s, AccessorySize::S}},
{3226, {"Long Connector 01"_s, AccessorySize::XL}},
{3227, {"Long Connector 02"_s, AccessorySize::XL}},
{3228, {"Long Connector 03"_s, AccessorySize::XL}},
{3229, {"Long Connector 04"_s, AccessorySize::XL}},
{3230, {"Long Connector 05"_s, AccessorySize::XL}},
{3231, {"Long Connector 06"_s, AccessorySize::XL}},
{3232, {"Long Connector 07"_s, AccessorySize::XL}},
{3233, {"Long Connector 08"_s, AccessorySize::XL}},
{3234, {"Long Connector 09"_s, AccessorySize::XL}},
{3235, {"Long Connector 10"_s, AccessorySize::XL}},
{3251, {"Complex Connector 01"_s, AccessorySize::XL}},
{3252, {"Complex Connector 02"_s, AccessorySize::XL}},
{3253, {"Complex Connector 03"_s, AccessorySize::XL}},
{3254, {"Complex Connector 04"_s, AccessorySize::XL}},
{3255, {"Complex Connector 05"_s, AccessorySize::XL}},
{3301, {"Tube Line 01"_s, AccessorySize::L}},
{3302, {"Tube Line 02"_s, AccessorySize::L}},
{3303, {"Tube Line 03"_s, AccessorySize::L}},
{3304, {"Tube Line 04"_s, AccessorySize::XL}},
{3305, {"Tube Line 05"_s, AccessorySize::XL}},
{3306, {"Tube Line 06"_s, AccessorySize::M}},
{3307, {"Tube Line 07"_s, AccessorySize::M}},
{3308, {"Tube Line 08"_s, AccessorySize::M}},
{3309, {"Tube Line 09"_s, AccessorySize::L}},
{3310, {"Tube Line 10"_s, AccessorySize::L}},
{3351, {"Radar Plate 01"_s, AccessorySize::M}},
{3352, {"Radar Plate 02"_s, AccessorySize::M}},
{3353, {"Radar Plate 03"_s, AccessorySize::M}},
{3354, {"Radar Plate 04"_s, AccessorySize::M}},
{3355, {"Radar Plate 05"_s, AccessorySize::M}},
{3356, {"Radar Pod 01"_s, AccessorySize::M}},
{3357, {"Radar Pod 02"_s, AccessorySize::M}},
{3358, {"Radar Pod 03"_s, AccessorySize::M}},
{3359, {"Radar Pod 04"_s, AccessorySize::M}},
{3360, {"Radar Pod 05"_s, AccessorySize::M}},
{3401, {"Tri Pod 01"_s, AccessorySize::M}},
{3402, {"Tri Pod 02"_s, AccessorySize::M}},
{3403, {"Tri Pod 03"_s, AccessorySize::M}},
{3404, {"Tri Pod 04"_s, AccessorySize::M}},
{3405, {"Tri Pod 05"_s, AccessorySize::M}},
{3406, {"Signal Pod 01"_s, AccessorySize::M}},
{3407, {"Signal Pod 02"_s, AccessorySize::M}},
{3408, {"Signal Pod 03"_s, AccessorySize::M}},
{3409, {"Signal Pod 04"_s, AccessorySize::M}},
{3410, {"Signal Pod 05"_s, AccessorySize::M}},
// endregion
};

View file

@ -1,7 +1,7 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -16,23 +16,22 @@
// 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>
#include <map>
#include <Corrade/Containers/StringView.h>
#include <Magnum/Types.h>
using namespace Corrade;
using namespace Containers::Literals;
namespace mbst::GameData {
using namespace Magnum;
struct ArmourSet {
Containers::StringView name;
bool neck_compatible;
};
static const std::map<std::int32_t, ArmourSet> armour_sets {
static const std::map<Int, ArmourSet> armour_sets {
{-1, {"<unequipped>"_s, true}},
{0, {"Vanguard"_s, true}},
{1, {"Assault Mk.I"_s, true}},
@ -56,9 +55,4 @@ static const std::map<std::int32_t, ArmourSet> armour_sets {
{25, {"Axial Core R-Type"_s, true}},
{26, {"Axial Core S-Type"_s, false}},
{27, {"Axial Core X-Type"_s, false}},
{28, {"Zenith-X"_s, true}},
{29, {"Zenith-Y"_s, false}},
{30, {"Zenith-Z"_s, false}},
};
}

View file

@ -1,5 +1,5 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -22,7 +22,6 @@ c(Neck, "enuArmorSlots::NewEnumerator3"_s, "Neck"_s)
c(UpperBody, "enuArmorSlots::NewEnumerator4"_s, "Upper body"_s)
c(MiddleBody, "enuArmorSlots::NewEnumerator5"_s, "Middle body"_s)
c(LowerBody, "enuArmorSlots::NewEnumerator6"_s, "Lower body"_s)
c(Backpack, "enuArmorSlots::NewEnumerator23"_s, "Backpack"_s)
c(FrontWaist, "enuArmorSlots::NewEnumerator7"_s, "Front waist"_s)
c(LeftFrontSkirt, "enuArmorSlots::NewEnumerator8"_s, "Left front skirt"_s)
c(RightFrontSkirt, "enuArmorSlots::NewEnumerator9"_s, "Right front skirt"_s)
@ -39,6 +38,7 @@ c(LeftElbow, "enuArmorSlots::NewEnumerator19"_s, "Left elbow"_s)
c(RightElbow, "enuArmorSlots::NewEnumerator20"_s, "Right elbow"_s)
c(LeftLowerArm, "enuArmorSlots::NewEnumerator21"_s, "Left lower arm"_s)
c(RightLowerArm, "enuArmorSlots::NewEnumerator22"_s, "Right lower arm"_s)
c(Backpack, "enuArmorSlots::NewEnumerator23"_s, "Backpack"_s)
c(LeftHand, "enuArmorSlots::NewEnumerator24"_s, "Left hand"_s)
c(RightHand, "enuArmorSlots::NewEnumerator25"_s, "Right hand"_s)
c(LeftUpperLeg, "enuArmorSlots::NewEnumerator26"_s, "Left upper leg"_s)

View file

@ -1,5 +1,5 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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

View file

@ -1,5 +1,5 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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

View file

@ -1,5 +1,5 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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

View file

@ -1,5 +1,5 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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

61
src/Maps/LastMissionId.h Normal file
View file

@ -0,0 +1,61 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2022 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 <map>
#include <Corrade/Containers/StringView.h>
#include <Magnum/Types.h>
using namespace Corrade;
using namespace Containers::Literals;
using namespace Magnum;
static const std::map<Int, Containers::StringView> mission_id_map {{
// Story missions
{0x0064, "Mission 1 - Training"_s},
{0x0065, "Mission 2 - Patrol Operation"_s},
{0x0066, "Mission 3 - Fusion Cells in the Snow"_s},
{0x0067, "Mission 4 - Earning Changes"_s},
{0x0068, "Mission 5 - Unexpected Coordination"_s},
{0x0069, "Mission 6 - Empowering Void"_s},
{0x006A, "Mission 7 - Logisitics Obstacles"_s},
{0x006B, "Mission 8 - Wrath of the Wastelands"_s},
{0x006C, "Mission 9 - Suspicious Originator"_s},
{0x006D, "Mission 10 - Researchers Data Recovery"_s},
{0x006E, "Mission 11 - Tempestuous Sector"_s},
{0x006F, "Mission 12 - Clashes of Metal"_s},
{0x0070, "Mission 13 - The Sandstorm Glutton"_s},
{0x0071, "Mission 14 - An Icy Investigation"_s},
{0x0072, "Mission 15 - Outposts Line of Defense"_s},
{0x0073, "Mission 16 - Hidden in the Pass"_s},
{0x0074, "Mission 17 - Homebase Security"_s},
// Hunting grounds
{0x00C8, "Hunt 1 - Desert Pathway Safety"_s},
{0x00C9, "Hunt 2 - Snowfield Custodian"_s},
{0x00CA, "Hunt 3 - Abandoned Valley Raid"_s},
{0x00CB, "Hunt 4 - Depths of the Machineries"_s},
{0x00CC, "Hunt 5 - Crater Crashers"_s},
{0x00CD, "Hunt 6 - Prototype Performance Tests"_s},
// Challenges
{0x012C, "Challenge 1 - Redline Battlefront"_s},
{0x0140, "Challenge 2 - Void Convergence"_s},
{0x0190, "Challenge 3 - Gates of Ascension"_s}
}};

View file

@ -1,7 +1,7 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -19,15 +19,16 @@
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/StringView.h>
#include <Magnum/Types.h>
using namespace Corrade;
using namespace Containers::Literals;
namespace mbst::GameData {
using namespace Magnum;
struct StoryProgressPoint {
std::int32_t id{};
Containers::StringView chapter = nullptr;
Containers::StringView point = nullptr;
Int id;
Containers::StringView chapter;
Containers::StringView point;
Containers::StringView after = nullptr;
};
@ -108,28 +109,6 @@ static const Corrade::Containers::Array<StoryProgressPoint> story_progress
{0x06A5, "Chapter 3"_s, "Returned to hangar"_s, "After mission 16"_s},
{0x06A6, "Chapter 3"_s, "Got mission 17 briefing"_s, "After mission 16"_s},
{0x0708, "Chapter 3"_s, "Debriefing"_s, "After mission 17"_s},
{0x0709, "Chapter 3"_s, "Returned to hangar"_s, "After mission 17"_s},
{0x070A, "Chapter 3"_s, "Got hunt 6 briefing"_s, "After mission 17"_s},
{0x070B, "Chapter 3"_s, "Returned to hangar"_s, "After mission 17"_s},
{0x070C, "Chapter 3"_s, "Got mission 18 briefing"_s, "After mission 17"_s},
{0x076C, "Chapter 3"_s, "Debriefing"_s, "After mission 18"_s},
{0x076D, "Chapter 3"_s, "Returned to hangar"_s, "After mission 18"_s},
{0x076E, "Chapter 3"_s, "Got hunt 7 and mission 19 briefing"_s, "After mission 18"_s},
{0x07D0, "Chapter 4"_s, "Debriefing"_s, "After mission 19"_s},
{0x07D1, "Chapter 4"_s, "Returned to hangar"_s, "After mission 19"_s},
{0x07D2, "Chapter 4"_s, "Got mission 20 briefing"_s, "After mission 19"_s},
{0x0834, "Chapter 4"_s, "Debriefing"_s, "After mission 20"_s},
{0x0835, "Chapter 4"_s, "Returned to hangar"_s, "After mission 20"_s},
{0x0836, "Chapter 4"_s, "Got hunt 8 and mission 21 briefing"_s, "After mission 20"_s},
{0x0898, "Chapter 4"_s, "Debriefing"_s, "After mission 21"_s},
{0x0899, "Chapter 4"_s, "Returned to hangar"_s, "After mission 21"_s},
{0x089A, "Chapter 4"_s, "Got mission 22 briefing"_s, "After mission 21"_s},
{0x08FC, "Chapter 4"_s, "Debriefing"_s, "After mission 22"_s},
{0x08FD, "Chapter 4"_s, "Returned to hangar"_s, "After mission 22"_s},
{0x08FE, "Chapter 4"_s, "Got mission 23 briefing"_s, "After mission 22"_s},
{0x0960, "Chapter 4"_s, "Returned to hangar"_s, "After mission 23"_s},
}
};
}

View file

@ -1,7 +1,7 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -16,20 +16,53 @@
// 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>
#include <map>
#include <Corrade/Containers/StringView.h>
#include <Magnum/Magnum.h>
using namespace Corrade;
using namespace Containers::Literals;
using namespace Magnum;
namespace mbst::GameData {
extern const std::map<std::int32_t, Containers::StringView> builtin_style_names
extern const std::map<Int, Containers::StringView> style_names
#ifdef STYLENAMES_DEFINITION
{
{0, "Custom Style 1"_s},
{1, "Custom Style 2"_s},
{2, "Custom Style 3"_s},
{3, "Custom Style 4"_s},
{4, "Custom Style 5"_s},
{5, "Custom Style 6"_s},
{6, "Custom Style 7"_s},
{7, "Custom Style 8"_s},
{8, "Custom Style 9"_s},
{9, "Custom Style 10"_s},
{10, "Custom Style 11"_s},
{11, "Custom Style 12"_s},
{12, "Custom Style 13"_s},
{13, "Custom Style 14"_s},
{14, "Custom Style 15"_s},
{15, "Custom Style 16"_s},
{50, "Global Style 1"_s},
{51, "Global Style 2"_s},
{52, "Global Style 3"_s},
{53, "Global Style 4"_s},
{54, "Global Style 5"_s},
{55, "Global Style 6"_s},
{56, "Global Style 7"_s},
{57, "Global Style 8"_s},
{58, "Global Style 9"_s},
{59, "Global Style 10"_s},
{60, "Global Style 11"_s},
{61, "Global Style 12"_s},
{62, "Global Style 13"_s},
{63, "Global Style 14"_s},
{64, "Global Style 15"_s},
{65, "Global Style 16"_s},
{100, "Iron"_s},
{101, "Silver"_s},
{102, "Gold"_s},
@ -161,5 +194,3 @@ extern const std::map<std::int32_t, Containers::StringView> builtin_style_names
}
#endif
;
}

View file

@ -1,6 +1,6 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -15,20 +15,19 @@
// 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>
#include <map>
#include <Corrade/Containers/StringView.h>
#include <Magnum/Types.h>
using namespace Corrade;
using namespace Magnum;
using namespace Containers::Literals;
namespace mbst::GameData {
// region Melee
static const std::map<std::int32_t, Containers::StringView> melee_grips {
static const std::map<Int, Containers::StringView> melee_grips {
{0, "Combat Grip (1H)"_s},
{1, "Knuckle Guard Grip (1H)"_s},
{2, "Dual Guard Grip (1H)"_s},
@ -37,7 +36,6 @@ static const std::map<std::int32_t, Containers::StringView> melee_grips {
{5, "Guardian Grip (1H)"_s},
{6, "Knight Guard Grip (1H)"_s},
{7, "Saber Guard Grip (1H)"_s},
{8, "Base Grip (1H)"_s},
{100, "Combat Side Grip (1H)"_s},
{101, "Hollowed Side Grip (1H)"_s},
@ -45,63 +43,42 @@ static const std::map<std::int32_t, Containers::StringView> melee_grips {
{103, "Plated Side Grip (1H)"_s},
{104, "Locked Side Grip (1H)"_s},
{105, "Longpoint Side Grip (1H)"_s},
{106, "Concave Side Grip (1H)"_s},
{107, "Polehead Side Grip (1H)"_s},
{108, "Base 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},
{400, "Combat Twin Grip (1H)"_s},
{401, "Sepal Twin Grip (1H)"_s},
{402, "Hollowed Twin Grip (1H)"_s},
{403, "Knuckle Guard Twin Grip (1H)"_s},
{404, "Arched Twin Grip (1H)"_s},
{405, "Handguard Twin Grip (1H)"_s},
{406, "Fullguard Twin Grip (1H)"_s},
{407, "Base Twin Grip (1H)"_s},
{1000, "Combat Knuckle (R/L)"_s},
{1001, "Battle Fist (R/L)"_s},
{1002, "Guard Knuckle (R/L)"_s},
{1003, "Heavy Fist (R/L)"_s},
{1004, "Thick Fist (R/L)"_s},
{1005, "Base Fist (R/L)"_s},
{2000, "Combat Polearm (2H)"_s},
{2001, "Dual Guard Polearm (2H)"_s},
{2002, "Sepal Polearm (2H)"_s},
{2003, "Fin Polearm (2H)"_s},
{2004, "Arched Polearm (2H)"_s},
{2005, "Sharp Polearm (2H)"_s},
{2006, "Ring Polearm (2H)"_s},
{2007, "Base Polearm (2H)"_s},
{2100, "Combat Side Polearm (2H)"_s},
{2101, "Plated Side Polearm (2H)"_s},
{2102, "Locked Side Polearm (2H)"_s},
{2103, "Fin Side Polearm (2H)"_s},
{2104, "Heavy Side Polearm (2H)"_s},
{2105, "Base Side Polearm (2H)"_s},
{2200, "Combat Dual Polearm (2H)"_s},
{2201, "Studded Dual Polearm (2H)"_s},
{2202, "Circular Dual Polearm (2H)"_s},
{2400, "Combat Twin Blade (2H)"_s},
{2401, "Guard Twin Blade (2H)"_s},
{2402, "Sepal Twin Blade (2H)"_s},
{2403, "Fin Twin Blade (2H)"_s},
{2404, "Arched Twin Blade (2H)"_s},
{2405, "Holder Twin Blade (2H)"_s},
{2406, "Ring Twin Blade (2H)"_s},
{2407, "Base Twin Blade (2H)"_s},
};
static const std::map<std::int32_t, Containers::StringView> melee_assaulters {
static const std::map<Int, Containers::StringView> melee_assaulters {
{0, "Long Metal Blade"_s},
{1, "Long Assault Blade"_s},
{2, "Long Fin Blade"_s},
@ -114,7 +91,6 @@ static const std::map<std::int32_t, Containers::StringView> melee_assaulters {
{9, "Long Flat Gouger"_s},
{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},
@ -128,7 +104,6 @@ static const std::map<std::int32_t, Containers::StringView> melee_assaulters {
{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},
@ -144,7 +119,6 @@ static const std::map<std::int32_t, Containers::StringView> melee_assaulters {
{200, "Bracer"_s},
{201, "Custom Bracer"_s},
{202, "Base Hammer"_s},
{210, "Expanded Bracer"_s},
{211, "Expanded Custom Bracer"_s},
@ -152,18 +126,10 @@ static const std::map<std::int32_t, Containers::StringView> melee_assaulters {
{300, "Heavy Smasher"_s},
{301, "Heavy Basher"_s},
{302, "Heavy Torch Mace"_s},
{303, "Heavy Spike Club"_s},
{304, "Heavy Diamond Smasher"_s},
{305, "Heavy Spinning Smasher (Motion)"_s},
{306, "Base L Mace"_s},
{400, "Light Smasher"_s},
{401, "Light Basher"_s},
{402, "Light Torch Mace"_s},
{403, "Light Spike Club"_s},
{404, "Light Diamond Smasher"_s},
{405, "Light Spinning Smasher"_s},
{406, "Base S Mace"_s},
{420, "War Hammer"_s},
{421, "Great Hammer"_s},
@ -175,16 +141,13 @@ static const std::map<std::int32_t, Containers::StringView> melee_assaulters {
{500, "Combat Lance"_s},
{501, "Gouger Lance"_s},
{502, "Pointy Lance"_s},
{503, "Spinning Pointy Lance (Motion)"_s},
{504, "Crystal Lance"_s},
{510, "Piercer"_s},
{600, "Short Combat Lance"_s},
{601, "Short Pointy Lance"_s},
{602, "Short Spinning Pointy Lance (Motion)"_s},
{603, "Short Crystal Lance"_s},
{605, "Short Combat Drill (Motion)"_s},
{610, "Short Piercer"_s},
{700, "Combat Axe"_s},
@ -209,7 +172,7 @@ static const std::map<std::int32_t, Containers::StringView> melee_assaulters {
// endregion
// region Shields
static const std::map<std::int32_t, Containers::StringView> shield_handles {
static const std::map<Int, Containers::StringView> shield_handles {
{0, "Balanced Handle"_s},
{1, "Expanded Handle"_s},
{2, "Lowguard Handle"_s},
@ -228,7 +191,7 @@ static const std::map<std::int32_t, Containers::StringView> shield_handles {
{101, "Star Handle"_s},
};
static const std::map<std::int32_t, Containers::StringView> shield_shells {
static const std::map<Int, Containers::StringView> shield_shells {
{0, "Balanced Shell"_s},
{1, "Compass Shell"_s},
{2, "Uppoint Shell"_s},
@ -249,7 +212,7 @@ static const std::map<std::int32_t, Containers::StringView> shield_shells {
// endregion
// region Bullet Shooters
static const std::map<std::int32_t, Containers::StringView> bshooter_triggers {
static const std::map<Int, Containers::StringView> bshooter_triggers {
{0, "BL-Combat Trigger (1H)"_s},
{1, "Light Machine Trigger (1H)"_s},
{2, "Tactical Trigger (1H)"_s},
@ -267,7 +230,7 @@ static const std::map<std::int32_t, Containers::StringView> bshooter_triggers {
{199, "2H Base Trigger (2H)"_s},
};
static const std::map<std::int32_t, Containers::StringView> bshooter_barrels {
static const std::map<Int, Containers::StringView> bshooter_barrels {
{0, "BL-Combat Barrel (1 shot)"_s},
{1, "Shock Absorb Barrel (1 shot) (Motion)"_s},
{2, "Muzzlemod Barrel (1 shot)"_s},
@ -300,16 +263,11 @@ static const std::map<std::int32_t, Containers::StringView> bshooter_barrels {
{397, "Short D Base Barrel (Detonate)"_s},
{398, "Medium D Base Barrel (Detonate)"_s},
{399, "Long D Base Barrel (Detonate)"_s},
{400, "Heavy Burst Barrel (Pile Bunker) (Motion)"_s},
{401, "Under Guard Barrel (Pile Bunker) (Motion)"_s},
{402, "Facthold Barrel (Pile Bunker) (Motion)"_s},
{499, "Long P Base Barrel (Pile Bunker) (Motion)"_s},
};
// endregion
//region Energy Shooters
static const std::map<std::int32_t, Containers::StringView> eshooter_triggers {
static const std::map<Int, Containers::StringView> eshooter_triggers {
{0, "EN-Rifle Trigger (1H)"_s},
{1, "Underarm Trigger (1H)"_s},
{2, "EN-Inverted Trigger (1H)"_s},
@ -327,7 +285,7 @@ static const std::map<std::int32_t, Containers::StringView> eshooter_triggers {
{199, "2H Base EnTrigger (2H)"_s},
};
static const std::map<std::int32_t, Containers::StringView> eshooter_busters {
static const std::map<Int, Containers::StringView> eshooter_busters {
{0, "EN-Combat Buster (1 shot)"_s},
{1, "Delta Cycler (1 shot) (Motion)"_s},
{2, "EN-Longbarrel Buster (1 shot)"_s},
@ -359,16 +317,11 @@ static const std::map<std::int32_t, Containers::StringView> eshooter_busters {
{397, "Short W Base Buster (Wave)"_s},
{398, "Medium W Base Buster (Wave)"_s},
{399, "Long W Base Buster (Wave)"_s},
{400, "Wiredcharge Buster (Prism) (Motion)"_s},
{402, "Heavyclamp Buster (Prism) (Motion)"_s},
{402, "Curlescent Buster (Prism) (Motion)"_s},
{499, "Long P Base Buster (Prism) (Motion)"_s},
};
// endregion
// region Bullet Launchers
static const std::map<std::int32_t, Containers::StringView> blauncher_pods {
static const std::map<Int, Containers::StringView> blauncher_pods {
{0, "BL-Delta Pack Launcher (Missile x12)"_s},
{1, "BL-Twin Pack Launcher (Missile x12)"_s},
{2, "Detector Launcher (Missile x12)"_s},
@ -398,7 +351,7 @@ static const std::map<std::int32_t, Containers::StringView> blauncher_pods {
{399, "C Base Pod (Cluster x40)"_s},
};
static const std::map<std::int32_t, Containers::StringView> blauncher_projectiles {
static const std::map<Int, Containers::StringView> blauncher_projectiles {
{0, "Flathead Missile"_s},
{1, "Warhead Missile"_s},
{2, "Pointhead Missile"_s},
@ -408,7 +361,7 @@ static const std::map<std::int32_t, Containers::StringView> blauncher_projectile
// endregion
// region Energy Launchers
static const std::map<std::int32_t, Containers::StringView> elauncher_generators {
static const std::map<Int, Containers::StringView> elauncher_generators {
{0, "Fly Unit"_s},
{1, "Assault Unit (Motion)"_s},
{2, "Falcon Unit"_s},
@ -440,7 +393,7 @@ static const std::map<std::int32_t, Containers::StringView> elauncher_generators
{99, "Base Generator"},
};
static const std::map<std::int32_t, Containers::StringView> elauncher_pods {
static const std::map<Int, Containers::StringView> elauncher_pods {
{0, "EN-Dual Claw Launcher (Echo) (Motion)"_s},
{1, "EN-Assault Launcher (Echo)"_s},
{2, "EN-Tactical Launcher (Echo)"_s},
@ -478,5 +431,3 @@ static const std::map<std::int32_t, Containers::StringView> elauncher_pods {
{399, "P Base EPod (Photon)"_s},
};
// endregion
}

View file

@ -1,5 +1,5 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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

View file

@ -1,7 +1,7 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -16,8 +16,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 <variant>
#include <Corrade/Containers/StaticArray.h>
#include <Magnum/Magnum.h>
@ -26,19 +24,13 @@
using namespace Corrade;
using namespace Magnum;
typedef std::variant<Vector3, Vector3d> Vector3Variant;
namespace mbst::GameObjects {
struct Accessory {
std::int32_t attachIndex = -1;
std::int32_t id = -1;
Containers::StaticArray<2, std::int32_t> styles{ValueInit};
Vector3Variant relativePosition{Vector3{}};
Vector3Variant relativePositionOffset{Vector3{}};
Vector3Variant relativeRotation{Vector3{}};
Vector3Variant relativeRotationOffset{Vector3{}};
Vector3Variant localScale{Vector3{1.0f}};
Int attachIndex = -1;
Int id = -1;
Containers::StaticArray<2, Int> styles{ValueInit};
Vector3 relativePosition{0.0f};
Vector3 relativePositionOffset{0.0f};
Vector3 relativeRotation{0.0f};
Vector3 relativeRotationOffset{0.0f};
Vector3 localScale{1.0f};
};
}

View file

@ -1,7 +1,7 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -19,24 +19,24 @@
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/StaticArray.h>
#include <Magnum/Types.h>
#include "Decal.h"
#include "Accessory.h"
using namespace Corrade;
using namespace Magnum;
namespace mbst::GameObjects {
enum class ArmourSlot {
#define c(enumerator, enumstr, name) enumerator,
#include "../Maps/ArmourSlots.hpp"
#undef c
};
struct ArmourPart {
enum class Slot {
#define c(enumerator, enumstr, name) enumerator,
#include "../Maps/ArmourSlots.hpp"
#undef c
};
Slot slot = Slot::Face;
std::int32_t id = 0;
Containers::StaticArray<4, std::int32_t> styles{ValueInit};
ArmourSlot slot = ArmourSlot::Face;
Int id = 0;
Containers::StaticArray<4, Int> styles{ValueInit};
Containers::Array<Decal> decals;
Containers::Array<Accessory> accessories;
};
}

View file

@ -1,7 +1,7 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -16,7 +16,7 @@
// 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 <variant>
#include <Corrade/Containers/String.h>
#include <Magnum/Magnum.h>
#include <Magnum/Math/Vector3.h>
@ -24,28 +24,23 @@
using namespace Corrade;
using namespace Magnum;
typedef std::variant<Vector3, Vector3d> Vector3Variant;
namespace mbst::GameObjects {
enum class BulletLauncherAttachmentStyle {
#define c(enumerator, enumstr) enumerator,
#include "../Maps/BulletLauncherAttachmentStyles.hpp"
#undef c
};
struct BulletLauncherAttachment {
enum class Socket {
#define c(enumerator, enumstr, name) enumerator,
#include "../Maps/BulletLauncherSockets.hpp"
#undef c
};
Socket socket = Socket::Auto;
Vector3Variant relativeLocation;
Vector3Variant offsetLocation;
Vector3Variant relativeRotation;
Vector3Variant offsetRotation;
Vector3Variant relativeScale;
enum class BulletLauncherSocket {
#define c(enumerator, enumstr, name) enumerator,
#include "../Maps/BulletLauncherSockets.hpp"
#undef c
};
}
struct BulletLauncherAttachment {
BulletLauncherSocket socket = BulletLauncherSocket::Auto;
Vector3 relativeLocation;
Vector3 offsetLocation;
Vector3 relativeRotation;
Vector3 offsetRotation;
Vector3 relativeScale;
};

View file

@ -1,7 +1,7 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -25,29 +25,16 @@
using namespace Corrade;
using namespace Magnum;
namespace mbst::GameObjects {
struct CustomStyle {
Containers::String name;
Color4 colour{0.0f};
float metallic = 0.5f;
float gloss = 0.5f;
Float metallic = 0.5f;
Float gloss = 0.5f;
bool glow = false;
std::int32_t patternId = 0;
float opacity = 0.5f;
Int patternId = 0;
Float opacity = 0.5f;
Vector2 offset{0.5f};
float rotation = 0.0f;
float scale = 0.5f;
// This is only used to know which style array the current style is located in when exporting a standalone style.
enum class Type: std::uint8_t {
Unknown,
Frame,
Armour,
Weapon,
Global
} type = Type::Unknown;
Float rotation = 0.0f;
Float scale = 0.5f;
};
}

View file

@ -1,7 +1,7 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -16,31 +16,21 @@
// 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 <variant>
#include <Magnum/Magnum.h>
#include <Magnum/Math/Color.h>
#include <Magnum/Math/Vector2.h>
#include <Magnum/Math/Vector3.h>
using namespace Magnum;
typedef std::variant<Vector2, Vector2d> Vector2Variant;
typedef std::variant<Vector3, Vector3d> Vector3Variant;
namespace mbst::GameObjects {
struct Decal {
std::int32_t id = -1;
Int id = -1;
Color4 colour{0.0f};
Vector3Variant position{Vector3{}};
Vector3Variant uAxis{Vector3{}};
Vector3Variant vAxis{Vector3{}};
Vector2Variant offset{Vector2{0.5f}};
float scale = 0.5f;
float rotation = 0.0f;
Vector3 position{0.0f};
Vector3 uAxis{0.0f};
Vector3 vAxis{0.0f};
Vector2 offset{0.5f};
Float scale = 0.5f;
Float rotation = 0.0f;
bool flip = false;
bool wrap = false;
};
}

View file

@ -1,7 +1,7 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -16,17 +16,17 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
namespace mbst::GameObjects {
#include <Magnum/Types.h>
using namespace Magnum;
struct Joints {
float neck = 0.0f;
float body = 0.0f;
float shoulders = 0.0f;
float hips = 0.0f;
float upperArms = 0.0f;
float lowerArms = 0.0f;
float upperLegs = 0.0f;
float lowerLegs = 0.0f;
Float neck = 0.0f;
Float body = 0.0f;
Float shoulders = 0.0f;
Float hips = 0.0f;
Float upperArms = 0.0f;
Float lowerArms = 0.0f;
Float upperLegs = 0.0f;
Float lowerLegs = 0.0f;
};
}

View file

@ -1,5 +1,5 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -23,19 +23,17 @@
#include "PropertyNames.h"
#include "../Logger/Logger.h"
#include "../Gvas/Types/ArrayProperty.h"
#include "../Gvas/Types/BoolProperty.h"
#include "../Gvas/Types/ColourStructProperty.h"
#include "../Gvas/Types/GenericStructProperty.h"
#include "../Gvas/Types/IntProperty.h"
#include "../Gvas/Types/StringProperty.h"
#include "../UESaveFile/Types/ArrayProperty.h"
#include "../UESaveFile/Types/BoolProperty.h"
#include "../UESaveFile/Types/ColourStructProperty.h"
#include "../UESaveFile/Types/GenericStructProperty.h"
#include "../UESaveFile/Types/IntProperty.h"
#include "../UESaveFile/Types/StringProperty.h"
#include "Mass.h"
using namespace Containers::Literals;
namespace mbst::GameObjects {
Mass::Mass(Containers::StringView path) {
auto split = Utility::Path::split(path);
_folder = split.first();
@ -44,33 +42,31 @@ Mass::Mass(Containers::StringView path) {
refreshValues();
}
Containers::StringView
Mass::lastError() {
auto Mass::lastError() -> Containers::StringView {
return _lastError;
}
Containers::Optional<Containers::String>
Mass::getNameFromFile(Containers::StringView path) {
auto Mass::getNameFromFile(Containers::StringView path) -> Containers::Optional<Containers::String> {
if(!Utility::Path::exists(path)) {
LOG_ERROR_FORMAT("{} couldn't be found.", path);
return Containers::NullOpt;
}
Gvas::File mass{path};
UESaveFile mass{path};
if(!mass.valid()) {
LOG_ERROR_FORMAT("{} is invalid: {}", path, mass.lastError());
return Containers::NullOpt;
}
auto unit_data = mass.at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
auto unit_data = mass.at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, path);
return Containers::NullOpt;
}
auto name_prop = unit_data->at<Gvas::Types::StringProperty>(MASS_NAME);
auto name_prop = unit_data->at<StringProperty>(MASS_NAME);
if(!name_prop) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_NAME, path);
@ -80,8 +76,7 @@ Mass::getNameFromFile(Containers::StringView path) {
return {name_prop->value};
}
void
Mass::refreshValues() {
void Mass::refreshValues() {
LOG_INFO_FORMAT("Refreshing values for {}.", _filename);
logger().lockMutex();
@ -127,7 +122,7 @@ Mass::refreshValues() {
}
LOG_INFO("Getting the unit data.");
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
@ -135,7 +130,7 @@ Mass::refreshValues() {
}
LOG_INFO("Reading the M.A.S.S. name.");
auto name_prop = unit_data->at<Gvas::Types::StringProperty>(MASS_NAME);
auto name_prop = unit_data->at<StringProperty>(MASS_NAME);
if(!name_prop) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_NAME, _filename);
@ -222,7 +217,7 @@ Mass::refreshValues() {
}
LOG_INFO("Getting the associated account.");
auto account_prop = _mass->at<Gvas::Types::StringProperty>("Account"_s);
auto account_prop = _mass->at<StringProperty>("Account"_s);
if(!account_prop) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_ACCOUNT, _filename);
_state = State::Invalid;
@ -234,22 +229,19 @@ Mass::refreshValues() {
_state = State::Valid;
}
Containers::StringView
Mass::filename() {
auto Mass::filename() -> Containers::StringView {
return _filename;
}
Containers::StringView
Mass::name() {
auto Mass::name() -> Containers::StringView {
CORRADE_INTERNAL_ASSERT(_name);
return *_name;
}
bool
Mass::setName(Containers::StringView new_name) {
auto Mass::setName(Containers::StringView new_name) -> bool {
_name = {new_name};
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>("UnitData"_s);
auto unit_data = _mass->at<GenericStructProperty>("UnitData"_s);
if(!unit_data) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
@ -257,7 +249,7 @@ Mass::setName(Containers::StringView new_name) {
return false;
}
auto name_prop = unit_data->at<Gvas::Types::StringProperty>("Name_45_A037C5D54E53456407BDF091344529BB"_s);
auto name_prop = unit_data->at<StringProperty>("Name_45_A037C5D54E53456407BDF091344529BB"_s);
if(!name_prop) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_NAME, _filename);
@ -275,22 +267,19 @@ Mass::setName(Containers::StringView new_name) {
return true;
}
Mass::State
Mass::state() {
auto Mass::state() -> State {
return _state;
}
bool Mass::dirty() const {
auto Mass::dirty() const -> bool {
return _dirty;
}
void
Mass::setDirty(bool dirty) {
void Mass::setDirty(bool dirty) {
_dirty = dirty;
}
void
Mass::getTuning() {
void Mass::getTuning() {
getTuningCategory(MASS_ENGINE, _tuning.engineId,
MASS_GEARS, _tuning.gearIds);
if(_state == State::Invalid) {
@ -310,46 +299,38 @@ Mass::getTuning() {
}
}
std::int32_t&
Mass::engine() {
auto Mass::engine() -> Int& {
return _tuning.engineId;
}
Containers::ArrayView<std::int32_t>
Mass::gears() {
auto Mass::gears() -> Containers::ArrayView<Int> {
return _tuning.gearIds;
}
std::int32_t&
Mass::os() {
auto Mass::os() -> Int& {
return _tuning.osId;
}
Containers::ArrayView<std::int32_t>
Mass::modules() {
auto Mass::modules() -> Containers::ArrayView<Int> {
return _tuning.moduleIds;
}
std::int32_t&
Mass::architecture() {
auto Mass::architecture() -> Int& {
return _tuning.archId;
}
Containers::ArrayView<std::int32_t>
Mass::techs() {
auto Mass::techs() -> Containers::ArrayView<Int> {
return _tuning.techIds;
}
Containers::StringView
Mass::account() {
auto Mass::account() -> Containers::StringView {
return _account;
}
bool
Mass::updateAccount(Containers::StringView new_account) {
auto Mass::updateAccount(Containers::StringView new_account) -> bool {
_account = new_account;
auto account = _mass->at<Gvas::Types::StringProperty>(MASS_ACCOUNT);
auto account = _mass->at<StringProperty>(MASS_ACCOUNT);
if(!account) {
_lastError = "Couldn't find the " MASS_ACCOUNT " property."_s;
_state = State::Invalid;
@ -366,14 +347,12 @@ Mass::updateAccount(Containers::StringView new_account) {
return true;
}
void
Mass::getTuningCategory(Containers::StringView big_node_prop_name, std::int32_t& big_node_id,
Containers::StringView small_nodes_prop_name,
Containers::ArrayView<std::int32_t> small_nodes_ids)
void Mass::getTuningCategory(Containers::StringView big_node_prop_name, Int& big_node_id,
Containers::StringView small_nodes_prop_name, Containers::ArrayView<Int> small_nodes_ids)
{
LOG_INFO_FORMAT("Getting tuning data ({}, {}).", big_node_prop_name, small_nodes_prop_name);
auto node_id = _mass->at<Gvas::Types::IntProperty>(big_node_prop_name);
auto node_id = _mass->at<IntProperty>(big_node_prop_name);
if(!node_id) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", big_node_prop_name, _filename);
_state = State::Invalid;
@ -381,7 +360,7 @@ Mass::getTuningCategory(Containers::StringView big_node_prop_name, std::int32_t&
}
big_node_id = node_id->value;
auto node_ids = _mass->at<Gvas::Types::ArrayProperty>(small_nodes_prop_name);
auto node_ids = _mass->at<ArrayProperty>(small_nodes_prop_name);
if(!node_ids) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", small_nodes_prop_name, _filename);
_state = State::Invalid;
@ -395,11 +374,9 @@ Mass::getTuningCategory(Containers::StringView big_node_prop_name, std::int32_t&
return;
}
for(std::uint32_t i = 0; i < small_nodes_ids.size(); i++) {
auto small_node_id = node_ids->at<Gvas::Types::IntProperty>(i);
for(UnsignedInt i = 0; i < small_nodes_ids.size(); i++) {
auto small_node_id = node_ids->at<IntProperty>(i);
CORRADE_INTERNAL_ASSERT(small_node_id);
small_nodes_ids[i] = small_node_id->value;
}
}
}

View file

@ -1,7 +1,7 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -36,16 +36,19 @@
#include "WeaponPart.h"
#include "Weapon.h"
#include "../Gvas/File.h"
#include "../Gvas/Types/Types.h"
#include "../UESaveFile/UESaveFile.h"
using namespace Corrade;
using namespace Magnum;
namespace mbst::GameObjects {
struct ArrayProperty;
class Mass {
public:
enum class State : UnsignedByte {
Empty, Invalid, Valid
};
explicit Mass(Containers::StringView path);
Mass(const Mass&) = delete;
@ -63,106 +66,101 @@ class Mass {
auto filename() -> Containers::StringView;
auto name() -> Containers::StringView;
bool setName(Containers::StringView new_name);
enum class State: std::uint8_t {
Empty, Invalid, Valid
};
auto setName(Containers::StringView new_name) -> bool;
auto state() -> State;
bool dirty() const;
auto dirty() const -> bool;
void setDirty(bool dirty = true);
auto jointSliders() -> Joints&;
void getJointSliders();
bool writeJointSliders();
auto writeJointSliders() -> bool;
auto frameStyles() -> Containers::ArrayView<std::int32_t>;
auto frameStyles() -> Containers::ArrayView<Int>;
void getFrameStyles();
bool writeFrameStyles();
auto writeFrameStyles() -> bool;
auto eyeFlareColour() -> Color4&;
void getEyeFlareColour();
bool writeEyeFlareColour();
auto writeEyeFlareColour() -> bool;
auto frameCustomStyles() -> Containers::ArrayView<CustomStyle>;
void getFrameCustomStyles();
bool writeFrameCustomStyle(std::size_t index);
auto writeFrameCustomStyle(UnsignedLong index) -> bool;
auto armourParts() -> Containers::ArrayView<ArmourPart>;
void getArmourParts();
bool writeArmourPart(ArmourPart::Slot slot);
auto writeArmourPart(ArmourSlot slot) -> bool;
auto bulletLauncherAttachmentStyle() -> BulletLauncherAttachmentStyle&;
auto bulletLauncherAttachments() -> Containers::ArrayView<BulletLauncherAttachment>;
void getBulletLauncherAttachments();
bool writeBulletLauncherAttachments();
auto writeBulletLauncherAttachments() -> bool;
auto armourCustomStyles() -> Containers::ArrayView<CustomStyle>;
void getArmourCustomStyles();
bool writeArmourCustomStyle(std::size_t index);
auto writeArmourCustomStyle(UnsignedLong index) -> bool;
auto meleeWeapons() -> Containers::ArrayView<Weapon>;
void getMeleeWeapons();
bool writeMeleeWeapons();
auto writeMeleeWeapons() -> bool;
auto shields() -> Containers::ArrayView<Weapon>;
void getShields();
bool writeShields();
auto writeShields() -> bool;
auto bulletShooters() -> Containers::ArrayView<Weapon>;
void getBulletShooters();
bool writeBulletShooters();
auto writeBulletShooters() -> bool;
auto energyShooters() -> Containers::ArrayView<Weapon>;
void getEnergyShooters();
bool writeEnergyShooters();
auto writeEnergyShooters() -> bool;
auto bulletLaunchers() -> Containers::ArrayView<Weapon>;
void getBulletLaunchers();
bool writeBulletLaunchers();
auto writeBulletLaunchers() -> bool;
auto energyLaunchers() -> Containers::ArrayView<Weapon>;
void getEnergyLaunchers();
bool writeEnergyLaunchers();
auto writeEnergyLaunchers() -> bool;
auto globalStyles() -> Containers::ArrayView<CustomStyle>;
void getGlobalStyles();
bool writeGlobalStyle(std::size_t index);
auto writeGlobalStyle(UnsignedLong index) -> bool;
void getTuning();
auto engine() -> std::int32_t&;
auto gears() -> Containers::ArrayView<std::int32_t>;
auto engine() -> Int&;
auto gears() -> Containers::ArrayView<Int>;
auto os() -> std::int32_t&;
auto modules() -> Containers::ArrayView<std::int32_t>;
auto os() -> Int&;
auto modules() -> Containers::ArrayView<Int>;
auto architecture() -> std::int32_t&;
auto techs() -> Containers::ArrayView<std::int32_t>;
auto architecture() -> Int&;
auto techs() -> Containers::ArrayView<Int>;
auto account() -> Containers::StringView;
bool updateAccount(Containers::StringView new_account);
auto updateAccount(Containers::StringView new_account) -> bool;
private:
void getCustomStyles(Containers::ArrayView<CustomStyle> styles, Gvas::Types::ArrayProperty* style_array);
bool writeCustomStyle(const CustomStyle& style, std::size_t index, Gvas::Types::ArrayProperty* style_array);
void getCustomStyles(Containers::ArrayView<CustomStyle> styles, ArrayProperty* style_array);
auto writeCustomStyle(const CustomStyle& style, UnsignedLong index, ArrayProperty* style_array) -> bool;
void getDecals(Containers::ArrayView<Decal> decals, Gvas::Types::ArrayProperty* decal_array);
void writeDecals(Containers::ArrayView<Decal> decals, Gvas::Types::ArrayProperty* decal_array);
void getDecals(Containers::ArrayView<Decal> decals, ArrayProperty* decal_array);
void writeDecals(Containers::ArrayView<Decal> decals, ArrayProperty* decal_array);
void getAccessories(Containers::ArrayView<Accessory> accessories, Gvas::Types::ArrayProperty* accessory_array);
void writeAccessories(Containers::ArrayView<Accessory> accessories, Gvas::Types::ArrayProperty* accs_array);
void getAccessories(Containers::ArrayView<Accessory> accessories, ArrayProperty* accessory_array);
void writeAccessories(Containers::ArrayView<Accessory> accessories, ArrayProperty* accs_array);
void getWeaponType(Containers::StringView prop_name, Containers::ArrayView<Weapon> weapon_array);
bool writeWeaponType(Containers::StringView prop_name, Containers::ArrayView<Weapon> weapon_array);
auto writeWeaponType(Containers::StringView prop_name, Containers::ArrayView<Weapon> weapon_array) -> bool;
void getTuningCategory(Containers::StringView big_node_prop_name, std::int32_t& big_node_id,
Containers::StringView small_nodes_prop_name,
Containers::ArrayView<std::int32_t> small_nodes_ids);
void getTuningCategory(Containers::StringView big_node_prop_name, Int& big_node_id,
Containers::StringView small_nodes_prop_name, Containers::ArrayView<Int> small_nodes_ids);
Containers::Optional<Gvas::File> _mass;
Containers::Optional<UESaveFile> _mass;
Containers::String _lastError;
@ -176,7 +174,7 @@ class Mass {
struct {
Joints joints{};
Containers::StaticArray<4, std::int32_t> styles{ValueInit};
Containers::StaticArray<4, Int> styles{ValueInit};
Color4 eyeFlare{0.0f};
Containers::StaticArray<16, CustomStyle> customStyles;
} _frame;
@ -200,17 +198,15 @@ class Mass {
Containers::Array<CustomStyle> _globalStyles;
struct {
std::int32_t engineId;
Containers::StaticArray<7, std::int32_t> gearIds;
Int engineId;
Containers::StaticArray<7, Int> gearIds;
std::int32_t osId;
Containers::StaticArray<7, std::int32_t> moduleIds;
Int osId;
Containers::StaticArray<7, Int> moduleIds;
std::int32_t archId;
Containers::StaticArray<7, std::int32_t> techIds;
Int archId;
Containers::StaticArray<7, Int> techIds;
} _tuning;
Containers::String _account;
};
}

View file

@ -1,5 +1,5 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -18,36 +18,32 @@
#include "PropertyNames.h"
#include "../Logger/Logger.h"
#include "../Gvas/Types/ArrayProperty.h"
#include "../Gvas/Types/ByteProperty.h"
#include "../Gvas/Types/GenericStructProperty.h"
#include "../Gvas/Types/IntProperty.h"
#include "../Gvas/Types/StringProperty.h"
#include "../Gvas/Types/VectorStructProperty.h"
#include "../UESaveFile/Types/ArrayProperty.h"
#include "../UESaveFile/Types/ByteProperty.h"
#include "../UESaveFile/Types/GenericStructProperty.h"
#include "../UESaveFile/Types/IntProperty.h"
#include "../UESaveFile/Types/StringProperty.h"
#include "../UESaveFile/Types/VectorStructProperty.h"
#include "Mass.h"
using namespace Containers::Literals;
namespace mbst::GameObjects {
Containers::ArrayView<ArmourPart>
Mass::armourParts() {
auto Mass::armourParts() -> Containers::ArrayView<ArmourPart> {
return _armour.parts;
}
void
Mass::getArmourParts() {
void Mass::getArmourParts() {
LOG_INFO("Getting armour parts.");
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto armour_array = unit_data->at<Gvas::Types::ArrayProperty>(MASS_ARMOUR_PARTS);
auto armour_array = unit_data->at<ArrayProperty>(MASS_ARMOUR_PARTS);
if(!armour_array) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_ARMOUR_PARTS, _filename);
_state = State::Invalid;
@ -61,12 +57,12 @@ Mass::getArmourParts() {
return;
}
for(std::uint32_t i = 0; i < armour_array->items.size(); i++) {
auto part_prop = armour_array->at<Gvas::Types::GenericStructProperty>(i);
for(UnsignedInt i = 0; i < armour_array->items.size(); i++) {
auto part_prop = armour_array->at<GenericStructProperty>(i);
auto& part = _armour.parts[i];
auto& armour_slot = part_prop->at<Gvas::Types::ByteProperty>(MASS_ARMOUR_SLOT)->enumValue;
#define c(enumerator, strenum, name) if(armour_slot == (strenum)) { part.slot = ArmourPart::Slot::enumerator; } else
auto& armour_slot = part_prop->at<ByteProperty>(MASS_ARMOUR_SLOT)->enumValue;
#define c(enumerator, strenum, name) if(armour_slot == (strenum)) { part.slot = ArmourSlot::enumerator; } else
#include "../Maps/ArmourSlots.hpp"
#undef c
{
@ -75,9 +71,9 @@ Mass::getArmourParts() {
return;
}
part.id = part_prop->at<Gvas::Types::IntProperty>(MASS_ARMOUR_ID)->value;
part.id = part_prop->at<IntProperty>(MASS_ARMOUR_ID)->value;
auto part_styles = part_prop->at<Gvas::Types::ArrayProperty>(MASS_ARMOUR_STYLES);
auto part_styles = part_prop->at<ArrayProperty>(MASS_ARMOUR_STYLES);
if(!part_styles) {
LOG_ERROR_FORMAT("Part styles not found for part number {}.", i);
_state = State::Invalid;
@ -91,11 +87,11 @@ Mass::getArmourParts() {
return;
}
for(std::uint32_t j = 0; j < part_styles->items.size(); j++) {
part.styles[j] = part_styles->at<Gvas::Types::IntProperty>(j)->value;
for(UnsignedInt j = 0; j < part_styles->items.size(); j++) {
part.styles[j] = part_styles->at<IntProperty>(j)->value;
}
auto decals_array = part_prop->at<Gvas::Types::ArrayProperty>(MASS_ARMOUR_DECALS);
auto decals_array = part_prop->at<ArrayProperty>(MASS_ARMOUR_DECALS);
if(!decals_array) {
LOG_ERROR_FORMAT("Part decals not found for part number {}.", i);
_state = State::Invalid;
@ -106,7 +102,7 @@ Mass::getArmourParts() {
getDecals(part.decals, decals_array);
auto accs_array = part_prop->at<Gvas::Types::ArrayProperty>(MASS_ARMOUR_ACCESSORIES);
auto accs_array = part_prop->at<ArrayProperty>(MASS_ARMOUR_ACCESSORIES);
if(!accs_array) {
LOG_WARNING_FORMAT("Part accessories not found for part number {}.", i);
part.accessories = Containers::Array<Accessory>{};
@ -121,13 +117,12 @@ Mass::getArmourParts() {
}
}
bool
Mass::writeArmourPart(ArmourPart::Slot slot) {
auto Mass::writeArmourPart(ArmourSlot slot) -> bool {
LOG_INFO_FORMAT("Writing armour part in slot {}.", static_cast<int>(slot));
auto& part = *std::find_if(_armour.parts.begin(), _armour.parts.end(), [&slot](const ArmourPart& part){ return slot == part.slot; });
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_lastError = "Couldn't find the unit data in " + _filename + ".";
LOG_ERROR(_lastError);
@ -135,7 +130,7 @@ Mass::writeArmourPart(ArmourPart::Slot slot) {
return false;
}
auto armour_array = unit_data->at<Gvas::Types::ArrayProperty>(MASS_ARMOUR_PARTS);
auto armour_array = unit_data->at<ArrayProperty>(MASS_ARMOUR_PARTS);
if(!armour_array) {
_lastError = "Couldn't find the armour part array in " + _filename + ".";
LOG_ERROR(_lastError);
@ -145,18 +140,18 @@ Mass::writeArmourPart(ArmourPart::Slot slot) {
Containers::StringView slot_str = nullptr;
switch(slot) {
#define c(enumerator, strenum, name) case ArmourPart::Slot::enumerator: \
#define c(enumerator, strenum, name) case ArmourSlot::enumerator: \
slot_str = strenum; \
break;
#include "../Maps/ArmourSlots.hpp"
#undef c
}
Gvas::Types::GenericStructProperty* part_prop = nullptr;
GenericStructProperty* part_prop = nullptr;
for(std::uint32_t i = 0; i < armour_array->items.size(); i++) {
part_prop = armour_array->at<Gvas::Types::GenericStructProperty>(i);
if(slot_str == part_prop->at<Gvas::Types::ByteProperty>(MASS_ARMOUR_SLOT)->enumValue) {
for(UnsignedInt i = 0; i < armour_array->items.size(); i++) {
part_prop = armour_array->at<GenericStructProperty>(i);
if(slot_str == part_prop->at<ByteProperty>(MASS_ARMOUR_SLOT)->enumValue) {
break;
}
else {
@ -167,7 +162,7 @@ Mass::writeArmourPart(ArmourPart::Slot slot) {
if(!part_prop) {
auto prefix = "Couldn't find the armour part for slot "_s;
switch(slot) {
#define c(enumerator, strenum, name) case ArmourPart::Slot::enumerator: \
#define c(enumerator, strenum, name) case ArmourSlot::enumerator: \
_lastError = prefix + "ArmourSlot::" #enumerator "."_s; \
break;
#include "../Maps/ArmourSlots.hpp"
@ -177,18 +172,18 @@ Mass::writeArmourPart(ArmourPart::Slot slot) {
return false;
}
part_prop->at<Gvas::Types::IntProperty>(MASS_ARMOUR_ID)->value = part.id;
part_prop->at<IntProperty>(MASS_ARMOUR_ID)->value = part.id;
auto part_styles = part_prop->at<Gvas::Types::ArrayProperty>(MASS_ARMOUR_STYLES);
for(std::uint32_t i = 0; i < part.styles.size(); i++) {
part_styles->at<Gvas::Types::IntProperty>(i)->value = part.styles[i];
auto part_styles = part_prop->at<ArrayProperty>(MASS_ARMOUR_STYLES);
for(UnsignedInt i = 0; i < part.styles.size(); i++) {
part_styles->at<IntProperty>(i)->value = part.styles[i];
}
auto decals_array = part_prop->at<Gvas::Types::ArrayProperty>(MASS_ARMOUR_DECALS);
auto decals_array = part_prop->at<ArrayProperty>(MASS_ARMOUR_DECALS);
writeDecals(part.decals, decals_array);
if(!part.accessories.isEmpty()) {
auto accs_array = part_prop->at<Gvas::Types::ArrayProperty>(MASS_ARMOUR_ACCESSORIES);
if(part.accessories.size() != 0) {
auto accs_array = part_prop->at<ArrayProperty>(MASS_ARMOUR_ACCESSORIES);
writeAccessories(part.accessories, accs_array);
}
@ -200,29 +195,26 @@ Mass::writeArmourPart(ArmourPart::Slot slot) {
return true;
}
BulletLauncherAttachmentStyle&
Mass::bulletLauncherAttachmentStyle() {
auto Mass::bulletLauncherAttachmentStyle() -> BulletLauncherAttachmentStyle& {
return _armour.blAttachmentStyle;
}
Containers::ArrayView<BulletLauncherAttachment>
Mass::bulletLauncherAttachments() {
auto Mass::bulletLauncherAttachments() -> Containers::ArrayView<BulletLauncherAttachment> {
return _armour.blAttachment;
}
void
Mass::getBulletLauncherAttachments() {
void Mass::getBulletLauncherAttachments() {
LOG_INFO("Getting the bullet launcher attachment data.");
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto attach_style_prop = unit_data->at<Gvas::Types::ByteProperty>(MASS_BL_ATTACHMENT_STYLE);
auto attach_array = unit_data->at<Gvas::Types::ArrayProperty>(MASS_BL_ATTACHMENTS);
auto attach_style_prop = unit_data->at<ByteProperty>(MASS_BL_ATTACHMENT_STYLE);
auto attach_array = unit_data->at<ArrayProperty>(MASS_BL_ATTACHMENTS);
if(!attach_style_prop && !attach_array) {
LOG_WARNING_FORMAT("No bullet launcher attachment data found in {}.", _filename);
@ -240,12 +232,12 @@ Mass::getBulletLauncherAttachments() {
if(attach_array->items.size() == _weapons.bulletLaunchers.size() &&
attach_array->items.size() == _armour.blAttachment.size())
{
for(std::uint32_t i = 0; i < attach_array->items.size(); i++) {
auto attachment_prop = attach_array->at<Gvas::Types::GenericStructProperty>(i);
for(UnsignedInt i = 0; i < attach_array->items.size(); i++) {
auto attachment_prop = attach_array->at<GenericStructProperty>(i);
auto& attachment = _armour.blAttachment[i];
Containers::StringView socket = attachment_prop->at<Gvas::Types::StringProperty>(MASS_BL_ATTACHMENT_SOCKET)->value;
#define c(enumerator, strenum, name) if(socket == (strenum)) { attachment.socket = BulletLauncherAttachment::Socket::enumerator; } else
Containers::StringView socket = attachment_prop->at<StringProperty>(MASS_BL_ATTACHMENT_SOCKET)->value;
#define c(enumerator, strenum, name) if(socket == (strenum)) { attachment.socket = BulletLauncherSocket::enumerator; } else
#include "../Maps/BulletLauncherSockets.hpp"
#undef c
{
@ -254,16 +246,16 @@ Mass::getBulletLauncherAttachments() {
return;
}
auto rel_loc_prop = attachment_prop->at<Gvas::Types::VectorStructProperty>(MASS_BL_ATTACHMENT_RELLOC);
attachment.relativeLocation = rel_loc_prop->vector;
auto off_loc_prop = attachment_prop->at<Gvas::Types::VectorStructProperty>(MASS_BL_ATTACHMENT_OFFLOC);
attachment.offsetLocation = off_loc_prop->vector;
auto rel_rot_prop = attachment_prop->at<Gvas::Types::VectorStructProperty>(MASS_BL_ATTACHMENT_RELROT);
attachment.relativeRotation = rel_rot_prop->vector;
auto off_rot_prop = attachment_prop->at<Gvas::Types::VectorStructProperty>(MASS_BL_ATTACHMENT_OFFROT);
attachment.offsetRotation = off_rot_prop->vector;
auto rel_scale_prop = attachment_prop->at<Gvas::Types::VectorStructProperty>(MASS_BL_ATTACHMENT_RELSCALE);
attachment.relativeScale = rel_scale_prop->vector;
auto rel_loc_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_RELLOC);
attachment.relativeLocation = Vector3{rel_loc_prop->x, rel_loc_prop->y, rel_loc_prop->z};
auto off_loc_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_OFFLOC);
attachment.offsetLocation = Vector3{off_loc_prop->x, off_loc_prop->y, off_loc_prop->z};
auto rel_rot_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_RELROT);
attachment.relativeRotation = Vector3{rel_rot_prop->x, rel_rot_prop->y, rel_rot_prop->z};
auto off_rot_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_OFFROT);
attachment.offsetRotation = Vector3{off_rot_prop->x, off_rot_prop->y, off_rot_prop->z};
auto rel_scale_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_RELSCALE);
attachment.relativeScale = Vector3{rel_scale_prop->x, rel_scale_prop->y, rel_scale_prop->z};
}
}
@ -282,11 +274,10 @@ Mass::getBulletLauncherAttachments() {
}
}
bool
Mass::writeBulletLauncherAttachments() {
auto Mass::writeBulletLauncherAttachments() -> bool {
LOG_INFO("Writing bullet launcher attachments.");
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_lastError = "No unit data in " + _filename;
LOG_ERROR(_lastError);
@ -294,8 +285,8 @@ Mass::writeBulletLauncherAttachments() {
return false;
}
auto attach_style_prop = unit_data->at<Gvas::Types::ByteProperty>(MASS_BL_ATTACHMENT_STYLE);
auto attach_array = unit_data->at<Gvas::Types::ArrayProperty>(MASS_BL_ATTACHMENTS);
auto attach_style_prop = unit_data->at<ByteProperty>(MASS_BL_ATTACHMENT_STYLE);
auto attach_array = unit_data->at<ArrayProperty>(MASS_BL_ATTACHMENTS);
if(!attach_style_prop && !attach_array) {
_lastError = "No attachment properties to write to in " + _filename;
@ -315,13 +306,13 @@ Mass::writeBulletLauncherAttachments() {
if(attach_array->items.size() == _weapons.bulletLaunchers.size() &&
attach_array->items.size() == _armour.blAttachment.size())
{
for(std::uint32_t i = 0; i < attach_array->items.size(); i++) {
auto attachment_prop = attach_array->at<Gvas::Types::GenericStructProperty>(i);
for(UnsignedInt i = 0; i < attach_array->items.size(); i++) {
auto attachment_prop = attach_array->at<GenericStructProperty>(i);
auto& attachment = _armour.blAttachment[i];
auto& socket = attachment_prop->at<Gvas::Types::StringProperty>(MASS_BL_ATTACHMENT_SOCKET)->value;
auto& socket = attachment_prop->at<StringProperty>(MASS_BL_ATTACHMENT_SOCKET)->value;
switch(attachment.socket) {
#define c(enumerator, strenum, name) case BulletLauncherAttachment::Socket::enumerator: socket = strenum; break;
#define c(enumerator, strenum, name) case BulletLauncherSocket::enumerator: socket = strenum; break;
#include "../Maps/BulletLauncherSockets.hpp"
#undef c
default:
@ -330,25 +321,35 @@ Mass::writeBulletLauncherAttachments() {
return false;
}
auto rel_loc_prop = attachment_prop->at<Gvas::Types::VectorStructProperty>(MASS_BL_ATTACHMENT_RELLOC);
rel_loc_prop->vector = attachment.relativeLocation;
auto off_loc_prop = attachment_prop->at<Gvas::Types::VectorStructProperty>(MASS_BL_ATTACHMENT_OFFLOC);
off_loc_prop->vector = attachment.offsetLocation;
auto rel_rot_prop = attachment_prop->at<Gvas::Types::VectorStructProperty>(MASS_BL_ATTACHMENT_RELROT);
rel_rot_prop->vector = attachment.relativeRotation;
auto off_rot_prop = attachment_prop->at<Gvas::Types::VectorStructProperty>(MASS_BL_ATTACHMENT_OFFROT);
off_rot_prop->vector = attachment.offsetRotation;
auto rel_scale_prop = attachment_prop->at<Gvas::Types::VectorStructProperty>(MASS_BL_ATTACHMENT_RELSCALE);
rel_scale_prop->vector = attachment.relativeScale;
auto rel_loc_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_RELLOC);
rel_loc_prop->x = attachment.relativeLocation.x();
rel_loc_prop->y = attachment.relativeLocation.y();
rel_loc_prop->z = attachment.relativeLocation.z();
auto off_loc_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_OFFLOC);
off_loc_prop->x = attachment.offsetLocation.x();
off_loc_prop->y = attachment.offsetLocation.y();
off_loc_prop->z = attachment.offsetLocation.z();
auto rel_rot_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_RELROT);
rel_rot_prop->x = attachment.relativeRotation.x();
rel_rot_prop->y = attachment.relativeRotation.y();
rel_rot_prop->z = attachment.relativeRotation.z();
auto off_rot_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_OFFROT);
off_rot_prop->x = attachment.offsetRotation.x();
off_rot_prop->y = attachment.offsetRotation.y();
off_rot_prop->z = attachment.offsetRotation.z();
auto rel_scale_prop = attachment_prop->at<VectorStructProperty>(MASS_BL_ATTACHMENT_RELSCALE);
rel_scale_prop->x = attachment.relativeScale.x();
rel_scale_prop->y = attachment.relativeScale.y();
rel_scale_prop->z = attachment.relativeScale.z();
}
}
if(!attach_style_prop) {
attach_style_prop = new Gvas::Types::ByteProperty;
attach_style_prop = new ByteProperty;
attach_style_prop->name.emplace(MASS_BL_ATTACHMENT_STYLE);
attach_style_prop->enumType = "enuBLAttachmentStyle"_s;
Gvas::Types::ByteProperty::ptr prop{attach_style_prop};
arrayAppend(unit_data->properties, Utility::move(prop));
ByteProperty::ptr prop{attach_style_prop};
arrayAppend(unit_data->properties, std::move(prop));
}
auto& attach_style = attach_style_prop->enumValue;
@ -372,23 +373,21 @@ Mass::writeBulletLauncherAttachments() {
return true;
}
Containers::ArrayView<CustomStyle>
Mass::armourCustomStyles() {
auto Mass::armourCustomStyles() -> Containers::ArrayView<CustomStyle> {
return _armour.customStyles;
}
void
Mass::getArmourCustomStyles() {
void Mass::getArmourCustomStyles() {
LOG_INFO("Getting the custom armour styles.");
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto armour_styles = unit_data->at<Gvas::Types::ArrayProperty>(MASS_CUSTOM_ARMOUR_STYLES);
auto armour_styles = unit_data->at<ArrayProperty>(MASS_CUSTOM_ARMOUR_STYLES);
if(!armour_styles) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_CUSTOM_ARMOUR_STYLES, _filename);
_state = State::Invalid;
@ -403,14 +402,9 @@ Mass::getArmourCustomStyles() {
}
getCustomStyles(_armour.customStyles, armour_styles);
for(auto& style : _armour.customStyles) {
style.type = CustomStyle::Type::Armour;
}
}
bool
Mass::writeArmourCustomStyle(std::size_t index) {
auto Mass::writeArmourCustomStyle(UnsignedLong index) -> bool {
LOG_INFO_FORMAT("Writing custom armour style {}.", index);
if(index > _armour.customStyles.size()) {
@ -419,7 +413,7 @@ Mass::writeArmourCustomStyle(std::size_t index) {
return false;
}
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_lastError = "Couldn't find unit data in "_s + _filename;
LOG_ERROR(_lastError);
@ -427,7 +421,7 @@ Mass::writeArmourCustomStyle(std::size_t index) {
return false;
}
auto armour_styles = unit_data->at<Gvas::Types::ArrayProperty>(MASS_CUSTOM_ARMOUR_STYLES);
auto armour_styles = unit_data->at<ArrayProperty>(MASS_CUSTOM_ARMOUR_STYLES);
if(!armour_styles) {
_lastError = "Couldn't find armour custom styles in "_s + _filename;
LOG_ERROR(_lastError);
@ -437,5 +431,3 @@ Mass::writeArmourCustomStyle(std::size_t index) {
return writeCustomStyle(_armour.customStyles[index], index, armour_styles);
}
}

View file

@ -0,0 +1,148 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2022 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 "PropertyNames.h"
#include "../UESaveFile/Types/ArrayProperty.h"
#include "../UESaveFile/Types/BoolProperty.h"
#include "../UESaveFile/Types/ColourStructProperty.h"
#include "../UESaveFile/Types/FloatProperty.h"
#include "../UESaveFile/Types/GenericStructProperty.h"
#include "../UESaveFile/Types/RotatorStructProperty.h"
#include "../UESaveFile/Types/VectorStructProperty.h"
#include "../UESaveFile/Types/Vector2DStructProperty.h"
#include "../UESaveFile/Types/IntProperty.h"
#include "Mass.h"
using namespace Containers::Literals;
void Mass::getDecals(Containers::ArrayView<Decal> decals, ArrayProperty* decal_array) {
for(UnsignedInt i = 0; i < decal_array->items.size(); i++) {
auto decal_prop = decal_array->at<GenericStructProperty>(i);
CORRADE_INTERNAL_ASSERT(decal_prop);
auto& decal = decals[i];
decal.id = decal_prop->at<IntProperty>(MASS_DECAL_ID)->value;
auto colour_prop = decal_prop->at<ColourStructProperty>(MASS_DECAL_COLOUR);
decal.colour = Color4{colour_prop->r, colour_prop->g, colour_prop->b, colour_prop->a};
auto pos_prop = decal_prop->at<VectorStructProperty>(MASS_DECAL_POSITION);
decal.position = Vector3{pos_prop->x, pos_prop->y, pos_prop->z};
auto u_prop = decal_prop->at<VectorStructProperty>(MASS_DECAL_UAXIS);
decal.uAxis = Vector3{u_prop->x, u_prop->y, u_prop->z};
auto v_prop = decal_prop->at<VectorStructProperty>(MASS_DECAL_VAXIS);
decal.vAxis = Vector3{v_prop->x, v_prop->y, v_prop->z};
auto offset_prop = decal_prop->at<Vector2DStructProperty>(MASS_DECAL_OFFSET);
decal.offset = Vector2{offset_prop->x, offset_prop->y};
decal.scale = decal_prop->at<FloatProperty>(MASS_DECAL_SCALE)->value;
decal.rotation = decal_prop->at<FloatProperty>(MASS_DECAL_ROTATION)->value;
decal.flip = decal_prop->at<BoolProperty>(MASS_DECAL_FLIP)->value;
decal.wrap = decal_prop->at<BoolProperty>(MASS_DECAL_WRAP)->value;
}
}
void Mass::writeDecals(Containers::ArrayView<Decal> decals, ArrayProperty* decal_array) {
for(UnsignedInt i = 0; i < decal_array->items.size(); i++) {
auto decal_prop = decal_array->at<GenericStructProperty>(i);
CORRADE_INTERNAL_ASSERT(decal_prop);
auto& decal = decals[i];
decal_prop->at<IntProperty>(MASS_DECAL_ID)->value = decal.id;
auto colour_prop = decal_prop->at<ColourStructProperty>(MASS_DECAL_COLOUR);
colour_prop->r = decal.colour.r();
colour_prop->g = decal.colour.g();
colour_prop->b = decal.colour.b();
colour_prop->a = decal.colour.a();
auto pos_prop = decal_prop->at<VectorStructProperty>(MASS_DECAL_POSITION);
pos_prop->x = decal.position.x();
pos_prop->y = decal.position.y();
pos_prop->z = decal.position.z();
auto u_prop = decal_prop->at<VectorStructProperty>(MASS_DECAL_UAXIS);
u_prop->x = decal.uAxis.x();
u_prop->y = decal.uAxis.y();
u_prop->z = decal.uAxis.z();
auto v_prop = decal_prop->at<VectorStructProperty>(MASS_DECAL_VAXIS);
v_prop->x = decal.vAxis.x();
v_prop->y = decal.vAxis.y();
v_prop->z = decal.vAxis.z();
auto offset_prop = decal_prop->at<Vector2DStructProperty>(MASS_DECAL_OFFSET);
offset_prop->x = decal.offset.x();
offset_prop->y = decal.offset.y();
decal_prop->at<FloatProperty>(MASS_DECAL_SCALE)->value = decal.scale;
decal_prop->at<FloatProperty>(MASS_DECAL_ROTATION)->value = decal.rotation;
decal_prop->at<BoolProperty>(MASS_DECAL_FLIP)->value = decal.flip;
decal_prop->at<BoolProperty>(MASS_DECAL_WRAP)->value = decal.wrap;
}
}
void Mass::getAccessories(Containers::ArrayView<Accessory> accessories, ArrayProperty* accessory_array) {
for(UnsignedInt i = 0; i < accessory_array->items.size(); i++) {
auto acc_prop = accessory_array->at<GenericStructProperty>(i);
CORRADE_INTERNAL_ASSERT(acc_prop);
auto& accessory = accessories[i];
accessory.attachIndex = acc_prop->at<IntProperty>(MASS_ACCESSORY_ATTACH_INDEX)->value;
accessory.id = acc_prop->at<IntProperty>(MASS_ACCESSORY_ID)->value;
auto acc_styles = acc_prop->at<ArrayProperty>(MASS_ACCESSORY_STYLES);
for(UnsignedInt j = 0; j < acc_styles->items.size(); j++) {
accessory.styles[j] = acc_styles->at<IntProperty>(j)->value;
}
auto rel_pos_prop = acc_prop->at<VectorStructProperty>(MASS_ACCESSORY_RELPOS);
accessory.relativePosition = Vector3{rel_pos_prop->x, rel_pos_prop->y, rel_pos_prop->z};
auto rel_pos_offset_prop = acc_prop->at<VectorStructProperty>(MASS_ACCESSORY_OFFPOS);
accessory.relativePositionOffset = Vector3{rel_pos_offset_prop->x, rel_pos_offset_prop->y, rel_pos_offset_prop->z};
auto rel_rot_prop = acc_prop->at<RotatorStructProperty>(MASS_ACCESSORY_RELROT);
accessory.relativeRotation = Vector3{rel_rot_prop->x, rel_rot_prop->y, rel_rot_prop->z};
auto rel_rot_offset_prop = acc_prop->at<RotatorStructProperty>(MASS_ACCESSORY_OFFROT);
accessory.relativeRotationOffset = Vector3{rel_rot_offset_prop->x, rel_rot_offset_prop->y, rel_rot_offset_prop->z};
auto local_scale_prop = acc_prop->at<VectorStructProperty>(MASS_ACCESSORY_SCALE);
accessory.localScale = Vector3{local_scale_prop->x, local_scale_prop->y, local_scale_prop->z};
}
}
void Mass::writeAccessories(Containers::ArrayView<Accessory> accessories, ArrayProperty* accs_array) {
for(UnsignedInt i = 0; i < accs_array->items.size(); i++) {
auto acc_prop = accs_array->at<GenericStructProperty>(i);
CORRADE_INTERNAL_ASSERT(acc_prop);
auto& accessory = accessories[i];
acc_prop->at<IntProperty>(MASS_ACCESSORY_ATTACH_INDEX)->value = accessory.attachIndex;
acc_prop->at<IntProperty>(MASS_ACCESSORY_ID)->value = accessory.id;
auto acc_styles = acc_prop->at<ArrayProperty>(MASS_ACCESSORY_STYLES);
for(UnsignedInt j = 0; j < acc_styles->items.size(); j++) {
acc_styles->at<IntProperty>(j)->value = accessory.styles[j];
}
auto rel_pos_prop = acc_prop->at<VectorStructProperty>(MASS_ACCESSORY_RELPOS);
rel_pos_prop->x = accessory.relativePosition.x();
rel_pos_prop->y = accessory.relativePosition.y();
rel_pos_prop->z = accessory.relativePosition.z();
auto rel_pos_offset_prop = acc_prop->at<VectorStructProperty>(MASS_ACCESSORY_OFFPOS);
rel_pos_offset_prop->x = accessory.relativePositionOffset.x();
rel_pos_offset_prop->y = accessory.relativePositionOffset.y();
rel_pos_offset_prop->z = accessory.relativePositionOffset.z();
auto rel_rot_prop = acc_prop->at<RotatorStructProperty>(MASS_ACCESSORY_RELROT);
rel_rot_prop->x = accessory.relativeRotation.x();
rel_rot_prop->y = accessory.relativeRotation.y();
rel_rot_prop->z = accessory.relativeRotation.z();
auto rel_rot_offset_prop = acc_prop->at<RotatorStructProperty>(MASS_ACCESSORY_OFFROT);
rel_rot_offset_prop->x = accessory.relativeRotationOffset.x();
rel_rot_offset_prop->y = accessory.relativeRotationOffset.y();
rel_rot_offset_prop->z = accessory.relativeRotationOffset.z();
auto local_scale_prop = acc_prop->at<VectorStructProperty>(MASS_ACCESSORY_SCALE);
local_scale_prop->x = accessory.localScale.x();
local_scale_prop->y = accessory.localScale.y();
local_scale_prop->z = accessory.localScale.z();
}
}

View file

@ -1,5 +1,5 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -16,64 +16,59 @@
#include "PropertyNames.h"
#include "../Logger/Logger.h"
#include "../Gvas/Types/ArrayProperty.h"
#include "../Gvas/Types/ColourStructProperty.h"
#include "../Gvas/Types/FloatProperty.h"
#include "../Gvas/Types/GenericStructProperty.h"
#include "../Gvas/Types/IntProperty.h"
#include "../UESaveFile/Types/ArrayProperty.h"
#include "../UESaveFile/Types/ColourStructProperty.h"
#include "../UESaveFile/Types/FloatProperty.h"
#include "../UESaveFile/Types/GenericStructProperty.h"
#include "../UESaveFile/Types/IntProperty.h"
#include "Mass.h"
using namespace Containers::Literals;
namespace mbst::GameObjects {
Joints&
Mass::jointSliders() {
auto Mass::jointSliders() -> Joints& {
return _frame.joints;
}
void
Mass::getJointSliders() {
void Mass::getJointSliders() {
LOG_INFO("Getting joint sliders.");
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto frame_prop = unit_data->at<Gvas::Types::GenericStructProperty>(MASS_FRAME);
auto frame_prop = unit_data->at<GenericStructProperty>(MASS_FRAME);
if(!frame_prop) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_FRAME, _filename);
_state = State::Invalid;
return;
}
auto length = frame_prop->at<Gvas::Types::FloatProperty>(MASS_JOINT_NECK);
auto length = frame_prop->at<FloatProperty>(MASS_JOINT_NECK);
_frame.joints.neck = (length ? length->value : 0.0f);
length = frame_prop->at<Gvas::Types::FloatProperty>(MASS_JOINT_BODY);
length = frame_prop->at<FloatProperty>(MASS_JOINT_BODY);
_frame.joints.body = (length ? length->value : 0.0f);
length = frame_prop->at<Gvas::Types::FloatProperty>(MASS_JOINT_SHOULDER);
length = frame_prop->at<FloatProperty>(MASS_JOINT_SHOULDER);
_frame.joints.shoulders = (length ? length->value : 0.0f);
length = frame_prop->at<Gvas::Types::FloatProperty>(MASS_JOINT_HIP);
length = frame_prop->at<FloatProperty>(MASS_JOINT_HIP);
_frame.joints.hips = (length ? length->value : 0.0f);
length = frame_prop->at<Gvas::Types::FloatProperty>(MASS_JOINT_ARM_UPPER);
length = frame_prop->at<FloatProperty>(MASS_JOINT_ARM_UPPER);
_frame.joints.upperArms = (length ? length->value : 0.0f);
length = frame_prop->at<Gvas::Types::FloatProperty>(MASS_JOINT_ARM_LOWER);
length = frame_prop->at<FloatProperty>(MASS_JOINT_ARM_LOWER);
_frame.joints.lowerArms = (length ? length->value : 0.0f);
length = frame_prop->at<Gvas::Types::FloatProperty>(MASS_JOINT_LEG_UPPER);
length = frame_prop->at<FloatProperty>(MASS_JOINT_LEG_UPPER);
_frame.joints.upperLegs = (length ? length->value : 0.0f);
length = frame_prop->at<Gvas::Types::FloatProperty>(MASS_JOINT_LEG_LOWER);
length = frame_prop->at<FloatProperty>(MASS_JOINT_LEG_LOWER);
_frame.joints.lowerLegs = (length ? length->value : 0.0f);
}
bool
Mass::writeJointSliders() {
auto Mass::writeJointSliders() -> bool {
LOG_INFO("Writing joint sliders");
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_lastError = "No unit data in "_s + _filename;
LOG_ERROR(_lastError);
@ -81,7 +76,7 @@ Mass::writeJointSliders() {
return false;
}
auto frame_prop = unit_data->at<Gvas::Types::GenericStructProperty>(MASS_FRAME);
auto frame_prop = unit_data->at<GenericStructProperty>(MASS_FRAME);
if(!frame_prop) {
_lastError = "No frame data in "_s + _filename;
@ -90,93 +85,93 @@ Mass::writeJointSliders() {
return false;
}
Containers::Array<Gvas::Types::UnrealPropertyBase::ptr> temp;
Containers::Array<UnrealPropertyBase::ptr> temp;
auto length = frame_prop->atMove<Gvas::Types::FloatProperty>(MASS_JOINT_NECK);
auto length = frame_prop->atMove<FloatProperty>(MASS_JOINT_NECK);
if(_frame.joints.neck != 0.0f) {
if(!length) {
length.emplace();
length->name.emplace(MASS_JOINT_NECK);
}
length->value = _frame.joints.neck;
arrayAppend(temp, Utility::move(length));
arrayAppend(temp, std::move(length));
}
length = frame_prop->atMove<Gvas::Types::FloatProperty>(MASS_JOINT_BODY);
length = frame_prop->atMove<FloatProperty>(MASS_JOINT_BODY);
if(_frame.joints.body != 0.0f) {
if(!length) {
length.emplace();
length->name.emplace(MASS_JOINT_BODY);
}
length->value = _frame.joints.body;
arrayAppend(temp, Utility::move(length));
arrayAppend(temp, std::move(length));
}
length = frame_prop->atMove<Gvas::Types::FloatProperty>(MASS_JOINT_SHOULDER);
length = frame_prop->atMove<FloatProperty>(MASS_JOINT_SHOULDER);
if(_frame.joints.shoulders != 0.0f) {
if(!length) {
length.emplace();
length->name.emplace(MASS_JOINT_SHOULDER);
}
length->value = _frame.joints.shoulders;
arrayAppend(temp, Utility::move(length));
arrayAppend(temp, std::move(length));
}
length = frame_prop->atMove<Gvas::Types::FloatProperty>(MASS_JOINT_ARM_UPPER);
length = frame_prop->atMove<FloatProperty>(MASS_JOINT_ARM_UPPER);
if(_frame.joints.upperArms != 0.0f) {
if(!length) {
length.emplace();
length->name.emplace(MASS_JOINT_ARM_UPPER);
}
length->value = _frame.joints.upperArms;
arrayAppend(temp, Utility::move(length));
arrayAppend(temp, std::move(length));
}
length = frame_prop->atMove<Gvas::Types::FloatProperty>(MASS_JOINT_ARM_LOWER);
length = frame_prop->atMove<FloatProperty>(MASS_JOINT_ARM_LOWER);
if(_frame.joints.lowerArms != 0.0f) {
if(!length) {
length.emplace();
length->name.emplace(MASS_JOINT_ARM_LOWER);
}
length->value = _frame.joints.lowerArms;
arrayAppend(temp, Utility::move(length));
arrayAppend(temp, std::move(length));
}
length = frame_prop->atMove<Gvas::Types::FloatProperty>(MASS_JOINT_HIP);
length = frame_prop->atMove<FloatProperty>(MASS_JOINT_HIP);
if(_frame.joints.hips != 0.0f) {
if(!length) {
length.emplace();
length->name.emplace(MASS_JOINT_HIP);
}
length->value = _frame.joints.hips;
arrayAppend(temp, Utility::move(length));
arrayAppend(temp, std::move(length));
}
length = frame_prop->atMove<Gvas::Types::FloatProperty>(MASS_JOINT_LEG_UPPER);
length = frame_prop->atMove<FloatProperty>(MASS_JOINT_LEG_UPPER);
if(_frame.joints.upperLegs != 0.0f) {
if(!length) {
length.emplace();
length->name.emplace(MASS_JOINT_LEG_UPPER);
}
length->value = _frame.joints.upperLegs;
arrayAppend(temp, Utility::move(length));
arrayAppend(temp, std::move(length));
}
length = frame_prop->atMove<Gvas::Types::FloatProperty>(MASS_JOINT_LEG_LOWER);
length = frame_prop->atMove<FloatProperty>(MASS_JOINT_LEG_LOWER);
if(_frame.joints.lowerLegs != 0.0f) {
if(!length) {
length.emplace();
length->name.emplace(MASS_JOINT_LEG_LOWER);
}
length->value = _frame.joints.lowerLegs;
arrayAppend(temp, Utility::move(length));
arrayAppend(temp, std::move(length));
}
arrayAppend(temp, Utility::move(frame_prop->properties[frame_prop->properties.size() - 3]));
arrayAppend(temp, Utility::move(frame_prop->properties[frame_prop->properties.size() - 2]));
arrayAppend(temp, Utility::move(frame_prop->properties[frame_prop->properties.size() - 1]));
arrayAppend(temp, std::move(frame_prop->properties[frame_prop->properties.size() - 3]));
arrayAppend(temp, std::move(frame_prop->properties[frame_prop->properties.size() - 2]));
arrayAppend(temp, std::move(frame_prop->properties[frame_prop->properties.size() - 1]));
frame_prop->properties = Utility::move(temp);
frame_prop->properties = std::move(temp);
if(!_mass->saveToFile()) {
_lastError = _mass->lastError();
@ -186,30 +181,28 @@ Mass::writeJointSliders() {
return true;
}
Containers::ArrayView<std::int32_t>
Mass::frameStyles() {
auto Mass::frameStyles() -> Containers::ArrayView<Int> {
return _frame.styles;
}
void
Mass::getFrameStyles() {
void Mass::getFrameStyles() {
LOG_INFO("Getting frame styles.");
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto frame_prop = unit_data->at<Gvas::Types::GenericStructProperty>(MASS_FRAME);
auto frame_prop = unit_data->at<GenericStructProperty>(MASS_FRAME);
if(!frame_prop) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_FRAME, _filename);
_state = State::Invalid;
return;
}
auto frame_styles = frame_prop->at<Gvas::Types::ArrayProperty>(MASS_FRAME_STYLES);
auto frame_styles = frame_prop->at<ArrayProperty>(MASS_FRAME_STYLES);
if(!frame_styles) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_FRAME_STYLES, _filename);
_state = State::Invalid;
@ -223,16 +216,15 @@ Mass::getFrameStyles() {
return;
}
for(std::uint32_t i = 0; i < frame_styles->items.size(); i++) {
_frame.styles[i] = frame_styles->at<Gvas::Types::IntProperty>(i)->value;
for(UnsignedInt i = 0; i < frame_styles->items.size(); i++) {
_frame.styles[i] = frame_styles->at<IntProperty>(i)->value;
}
}
bool
Mass::writeFrameStyles() {
auto Mass::writeFrameStyles() -> bool {
LOG_INFO("Writing frame styles.");
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_lastError = "No unit data in "_s + _filename;
LOG_ERROR(_lastError);
@ -240,7 +232,7 @@ Mass::writeFrameStyles() {
return false;
}
auto frame = unit_data->at<Gvas::Types::GenericStructProperty>(MASS_FRAME);
auto frame = unit_data->at<GenericStructProperty>(MASS_FRAME);
if(!frame) {
_lastError = "No frame data in "_s + _filename;
LOG_ERROR(_lastError);
@ -248,7 +240,7 @@ Mass::writeFrameStyles() {
return false;
}
auto frame_styles = frame->at<Gvas::Types::ArrayProperty>(MASS_FRAME_STYLES);
auto frame_styles = frame->at<ArrayProperty>(MASS_FRAME_STYLES);
if(!frame_styles) {
_lastError = "No frame styles in "_s + _filename;
LOG_ERROR(_lastError);
@ -256,8 +248,8 @@ Mass::writeFrameStyles() {
return false;
}
for(std::uint32_t i = 0; i < frame_styles->items.size(); i++) {
frame_styles->at<Gvas::Types::IntProperty>(i)->value = _frame.styles[i];
for(UnsignedInt i = 0; i < frame_styles->items.size(); i++) {
frame_styles->at<IntProperty>(i)->value = _frame.styles[i];
}
if(!_mass->saveToFile()) {
@ -268,30 +260,28 @@ Mass::writeFrameStyles() {
return true;
}
Color4&
Mass::eyeFlareColour() {
auto Mass::eyeFlareColour() -> Color4& {
return _frame.eyeFlare;
}
void
Mass::getEyeFlareColour() {
void Mass::getEyeFlareColour() {
LOG_INFO("Getting the eye flare colour.");
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto frame_prop = unit_data->at<Gvas::Types::GenericStructProperty>(MASS_FRAME);
auto frame_prop = unit_data->at<GenericStructProperty>(MASS_FRAME);
if(!frame_prop) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_FRAME, _filename);
_state = State::Invalid;
return;
}
auto eye_flare_prop = frame_prop->at<Gvas::Types::ColourStructProperty>(MASS_EYE_FLARE);
auto eye_flare_prop = frame_prop->at<ColourStructProperty>(MASS_EYE_FLARE);
if(!eye_flare_prop) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_EYE_FLARE, _filename);
_state = State::Invalid;
@ -301,11 +291,10 @@ Mass::getEyeFlareColour() {
_frame.eyeFlare = Color4{eye_flare_prop->r, eye_flare_prop->g, eye_flare_prop->b, eye_flare_prop->a};
}
bool
Mass::writeEyeFlareColour() {
auto Mass::writeEyeFlareColour() -> bool {
LOG_INFO("Writing the eye flare colour.");
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_lastError = "No unit data in "_s + _filename;
LOG_ERROR(_lastError);
@ -313,7 +302,7 @@ Mass::writeEyeFlareColour() {
return false;
}
auto frame = unit_data->at<Gvas::Types::GenericStructProperty>(MASS_FRAME);
auto frame = unit_data->at<GenericStructProperty>(MASS_FRAME);
if(!frame) {
_lastError = "No frame data in "_s + _filename;
LOG_ERROR(_lastError);
@ -321,7 +310,7 @@ Mass::writeEyeFlareColour() {
return false;
}
auto eye_flare_prop = frame->at<Gvas::Types::ColourStructProperty>(MASS_EYE_FLARE);
auto eye_flare_prop = frame->at<ColourStructProperty>(MASS_EYE_FLARE);
if(!eye_flare_prop) {
_lastError = "No eye flare property in "_s + _filename;
LOG_ERROR(_lastError);
@ -342,23 +331,21 @@ Mass::writeEyeFlareColour() {
return true;
}
Containers::ArrayView<CustomStyle>
Mass::frameCustomStyles() {
auto Mass::frameCustomStyles() -> Containers::ArrayView<CustomStyle> {
return _frame.customStyles;
}
void
Mass::getFrameCustomStyles() {
void Mass::getFrameCustomStyles() {
LOG_INFO("Getting the frame's custom styles.");
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto frame_styles = unit_data->at<Gvas::Types::ArrayProperty>(MASS_CUSTOM_FRAME_STYLES);
auto frame_styles = unit_data->at<ArrayProperty>(MASS_CUSTOM_FRAME_STYLES);
if(!frame_styles) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_CUSTOM_FRAME_STYLES, _filename);
_state = State::Invalid;
@ -373,14 +360,9 @@ Mass::getFrameCustomStyles() {
}
getCustomStyles(_frame.customStyles, frame_styles);
for(auto& style : _frame.customStyles) {
style.type = CustomStyle::Type::Frame;
}
}
bool
Mass::writeFrameCustomStyle(std::size_t index) {
auto Mass::writeFrameCustomStyle(UnsignedLong index) -> bool {
LOG_INFO_FORMAT("Writing frame custom style number {}.", index);
if(index > _frame.customStyles.size()) {
@ -389,7 +371,7 @@ Mass::writeFrameCustomStyle(std::size_t index) {
return false;
}
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_lastError = "No unit data in "_s + _filename;
LOG_ERROR(_lastError);
@ -397,7 +379,7 @@ Mass::writeFrameCustomStyle(std::size_t index) {
return false;
}
auto frame_styles = unit_data->at<Gvas::Types::ArrayProperty>(MASS_CUSTOM_FRAME_STYLES);
auto frame_styles = unit_data->at<ArrayProperty>(MASS_CUSTOM_FRAME_STYLES);
if(!frame_styles) {
_lastError = "No frame styles in "_s + _filename;
LOG_ERROR(_lastError);
@ -407,5 +389,3 @@ Mass::writeFrameCustomStyle(std::size_t index) {
return writeCustomStyle(_frame.customStyles[index], index, frame_styles);
}
}

145
src/Mass/Mass_Styles.cpp Normal file
View file

@ -0,0 +1,145 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2022 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 "PropertyNames.h"
#include "../Logger/Logger.h"
#include "../UESaveFile/Types/ArrayProperty.h"
#include "../UESaveFile/Types/ColourStructProperty.h"
#include "../UESaveFile/Types/FloatProperty.h"
#include "../UESaveFile/Types/GenericStructProperty.h"
#include "../UESaveFile/Types/IntProperty.h"
#include "../UESaveFile/Types/StringProperty.h"
#include "Mass.h"
using namespace Containers::Literals;
auto Mass::globalStyles() -> Containers::ArrayView<CustomStyle> {
return _globalStyles;
}
void Mass::getGlobalStyles() {
LOG_INFO("Getting global styles.");
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto global_styles = unit_data->at<ArrayProperty>(MASS_GLOBAL_STYLES);
if(!global_styles) {
LOG_WARNING_FORMAT("Couldn't find global styles in {}.", _filename);
_globalStyles = Containers::Array<CustomStyle>{0};
return;
}
if(global_styles->items.size() != _globalStyles.size()) {
_globalStyles = Containers::Array<CustomStyle>{global_styles->items.size()};
}
getCustomStyles(_globalStyles, global_styles);
}
auto Mass::writeGlobalStyle(UnsignedLong index) -> bool {
LOG_INFO_FORMAT("Writing global style number {}.", index);
if(index > _globalStyles.size()) {
_lastError = "Global style index out of range"_s;
LOG_ERROR(_lastError);
return false;
}
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_lastError = "No unit data found in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
auto global_styles = unit_data->at<ArrayProperty>(MASS_GLOBAL_STYLES);
if(!global_styles) {
_lastError = "No global styles found in "_s + _filename;
LOG_ERROR(_lastError);
_state = State::Invalid;
return false;
}
return writeCustomStyle(_globalStyles[index], index, global_styles);
}
void Mass::getCustomStyles(Containers::ArrayView<CustomStyle> styles, ArrayProperty* style_array) {
for(UnsignedInt i = 0; i < style_array->items.size(); i++) {
auto style_prop = style_array->at<GenericStructProperty>(i);
auto& style = styles[i];
style.name = style_prop->at<StringProperty>(MASS_STYLE_NAME)->value;
auto colour_prop = style_prop->at<ColourStructProperty>(MASS_STYLE_COLOUR);
style.colour = Color4{colour_prop->r, colour_prop->g, colour_prop->b, colour_prop->a};
style.metallic = style_prop->at<FloatProperty>(MASS_STYLE_METALLIC)->value;
style.gloss = style_prop->at<FloatProperty>(MASS_STYLE_GLOSS)->value;
style.glow = colour_prop->a != 0.0f;
style.patternId = style_prop->at<IntProperty>(MASS_STYLE_PATTERN_ID)->value;
style.opacity = style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_OPACITY)->value;
style.offset = Vector2{
style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_OFFSETX)->value,
style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_OFFSETY)->value
};
style.rotation = style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_ROTATION)->value;
style.scale = style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_SCALE)->value;
}
}
auto Mass::writeCustomStyle(const CustomStyle& style, UnsignedLong index, ArrayProperty* style_array) -> bool {
if(!style_array) {
_lastError = "style_array is null."_s;
LOG_ERROR(_lastError);
return false;
}
auto style_prop = style_array->at<GenericStructProperty>(index);
if(!style_prop) {
_lastError = "Style index is out of range in "_s + _filename;
LOG_ERROR(_lastError);
return false;
}
style_prop->at<StringProperty>(MASS_STYLE_NAME)->value = style.name;
auto colour_prop = style_prop->at<ColourStructProperty>(MASS_STYLE_COLOUR);
colour_prop->r = style.colour.r();
colour_prop->g = style.colour.g();
colour_prop->b = style.colour.b();
colour_prop->a = style.glow ? 1.0f : 0.0f;
style_prop->at<FloatProperty>(MASS_STYLE_METALLIC)->value = style.metallic;
style_prop->at<FloatProperty>(MASS_STYLE_GLOSS)->value = style.gloss;
style_prop->at<IntProperty>(MASS_STYLE_PATTERN_ID)->value = style.patternId;
style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_OPACITY)->value = style.opacity;
style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_OFFSETX)->value = style.offset.x();
style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_OFFSETY)->value = style.offset.y();
style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_ROTATION)->value = style.rotation;
style_prop->at<FloatProperty>(MASS_STYLE_PATTERN_SCALE)->value = style.scale;
if(!_mass->saveToFile()) {
_lastError = _mass->lastError();
return false;
}
return true;
}

View file

@ -1,5 +1,5 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -16,132 +16,111 @@
#include "PropertyNames.h"
#include "../Logger/Logger.h"
#include "../Gvas/Types/ArrayProperty.h"
#include "../Gvas/Types/BoolProperty.h"
#include "../Gvas/Types/ByteProperty.h"
#include "../Gvas/Types/ColourStructProperty.h"
#include "../Gvas/Types/GenericStructProperty.h"
#include "../Gvas/Types/IntProperty.h"
#include "../Gvas/Types/StringProperty.h"
#include "../UESaveFile/Types/ArrayProperty.h"
#include "../UESaveFile/Types/BoolProperty.h"
#include "../UESaveFile/Types/ByteProperty.h"
#include "../UESaveFile/Types/ColourStructProperty.h"
#include "../UESaveFile/Types/GenericStructProperty.h"
#include "../UESaveFile/Types/IntProperty.h"
#include "../UESaveFile/Types/StringProperty.h"
#include "Mass.h"
using namespace Containers::Literals;
namespace mbst::GameObjects {
Containers::ArrayView<Weapon>
Mass::meleeWeapons() {
auto Mass::meleeWeapons() -> Containers::ArrayView<Weapon> {
return _weapons.melee;
}
void
Mass::getMeleeWeapons() {
void Mass::getMeleeWeapons() {
LOG_INFO("Getting melee weapons.");
getWeaponType(MASS_WEAPONS_MELEE, _weapons.melee);
}
bool
Mass::writeMeleeWeapons() {
auto Mass::writeMeleeWeapons() -> bool {
LOG_INFO("Writing melee weapons.");
return writeWeaponType(MASS_WEAPONS_MELEE, _weapons.melee);
}
Containers::ArrayView<Weapon>
Mass::shields() {
auto Mass::shields() -> Containers::ArrayView<Weapon> {
return _weapons.shields;
}
void
Mass::getShields() {
void Mass::getShields() {
LOG_INFO("Getting shields.");
getWeaponType(MASS_WEAPONS_SHIELD, _weapons.shields);
}
bool
Mass::writeShields() {
auto Mass::writeShields() -> bool {
LOG_INFO("Writing shields.");
return writeWeaponType(MASS_WEAPONS_SHIELD, _weapons.shields);
}
Containers::ArrayView<Weapon>
Mass::bulletShooters() {
auto Mass::bulletShooters() -> Containers::ArrayView<Weapon> {
return _weapons.bulletShooters;
}
void
Mass::getBulletShooters() {
void Mass::getBulletShooters() {
LOG_INFO("Getting bullet shooters.");
getWeaponType(MASS_WEAPONS_BSHOOTER, _weapons.bulletShooters);
}
bool
Mass::writeBulletShooters() {
auto Mass::writeBulletShooters() -> bool {
LOG_INFO("Writing bullet shooters.");
return writeWeaponType(MASS_WEAPONS_BSHOOTER, _weapons.bulletShooters);
}
Containers::ArrayView<Weapon>
Mass::energyShooters() {
auto Mass::energyShooters() -> Containers::ArrayView<Weapon> {
return _weapons.energyShooters;
}
void
Mass::getEnergyShooters() {
void Mass::getEnergyShooters() {
LOG_INFO("Getting energy shooters.");
getWeaponType(MASS_WEAPONS_ESHOOTER, _weapons.energyShooters);
}
bool
Mass::writeEnergyShooters() {
auto Mass::writeEnergyShooters() -> bool {
LOG_INFO("Writing energy shooters.");
return writeWeaponType(MASS_WEAPONS_ESHOOTER, _weapons.energyShooters);
}
Containers::ArrayView<Weapon>
Mass::bulletLaunchers() {
auto Mass::bulletLaunchers() -> Containers::ArrayView<Weapon> {
return _weapons.bulletLaunchers;
}
void
Mass::getBulletLaunchers() {
void Mass::getBulletLaunchers() {
LOG_INFO("Getting bullet launchers.");
getWeaponType(MASS_WEAPONS_BLAUNCHER, _weapons.bulletLaunchers);
}
bool
Mass::writeBulletLaunchers() {
auto Mass::writeBulletLaunchers() -> bool {
LOG_INFO("Writing bullet launchers.");
return writeWeaponType(MASS_WEAPONS_BLAUNCHER, _weapons.bulletLaunchers);
}
Containers::ArrayView<Weapon>
Mass::energyLaunchers() {
auto Mass::energyLaunchers() -> Containers::ArrayView<Weapon> {
return _weapons.energyLaunchers;
}
void
Mass::getEnergyLaunchers() {
void Mass::getEnergyLaunchers() {
LOG_INFO("Getting energy launchers.");
getWeaponType(MASS_WEAPONS_ELAUNCHER, _weapons.energyLaunchers);
}
bool
Mass::writeEnergyLaunchers() {
auto Mass::writeEnergyLaunchers() -> bool {
LOG_INFO("Writing energy launchers.");
return writeWeaponType(MASS_WEAPONS_ELAUNCHER, _weapons.energyLaunchers);
}
void
Mass::getWeaponType(Containers::StringView prop_name, Containers::ArrayView<Weapon> weapon_array) {
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
void Mass::getWeaponType(Containers::StringView prop_name, Containers::ArrayView<Weapon> weapon_array) {
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", MASS_UNIT_DATA, _filename);
_state = State::Invalid;
return;
}
auto prop = unit_data->at<Gvas::Types::ArrayProperty>(prop_name);
auto prop = unit_data->at<ArrayProperty>(prop_name);
if(!prop) {
LOG_ERROR_FORMAT("Couldn't find {} in {}.", prop_name, _filename);
_state = State::Invalid;
@ -155,13 +134,13 @@ Mass::getWeaponType(Containers::StringView prop_name, Containers::ArrayView<Weap
return;
}
for(std::uint32_t i = 0; i < weapon_array.size(); i++) {
auto weapon_prop = prop->at<Gvas::Types::GenericStructProperty>(i);
for(UnsignedInt i = 0; i < weapon_array.size(); i++) {
auto weapon_prop = prop->at<GenericStructProperty>(i);
auto& weapon = weapon_array[i];
weapon.name = weapon_prop->at<Gvas::Types::StringProperty>(MASS_WEAPON_NAME)->value;
auto& weapon_type = weapon_prop->at<Gvas::Types::ByteProperty>(MASS_WEAPON_TYPE)->enumValue;
#define c(enumerator, strenum, name) if(weapon_type == (strenum)) { weapon.type = Weapon::Type::enumerator; } else
weapon.name = weapon_prop->at<StringProperty>(MASS_WEAPON_NAME)->value;
auto& weapon_type = weapon_prop->at<ByteProperty>(MASS_WEAPON_TYPE)->enumValue;
#define c(enumerator, strenum, name) if(weapon_type == (strenum)) { weapon.type = WeaponType::enumerator; } else
#include "../Maps/WeaponTypes.hpp"
#undef c
{
@ -170,28 +149,28 @@ Mass::getWeaponType(Containers::StringView prop_name, Containers::ArrayView<Weap
return;
}
auto parts_prop = weapon_prop->at<Gvas::Types::ArrayProperty>(MASS_WEAPON_ELEMENT);
auto parts_prop = weapon_prop->at<ArrayProperty>(MASS_WEAPON_ELEMENT);
weapon.parts = Containers::Array<WeaponPart>{ValueInit, parts_prop->items.size()};
for(std::uint32_t j = 0; j < parts_prop->items.size(); j++) {
auto part_prop = parts_prop->at<Gvas::Types::GenericStructProperty>(j);
for(UnsignedInt j = 0; j < parts_prop->items.size(); j++) {
auto part_prop = parts_prop->at<GenericStructProperty>(j);
auto& part = weapon.parts[j];
part.id = part_prop->at<Gvas::Types::IntProperty>(MASS_WEAPON_PART_ID)->value;
part.id = part_prop->at<IntProperty>(MASS_WEAPON_PART_ID)->value;
auto part_styles = part_prop->at<Gvas::Types::ArrayProperty>(MASS_WEAPON_PART_STYLES);
for(std::uint32_t k = 0; k < part_styles->items.size(); k++) {
part.styles[k] = part_styles->at<Gvas::Types::IntProperty>(k)->value;
auto part_styles = part_prop->at<ArrayProperty>(MASS_WEAPON_PART_STYLES);
for(UnsignedInt k = 0; k < part_styles->items.size(); k++) {
part.styles[k] = part_styles->at<IntProperty>(k)->value;
}
auto part_decals = part_prop->at<Gvas::Types::ArrayProperty>(MASS_WEAPON_PART_DECALS);
auto part_decals = part_prop->at<ArrayProperty>(MASS_WEAPON_PART_DECALS);
if(part_decals->items.size() != part.decals.size()) {
part.decals = Containers::Array<Decal>{part_decals->items.size()};
}
getDecals(part.decals, part_decals);
auto part_accs = part_prop->at<Gvas::Types::ArrayProperty>(MASS_WEAPON_PART_ACCESSORIES);
auto part_accs = part_prop->at<ArrayProperty>(MASS_WEAPON_PART_ACCESSORIES);
if(!part_accs) {
part.accessories = Containers::Array<Accessory>{0};
continue;
@ -203,7 +182,7 @@ Mass::getWeaponType(Containers::StringView prop_name, Containers::ArrayView<Weap
getAccessories(part.accessories, part_accs);
}
auto custom_styles = weapon_prop->at<Gvas::Types::ArrayProperty>(MASS_CUSTOM_WEAPON_STYLES);
auto custom_styles = weapon_prop->at<ArrayProperty>(MASS_CUSTOM_WEAPON_STYLES);
if(!custom_styles) {
LOG_ERROR_FORMAT("Can't find weapon custom styles in {}", _filename);
_state = State::Invalid;
@ -219,13 +198,9 @@ Mass::getWeaponType(Containers::StringView prop_name, Containers::ArrayView<Weap
getCustomStyles(weapon.customStyles, custom_styles);
for(auto& style : weapon.customStyles) {
style.type = CustomStyle::Type::Weapon;
}
weapon.attached = weapon_prop->at<Gvas::Types::BoolProperty>(MASS_WEAPON_ATTACH)->value;
auto& damage_type = weapon_prop->at<Gvas::Types::ByteProperty>(MASS_WEAPON_DAMAGE_TYPE)->enumValue;
#define c(enumerator, strenum) if(damage_type == (strenum)) { weapon.damageType = Weapon::DamageType::enumerator; } else
weapon.attached = weapon_prop->at<BoolProperty>(MASS_WEAPON_ATTACH)->value;
auto& damage_type = weapon_prop->at<ByteProperty>(MASS_WEAPON_DAMAGE_TYPE)->enumValue;
#define c(enumerator, strenum) if(damage_type == (strenum)) { weapon.damageType = DamageType::enumerator; } else
#include "../Maps/DamageTypes.hpp"
#undef c
{
@ -233,9 +208,9 @@ Mass::getWeaponType(Containers::StringView prop_name, Containers::ArrayView<Weap
_state = State::Invalid;
return;
}
weapon.dualWield = weapon_prop->at<Gvas::Types::BoolProperty>(MASS_WEAPON_DUAL_WIELD)->value;
auto& effect_colour_mode = weapon_prop->at<Gvas::Types::ByteProperty>(MASS_WEAPON_COLOUR_EFX_MODE)->enumValue;
#define c(enumerator, strenum) if(effect_colour_mode == (strenum)) { weapon.effectColourMode = Weapon::EffectColourMode::enumerator; } else
weapon.dualWield = weapon_prop->at<BoolProperty>(MASS_WEAPON_DUAL_WIELD)->value;
auto& effect_colour_mode = weapon_prop->at<ByteProperty>(MASS_WEAPON_COLOUR_EFX_MODE)->enumValue;
#define c(enumerator, strenum) if(effect_colour_mode == (strenum)) { weapon.effectColourMode = EffectColourMode::enumerator; } else
#include "../Maps/EffectColourModes.hpp"
#undef c
{
@ -243,14 +218,13 @@ Mass::getWeaponType(Containers::StringView prop_name, Containers::ArrayView<Weap
_state = State::Invalid;
return;
}
auto effect_colour = weapon_prop->at<Gvas::Types::ColourStructProperty>(MASS_WEAPON_COLOUR_EFX);
auto effect_colour = weapon_prop->at<ColourStructProperty>(MASS_WEAPON_COLOUR_EFX);
weapon.effectColour = Color4{effect_colour->r, effect_colour->g, effect_colour->b, effect_colour->a};
}
}
bool
Mass::writeWeaponType(Containers::StringView prop_name, Containers::ArrayView<Weapon> weapon_array) {
auto unit_data = _mass->at<Gvas::Types::GenericStructProperty>(MASS_UNIT_DATA);
auto Mass::writeWeaponType(Containers::StringView prop_name, Containers::ArrayView<Weapon> weapon_array) -> bool {
auto unit_data = _mass->at<GenericStructProperty>(MASS_UNIT_DATA);
if(!unit_data) {
_lastError = "No unit data in "_s + _filename;
LOG_ERROR(_lastError);
@ -258,7 +232,7 @@ Mass::writeWeaponType(Containers::StringView prop_name, Containers::ArrayView<We
return false;
}
auto prop = unit_data->at<Gvas::Types::ArrayProperty>(prop_name);
auto prop = unit_data->at<ArrayProperty>(prop_name);
if(!prop) {
_lastError = prop_name + " not found in "_s + _filename;
LOG_ERROR(_lastError);
@ -274,13 +248,13 @@ Mass::writeWeaponType(Containers::StringView prop_name, Containers::ArrayView<We
return false;
}
for(std::uint32_t i = 0; i < weapon_array.size(); i++) {
auto weapon_prop = prop->at<Gvas::Types::GenericStructProperty>(i);
for(UnsignedInt i = 0; i < weapon_array.size(); i++) {
auto weapon_prop = prop->at<GenericStructProperty>(i);
auto& weapon = weapon_array[i];
weapon_prop->at<Gvas::Types::StringProperty>(MASS_WEAPON_NAME)->value = weapon.name;
weapon_prop->at<StringProperty>(MASS_WEAPON_NAME)->value = weapon.name;
switch(weapon.type) {
#define c(enumerator, strenum, name) case Weapon::Type::enumerator: weapon_prop->at<Gvas::Types::ByteProperty>(MASS_WEAPON_TYPE)->enumValue = strenum; break;
#define c(enumerator, strenum, name) case WeaponType::enumerator: weapon_prop->at<ByteProperty>(MASS_WEAPON_TYPE)->enumValue = strenum; break;
#include "../Maps/WeaponTypes.hpp"
#undef c
default:
@ -289,7 +263,7 @@ Mass::writeWeaponType(Containers::StringView prop_name, Containers::ArrayView<We
return false;
}
auto parts_prop = weapon_prop->at<Gvas::Types::ArrayProperty>(MASS_WEAPON_ELEMENT);
auto parts_prop = weapon_prop->at<ArrayProperty>(MASS_WEAPON_ELEMENT);
if(parts_prop->items.size() != weapon.parts.size()) {
_lastError = Utility::format("Weapon part arrays are not of the same size. Expected {}, got {} instead.",
weapon.parts.size(), parts_prop->items.size());
@ -298,21 +272,21 @@ Mass::writeWeaponType(Containers::StringView prop_name, Containers::ArrayView<We
return false;
}
for(std::uint32_t j = 0; j < parts_prop->items.size(); j++) {
auto part_prop = parts_prop->at<Gvas::Types::GenericStructProperty>(j);
for(UnsignedInt j = 0; j < parts_prop->items.size(); j++) {
auto part_prop = parts_prop->at<GenericStructProperty>(j);
auto& part = weapon.parts[j];
part_prop->at<Gvas::Types::IntProperty>(MASS_WEAPON_PART_ID)->value = part.id;
part_prop->at<IntProperty>(MASS_WEAPON_PART_ID)->value = part.id;
auto part_styles = part_prop->at<Gvas::Types::ArrayProperty>(MASS_WEAPON_PART_STYLES);
for(std::uint32_t k = 0; k < part_styles->items.size(); k++) {
part_styles->at<Gvas::Types::IntProperty>(k)->value = part.styles[k];
auto part_styles = part_prop->at<ArrayProperty>(MASS_WEAPON_PART_STYLES);
for(UnsignedInt k = 0; k < part_styles->items.size(); k++) {
part_styles->at<IntProperty>(k)->value = part.styles[k];
}
auto part_decals = part_prop->at<Gvas::Types::ArrayProperty>(MASS_WEAPON_PART_DECALS);
auto part_decals = part_prop->at<ArrayProperty>(MASS_WEAPON_PART_DECALS);
writeDecals(part.decals, part_decals);
auto part_accs = part_prop->at<Gvas::Types::ArrayProperty>(MASS_WEAPON_PART_ACCESSORIES);
auto part_accs = part_prop->at<ArrayProperty>(MASS_WEAPON_PART_ACCESSORIES);
if(!part_accs) {
continue;
}
@ -328,7 +302,7 @@ Mass::writeWeaponType(Containers::StringView prop_name, Containers::ArrayView<We
writeAccessories(part.accessories, part_accs);
}
auto custom_styles = weapon_prop->at<Gvas::Types::ArrayProperty>(MASS_CUSTOM_WEAPON_STYLES);
auto custom_styles = weapon_prop->at<ArrayProperty>(MASS_CUSTOM_WEAPON_STYLES);
if(!custom_styles) {
_lastError = "No custom styles found for weapon."_s;
LOG_ERROR(_lastError);
@ -344,13 +318,13 @@ Mass::writeWeaponType(Containers::StringView prop_name, Containers::ArrayView<We
return false;
}
for(std::uint32_t j = 0; j < weapon.customStyles.size(); j++) {
for(UnsignedInt j = 0; j < weapon.customStyles.size(); j++) {
writeCustomStyle(weapon.customStyles[j], j, custom_styles);
}
weapon_prop->at<Gvas::Types::BoolProperty>(MASS_WEAPON_ATTACH)->value = weapon.attached;
weapon_prop->at<BoolProperty>(MASS_WEAPON_ATTACH)->value = weapon.attached;
switch(weapon.damageType) {
#define c(enumerator, strenum) case Weapon::DamageType::enumerator: weapon_prop->at<Gvas::Types::ByteProperty>(MASS_WEAPON_DAMAGE_TYPE)->enumValue = strenum; break;
#define c(enumerator, strenum) case DamageType::enumerator: weapon_prop->at<ByteProperty>(MASS_WEAPON_DAMAGE_TYPE)->enumValue = strenum; break;
#include "../Maps/DamageTypes.hpp"
#undef c
default:
@ -358,10 +332,10 @@ Mass::writeWeaponType(Containers::StringView prop_name, Containers::ArrayView<We
LOG_ERROR(_lastError);
return false;
}
weapon_prop->at<Gvas::Types::BoolProperty>(MASS_WEAPON_DUAL_WIELD)->value = weapon.dualWield;
weapon_prop->at<BoolProperty>(MASS_WEAPON_DUAL_WIELD)->value = weapon.dualWield;
switch(weapon.effectColourMode) {
#define c(enumerator, enumstr) case Weapon::EffectColourMode::enumerator: \
weapon_prop->at<Gvas::Types::ByteProperty>(MASS_WEAPON_COLOUR_EFX_MODE)->enumValue = enumstr; \
#define c(enumerator, enumstr) case EffectColourMode::enumerator: \
weapon_prop->at<ByteProperty>(MASS_WEAPON_COLOUR_EFX_MODE)->enumValue = enumstr; \
break;
#include "../Maps/EffectColourModes.hpp"
#undef c
@ -370,7 +344,7 @@ Mass::writeWeaponType(Containers::StringView prop_name, Containers::ArrayView<We
LOG_ERROR(_lastError);
return false;
}
auto effect_colour = weapon_prop->at<Gvas::Types::ColourStructProperty>(MASS_WEAPON_COLOUR_EFX);
auto effect_colour = weapon_prop->at<ColourStructProperty>(MASS_WEAPON_COLOUR_EFX);
effect_colour->r = weapon.effectColour.r();
effect_colour->g = weapon.effectColour.g();
effect_colour->b = weapon.effectColour.b();
@ -384,5 +358,3 @@ Mass::writeWeaponType(Containers::StringView prop_name, Containers::ArrayView<We
return true;
}
}

View file

@ -1,32 +1,5 @@
#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/>.
// Profile stuff
#define PROFILE_NAME "CompanyName"
#define PROFILE_ACTIVE_FRAME_SLOT "ActiveFrameSlot"
#define PROFILE_CREDITS "Credit"
#define PROFILE_STORY_PROGRESS "StoryProgress"
#define PROFILE_LAST_MISSION_ID "LastMissionID"
#define PROFILE_MATERIAL "ResourceMaterial"
#define PROFILE_QUARK_DATA "ResourceQuarkData"
#define PROFILE_ACCOUNT "Account"
// Basic MASS stuff
#define MASS_UNIT_DATA "UnitData"
#define MASS_NAME "Name_45_A037C5D54E53456407BDF091344529BB"
#define MASS_ACCOUNT "Account"

View file

@ -1,5 +1,5 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -16,13 +16,11 @@
#include "Weapon.h"
namespace mbst::GameObjects {
Weapon::Weapon(const Weapon& other) {
name = other.name;
type = other.type;
parts = Containers::Array<WeaponPart>{other.parts.size()};
for(std::uint32_t i = 0; i < parts.size(); i++) {
for(UnsignedInt i = 0; i < parts.size(); i++) {
parts[i] = other.parts[i];
}
customStyles = other.customStyles;
@ -33,12 +31,11 @@ Weapon::Weapon(const Weapon& other) {
effectColour = other.effectColour;
}
Weapon&
Weapon::operator=(const Weapon& other) {
Weapon& Weapon::operator=(const Weapon& other) {
name = other.name;
type = other.type;
parts = Containers::Array<WeaponPart>{other.parts.size()};
for(std::uint32_t i = 0; i < parts.size(); i++) {
for(UnsignedInt i = 0; i < parts.size(); i++) {
parts[i] = other.parts[i];
}
customStyles = other.customStyles;
@ -50,5 +47,3 @@ Weapon::operator=(const Weapon& other) {
return *this;
}
}

View file

@ -1,7 +1,7 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -29,7 +29,19 @@
using namespace Corrade;
using namespace Magnum;
namespace mbst::GameObjects {
#define c(enumerator, ...) enumerator,
enum class WeaponType {
#include "../Maps/WeaponTypes.hpp"
};
enum class DamageType {
#include "../Maps/DamageTypes.hpp"
};
enum class EffectColourMode {
#include "../Maps/EffectColourModes.hpp"
};
#undef c
struct Weapon {
Weapon() = default;
@ -40,20 +52,8 @@ struct Weapon {
Weapon(Weapon&& other) = default;
Weapon& operator=(Weapon&& other) = default;
#define c(enumerator, ...) enumerator,
enum class Type {
#include "../Maps/WeaponTypes.hpp"
};
enum class DamageType {
#include "../Maps/DamageTypes.hpp"
};
enum class EffectColourMode {
#include "../Maps/EffectColourModes.hpp"
};
#undef c
Containers::String name;
Type type = Type::Melee;
WeaponType type = WeaponType::Melee;
Containers::Array<WeaponPart> parts;
Containers::StaticArray<16, CustomStyle> customStyles{ValueInit};
bool attached = false;
@ -62,5 +62,3 @@ struct Weapon {
EffectColourMode effectColourMode = EffectColourMode::Default;
Color4 effectColour{0.0f};
};
}

View file

@ -1,7 +1,7 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -19,12 +19,13 @@
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/StaticArray.h>
#include <Magnum/Types.h>
#include "Decal.h"
#include "Accessory.h"
using namespace Corrade;
namespace mbst::GameObjects {
using namespace Magnum;
struct WeaponPart {
WeaponPart() = default;
@ -33,11 +34,11 @@ struct WeaponPart {
id = other.id;
styles = other.styles;
decals = Containers::Array<Decal>{other.decals.size()};
for(auto i = 0u; i < decals.size(); i++) {
for(UnsignedInt i = 0; i < decals.size(); i++) {
decals[i] = other.decals[i];
}
accessories = Containers::Array<Accessory>{other.accessories.size()};
for(auto i = 0u; i < accessories.size(); i++) {
for(UnsignedInt i = 0; i < accessories.size(); i++) {
accessories[i] = other.accessories[i];
}
}
@ -45,11 +46,11 @@ struct WeaponPart {
id = other.id;
styles = other.styles;
decals = Containers::Array<Decal>{other.decals.size()};
for(auto i = 0u; i < decals.size(); i++) {
for(UnsignedInt i = 0; i < decals.size(); i++) {
decals[i] = other.decals[i];
}
accessories = Containers::Array<Accessory>{other.accessories.size()};
for(auto i = 0u; i < accessories.size(); i++) {
for(UnsignedInt i = 0; i < accessories.size(); i++) {
accessories[i] = other.accessories[i];
}
return *this;
@ -58,10 +59,8 @@ struct WeaponPart {
WeaponPart(WeaponPart&& other) = default;
WeaponPart& operator=(WeaponPart&& other) = default;
std::int32_t id = 0;
Containers::StaticArray<4, std::int32_t> styles{ValueInit};
Int id = 0;
Containers::StaticArray<4, Int> styles{ValueInit};
Containers::Array<Decal> decals{};
Containers::Array<Accessory> accessories{};
};
}

View file

@ -0,0 +1,254 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2022 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 <algorithm>
#include <Corrade/Utility/Format.h>
#include <Corrade/Utility/Path.h>
#include "../Logger/Logger.h"
#include "MassManager.h"
using namespace Containers::Literals;
MassManager::MassManager(Containers::StringView save_path, Containers::StringView account, bool demo,
Containers::StringView staging_dir):
_saveDirectory{save_path}, _account{account}, _demo{demo}, _stagingAreaDirectory{staging_dir}
{
Containers::String mass_filename = "";
for(UnsignedInt i = 0; i < _hangars.size(); i++) {
mass_filename = Utility::Path::join(_saveDirectory,
Utility::format("{}Unit{:.2d}{}.sav", demo ? "Demo"_s : ""_s, i, _account));
new(&_hangars[i]) Mass{mass_filename};
}
refreshStagedMasses();
}
auto MassManager::lastError() -> Containers::StringView {
return _lastError;
}
auto MassManager::hangar(Int hangar) -> Mass& {
return _hangars[hangar];
}
void MassManager::refreshHangar(Int hangar) {
if(hangar < 0 || hangar >= 32) {
_lastError = "Hangar index out of range.";
LOG_ERROR(_lastError);
return;
}
Containers::String mass_filename =
Utility::Path::join(_saveDirectory,
Utility::format("{}Unit{:.2d}{}.sav", _demo ? "Demo" : "", hangar, _account));
_hangars[hangar] = Mass{mass_filename};
}
auto MassManager::importMass(Containers::StringView staged_fn, Int hangar) -> bool {
if(hangar < 0 || hangar >= 32) {
_lastError = "Hangar index out of range.";
LOG_ERROR(_lastError);
return false;
}
auto it = _stagedMasses.find(Containers::String::nullTerminatedView(staged_fn));
if(it == _stagedMasses.end()) {
_lastError = "Couldn't find "_s + staged_fn + " in the staged M.A.S.S.es."_s;
LOG_ERROR(_lastError);
return false;
}
Containers::String source = Utility::Path::join(_stagingAreaDirectory, staged_fn);
Utility::Path::copy(source, source + ".tmp"_s);
{
Mass mass{source + ".tmp"_s};
if(!mass.updateAccount(_account)) {
_lastError = mass.lastError();
Utility::Path::remove(source + ".tmp"_s);
return false;
}
}
Containers::String dest = Utility::Path::join(_saveDirectory, _hangars[hangar].filename());
if(Utility::Path::exists(dest)) {
Utility::Path::remove(dest);
}
if(!Utility::Path::move(source + ".tmp"_s, dest)) {
_lastError = Utility::format("Couldn't move {} to hangar {:.2d}", staged_fn, hangar + 1);
LOG_ERROR(_lastError);
return false;
}
return true;
}
auto MassManager::exportMass(Int hangar) -> bool {
if(hangar < 0 || hangar >= 32) {
_lastError = "Hangar index out of range."_s;
LOG_ERROR(_lastError);
return false;
}
if(_hangars[hangar].state() != Mass::State::Valid) {
_lastError = Utility::format("There is no valid data to export in hangar {:.2d}", hangar + 1);
LOG_ERROR(_lastError);
return false;
}
Containers::String source = Utility::Path::join(_saveDirectory, _hangars[hangar].filename());
Containers::String dest = Utility::Path::join(_stagingAreaDirectory,
Utility::format("{}_{}.sav", _hangars[hangar].name(), _account));
if(!Utility::Path::copy(source, dest)) {
_lastError = Utility::format("Couldn't export data from hangar {:.2d} to {}", hangar, dest);
LOG_ERROR(_lastError);
return false;
}
return true;
}
auto MassManager::moveMass(Int source, Int destination) -> bool {
if(source < 0 || source >= 32) {
_lastError = "Source hangar index out of range."_s;
LOG_ERROR(_lastError);
return false;
}
if(destination < 0 || destination >= 32) {
_lastError = "Destination hangar index out of range."_s;
LOG_ERROR(_lastError);
return false;
}
Containers::String source_file = Utility::Path::join(_saveDirectory, _hangars[source].filename());
Containers::String dest_file = Utility::Path::join(_saveDirectory, _hangars[destination].filename());
Mass::State dest_state = _hangars[destination].state();
switch(dest_state) {
case Mass::State::Empty:
break;
case Mass::State::Invalid:
Utility::Path::remove(dest_file);
break;
case Mass::State::Valid:
Utility::Path::move(dest_file, dest_file + ".tmp"_s);
break;
}
Utility::Path::move(source_file, dest_file);
if(dest_state == Mass::State::Valid) {
Utility::Path::move(dest_file + ".tmp"_s, source_file);
}
return true;
}
auto MassManager::deleteMass(Int hangar) -> bool {
if(hangar < 0 || hangar >= 32) {
_lastError = "Hangar index out of range."_s;
LOG_ERROR(_lastError);
return false;
}
if(!Utility::Path::remove(Utility::Path::join(_saveDirectory, _hangars[hangar].filename()))) {
_lastError = Utility::format("Deletion failed: {}", std::strerror(errno));
LOG_ERROR(_lastError);
return false;
}
return true;
}
auto MassManager::stagedMasses() -> std::map<Containers::String, Containers::String> const& {
return _stagedMasses;
}
void MassManager::refreshStagedMasses() {
_stagedMasses.clear();
using Utility::Path::ListFlag;
auto file_list = Utility::Path::list(_stagingAreaDirectory,
ListFlag::SkipSpecial|ListFlag::SkipDirectories|ListFlag::SkipDotAndDotDot);
if(!file_list) {
LOG_ERROR_FORMAT("{} couldn't be opened.", _stagingAreaDirectory);
return;
}
auto iter = std::remove_if(file_list->begin(), file_list->end(), [](Containers::StringView file){
return !file.hasSuffix(".sav"_s);
});
auto list_view = file_list->exceptSuffix(file_list->end() - iter);
LOG_INFO("Scanning for staged M.A.S.S.es...");
for(Containers::StringView file : list_view) {
auto name = Mass::getNameFromFile(Utility::Path::join(_stagingAreaDirectory, file));
if(name) {
LOG_INFO_FORMAT("Found staged M.A.S.S.: {}", *name);
_stagedMasses[file] = *name;
}
else {
LOG_WARNING_FORMAT("Skipped {}.", file);
}
}
}
void MassManager::refreshStagedMass(Containers::StringView filename) {
LOG_INFO_FORMAT("Refreshing staged unit with filename {}.", filename);
bool file_exists = Utility::Path::exists(Utility::Path::join(_stagingAreaDirectory, filename));
auto it = _stagedMasses.find(filename);
if(file_exists) {
auto name = Mass::getNameFromFile(Utility::Path::join(_stagingAreaDirectory, filename));
if(name) {
_stagedMasses[filename] = *name;
}
else if(it != _stagedMasses.cend()) {
_stagedMasses.erase(it);
}
}
else if(it != _stagedMasses.cend()) {
_stagedMasses.erase(it);
}
}
auto MassManager::deleteStagedMass(Containers::StringView filename) -> bool {
if(_stagedMasses.find(filename) == _stagedMasses.cend()) {
_lastError = "The file "_s + filename + " couldn't be found in the list of staged M.A.S.S.es."_s;
LOG_ERROR(_lastError);
return false;
}
if(!Utility::Path::remove(Utility::Path::join(_stagingAreaDirectory, filename))) {
_lastError = filename + " couldn't be deleted: " + std::strerror(errno);
LOG_ERROR(_lastError);
return false;
}
return true;
}

View file

@ -1,7 +1,7 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -16,38 +16,47 @@
// 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 <map>
#include <Corrade/Containers/StaticArray.h>
#include <Corrade/Containers/String.h>
#include <Corrade/Containers/StringView.h>
#include "../GameObjects/Mass.h"
#include "../Mass/Mass.h"
using namespace Corrade;
namespace mbst::Managers {
class MassManager {
public:
MassManager(Containers::StringView account, bool demo);
MassManager(Containers::StringView save_path, Containers::StringView account, bool demo, Containers::StringView staging_dir);
auto lastError() -> Containers::StringView;
auto hangar(int hangar) -> GameObjects::Mass&;
auto hangar(int hangar) -> Mass&;
void refreshHangar(int hangar);
bool exportMass(int hangar);
auto importMass(Containers::StringView staged_fn, int hangar) -> bool;
auto exportMass(int hangar) -> bool;
bool moveMass(int source, int destination);
bool deleteMass(int hangar);
auto moveMass(int source, int destination) -> bool;
auto deleteMass(int hangar) -> bool;
auto stagedMasses() -> std::map<Containers::String, Containers::String> const&;
void refreshStagedMasses();
void refreshStagedMass(Containers::StringView filename);
auto deleteStagedMass(Containers::StringView filename) -> bool;
private:
Containers::StringView _saveDirectory;
Containers::StringView _account;
bool _demo;
Containers::String _lastError;
Containers::StaticArray<32, GameObjects::Mass> _hangars{NoInit};
};
Containers::StaticArray<32, Mass> _hangars{NoInit};
}
Containers::StringView _stagingAreaDirectory;
std::map<Containers::String, Containers::String> _stagedMasses;
};

494
src/Profile/Profile.cpp Normal file
View file

@ -0,0 +1,494 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2022 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 <algorithm>
#include <Corrade/Containers/Pair.h>
#include <Corrade/Utility/Path.h>
#include "PropertyNames.h"
#include "../Logger/Logger.h"
#include "../UESaveFile/Types/ArrayProperty.h"
#include "../UESaveFile/Types/ResourceItemValue.h"
#include "../UESaveFile/Types/IntProperty.h"
#include "../UESaveFile/Types/StringProperty.h"
#include "Profile.h"
using namespace Corrade;
using namespace Containers::Literals;
Profile::Profile(Containers::StringView path):
_profile(path)
{
LOG_INFO_FORMAT("Reading profile at {}.", path);
if(!_profile.valid()) {
_lastError = _profile.lastError();
_valid = false;
return;
}
_filename = Utility::Path::split(path).second();
if(_filename.hasPrefix("Demo"_s)) {
_type = ProfileType::Demo;
}
else {
_type = ProfileType::FullGame;
}
auto account_prop = _profile.at<StringProperty>(PROFILE_ACCOUNT);
if(!account_prop) {
_lastError = "Couldn't find an account ID in "_s + _filename;
_valid = false;
return;
}
_account = account_prop->value;
refreshValues();
}
auto Profile::valid() const -> bool {
return _valid;
}
auto Profile::lastError() const -> Containers::StringView {
return _lastError;
}
auto Profile::filename() const -> Containers::StringView {
return _filename;
}
auto Profile::type() const -> ProfileType {
return _type;
}
auto Profile::isDemo() const -> bool {
return _type == ProfileType::Demo;
}
auto Profile::account() const -> Containers::StringView {
return _account;
}
void Profile::refreshValues() {
if(!_profile.reloadData()) {
LOG_ERROR(_profile.lastError());
_valid = false;
return;
}
if(_profile.saveType() != "/Game/Core/Save/bpSaveGameProfile.bpSaveGameProfile_C"_s) {
LOG_ERROR_FORMAT("{} is not a valid profile save.", _filename);
_valid = false;
return;
}
LOG_INFO("Getting the company name.");
auto name_prop = _profile.at<StringProperty>(PROFILE_NAME);
if(!name_prop) {
_lastError = "No company name in "_s + _filename;
LOG_ERROR(_lastError);
_valid = false;
return;
}
_name = name_prop->value;
LOG_INFO("Getting the active frame slot.");
auto prop = _profile.at<IntProperty>(PROFILE_ACTIVE_FRAME_SLOT);
_activeFrameSlot = prop ? prop->value : 0;
LOG_INFO("Getting the credits.");
prop = _profile.at<IntProperty>(PROFILE_CREDITS);
_credits = prop ? prop->value : 0;
LOG_INFO("Getting the story progress.");
prop = _profile.at<IntProperty>(PROFILE_STORY_PROGRESS);
_storyProgress = prop ? prop->value : 0;
LOG_INFO("Getting the last mission ID.");
prop = _profile.at<IntProperty>(PROFILE_LAST_MISSION_ID);
_lastMissionId = prop ? prop->value : 0;
LOG_INFO("Getting the materials.");
_verseSteel = getResource(PROFILE_MATERIAL, VerseSteel);
_undinium = getResource(PROFILE_MATERIAL, Undinium);
_necriumAlloy = getResource(PROFILE_MATERIAL, NecriumAlloy);
_lunarite = getResource(PROFILE_MATERIAL, Lunarite);
_asterite = getResource(PROFILE_MATERIAL, Asterite);
_halliteFragma = getResource(PROFILE_MATERIAL, HalliteFragma);
_ednil = getResource(PROFILE_MATERIAL, Ednil);
_nuflalt = getResource(PROFILE_MATERIAL, Nuflalt);
_aurelene = getResource(PROFILE_MATERIAL, Aurelene);
_soldus = getResource(PROFILE_MATERIAL, Soldus);
_synthesisedN = getResource(PROFILE_MATERIAL, SynthesisedN);
_nanoc = getResource(PROFILE_MATERIAL, Nanoc);
_alcarbonite = getResource(PROFILE_MATERIAL, Alcarbonite);
_keriphene = getResource(PROFILE_MATERIAL, Keriphene);
_nitinolCM = getResource(PROFILE_MATERIAL, NitinolCM);
_quarkium = getResource(PROFILE_MATERIAL, Quarkium);
_alterene = getResource(PROFILE_MATERIAL, Alterene);
_cosmium = getResource(PROFILE_MATERIAL, Cosmium);
_mixedComposition = getResource(PROFILE_QUARK_DATA, MixedComposition);
_voidResidue = getResource(PROFILE_QUARK_DATA, VoidResidue);
_muscularConstruction = getResource(PROFILE_QUARK_DATA, MuscularConstruction);
_mineralExoskeletology = getResource(PROFILE_QUARK_DATA, MineralExoskeletology);
_carbonisedSkin = getResource(PROFILE_QUARK_DATA, CarbonisedSkin);
_isolatedVoidParticle = getResource(PROFILE_QUARK_DATA, IsolatedVoidParticle);
_valid = true;
}
auto Profile::companyName() const -> Containers::StringView {
return _name;
}
auto Profile::renameCompany(Containers::StringView new_name) -> bool {
auto name_prop = _profile.at<StringProperty>(PROFILE_NAME);
if(!name_prop) {
_lastError = "No company name in "_s + _filename;
LOG_ERROR(_lastError);
_valid = false;
return false;
}
name_prop->value = new_name;
if(!_profile.saveToFile()) {
_lastError = _profile.lastError();
return false;
}
return true;
}
auto Profile::activeFrameSlot() const -> Int {
return _activeFrameSlot;
}
auto Profile::credits() const -> Int {
return _credits;
}
auto Profile::setCredits(Int amount) -> bool {
auto credits_prop = _profile.at<IntProperty>(PROFILE_CREDITS);
if(!credits_prop) {
credits_prop = new IntProperty;
credits_prop->name.emplace("Credit"_s);
credits_prop->valueLength = sizeof(Int);
_profile.appendProperty(IntProperty::ptr{credits_prop});
}
credits_prop->value = amount;
if(!_profile.saveToFile()) {
_lastError = _profile.lastError();
return false;
}
return true;
}
auto Profile::storyProgress() const -> Int {
return _storyProgress;
}
auto Profile::setStoryProgress(Int progress) -> bool {
auto story_progress_prop = _profile.at<IntProperty>("StoryProgress"_s);
if(!story_progress_prop) {
story_progress_prop = new IntProperty;
story_progress_prop->name.emplace("StoryProgress"_s);
story_progress_prop->valueLength = sizeof(Int);
_profile.appendProperty(IntProperty::ptr{story_progress_prop});
}
story_progress_prop->value = progress;
if(!_profile.saveToFile()) {
_lastError = _profile.lastError();
return false;
}
return true;
}
auto Profile::lastMissionId() const -> Int {
return _lastMissionId;
}
auto Profile::verseSteel() const -> Int {
return _verseSteel;
}
auto Profile::setVerseSteel(Int amount) -> bool {
return setResource(PROFILE_MATERIAL, VerseSteel, amount);
}
auto Profile::undinium() const -> Int {
return _undinium;
}
auto Profile::setUndinium(Int amount) -> bool {
return setResource(PROFILE_MATERIAL, Undinium, amount);
}
auto Profile::necriumAlloy() const -> Int {
return _necriumAlloy;
}
auto Profile::setNecriumAlloy(Int amount) -> bool {
return setResource(PROFILE_MATERIAL, NecriumAlloy, amount);
}
auto Profile::lunarite() const -> Int {
return _lunarite;
}
auto Profile::setLunarite(Int amount) -> bool {
return setResource(PROFILE_MATERIAL, Lunarite, amount);
}
auto Profile::asterite() const -> Int {
return _asterite;
}
auto Profile::setAsterite(Int amount) -> bool {
return setResource(PROFILE_MATERIAL, Asterite, amount);
}
Int
Profile::halliteFragma() const {
return _halliteFragma;
}
bool
Profile::setHalliteFragma(Int amount) {
return setResource(PROFILE_MATERIAL, HalliteFragma, amount);
}
auto Profile::ednil() const -> Int {
return _ednil;
}
auto Profile::setEdnil(Int amount) -> bool {
return setResource(PROFILE_MATERIAL, Ednil, amount);
}
auto Profile::nuflalt() const -> Int {
return _nuflalt;
}
auto Profile::setNuflalt(Int amount) -> bool {
return setResource(PROFILE_MATERIAL, Nuflalt, amount);
}
auto Profile::aurelene() const -> Int {
return _aurelene;
}
auto Profile::setAurelene(Int amount) -> bool {
return setResource(PROFILE_MATERIAL, Aurelene, amount);
}
auto Profile::soldus() const -> Int {
return _soldus;
}
auto Profile::setSoldus(Int amount) -> bool {
return setResource(PROFILE_MATERIAL, Soldus, amount);
}
auto Profile::synthesisedN() const -> Int {
return _synthesisedN;
}
auto Profile::setSynthesisedN(Int amount) -> bool {
return setResource(PROFILE_MATERIAL, SynthesisedN, amount);
}
Int
Profile::nanoc() const {
return _nanoc;
}
bool
Profile::setNanoc(Int amount) {
return setResource(PROFILE_MATERIAL, Nanoc, amount);
}
auto Profile::alcarbonite() const -> Int {
return _alcarbonite;
}
auto Profile::setAlcarbonite(Int amount) -> bool {
return setResource(PROFILE_MATERIAL, Alcarbonite, amount);
}
auto Profile::keriphene() const -> Int {
return _keriphene;
}
auto Profile::setKeriphene(Int amount) -> bool {
return setResource(PROFILE_MATERIAL, Keriphene, amount);
}
auto Profile::nitinolCM() const -> Int {
return _nitinolCM;
}
auto Profile::setNitinolCM(Int amount) -> bool {
return setResource(PROFILE_MATERIAL, NitinolCM, amount);
}
auto Profile::quarkium() const -> Int {
return _quarkium;
}
auto Profile::setQuarkium(Int amount) -> bool {
return setResource(PROFILE_MATERIAL, Quarkium, amount);
}
auto Profile::alterene() const -> Int {
return _alterene;
}
auto Profile::setAlterene(Int amount) -> bool {
return setResource(PROFILE_MATERIAL, Alterene, amount);
}
Int
Profile::cosmium() const {
return _cosmium;
}
bool
Profile::setCosmium(Int amount) {
return setResource(PROFILE_MATERIAL, Cosmium, amount);
}
auto Profile::mixedComposition() const -> Int {
return _mixedComposition;
}
auto Profile::setMixedComposition(Int amount) -> bool {
return setResource(PROFILE_QUARK_DATA, MixedComposition, amount);
}
auto Profile::voidResidue() const -> Int {
return _voidResidue;
}
auto Profile::setVoidResidue(Int amount) -> bool {
return setResource(PROFILE_QUARK_DATA, VoidResidue, amount);
}
auto Profile::muscularConstruction() const -> Int {
return _muscularConstruction;
}
auto Profile::setMuscularConstruction(Int amount) -> bool {
return setResource(PROFILE_QUARK_DATA, MuscularConstruction, amount);
}
auto Profile::mineralExoskeletology() const -> Int {
return _mineralExoskeletology;
}
auto Profile::setMineralExoskeletology(Int amount) -> bool {
return setResource(PROFILE_QUARK_DATA, MineralExoskeletology, amount);
}
auto Profile::carbonisedSkin() const -> Int {
return _carbonisedSkin;
}
auto Profile::setCarbonisedSkin(Int amount) -> bool {
return setResource(PROFILE_QUARK_DATA, CarbonisedSkin, amount);
}
Int
Profile::isolatedVoidParticle() const {
return _isolatedVoidParticle;
}
bool
Profile::setIsolatedVoidParticle(Int amount) {
return setResource(PROFILE_QUARK_DATA, IsolatedVoidParticle, amount);
}
auto Profile::getResource(Containers::StringView container, MaterialID id) -> Int {
auto mats_prop = _profile.at<ArrayProperty>(container);
if(!mats_prop) {
return 0;
}
auto predicate = [&id](UnrealPropertyBase::ptr& prop){
auto res_prop = static_cast<ResourceItemValue*>(prop.get());
return res_prop->id == id;
};
auto it = std::find_if(mats_prop->items.begin(), mats_prop->items.end(), predicate);
return it != mats_prop->items.end() ? static_cast<ResourceItemValue*>(it->get())->quantity : 0;
}
auto Profile::setResource(Containers::StringView container, MaterialID id, Int amount) -> bool {
auto mats_prop = _profile.at<ArrayProperty>(container);
if(!mats_prop) {
mats_prop = new ArrayProperty;
mats_prop->name.emplace(container);
mats_prop->itemType = "StructProperty";
_profile.appendProperty(ArrayProperty::ptr{mats_prop});
}
auto predicate = [&id](UnrealPropertyBase::ptr& prop){
auto res_prop = static_cast<ResourceItemValue*>(prop.get());
return res_prop->id == id;
};
auto it = std::find_if(mats_prop->items.begin(), mats_prop->items.end(), predicate);
ResourceItemValue* res_prop;
if(it == mats_prop->items.end()) {
res_prop = new ResourceItemValue;
if(mats_prop->items.isEmpty()) {
res_prop->name.emplace(container);
}
res_prop->id = id;
ResourceItemValue::ptr prop{res_prop};
arrayAppend(mats_prop->items, std::move(prop));
}
else {
res_prop = static_cast<ResourceItemValue*>(it->get());
}
res_prop->quantity = amount;
if(!_profile.saveToFile()) {
_lastError = _profile.lastError();
return false;
}
return true;
}

186
src/Profile/Profile.h Normal file
View file

@ -0,0 +1,186 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2022 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/String.h>
#include <Corrade/Containers/StringView.h>
#include <Magnum/Magnum.h>
#include "../UESaveFile/UESaveFile.h"
#include "ResourceIDs.h"
using namespace Corrade;
using namespace Magnum;
enum class ProfileType : UnsignedByte {
Demo,
FullGame
};
class Profile {
public:
explicit Profile(Containers::StringView path);
auto valid() const -> bool;
auto lastError() const -> Containers::StringView;
auto filename() const -> Containers::StringView;
auto type() const -> ProfileType;
auto isDemo() const -> bool;
auto account() const -> Containers::StringView;
void refreshValues();
auto companyName() const -> Containers::StringView;
auto renameCompany(Containers::StringView new_name) -> bool;
auto activeFrameSlot() const -> Int;
auto credits() const -> Int;
auto setCredits(Int credits) -> bool;
auto storyProgress() const -> Int;
auto setStoryProgress(Int progress) -> bool;
auto lastMissionId() const -> Int;
auto verseSteel() const -> Int;
auto setVerseSteel(Int amount) -> bool;
auto undinium() const -> Int;
auto setUndinium(Int amount) -> bool;
auto necriumAlloy() const -> Int;
auto setNecriumAlloy(Int amount) -> bool;
auto lunarite() const -> Int;
auto setLunarite(Int amount) -> bool;
auto asterite() const -> Int;
auto setAsterite(Int amount) -> bool;
Int halliteFragma() const;
bool setHalliteFragma(Int amount);
auto ednil() const -> Int;
auto setEdnil(Int amount) -> bool;
auto nuflalt() const -> Int;
auto setNuflalt(Int amount) -> bool;
auto aurelene() const -> Int;
auto setAurelene(Int amount) -> bool;
auto soldus() const -> Int;
auto setSoldus(Int amount) -> bool;
auto synthesisedN() const -> Int;
auto setSynthesisedN(Int amount) -> bool;
Int nanoc() const;
bool setNanoc(Int amount);
auto alcarbonite() const -> Int;
auto setAlcarbonite(Int amount) -> bool;
auto keriphene() const -> Int;
auto setKeriphene(Int amount) -> bool;
auto nitinolCM() const -> Int;
auto setNitinolCM(Int amount) -> bool;
auto quarkium() const -> Int;
auto setQuarkium(Int amount) -> bool;
auto alterene() const -> Int;
auto setAlterene(Int amount) -> bool;
Int cosmium() const;
bool setCosmium(Int amount);
auto mixedComposition() const -> Int;
auto setMixedComposition(Int amount) -> bool;
auto voidResidue() const -> Int;
auto setVoidResidue(Int amount) -> bool;
auto muscularConstruction() const -> Int;
auto setMuscularConstruction(Int amount) -> bool;
auto mineralExoskeletology() const -> Int;
auto setMineralExoskeletology(Int amount) -> bool;
auto carbonisedSkin() const -> Int;
auto setCarbonisedSkin(Int amount) -> bool;
Int isolatedVoidParticle() const;
bool setIsolatedVoidParticle(Int amount);
private:
auto getResource(Containers::StringView container, MaterialID id) -> Int;
auto setResource(Containers::StringView container, MaterialID id, Int amount) -> bool;
Containers::String _filename;
ProfileType _type;
UESaveFile _profile;
Containers::String _name;
Int _activeFrameSlot = 0;
Int _credits = 0;
Int _storyProgress = 0;
Int _lastMissionId = 0;
Int _verseSteel = 0;
Int _undinium = 0;
Int _necriumAlloy = 0;
Int _lunarite = 0;
Int _asterite = 0;
Int _halliteFragma = 0;
Int _ednil = 0;
Int _nuflalt = 0;
Int _aurelene = 0;
Int _soldus = 0;
Int _synthesisedN = 0;
Int _nanoc = 0;
Int _alcarbonite = 0;
Int _keriphene = 0;
Int _nitinolCM = 0;
Int _quarkium = 0;
Int _alterene = 0;
Int _cosmium = 0;
Int _mixedComposition = 0;
Int _voidResidue = 0;
Int _muscularConstruction = 0;
Int _mineralExoskeletology = 0;
Int _carbonisedSkin = 0;
Int _isolatedVoidParticle = 0;
Containers::String _account;
bool _valid = false;
Containers::String _lastError;
};

View file

@ -0,0 +1,10 @@
#pragma once
#define PROFILE_NAME "CompanyName"
#define PROFILE_ACTIVE_FRAME_SLOT "ActiveFrameSlot"
#define PROFILE_CREDITS "Credit"
#define PROFILE_STORY_PROGRESS "StoryProgress"
#define PROFILE_LAST_MISSION_ID "LastMissionID"
#define PROFILE_MATERIAL "ResourceMaterial"
#define PROFILE_QUARK_DATA "ResourceQuarkData"
#define PROFILE_ACCOUNT "Account"

View file

@ -1,7 +1,7 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -16,16 +16,17 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
namespace mbst::GameData {
#include <Magnum/Types.h>
enum MaterialID : std::int32_t {
using namespace Magnum;
enum MaterialID : Int {
VerseSteel = 0xC3500,
Undinium = 0xC3501,
NecriumAlloy = 0xC3502,
Lunarite = 0xC3503,
Asterite = 0xC3504,
HalliteFragma = 0xC3505,
Unnoctinium = 0xC3506,
Ednil = 0xC350A,
Nuflalt = 0xC350B,
@ -33,15 +34,13 @@ enum MaterialID : std::int32_t {
Soldus = 0xC350D,
SynthesisedN = 0xC350E,
Nanoc = 0xC350F,
Abyssillite = 0xC3510,
Alcarbonite = 0xC3514,
Keriphene = 0xC3515,
NitinolCM = 0xC3516,
Quarkium = 0xC3517,
Alterene = 0xC3518,
Cosmium = 0xC3519,
PurifiedQuarkium = 0xC351A,
Alcarbonite = 0xC3514,
Keriphene = 0xC3515,
NitinolCM = 0xC3516,
Quarkium = 0xC3517,
Alterene = 0xC3518,
Cosmium = 0xC3519,
MixedComposition = 0xDBBA0,
VoidResidue = 0xDBBA1,
@ -49,7 +48,4 @@ enum MaterialID : std::int32_t {
MineralExoskeletology = 0xDBBA3,
CarbonisedSkin = 0xDBBA4,
IsolatedVoidParticle = 0xDBBA5,
WeaponisedPhysiology = 0xDBBA6,
};
}

View file

@ -0,0 +1,367 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2022 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 <cstdio>
#include <algorithm>
#include <chrono>
#include <Corrade/Containers/ScopeGuard.h>
#include <Corrade/Containers/StaticArray.h>
#include <Corrade/Utility/Format.h>
#include <Corrade/Utility/Path.h>
#include <Corrade/Utility/String.h>
#include <zip.h>
#include "../Logger/Logger.h"
#include "ProfileManager.h"
using namespace Containers::Literals;
ProfileManager::ProfileManager(Containers::StringView save_dir, Containers::StringView backup_dir):
_saveDirectory{save_dir},
_backupsDirectory{backup_dir}
{
_ready = refreshProfiles();
}
auto ProfileManager::ready() const -> bool {
return _ready;
}
auto ProfileManager::lastError() -> Containers::StringView {
return _lastError;
}
auto ProfileManager::profiles() -> Containers::ArrayView<Profile> {
return _profiles;
}
auto ProfileManager::refreshProfiles() -> bool {
LOG_INFO("Refreshing profiles.");
_profiles = Containers::Array<Profile>{};
using Utility::Path::ListFlag;
auto files = Utility::Path::list(_saveDirectory,
ListFlag::SkipSpecial|ListFlag::SkipDirectories|ListFlag::SkipDotAndDotDot);
if(!files) {
_lastError = _saveDirectory + " can't be opened.";
LOG_ERROR(_lastError);
return false;
}
auto predicate = [](Containers::StringView file)->bool{
return !((file.hasPrefix("DemoProfile") || file.hasPrefix("Profile")) && file.hasSuffix(".sav"));
};
auto files_view = files->exceptSuffix(files->end() - std::remove_if(files->begin(), files->end(), predicate));
for(const auto& file : files_view) {
Profile profile{Utility::Path::join(_saveDirectory, file)};
if(!profile.valid()) {
LOG_WARNING_FORMAT("Profile {} is invalid: {}", file, profile.lastError());
continue;
}
arrayAppend(_profiles, std::move(profile));
}
if(_profiles.isEmpty()) {
_lastError = "No valid profiles were found."_s;
LOG_ERROR(_lastError);
return false;
}
return true;
}
auto ProfileManager::getProfile(std::size_t index) -> Profile* {
return index <= _profiles.size() ? &(_profiles[index]) : nullptr;
}
auto ProfileManager::deleteProfile(std::size_t index, bool delete_builds) -> bool {
if(!Utility::Path::remove(Utility::Path::join(_saveDirectory, _profiles[index].filename()))) {
_lastError = Utility::format("Couldn't delete {} (filename: {}).",
_profiles[index].companyName(),
_profiles[index].filename());
LOG_ERROR(_lastError);
refreshProfiles();
return false;
}
if(delete_builds) {
for(UnsignedByte i = 0; i < 32; ++i) {
auto filename = Utility::format("{}Unit{:.2d}{}.sav",
_profiles[index].type() == ProfileType::Demo ? "Demo": "",
i, _profiles[index].account());
Utility::Path::remove(Utility::Path::join(_saveDirectory, filename));
}
}
auto file = _profiles[index].filename();
auto it = std::remove_if(_profiles.begin(), _profiles.end(), [&file](Profile& profile){ return profile.filename() == file; });
if(it != _profiles.end()) {
arrayRemoveSuffix(_profiles, 1);
}
return true;
}
auto ProfileManager::backupProfile(std::size_t index, bool backup_builds) -> bool {
std::time_t timestamp = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::tm* time = std::localtime(&timestamp);
auto& profile = _profiles[index];
auto filename = Utility::format("{}_{}{:.2d}{:.2d}_{:.2d}{:.2d}{:.2d}.backup.mbst",
Utility::String::replaceAll(profile.companyName().data(), " ", "_").c_str(),
time->tm_year + 1900, time->tm_mon + 1, time->tm_mday,
time->tm_hour, time->tm_min, time->tm_sec);
int error_code = 0;
zip_error_t error;
zip_t* zip = zip_open(Utility::Path::join(_backupsDirectory, filename).data(), ZIP_CREATE|ZIP_TRUNCATE, &error_code);
if(zip == nullptr) {
zip_error_init_with_code(&error, error_code);
_lastError = zip_error_strerror(&error);
LOG_ERROR(_lastError);
return false;
}
zip_source_t* profile_source = zip_source_file(zip, Utility::Path::toNativeSeparators(Utility::Path::join(_saveDirectory, profile.filename())).data(), 0, 0);
if(profile_source == nullptr) {
_lastError = zip_strerror(zip);
LOG_ERROR(_lastError);
zip_source_free(profile_source);
return false;
}
if(zip_file_add(zip, profile.filename().data(), profile_source, ZIP_FL_ENC_UTF_8) == -1) {
_lastError = zip_strerror(zip);
LOG_ERROR(_lastError);
zip_source_free(profile_source);
return false;
}
auto comment = Utility::format("{}|{}|{}-{:.2d}-{:.2d}-{:.2d}-{:.2d}-{:.2d}",
profile.companyName(),
profile.isDemo() ? "demo"_s : "full"_s,
time->tm_year + 1900, time->tm_mon + 1, time->tm_mday,
time->tm_hour, time->tm_min, time->tm_sec);
zip_set_archive_comment(zip, comment.data(), comment.size());
if(backup_builds) {
for(UnsignedByte i = 0; i < 32; ++i) {
auto build_filename = Utility::format("{}Unit{:.2d}{}.sav",
profile.isDemo() ? "Demo"_s : ""_s, i,
profile.account());
if(!Utility::Path::exists(Utility::Path::join(_saveDirectory, build_filename))) {
continue;
}
zip_source_t* build_source = zip_source_file(zip, Utility::Path::toNativeSeparators(Utility::Path::join(_saveDirectory, build_filename)).data(), 0, 0);
if(build_source == nullptr) {
zip_source_free(build_source);
continue;
}
if(zip_file_add(zip, build_filename.data(), build_source, ZIP_FL_ENC_UTF_8) == -1) {
zip_source_free(build_source);
continue;
}
}
}
if(zip_close(zip) == -1) {
_lastError = zip_strerror(zip);
LOG_ERROR(_lastError);
return false;
}
refreshBackups();
return true;
}
auto ProfileManager::backups() -> Containers::ArrayView<Backup> {
return _backups;
}
void ProfileManager::refreshBackups() {
_backups = Containers::Array<Backup>{};
using Utility::Path::ListFlag;
auto files = Utility::Path::list(_backupsDirectory,
ListFlag::SkipSpecial|ListFlag::SkipDirectories|ListFlag::SkipDotAndDotDot);
if(!files) {
_lastError = _backupsDirectory + " can't be opened.";
LOG_ERROR(_lastError);
return;
}
auto predicate = [](Containers::StringView file)->bool{
return !(file.hasSuffix(".mbprofbackup"_s) || file.hasSuffix(".backup.mbst"));
};
auto files_view = files->exceptSuffix(files->end() - std::remove_if(files->begin(), files->end(), predicate));
int error_code = 0;
zip_t* zip = nullptr;
for(Containers::StringView file : files_view) {
Backup backup;
backup.filename = file;
zip = zip_open(Utility::Path::join(_backupsDirectory, file).data(), ZIP_RDONLY, &error_code);
if(zip == nullptr) {
continue;
}
Containers::ScopeGuard guard{zip, zip_close};
Long num_entries = zip_get_num_entries(zip, ZIP_FL_UNCHANGED);
if(num_entries == 0) {
continue;
}
int comment_length;
Containers::StringView comment = zip_get_archive_comment(zip, &comment_length, ZIP_FL_UNCHANGED);
if(comment == nullptr) {
continue;
}
auto info = comment.split('|');
if(info.size() != 3) {
continue;
}
backup.company = info[0];
if(info[1].hasPrefix("full")) {
backup.type = ProfileType::FullGame;
}
else if(info[1].hasPrefix("demo")) {
backup.type = ProfileType::Demo;
}
else {
continue;
}
auto ts = info[2].split('-');
if(ts.size() != 6) {
continue;
}
backup.timestamp.year = std::strtol(ts[0].data(), nullptr, 10);
backup.timestamp.month = std::strtol(ts[1].data(), nullptr, 10);
backup.timestamp.day = std::strtol(ts[2].data(), nullptr, 10);
backup.timestamp.hour = std::strtol(ts[3].data(), nullptr, 10);
backup.timestamp.minute = std::strtol(ts[4].data(), nullptr, 10);
backup.timestamp.second = std::strtol(ts[5].data(), nullptr, 10);
arrayReserve(backup.includedFiles, num_entries);
for(Long i = 0; i < num_entries; i++) {
arrayAppend(backup.includedFiles, InPlaceInit, zip_get_name(zip, i, ZIP_FL_UNCHANGED));
}
arrayAppend(_backups, std::move(backup));
}
}
auto ProfileManager::deleteBackup(std::size_t index) -> bool {
if(!Utility::Path::remove(Utility::Path::join(_backupsDirectory, _backups[index].filename))) {
_lastError = "Couldn't delete " + _backups[index].filename;
LOG_ERROR(_lastError);
return false;
}
auto file = _backups[index].filename;
auto it = std::remove_if(_backups.begin(), _backups.end(), [&file](Backup& backup){return backup.filename == file;});
if(it != _backups.end()) {
arrayRemoveSuffix(_backups, 1);
}
return true;
}
auto ProfileManager::restoreBackup(std::size_t index) -> bool {
const Backup& backup = _backups[index];
auto error_format = "Extraction of file {} failed: {}"_s;
int error_code = 0;
zip_t* zip = nullptr;
zip = zip_open(Utility::Path::join(_backupsDirectory, backup.filename).data(), ZIP_RDONLY, &error_code);
if(zip == nullptr) {
zip_error_t error;
zip_error_init_with_code(&error, error_code);
_lastError = zip_error_strerror(&error);
LOG_ERROR(_lastError);
return false;
}
Containers::ScopeGuard zip_guard{zip, zip_close};
for(Containers::StringView file : backup.includedFiles) {
FILE* out = std::fopen(Utility::Path::join(_saveDirectory, file).data(), "wb");
if(out == nullptr) {
_lastError = Utility::format(error_format.data(), file, std::strerror(errno));
LOG_ERROR(_lastError);
return false;
}
Containers::ScopeGuard out_guard{out, std::fclose};
zip_file_t* zf = zip_fopen(zip, file.data(), ZIP_FL_ENC_GUESS);
if(zf == nullptr) {
_lastError = Utility::format(error_format.data(), file, zip_strerror(zip));
LOG_ERROR(_lastError);
return false;
}
Containers::ScopeGuard zf_guard{zf, zip_fclose};
Containers::StaticArray<8192, char> buf{ValueInit};
Long bytes_read = 0;
while((bytes_read = zip_fread(zf, buf.data(), buf.size())) > 0) {
if(std::fwrite(buf.data(), sizeof(char), bytes_read, out) < static_cast<std::size_t>(bytes_read)) {
_lastError = Utility::format(error_format.data(), file, "not enough bytes written.");
LOG_ERROR(_lastError);
return false;
}
}
if(bytes_read == -1) {
_lastError = Utility::format(error_format.data(), file, "couldn't read bytes from archive.");
LOG_ERROR(_lastError);
return false;
}
}
return true;
}

View file

@ -0,0 +1,72 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2022 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 <string>
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/String.h>
#include "../Profile/Profile.h"
using namespace Corrade;
struct Backup {
Containers::String filename;
Containers::String company;
ProfileType type;
struct {
int year;
int month;
int day;
int hour;
int minute;
int second;
} timestamp;
Containers::Array<Containers::String> includedFiles;
};
class ProfileManager {
public:
explicit ProfileManager(Containers::StringView save_dir, Containers::StringView backup_dir);
auto ready() const -> bool;
auto lastError() -> Containers::StringView;
auto profiles() -> Containers::ArrayView<Profile>;
auto refreshProfiles() -> bool;
auto getProfile(std::size_t index) -> Profile*;
auto deleteProfile(std::size_t index, bool delete_builds) -> bool;
auto backupProfile(std::size_t index, bool backup_builds) -> bool;
auto backups() -> Containers::ArrayView<Backup>;
void refreshBackups();
auto deleteBackup(std::size_t index) -> bool;
auto restoreBackup(std::size_t index) -> bool;
private:
bool _ready = false;
Containers::String _lastError;
Containers::StringView _saveDirectory;
Containers::StringView _backupsDirectory;
Containers::Array<Profile> _profiles;
Containers::Array<Backup> _backups;
};

View file

@ -1,5 +1,5 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -14,6 +14,8 @@
// 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 "SaveTool.h"
#include <Corrade/Containers/ScopeGuard.h>
#include <Corrade/Utility/Unicode.h>
@ -27,18 +29,14 @@
#include <SDL.h>
#include <imgui_internal.h>
#include <curl/curl.h>
#include <wtypesbase.h>
#include <shellapi.h>
#include <wtsapi32.h>
#include "../FontAwesome/IconsFontAwesome5.h"
#include "../Configuration/Configuration.h"
#include "../Logger/Logger.h"
#include "Application.h"
using namespace Containers::Literals;
extern const ImVec2 center_pivot = {0.5f, 0.5f};
@ -47,24 +45,20 @@ extern const ImVec2 center_pivot = {0.5f, 0.5f};
Utility::Tweakable tweak;
#endif
namespace mbst {
Application::Application(const Arguments& arguments):
SaveTool::SaveTool(const Arguments& arguments):
Platform::Sdl2Application{arguments,
Configuration{}.setTitle("M.A.S.S. Builder Save Tool " SAVETOOL_VERSION_STRING " (\"" SAVETOOL_CODENAME "\")")
Configuration{}.setTitle("M.A.S.S. Builder Save Tool " SAVETOOL_VERSION " (\"" SAVETOOL_CODENAME "\")")
.setSize({960, 720})}
{
#ifdef SAVETOOL_DEBUG_BUILD
tweak.enable("", "../../");
#endif
LOG_INFO_FORMAT("Framebuffer size: {}x{}", framebufferSize().x(), framebufferSize().y());
LOG_INFO_FORMAT("Window size: {}x{}", windowSize().x(), windowSize().y());
LOG_INFO_FORMAT("DPI scaling: {}x{}", dpiScaling().x(), dpiScaling().y());
LOG_INFO("Configuring OpenGL renderer.");
GL::Renderer::enable(GL::Renderer::Feature::Blending);
GL::Renderer::enable(GL::Renderer::Feature::ScissorTest);
GL::Renderer::disable(GL::Renderer::Feature::FaceCulling);
GL::Renderer::disable(GL::Renderer::Feature::DepthTest);
GL::Renderer::setBlendFunction(GL::Renderer::BlendFunction::SourceAlpha,
GL::Renderer::BlendFunction::OneMinusSourceAlpha);
GL::Renderer::setBlendEquation(GL::Renderer::BlendEquation::Add,
@ -84,9 +78,9 @@ Application::Application(const Arguments& arguments):
#endif
LOG_INFO("Registering custom events.");
if((_initEventId = SDL_RegisterEvents(3)) == std::uint32_t(-1)) {
if((_initEventId = SDL_RegisterEvents(3)) == UnsignedInt(-1)) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error",
"SDL_RegisterEvents() failed in Application::SaveTool(). Exiting...", window());
"SDL_RegisterEvents() failed in SaveTool::SaveTool(). Exiting...", window());
exit(EXIT_FAILURE);
return;
}
@ -105,10 +99,24 @@ Application::Application(const Arguments& arguments):
initialiseGui();
if(!initialiseToolDirectories()) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising the app",
_lastError.data(), window());
exit(EXIT_FAILURE);
return;
}
if(!findGameDataDirectory()) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error initialising the app",
_lastError.data(), window());
exit(EXIT_FAILURE);
return;
}
checkGameState();
_gameCheckTimerId = SDL_AddTimer(2000,
[](std::uint32_t interval, void* param)->std::uint32_t{
static_cast<Application*>(param)->checkGameState();
[](UnsignedInt interval, void* param)->UnsignedInt{
static_cast<SaveTool*>(param)->checkGameState();
return interval;
}, this);
if(_gameCheckTimerId == 0) {
@ -120,7 +128,9 @@ Application::Application(const Arguments& arguments):
initialiseConfiguration();
if(conf().checkUpdatesOnStartup()) {
LOG_INFO("Initialising update checker.");
curl_global_init(CURL_GLOBAL_DEFAULT);
if(_checkUpdatesOnStartup) {
_queue.addToast(Toast::Type::Default, "Checking for updates..."_s);
_updateThread = std::thread{[this]{ checkForUpdates(); }};
}
@ -131,28 +141,37 @@ Application::Application(const Arguments& arguments):
GL::DebugOutput::setEnabled(GL::DebugOutput::Source::Api, GL::DebugOutput::Type::Other, {131185}, false);
}
if(conf().skipDisclaimer()) {
_uiState = UiState::Initialising;
if(_skipDisclaimer) {
_uiState = UiState::Initialising;
_initThread = std::thread{[this]{ initialiseManager(); }};
}
_timeline.start();
}
Application::~Application() {
SaveTool::~SaveTool() {
LOG_INFO("Cleaning up.");
LOG_INFO("Shutting libcurl down.");
curl_global_cleanup();
SDL_RemoveTimer(_gameCheckTimerId);
LOG_INFO("Saving the configuration.");
conf().save();
_conf.setValue("cheat_mode"_s, _cheatMode);
_conf.setValue("advanced_mode"_s, _advancedMode);
_conf.setValue("startup_update_check"_s, _checkUpdatesOnStartup);
_conf.setValue("skip_disclaimer"_s, _skipDisclaimer);
_conf.setValue("swap_interval"_s, _swapInterval);
_conf.setValue("fps_cap"_s, _fpsCap);
_conf.save();
LOG_INFO("Exiting.");
}
void
Application::drawEvent() {
void SaveTool::drawEvent() {
#ifdef SAVETOOL_DEBUG_BUILD
tweak.update();
#endif
@ -163,8 +182,8 @@ Application::drawEvent() {
swapBuffers();
if(conf().swapInterval() == 0 && conf().fpsCap() < 301.0f) {
while(_timeline.currentFrameDuration() < (1.0f / conf().fpsCap()));
if(_swapInterval == 0 && _fpsCap < 301.0f) {
while(_timeline.currentFrameDuration() < (1.0f / _fpsCap));
}
redraw();
@ -172,54 +191,45 @@ Application::drawEvent() {
_timeline.nextFrame();
}
void
Application::viewportEvent(ViewportEvent& event) {
void SaveTool::viewportEvent(ViewportEvent& event) {
GL::defaultFramebuffer.setViewport({{}, event.framebufferSize()});
const auto size = Vector2{windowSize()}/dpiScaling();
const Vector2 size = Vector2{windowSize()}/dpiScaling();
_imgui.relayout(size, windowSize(), framebufferSize());
}
void
Application::keyPressEvent(KeyEvent& event) {
void SaveTool::keyPressEvent(KeyEvent& event) {
if(_imgui.handleKeyPressEvent(event)) return;
}
void
Application::keyReleaseEvent(KeyEvent& event) {
void SaveTool::keyReleaseEvent(KeyEvent& event) {
if(_imgui.handleKeyReleaseEvent(event)) return;
}
void
Application::mousePressEvent(MouseEvent& event) {
void SaveTool::mousePressEvent(MouseEvent& event) {
if(_imgui.handleMousePressEvent(event)) return;
}
void
Application::mouseReleaseEvent(MouseEvent& event) {
void SaveTool::mouseReleaseEvent(MouseEvent& event) {
if(_imgui.handleMouseReleaseEvent(event)) return;
}
void
Application::mouseMoveEvent(MouseMoveEvent& event) {
void SaveTool::mouseMoveEvent(MouseMoveEvent& event) {
if(_imgui.handleMouseMoveEvent(event)) return;
}
void
Application::mouseScrollEvent(MouseScrollEvent& event) {
void SaveTool::mouseScrollEvent(MouseScrollEvent& event) {
if(_imgui.handleMouseScrollEvent(event)) {
event.setAccepted();
return;
}
}
void
Application::textInputEvent(TextInputEvent& event) {
void SaveTool::textInputEvent(TextInputEvent& event) {
if(_imgui.handleTextInputEvent(event)) return;
}
void
Application::anyEvent(SDL_Event& event) {
void SaveTool::anyEvent(SDL_Event& event) {
if(event.type == _initEventId) {
initEvent(event);
}
@ -231,8 +241,7 @@ Application::anyEvent(SDL_Event& event) {
}
}
void
Application::drawImGui() {
void SaveTool::drawImGui() {
_imgui.newFrame();
if(ImGui::GetIO().WantTextInput && !isTextInputActive()) {
@ -249,8 +258,7 @@ Application::drawImGui() {
_imgui.drawFrame();
}
void
Application::drawGui() {
void SaveTool::drawGui() {
drawMainMenu();
switch(_uiState) {
@ -292,9 +300,8 @@ Application::drawGui() {
_queue.draw(windowSize());
}
void
Application::drawDisclaimer() {
ImGui::SetNextWindowPos(ImVec2{Vector2{windowSize() / 2.0f} / dpiScaling()}, ImGuiCond_Always, center_pivot);
void SaveTool::drawDisclaimer() {
ImGui::SetNextWindowPos(ImVec2{Vector2{windowSize() / 2.0f}}, ImGuiCond_Always, center_pivot);
if(ImGui::Begin("Disclaimer##DisclaimerWindow", nullptr,
ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoBringToFrontOnFocus|
@ -323,13 +330,7 @@ Application::drawDisclaimer() {
ImGui::Bullet();
ImGui::SameLine();
ImGui::TextUnformatted("This version of the application was tested on M.A.S.S. Builder early access version " SAVETOOL_SUPPORTED_GAME_VERSION ". It may or may not work with other versions of the game.");
if(conf().isRunningInWine()) {
ImGui::Bullet();
ImGui::SameLine();
ImGui::TextUnformatted("You are currently running this application in Wine/Proton. It hasn't been fully tested, so some issues may arise. Furthermore, features may be unavailable.");
}
ImGui::TextUnformatted("This version of the application was tested on M.A.S.S. Builder early access version " SUPPORTED_GAME_VERSION ". It may or may not work with other versions of the game.");
ImGui::PopTextWrapPos();
@ -343,13 +344,11 @@ Application::drawDisclaimer() {
ImGui::Dummy({0.0f, 5.0f});
ImGui::Dummy({4.0f, 0.0f});
ImGui::SameLine();
if(drawCheckbox("Don't show next time", conf().skipDisclaimer())) {
conf().setSkipDisclaimer(!conf().skipDisclaimer());
}
ImGui::Checkbox("Don't show next time", &_skipDisclaimer);
ImGui::TableSetColumnIndex(1);
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {24.0f, 12.0f});
if(ImGui::Button("I understand the risks")) {
_uiState = UiState::Initialising;
_uiState = UiState::Initialising;
_initThread = std::thread{[this]{ initialiseManager(); }};
}
ImGui::PopStyleVar();
@ -360,9 +359,8 @@ Application::drawDisclaimer() {
ImGui::End();
}
void
Application::drawInitialisation() {
ImGui::SetNextWindowPos(ImVec2{Vector2{windowSize() / 2.0f} / dpiScaling()}, ImGuiCond_Always, center_pivot);
void SaveTool::drawInitialisation() {
ImGui::SetNextWindowPos(ImVec2{Vector2{windowSize() / 2.0f}}, ImGuiCond_Always, center_pivot);
if(ImGui::BeginPopupModal("##InitPopup", nullptr,
ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar))
@ -374,8 +372,7 @@ Application::drawInitialisation() {
ImGui::OpenPopup("##InitPopup");
}
void
Application::drawGameState() {
void SaveTool::drawGameState() {
ImGui::TextUnformatted("Game state:");
ImGui::SameLine();
{
@ -396,19 +393,18 @@ Application::drawGameState() {
}
}
void
Application::drawHelpMarker(Containers::StringView text, float wrap_pos) {
void SaveTool::drawHelpMarker(Containers::StringView text, Float wrap_pos) {
ImGui::TextUnformatted(ICON_FA_QUESTION_CIRCLE);
drawTooltip(text, wrap_pos);
}
void
Application::drawTooltip(Containers::StringView text, float wrap_pos) {
if(ImGui::IsItemHovered() && ImGui::BeginTooltip()) {
void SaveTool::drawTooltip(Containers::StringView text, Float wrap_pos) {
if(ImGui::IsItemHovered()){
ImGui::BeginTooltip();
if(wrap_pos > 0.0f) {
ImGui::PushTextWrapPos(wrap_pos);
}
ImGui::TextUnformatted(text.cbegin(), text.cend());
ImGui::TextUnformatted(text.data());
if(wrap_pos > 0.0f) {
ImGui::PopTextWrapPos();
}
@ -416,121 +412,11 @@ Application::drawTooltip(Containers::StringView text, float wrap_pos) {
}
}
bool
Application::drawCheckbox(Containers::StringView label, bool value) {
return ImGui::Checkbox(label.data(), &value);
void SaveTool::openUri(Containers::StringView uri) {
ShellExecuteW(nullptr, nullptr, Utility::Unicode::widen(uri.data()), nullptr, nullptr, SW_SHOWDEFAULT);
}
void
Application::drawVector3Drag(Containers::StringView id, Vector3& vec, float speed, Vector3 min, Vector3 max,
const char* x_format, const char* y_format, const char* z_format, ImGuiSliderFlags_ flags)
{
ImGui::PushID(id.cbegin());
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::DragFloat("##X", &vec.x(), speed, min.x(), max.x(), x_format, flags);
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragFloat("##Y", &vec.y(), speed, min.y(), max.y(), y_format, flags);
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragFloat("##Z", &vec.z(), speed, min.z(), max.z(), z_format, flags);
ImGui::PopItemWidth();
ImGui::PopID();
}
void
Application::drawVector3dDrag(Containers::StringView id, Vector3d& vec, float speed, Vector3d min, Vector3d max,
const char* x_format, const char* y_format, const char* z_format,
ImGuiSliderFlags_ flags)
{
ImGui::PushID(id.cbegin());
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::DragScalar("##X", ImGuiDataType_Double, &vec.x(), speed, &min.x(), &max.x(), x_format, flags);
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragScalar("##Y", ImGuiDataType_Double, &vec.y(), speed, &min.y(), &max.y(), y_format, flags);
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragScalar("##X", ImGuiDataType_Double, &vec.x(), speed, &min.z(), &max.z(), z_format, flags);
ImGui::PopItemWidth();
ImGui::PopID();
}
void
Application::drawVector3Slider(Containers::StringView id, Vector3& vec, Vector3 min, Vector3 max, const char* x_format,
const char* y_format, const char* z_format, ImGuiSliderFlags_ flags)
{
ImGui::PushID(id.cbegin());
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::SliderFloat("##X", &vec.x(), min.x(), max.x(), x_format, flags);
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderFloat("##Y", &vec.y(), min.y(), max.y(), y_format, flags);
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderFloat("##Z", &vec.z(), min.z(), max.z(), z_format, flags);
ImGui::PopItemWidth();
ImGui::PopID();
}
void
Application::drawVector3dSlider(Containers::StringView id, Vector3d& vec, Vector3d min, Vector3d max,
const char* x_format, const char* y_format, const char* z_format,
ImGuiSliderFlags_ flags)
{
ImGui::PushID(id.cbegin());
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::SliderScalar("##X", ImGuiDataType_Double, &vec.x(), &min.x(), &max.x(), x_format, flags);
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderScalar("##Y", ImGuiDataType_Double, &vec.y(), &min.y(), &max.y(), y_format, flags);
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderScalar("##X", ImGuiDataType_Double, &vec.x(), &min.z(), &max.z(), z_format, flags);
ImGui::PopItemWidth();
ImGui::PopID();
}
void
Application::drawVector2Slider(Containers::StringView id, Vector2& vec, Vector2 min, Vector2 max, const char* x_format,
const char* y_format, ImGuiSliderFlags_ flags)
{
ImGui::PushID(id.cbegin());
ImGui::PushMultiItemsWidths(2, ImGui::CalcItemWidth());
ImGui::SliderFloat("##X", &vec.x(), min.x(), max.x(), x_format, flags);
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderFloat("##Y", &vec.y(), min.y(), max.y(), y_format, flags);
ImGui::PopItemWidth();
ImGui::PopID();
}
void
Application::drawVector2dSlider(Containers::StringView id, Vector2d& vec, Vector2d min, Vector2d max,
const char* x_format, const char* y_format, ImGuiSliderFlags_ flags)
{
ImGui::PushID(id.cbegin());
ImGui::PushMultiItemsWidths(2, ImGui::CalcItemWidth());
ImGui::SliderScalar("##X", ImGuiDataType_Double, &vec.x(), &min.x(), &max.x(), x_format, flags);
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderScalar("##Y", ImGuiDataType_Double, &vec.y(), &min.y(), &max.y(), y_format, flags);
ImGui::PopItemWidth();
ImGui::PopID();
}
void
Application::openUri(Containers::StringView uri) {
if(!conf().isRunningInWine()) {
ShellExecuteA(nullptr, nullptr, uri.data(), nullptr, nullptr, SW_SHOWDEFAULT);
}
else {
std::system(Utility::format("winebrowser.exe {}", uri).cbegin());
}
}
void
Application::checkGameState() {
void SaveTool::checkGameState() {
WTS_PROCESS_INFOW* process_infos = nullptr;
unsigned long process_count = 0;
@ -551,5 +437,3 @@ Application::checkGameState() {
_gameState = GameState::Unknown;
}
}
}

View file

@ -1,7 +1,7 @@
#pragma once
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -16,12 +16,12 @@
// 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 <mutex>
#include <string>
#include <thread>
#include <Corrade/Containers/Pointer.h>
#include <Corrade/Containers/String.h>
#include <Corrade/Utility/Configuration.h>
#include <Corrade/Utility/Resource.h>
#ifdef SAVETOOL_DEBUG_BUILD
#include <Corrade/Utility/Tweakable.h>
@ -34,15 +34,13 @@
#include <SDL_timer.h>
#include <imgui.h>
#include <imgui_internal.h>
#include <efsw/efsw.hpp>
#include "../Managers/BackupManager.h"
#include "../Managers/MassManager.h"
#include "../Managers/ProfileManager.h"
#include "../Managers/StagedMassManager.h"
#include "../ProfileManager/ProfileManager.h"
#include "../MassManager/MassManager.h"
#include "../ToastQueue/ToastQueue.h"
#include "../UpdateChecker/UpdateChecker.h"
#ifdef SAVETOOL_DEBUG_BUILD
#define tw CORRADE_TWEAKABLE
@ -52,19 +50,17 @@ using namespace Corrade;
using namespace Containers::Literals;
using namespace Magnum;
namespace mbst {
class Application: public Platform::Sdl2Application, public efsw::FileWatchListener {
class SaveTool: public Platform::Sdl2Application, public efsw::FileWatchListener {
public:
explicit Application(const Arguments& arguments);
explicit SaveTool(const Arguments& arguments);
~Application() override;
~SaveTool() override;
void handleFileAction(efsw::WatchID watch_id,
const std::string& dir,
const std::string& filename,
efsw::Action action,
std::string old_filename) override;
std::string old_filename = "") override;
private:
// Events
@ -82,15 +78,20 @@ class Application: public Platform::Sdl2Application, public efsw::FileWatchListe
void anyEvent(SDL_Event& event) override;
enum InitStatus: std::int32_t {
enum InitStatus: Int {
InitSuccess,
ProfileManagerFailure
};
void initEvent(SDL_Event& event);
enum UpdateCheckStatus : Int {
CurlInitFailed = 0,
CurlError = 1,
CurlTimeout = 2,
};
void updateCheckEvent(SDL_Event& event);
enum FileEventType: std::int32_t {
enum FileEventType: Int {
FileAdded = efsw::Action::Add,
FileDeleted = efsw::Action::Delete,
FileModified = efsw::Action::Modified,
@ -103,6 +104,8 @@ class Application: public Platform::Sdl2Application, public efsw::FileWatchListe
void initialiseConfiguration();
void initialiseGui();
void initialiseManager();
auto initialiseToolDirectories() -> bool;
auto findGameDataDirectory() -> bool;
void initialiseMassManager();
void initialiseFileWatcher();
@ -114,21 +117,21 @@ class Application: public Platform::Sdl2Application, public efsw::FileWatchListe
void drawInitialisation();
void drawProfileManager();
void drawBackupListPopup();
void drawBackupFolder(const Managers::Vfs::Directory<Managers::Backup>& dir);
void drawBackupRestorePopup(std::size_t backup_index);
void drawBackupDeletePopup(std::size_t backup_index);
void drawDeleteProfilePopup(std::size_t profile_index);
auto drawBackupListPopup() -> ImGuiID;
auto drawBackupProfilePopup(std::size_t profile_index) -> ImGuiID;
auto drawDeleteProfilePopup(std::size_t profile_index) -> ImGuiID;
void drawManager();
bool drawIntEditPopup(int* value_to_edit, int max);
bool drawRenamePopup(Containers::ArrayView<char> name_view);
auto drawIntEditPopup(int* value_to_edit, int max) -> bool;
auto drawRenamePopup(Containers::ArrayView<char> name_view) -> bool;
void drawGeneralInfo();
void drawResearchInventory();
void drawMaterialRow(Containers::StringView name, std::int32_t tier, GameData::MaterialID id);
template<typename Getter, typename Setter>
void drawMaterialRow(Containers::StringView name, Int tier, Getter getter, Setter setter);
void drawUnavailableMaterialRow(Containers::StringView name, Int tier);
void drawMassManager();
void drawDeleteMassPopup(int mass_index);
void drawDeleteStagedMassPopup(Containers::StringView filename);
auto drawDeleteMassPopup(int mass_index) -> ImGuiID;
auto drawDeleteStagedMassPopup(Containers::StringView filename) -> ImGuiID;
void drawMassViewer();
void drawFrameInfo();
@ -137,55 +140,33 @@ class Application: public Platform::Sdl2Application, public efsw::FileWatchListe
void drawEyeColourPicker();
void drawCustomFrameStyles();
void drawArmour();
void drawBLAttachment();
void drawCustomArmourStyles();
void drawWeapons();
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 drawWeaponCategory(Containers::StringView name, Containers::ArrayView<Weapon> weapons_view, bool& dirty,
Containers::StringView payload_type, Containers::StringView payload_tooltip);
void drawWeaponEditor(Weapon& weapon);
void drawGlobalStyles();
void drawTuning();
void drawDecalEditor(GameObjects::Decal& decal);
void drawAccessoryEditor(GameObjects::Accessory& accessory,
Containers::ArrayView<GameObjects::CustomStyle> style_view);
auto getStyleName(std::int32_t id, Containers::ArrayView<GameObjects::CustomStyle> view)
-> Containers::StringView;
void drawDecalEditor(Decal& decal);
void drawAccessoryEditor(Accessory& accessory, Containers::ArrayView<CustomStyle> style_view);
auto getStyleName(Int id, Containers::ArrayView<CustomStyle> view) -> Containers::StringView;
enum DCSResult {
DCS_Fail,
DCS_ResetStyle,
DCS_Save
};
auto drawCustomStyle(GameObjects::CustomStyle& style) -> DCSResult;
auto drawCustomStyle(CustomStyle& style) -> DCSResult;
void drawAbout();
void drawGameState();
// Convenience wrappers over ImGui stuff
void drawHelpMarker(Containers::StringView text, float wrap_pos = 0.0f);
void drawTooltip(Containers::StringView text, float wrap_pos = 0.0f);
bool drawCheckbox(Containers::StringView label, bool value);
void drawVector3Drag(Containers::StringView id, Vector3& vec, float speed, Vector3 min, Vector3 max,
const char* x_format, const char* y_format, const char* z_format,
ImGuiSliderFlags_ flags = ImGuiSliderFlags_None);
void drawVector3dDrag(Containers::StringView id, Vector3d& vec, float speed, Vector3d min, Vector3d max,
const char* x_format, const char* y_format, const char* z_format,
ImGuiSliderFlags_ flags = ImGuiSliderFlags_None);
void drawVector3Slider(Containers::StringView id, Vector3& vec, Vector3 min, Vector3 max, const char* x_format,
const char* y_format, const char* z_format,
ImGuiSliderFlags_ flags = ImGuiSliderFlags_None);
void drawVector3dSlider(Containers::StringView id, Vector3d& vec, Vector3d min, Vector3d max,
const char* x_format, const char* y_format, const char* z_format,
ImGuiSliderFlags_ flags = ImGuiSliderFlags_None);
void drawVector2Slider(Containers::StringView id, Vector2& vec, Vector2 min, Vector2 max, const char* x_format,
const char* y_format, ImGuiSliderFlags_ flags = ImGuiSliderFlags_None);
void drawVector2dSlider(Containers::StringView id, Vector2d& vec, Vector2d min, Vector2d max,
const char* x_format, const char* y_format,
ImGuiSliderFlags_ flags = ImGuiSliderFlags_None);
void drawHelpMarker(Containers::StringView text, Float wrap_pos = 0.0f);
void drawTooltip(Containers::StringView text, Float wrap_pos = 0.0f);
template<typename Functor, typename... Args>
bool drawUnsafeWidget(Functor func, Args... args) {
auto drawUnsafeWidget(Functor func, Args... args) -> bool {
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)...);
@ -216,6 +197,7 @@ class Application: public Platform::Sdl2Application, public efsw::FileWatchListe
void checkForUpdates();
Utility::Configuration _conf{"MassBuilderSaveTool.ini"_s};
Utility::Resource _rs{"assets"_s};
// GUI-related members
@ -229,11 +211,11 @@ class Application: public Platform::Sdl2Application, public efsw::FileWatchListe
MassViewer
} _uiState{UiState::Disclaimer};
bool _aboutPopup = false;
bool _aboutPopup{false};
#ifdef SAVETOOL_DEBUG_BUILD
bool _demoWindow = false;
bool _styleEditor = false;
bool _metricsWindow = false;
bool _demoWindow{false};
bool _styleEditor{false};
bool _metricsWindow{false};
#endif
ToastQueue _queue;
@ -241,29 +223,37 @@ class Application: public Platform::Sdl2Application, public efsw::FileWatchListe
std::thread _initThread;
std::thread _updateThread;
std::uint32_t _initEventId;
std::uint32_t _updateEventId;
std::uint32_t _fileEventId;
UnsignedInt _initEventId;
UnsignedInt _updateEventId;
UnsignedInt _fileEventId;
Containers::String _lastError;
enum class GameState : std::uint8_t {
Containers::String _gameDataDir;
Containers::String _configDir;
Containers::String _saveDir;
Containers::String _screenshotsDir;
Containers::String _backupsDir;
Containers::String _stagingDir;
//Containers::String _armouryDir;
//Containers::String _armoursDir;
//Containers::String _weaponsDir;
//Containers::String _stylesDir;
enum class GameState : UnsignedByte {
Unknown, NotRunning, Running
} _gameState{GameState::Unknown};
SDL_TimerID _gameCheckTimerId = 0;
Containers::Pointer<Managers::ProfileManager> _profileManager;
GameObjects::Profile* _currentProfile = nullptr;
Containers::Pointer<ProfileManager> _profileManager;
Profile* _currentProfile{nullptr};
Containers::Pointer<Managers::BackupManager> _backupManager;
Containers::Pointer<MassManager> _massManager;
Mass* _currentMass{nullptr};
Containers::Pointer<Managers::MassManager> _massManager;
GameObjects::Mass* _currentMass = nullptr;
Containers::Pointer<Managers::StagedMassManager> _stagedMassManager;
GameObjects::Weapon* _currentWeapon = nullptr;
Weapon* _currentWeapon = nullptr;
Containers::Pointer<efsw::FileWatcher> _fileWatcher;
enum watchID {
@ -272,29 +262,36 @@ class Application: public Platform::Sdl2Application, public efsw::FileWatchListe
};
Containers::StaticArray<2, efsw::WatchID> _watchIDs;
Containers::Optional<UpdateChecker> _checker{Containers::NullOpt};
std::mutex _checkerMutex;
int _swapInterval = 1;
float _fpsCap = 60.0f;
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;
bool _skipDisclaimer{false};
bool _checkUpdatesOnStartup{true};
Containers::Optional<std::size_t> _selectedArmourSlot{Containers::NullOpt};
Containers::StaticArray<38, std::int32_t> _selectedArmourDecals{ValueInit};
Containers::StaticArray<38, std::int32_t> _selectedArmourAccessories{ValueInit};
std::uint32_t _selectedBLPlacement = 0;
std::int32_t _selectedWeaponPart = 0;
std::int32_t _selectedWeaponDecal = 0;
std::int32_t _selectedWeaponAccessory = 0;
bool _updateAvailable{false};
Containers::String _latestVersion;
Containers::String _releaseLink;
Containers::String _downloadLink;
bool _modifiedBySaveTool{false};
bool _jointsDirty{false};
bool _stylesDirty{false};
bool _eyeFlareDirty{false};
Containers::StaticArray<38, Int> _selectedArmourDecals{ValueInit};
Containers::StaticArray<38, Int> _selectedArmourAccessories{ValueInit};
Int _selectedBLPlacement{0};
Int _selectedWeaponPart{0};
Int _selectedWeaponDecal{0};
Int _selectedWeaponAccessory{0};
bool _meleeDirty{false};
bool _shieldsDirty{false};
bool _bShootersDirty{false};
bool _eShootersDirty{false};
bool _bLaunchersDirty{false};
bool _eLaunchersDirty{false};
bool _cheatMode{false};
bool _advancedMode{false};
Timeline _timeline;
};
}

View file

@ -1,5 +1,5 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -24,13 +24,13 @@
#include <fileapi.h>
#include <handleapi.h>
#include "Application.h"
#include "SaveTool.h"
namespace mbst {
void
Application::handleFileAction(efsw::WatchID watch_id, const std::string&, const std::string& filename,
efsw::Action action, std::string old_filename)
void SaveTool::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);
@ -48,9 +48,7 @@ Application::handleFileAction(efsw::WatchID watch_id, const std::string&, const
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)
{
if(!Utility::String::endsWith(filename, Utility::format("Profile{}.sav", _currentProfile->account()).data())) {
return;
}
@ -62,20 +60,19 @@ Application::handleFileAction(efsw::WatchID watch_id, const std::string&, const
SDL_PushEvent(&event);
}
void
Application::fileUpdateEvent(SDL_Event& event) {
void SaveTool::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);
_massManager->refreshStagedMass(filename);
return;
}
Containers::String old_filename;
std::int32_t index = 0;
std::int32_t old_index = 0;
Int index = 0;
Int old_index = 0;
bool is_current_profile = filename == _currentProfile->filename();
bool is_unit = filename.hasPrefix(_currentProfile->isDemo() ? "DemoUnit"_s : "Unit"_s);
if(is_unit) {
@ -84,10 +81,9 @@ Application::fileUpdateEvent(SDL_Event& event) {
}
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_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);
(old_filename[_currentProfile->isDemo() ? 9 : 5] - 0x30);
}
switch(event.user.code) {
@ -127,8 +123,8 @@ Application::fileUpdateEvent(SDL_Event& event) {
}
else {
if(_modifiedBySaveTool && _currentMass->filename() == filename) {
auto handle = CreateFileW(Utility::Unicode::widen(Containers::StringView{filename}),
GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);
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;
@ -152,5 +148,3 @@ Application::fileUpdateEvent(SDL_Event& event) {
_queue.addToast(Toast::Type::Warning, "Unknown file action type"_s);
}
}
}

View file

@ -0,0 +1,280 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2022 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/Pair.h>
#include <Corrade/Containers/ScopeGuard.h>
#include <Corrade/Utility/Path.h>
#include <Corrade/Utility/Unicode.h>
#include <SDL_events.h>
#include <SDL_messagebox.h>
#include <shlobj.h>
#include "../FontAwesome/IconsFontAwesome5.h"
#include "../FontAwesome/IconsFontAwesome5Brands.h"
#include "../Logger/Logger.h"
#include "SaveTool.h"
void SaveTool::initEvent(SDL_Event& event) {
_initThread.join();
switch(event.user.code) {
case InitSuccess:
_uiState = UiState::ProfileManager;
ImGui::CloseCurrentPopup();
break;
case ProfileManagerFailure:
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error ",
_profileManager->lastError().data(), window());
exit(EXIT_FAILURE);
break;
default:
break;
}
}
void SaveTool::initialiseConfiguration() {
LOG_INFO("Reading configuration file.");
if(_conf.hasValue("cheat_mode"_s)) {
_cheatMode = _conf.value<bool>("cheat_mode"_s);
}
else {
_conf.setValue("cheat_mode"_s, _cheatMode);
}
if(_conf.hasValue("advanced_mode"_s)) {
_advancedMode = _conf.value<bool>("advanced_mode"_s);
}
else {
_conf.setValue("advanced_mode"_s, _advancedMode);
}
if(_conf.hasValue("startup_update_check"_s)) {
_checkUpdatesOnStartup = _conf.value<bool>("startup_update_check"_s);
}
else {
_conf.setValue("startup_update_check"_s, _checkUpdatesOnStartup);
}
if(_conf.hasValue("skip_disclaimer"_s)) {
_skipDisclaimer = _conf.value<bool>("skip_disclaimer"_s);
}
else {
_conf.setValue("skip_disclaimer"_s, _skipDisclaimer);
}
if(_conf.hasValue("swap_interval"_s)) {
_swapInterval = _conf.value<int>("swap_interval"_s);
}
else {
_conf.setValue("swap_interval"_s, 1);
}
if(_conf.hasValue("fps_cap"_s)) {
_fpsCap = _conf.value<float>("fps_cap");
}
else {
_conf.setValue("fps_cap", 60.0f);
}
if(_conf.hasValue("frame_limit"_s)) {
std::string frame_limit = _conf.value("frame_limit"_s);
if(frame_limit == "half_vsync"_s) {
_swapInterval = 2;
}
_conf.removeValue("frame_limit"_s);
}
setSwapInterval(_swapInterval);
if(_swapInterval == 0) {
setMinimalLoopPeriod(0);
}
_conf.save();
}
void SaveTool::initialiseGui() {
LOG_INFO("Initialising Dear ImGui.");
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
const auto size = Vector2{windowSize()}/dpiScaling();
auto reg_font = _rs.getRaw("SourceSansPro-Regular.ttf"_s);
ImFontConfig font_config;
font_config.FontDataOwnedByAtlas = false;
std::strcpy(font_config.Name, "Source Sans Pro");
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(reg_font.data()), int(reg_font.size()),
20.0f * Float(framebufferSize().x()) / size.x(), &font_config);
auto icon_font = _rs.getRaw(FONT_ICON_FILE_NAME_FAS);
static const ImWchar icon_range[] = { ICON_MIN_FA, ICON_MAX_FA, 0 };
ImFontConfig icon_config;
icon_config.FontDataOwnedByAtlas = false;
icon_config.MergeMode = true;
icon_config.PixelSnapH = true;
icon_config.OversampleH = icon_config.OversampleV = 1;
icon_config.GlyphMinAdvanceX = 18.0f;
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(icon_font.data()), int(icon_font.size()),
16.0f * Float(framebufferSize().x()) / size.x(), &icon_config, icon_range);
auto brand_font = _rs.getRaw(FONT_ICON_FILE_NAME_FAB);
static const ImWchar brand_range[] = { ICON_MIN_FAB, ICON_MAX_FAB, 0 };
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(brand_font.data()), int(brand_font.size()),
16.0f * Float(framebufferSize().x()) / size.x(), &icon_config, brand_range);
auto mono_font = _rs.getRaw("SourceCodePro-Regular.ttf"_s);
ImVector<ImWchar> range;
ImFontGlyphRangesBuilder builder;
builder.AddRanges(io.Fonts->GetGlyphRangesDefault());
builder.AddChar(u'š'); // This allows displaying Vladimír Vondruš' name in Corrade's and Magnum's licences.
builder.BuildRanges(&range);
io.Fonts->AddFontFromMemoryTTF(const_cast<char*>(mono_font.data()), int(mono_font.size()),
18.0f * Float(framebufferSize().x()) / size.x(), &font_config, range.Data);
_imgui = ImGuiIntegration::Context(*ImGui::GetCurrentContext(), windowSize());
io.IniFilename = nullptr;
ImGuiStyle& style = ImGui::GetStyle();
style.WindowTitleAlign = {0.5f, 0.5f};
style.FrameRounding = 3.2f;
style.Colors[ImGuiCol_WindowBg] = ImColor(0xff1f1f1f);
}
void SaveTool::initialiseManager() {
LOG_INFO("Initialising the profile manager.");
SDL_Event event;
SDL_zero(event);
event.type = _initEventId;
_profileManager.emplace(_saveDir, _backupsDir);
if(!_profileManager->ready()) {
event.user.code = ProfileManagerFailure;
SDL_PushEvent(&event);
return;
}
event.user.code = InitSuccess;
SDL_PushEvent(&event);
}
auto SaveTool::initialiseToolDirectories() -> bool {
LOG_INFO("Initialising Save Tool directories.");
_backupsDir = Utility::Path::join(Utility::Path::split(*Utility::Path::executableLocation()).first(), "backups");
_stagingDir = Utility::Path::join(Utility::Path::split(*Utility::Path::executableLocation()).first(), "staging");
//_armouryDir = Utility::Directory::join(Utility::Directory::path(Utility::Directory::executableLocation()), "armoury");
//_armoursDir = Utility::Directory::join(_armouryDir, "armours");
//_weaponsDir = Utility::Directory::join(_armouryDir, "weapons");
//_stylesDir = Utility::Directory::join(_armouryDir, "styles");
if(!Utility::Path::exists(_backupsDir)) {
LOG_WARNING("Backups directory not found, creating...");
if(!Utility::Path::make(_backupsDir)) {
LOG_ERROR(_lastError = "Couldn't create the backups directory.");
return false;
}
}
if(!Utility::Path::exists(_stagingDir)) {
LOG_WARNING("Staging directory not found, creating...");
if(!Utility::Path::make(_stagingDir)) {
LOG_ERROR(_lastError = "Couldn't create the staging directory.");
return false;
}
}
//if(!Utility::Directory::exists(_armouryDir)) {
// Utility::Debug{} << "Armoury directory not found, creating...";
// if(!Utility::Path::make(_armouryDir)) {
// Utility::Error{} << (_lastError = "Couldn't create the armoury directory.");
// return false;
// }
//}
//if(!Utility::Directory::exists(_armoursDir)) {
// Utility::Debug{} << "Armours directory not found, creating...";
// if(!Utility::Path::make(_armoursDir)) {
// Utility::Error{} << (_lastError = "Couldn't create the armours directory.");
// return false;
// }
//}
//if(!Utility::Directory::exists(_weaponsDir)) {
// Utility::Debug{} << "Weapons directory not found, creating...";
// if(!Utility::Path::make(_weaponsDir)) {
// Utility::Error{} << (_lastError = "Couldn't create the weapons directory.");
// return false;
// }
//}
//if(!Utility::Directory::exists(_stylesDir)) {
// Utility::Debug{} << "Styles directory not found, creating...";
// if(!Utility::Path::make(_stylesDir)) {
// Utility::Error{} << (_lastError = "Couldn't create the styles directory.");
// return false;
// }
//}
return true;
}
auto SaveTool::findGameDataDirectory() -> bool {
LOG_INFO("Searching for the game's save directory.");
wchar_t* localappdata_path = nullptr;
Containers::ScopeGuard guard{localappdata_path, CoTaskMemFree};
if(SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_NO_APPCONTAINER_REDIRECTION, nullptr, &localappdata_path) != S_OK)
{
_lastError = Utility::format("SHGetKnownFolderPath() failed with error code {}.", GetLastError());
LOG_ERROR(_lastError);
return false;
}
_gameDataDir = Utility::Path::join(Utility::Path::fromNativeSeparators(Utility::Unicode::narrow(localappdata_path)), "MASS_Builder"_s);
if(!Utility::Path::exists(_gameDataDir)) {
LOG_ERROR(_lastError = _gameDataDir + " wasn't found. Make sure to play the game at least once."_s);
return false;
}
_configDir = Utility::Path::join(_gameDataDir, "Saved/Config/WindowsNoEditor"_s);
_saveDir = Utility::Path::join(_gameDataDir, "Saved/SaveGames"_s);
_screenshotsDir = Utility::Path::join(_gameDataDir, "Saved/Screenshots/WindowsNoEditor"_s);
return true;
}
void SaveTool::initialiseMassManager() {
LOG_INFO("Initialising the M.A.S.S. manager.");
_massManager.emplace(_saveDir, _currentProfile->account(), _currentProfile->isDemo(), _stagingDir);
}
void SaveTool::initialiseFileWatcher() {
LOG_INFO("Initialising the file watcher.");
_fileWatcher.emplace();
_watchIDs[SaveDir] = _fileWatcher->addWatch(_saveDir, this, false);
_watchIDs[StagingDir] = _fileWatcher->addWatch(_stagingDir, this, false);
_fileWatcher->watch();
}

View file

@ -1,5 +1,5 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -16,27 +16,20 @@
#include <algorithm>
#include <Corrade/Containers/ScopeGuard.h>
#include <Corrade/Utility/Format.h>
#include <Corrade/Utility/Path.h>
#include <Magnum/ImGuiIntegration/Integration.h>
#include <SDL_messagebox.h>
#include "../Configuration/Configuration.h"
#include "../FontAwesome/IconsFontAwesome5.h"
#include "../GameData/LastMissionId.h"
#include "../GameData/StoryProgress.h"
#include "../Maps/LastMissionId.h"
#include "../Maps/StoryProgress.h"
#include "Application.h"
#include "SaveTool.h"
namespace mbst {
void
Application::drawManager() {
void SaveTool::drawManager() {
ImGui::SetNextWindowPos({0.0f, ImGui::GetItemRectSize().y}, ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2{Vector2{float(windowSize().x()), float(windowSize().y()) - ImGui::GetItemRectSize().y} / dpiScaling()},
ImGui::SetNextWindowSize({Float(windowSize().x()), Float(windowSize().y()) - ImGui::GetItemRectSize().y},
ImGuiCond_Always);
if(!ImGui::Begin("##MainWindow", nullptr,
ImGuiWindowFlags_NoDecoration|ImGuiWindowFlags_NoMove|
@ -59,7 +52,7 @@ Application::drawManager() {
if(ImGui::BeginChild("##ProfileInfo",
{ImGui::GetContentRegionAvail().x * 0.60f, 0.0f},
ImGuiChildFlags_Border, ImGuiWindowFlags_MenuBar))
true, ImGuiWindowFlags_MenuBar))
{
if(ImGui::BeginMenuBar()) {
ImGui::TextUnformatted("Profile information");
@ -86,7 +79,7 @@ Application::drawManager() {
ImGui::SameLine();
if(ImGui::BeginChild("##MASSManager", {0.0f, 0.0f},
ImGuiChildFlags_Border, ImGuiWindowFlags_MenuBar))
true, ImGuiWindowFlags_MenuBar))
{
if(ImGui::BeginMenuBar()) {
ImGui::TextUnformatted("M.A.S.S. management");
@ -101,8 +94,7 @@ Application::drawManager() {
ImGui::End();
}
bool
Application::drawIntEditPopup(int* value_to_edit, int max) {
auto SaveTool::drawIntEditPopup(int* value_to_edit, int max) -> bool {
bool apply = false;
if(ImGui::BeginPopup("int_edit")) {
ImGui::Text("Please enter a value between 0 and %i:", max);
@ -111,7 +103,7 @@ Application::drawIntEditPopup(int* value_to_edit, int max) {
drawHelpMarker("You can either drag the widget left or right to change the value,\n"
"or click on it while holding Ctrl to edit the value directly.");
ImGui::SameLine();
drawUnsafeWidget([](auto... args){ return ImGui::SliderInt("##IntSlider", args...); },
drawUnsafeWidget([](auto... args){ return ImGui::SliderInt("", args...); },
value_to_edit, 0, max, "%d", ImGuiSliderFlags_AlwaysClamp);
ImGui::SameLine();
if(drawUnsafeWidget([]{ return ImGui::Button("Apply"); })) {
@ -125,8 +117,7 @@ Application::drawIntEditPopup(int* value_to_edit, int max) {
return apply;
}
bool
Application::drawRenamePopup(Containers::ArrayView<char> name_view) {
auto SaveTool::drawRenamePopup(Containers::ArrayView<char> name_view) -> bool {
bool apply = false;
if(ImGui::BeginPopup("name_edit")) {
ImGui::TextUnformatted("Please enter a new name. Conditions:");
@ -142,15 +133,13 @@ Application::drawRenamePopup(Containers::ArrayView<char> name_view) {
(name_view[0] != ' ' && name_view[len - 1] != ' ') ? ICON_FA_CHECK : ICON_FA_TIMES);
static auto callback = [](ImGuiInputTextCallbackData* data)->int {
if(data->EventChar < 256 &&
std::strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789- ", char(data->EventChar)))
{
if(data->EventChar < 256 && std::strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789- ", char(data->EventChar))) {
return 0;
}
return 1;
};
drawUnsafeWidget([](auto... args){ return ImGui::InputText("##NameField", args...); },
drawUnsafeWidget([](auto... args){ return ImGui::InputText("", args...); },
name_view.data(), name_view.size(),
ImGuiInputTextFlags_CallbackCharFilter,
callback, nullptr);
@ -180,17 +169,16 @@ Application::drawRenamePopup(Containers::ArrayView<char> name_view) {
return apply;
}
void
Application::drawGeneralInfo() {
void SaveTool::drawGeneralInfo() {
if(!_currentProfile) {
return;
}
ImGui::Text("Credits: %i", _currentProfile->credits());
auto it = std::find_if(GameData::story_progress.begin(), GameData::story_progress.end(),
[this](const GameData::StoryProgressPoint& p){ return p.id == _currentProfile->storyProgress(); });
if(it != GameData::story_progress.end())
auto it = std::find_if(story_progress.begin(), story_progress.end(),
[this](const StoryProgressPoint& p){ return p.id == _currentProfile->storyProgress(); });
if(it != story_progress.end())
{
ImGui::TextUnformatted("Story progress:");
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemSpacing.x / 4.0f);
@ -205,8 +193,8 @@ Application::drawGeneralInfo() {
ImGui::Text("Story progress: 0x%x", _currentProfile->storyProgress());
}
if(GameData::mission_id_map.find(_currentProfile->lastMissionId()) != GameData::mission_id_map.cend()) {
ImGui::Text("Last mission: %s", GameData::mission_id_map.at(_currentProfile->lastMissionId()).data());
if(mission_id_map.find(_currentProfile->lastMissionId()) != mission_id_map.cend()) {
ImGui::Text("Last mission: %s", mission_id_map.at(_currentProfile->lastMissionId()).data());
}
else if(_currentProfile->lastMissionId() == -1) {
ImGui::TextUnformatted("Last mission: none");
@ -217,7 +205,7 @@ Application::drawGeneralInfo() {
drawTooltip("This is the last mission selected in the mission selection screen, not the last mission played.",
float(windowSize().x()) * 0.35f);
const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
const Float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing();
ImGui::Dummy({ImGui::GetContentRegionAvail().x, ImGui::GetContentRegionAvail().y - footer_height_to_reserve});
ImGui::Separator();
@ -236,13 +224,13 @@ Application::drawGeneralInfo() {
}
}
if(!conf().cheatMode()) {
if(!_cheatMode) {
return;
}
ImGui::SameLine();
static std::int32_t credits;
static Int credits;
if(drawUnsafeWidget([]{ return ImGui::Button("Edit credits"); })) {
credits = _currentProfile->credits();
ImGui::OpenPopup("int_edit");
@ -263,7 +251,7 @@ Application::drawGeneralInfo() {
if(_gameState != GameState::NotRunning) {
ImGui::CloseCurrentPopup();
}
for(const auto& sp : GameData::story_progress) {
for(const auto& sp : story_progress) {
if(ImGui::BeginMenu(sp.chapter.data())) {
if(!sp.after) {
if(ImGui::MenuItem(sp.point.data())) {
@ -289,8 +277,7 @@ Application::drawGeneralInfo() {
}
}
void
Application::drawResearchInventory() {
void SaveTool::drawResearchInventory() {
if(!_currentProfile) {
return;
}
@ -305,90 +292,156 @@ Application::drawResearchInventory() {
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("Engine materials");
ImGui::Text("Engine materials");
drawMaterialRow("Verse steel", 1, GameData::MaterialID::VerseSteel);
drawMaterialRow("Undinium", 2, GameData::MaterialID::Undinium);
drawMaterialRow("Necrium alloy", 3, GameData::MaterialID::NecriumAlloy);
drawMaterialRow("Lunarite", 4, GameData::MaterialID::Lunarite);
drawMaterialRow("Asterite", 5, GameData::MaterialID::Asterite);
drawMaterialRow("Hallite fragma", 6, GameData::MaterialID::HalliteFragma);
drawMaterialRow("Unnoctinium", 7, GameData::MaterialID::Unnoctinium);
drawMaterialRow("Verse steel", 1,
[this]{ return _currentProfile->verseSteel(); },
[this](Int amount){ return _currentProfile->setVerseSteel(amount); });
drawMaterialRow("Undinium", 2,
[this]{ return _currentProfile->undinium(); },
[this](Int amount){ return _currentProfile->setUndinium(amount); });
drawMaterialRow("Necrium alloy", 3,
[this]{ return _currentProfile->necriumAlloy(); },
[this](Int amount){ return _currentProfile->setNecriumAlloy(amount); });
drawMaterialRow("Lunarite", 4,
[this]{ return _currentProfile->lunarite(); },
[this](Int amount){ return _currentProfile->setLunarite(amount); });
drawMaterialRow("Asterite", 5,
[this]{ return _currentProfile->asterite(); },
[this](Int amount){ return _currentProfile->setAsterite(amount); });
drawMaterialRow("Hallite fragma", 6,
[this]{ return _currentProfile->halliteFragma(); },
[this](Int amount){ return _currentProfile->setHalliteFragma(amount); });
drawUnavailableMaterialRow("Unnoctinium", 7);
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("OS materials");
ImGui::Text("OS materials");
drawMaterialRow("Ednil", 1, GameData::MaterialID::Ednil);
drawMaterialRow("Nuflalt", 2, GameData::MaterialID::Nuflalt);
drawMaterialRow("Aurelene", 3, GameData::MaterialID::Aurelene);
drawMaterialRow("Soldus", 4, GameData::MaterialID::Soldus);
drawMaterialRow("Synthesized N", 5, GameData::MaterialID::SynthesisedN);
drawMaterialRow("Nanoc", 6, GameData::MaterialID::Nanoc);
drawMaterialRow("Abyssillite", 7, GameData::MaterialID::Abyssillite);
drawMaterialRow("Ednil", 1,
[this]{ return _currentProfile->ednil(); },
[this](Int amount){ return _currentProfile->setEdnil(amount); });
drawMaterialRow("Nuflalt", 2,
[this]{ return _currentProfile->nuflalt(); },
[this](Int amount){ return _currentProfile->setNuflalt(amount); });
drawMaterialRow("Aurelene", 3,
[this]{ return _currentProfile->aurelene(); },
[this](Int amount){ return _currentProfile->setAurelene(amount); });
drawMaterialRow("Soldus", 4,
[this]{ return _currentProfile->soldus(); },
[this](Int amount){ return _currentProfile->setSoldus(amount); });
drawMaterialRow("Synthesized N", 5,
[this]{ return _currentProfile->synthesisedN(); },
[this](Int amount){ return _currentProfile->setSynthesisedN(amount); });
drawMaterialRow("Nanoc", 6,
[this]{ return _currentProfile->nanoc(); },
[this](Int amount){ return _currentProfile->setNanoc(amount); });
drawUnavailableMaterialRow("Abyssillite", 7);
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("Architect materials");
ImGui::Text("Architect materials");
drawMaterialRow("Alcarbonite", 1, GameData::MaterialID::Alcarbonite);
drawMaterialRow("Keriphene", 2, GameData::MaterialID::Keriphene);
drawMaterialRow("Nitinol-CM", 3, GameData::MaterialID::NitinolCM);
drawMaterialRow("Quarkium", 4, GameData::MaterialID::Quarkium);
drawMaterialRow("Alterene", 5, GameData::MaterialID::Alterene);
drawMaterialRow("Cosmium", 6, GameData::MaterialID::Cosmium);
drawMaterialRow("Purified quarkium", 7, GameData::MaterialID::PurifiedQuarkium);
drawMaterialRow("Alcarbonite", 1,
[this]{ return _currentProfile->alcarbonite(); },
[this](Int amount){ return _currentProfile->setAlcarbonite(amount); });
drawMaterialRow("Keripehene", 2,
[this]{ return _currentProfile->keriphene(); },
[this](Int amount){ return _currentProfile->setKeriphene(amount); });
drawMaterialRow("Nitinol-CM", 3,
[this]{ return _currentProfile->nitinolCM(); },
[this](Int amount){ return _currentProfile->setNitinolCM(amount); });
drawMaterialRow("Quarkium", 4,
[this]{ return _currentProfile->quarkium(); },
[this](Int amount){ return _currentProfile->setQuarkium(amount); });
drawMaterialRow("Alterene", 5,
[this]{ return _currentProfile->alterene(); },
[this](Int amount){ return _currentProfile->setAlterene(amount); });
drawMaterialRow("Cosmium", 6,
[this]{ return _currentProfile->cosmium(); },
[this](Int amount){ return _currentProfile->setCosmium(amount); });
drawUnavailableMaterialRow("Purified quarkium", 7);
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted("Quark data");
ImGui::Text("Quark data");
drawMaterialRow("Mixed composition", 1, GameData::MaterialID::MixedComposition);
drawMaterialRow("Void residue", 2, GameData::MaterialID::VoidResidue);
drawMaterialRow("Muscular construction", 3, GameData::MaterialID::MuscularConstruction);
drawMaterialRow("Mineral exoskeletology", 4, GameData::MaterialID::MineralExoskeletology);
drawMaterialRow("Carbonized skin", 5, GameData::MaterialID::CarbonisedSkin);
drawMaterialRow("Isolated void particle", 6, GameData::MaterialID::IsolatedVoidParticle);
drawMaterialRow("Weaponised physiology", 7, GameData::MaterialID::WeaponisedPhysiology);
drawMaterialRow("Mixed composition", 1,
[this]{ return _currentProfile->mixedComposition(); },
[this](Int amount){ return _currentProfile->setMixedComposition(amount); });
drawMaterialRow("Void residue", 2,
[this]{ return _currentProfile->voidResidue(); },
[this](Int amount){ return _currentProfile->setVoidResidue(amount); });
drawMaterialRow("Muscular construction", 3,
[this]{ return _currentProfile->muscularConstruction(); },
[this](Int amount){ return _currentProfile->setMuscularConstruction(amount); });
drawMaterialRow("Mineral exoskeletology", 4,
[this]{ return _currentProfile->mineralExoskeletology(); },
[this](Int amount){ return _currentProfile->setMineralExoskeletology(amount); });
drawMaterialRow("Carbonized skin", 5,
[this]{ return _currentProfile->carbonisedSkin(); },
[this](Int amount){ return _currentProfile->setCarbonisedSkin(amount); });
drawMaterialRow("Isolated void particle", 6,
[this]{ return _currentProfile->isolatedVoidParticle(); },
[this](Int amount){ return _currentProfile->setIsolatedVoidParticle(amount); });
drawUnavailableMaterialRow("Weaponised physiology", 7);
ImGui::EndTable();
}
}
void
Application::drawMaterialRow(Containers::StringView name, std::int32_t tier, GameData::MaterialID id) {
template<typename Getter, typename Setter>
void SaveTool::drawMaterialRow(Containers::StringView name, Int tier, Getter getter, Setter setter) {
static_assert(std::is_same<decltype(getter()), Int>::value, "getter doesn't return an Int, and/or doesn't take zero arguments.");
static_assert(std::is_same<decltype(setter(0)), bool>::value, "setter doesn't return a bool, and/or doesn't take a single Int as an argument.");
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("T%i", tier);
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted(name.cbegin(), name.cend());
ImGui::TextUnformatted(name.data());
ImGui::TableSetColumnIndex(2);
ImGui::Text("%i", _currentProfile->material(id));
if(conf().cheatMode()) {
ImGui::TableSetColumnIndex(3);
ImGui::PushID(name.data());
static std::int32_t var = 0;
if(drawUnsafeWidget(ImGui::SmallButton, ICON_FA_EDIT)) {
var = _currentProfile->material(id);
ImGui::OpenPopup("int_edit");
}
drawTooltip("Edit");
if(drawIntEditPopup(&var, 9999)) {
if(!_currentProfile->setMaterial(id, var)) {
_queue.addToast(Toast::Type::Error, _currentProfile->lastError());
if(getter() != -1) {
ImGui::Text("%i", getter());
if(_cheatMode) {
ImGui::TableSetColumnIndex(3);
ImGui::PushID(name.data());
static Int var = 0;
if(drawUnsafeWidget(ImGui::SmallButton, ICON_FA_EDIT)) {
(var) = getter();
ImGui::OpenPopup("int_edit");
}
drawTooltip("Edit");
if(drawIntEditPopup(&(var), 9999)) {
if(!setter(var)) {
_queue.addToast(Toast::Type::Error, _currentProfile->lastError());
}
}
ImGui::PopID();
}
ImGui::PopID();
}
else {
ImGui::TextDisabled("Not found in the save file");
}
}
void
Application::drawMassManager() {
void SaveTool::drawUnavailableMaterialRow(Containers::StringView name, Int tier) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("T%i", tier);
ImGui::TableSetColumnIndex(1);
ImGui::TextUnformatted(name.data());
ImGui::TableSetColumnIndex(2);
ImGui::TextDisabled("Unavailable as of game version " SUPPORTED_GAME_VERSION);
}
void SaveTool::drawMassManager() {
if(!_massManager) {
return;
}
static int mass_to_delete = 0;
static ImGuiID mass_deletion_popup_ID = drawDeleteMassPopup(mass_to_delete);
if(ImGui::BeginTable("##HangarsTable", 4,
ImGuiTableFlags_BordersOuter|ImGuiTableFlags_RowBg|ImGuiTableFlags_ScrollY,
@ -417,7 +470,7 @@ Application::drawMassManager() {
ImGui::TableSetColumnIndex(0);
ImGui::Selectable(Utility::format("{:.2d}", i + 1).data(),
false, ImGuiSelectableFlags_SpanAllColumns|ImGuiSelectableFlags_AllowItemOverlap);
if(_massManager->hangar(i).state() == GameObjects::Mass::State::Valid &&
if(_massManager->hangar(i).state() == Mass::State::Valid &&
ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceNoHoldToOpenOthers))
{
drag_drop_index = i;
@ -431,21 +484,21 @@ Application::drawMassManager() {
if(const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("StagedMass")) {
if(payload->DataSize != sizeof(Containers::String)) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal error",
"payload->DataSize != sizeof(Containers::String) in Application::drawMassManager()",
"payload->DataSize != sizeof(Containers::String) in SaveTool::drawMassManager()",
window());
exit(EXIT_FAILURE);
}
Containers::StringView file = *static_cast<Containers::String*>(payload->Data);
if(!_stagedMassManager->import(file, i, _currentProfile->account(), _currentProfile->isDemo())) {
if(!_massManager->importMass(file, i)) {
_queue.addToast(Toast::Type::Error, _massManager->lastError());
}
}
else if((payload = ImGui::AcceptDragDropPayload("Mass"))) {
if(payload->DataSize != sizeof(int)) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal error",
"payload->DataSize != sizeof(int) in Application::drawMassManager()",
"payload->DataSize != sizeof(int) in SaveTool::drawMassManager()",
window());
exit(EXIT_FAILURE);
}
@ -462,15 +515,14 @@ Application::drawMassManager() {
ImGui::TableSetColumnIndex(1);
switch(_massManager->hangar(i).state()) {
case GameObjects::Mass::State::Empty:
case Mass::State::Empty:
ImGui::TextDisabled("<empty>");
break;
case GameObjects::Mass::State::Invalid:
case Mass::State::Invalid:
ImGui::TextDisabled("<invalid>");
break;
case GameObjects::Mass::State::Valid:
ImGui::TextUnformatted(_massManager->hangar(i).name().cbegin(),
_massManager->hangar(i).name().cend());
case Mass::State::Valid:
ImGui::TextUnformatted(_massManager->hangar(i).name().data());
break;
}
@ -480,10 +532,10 @@ Application::drawMassManager() {
drawTooltip("This is the currently active frame slot.");
}
if(_massManager->hangar(i).state() != GameObjects::Mass::State::Empty) {
if(_massManager->hangar(i).state() != Mass::State::Empty) {
ImGui::TableSetColumnIndex(3);
ImGui::PushID(i);
if(_massManager->hangar(i).state() == GameObjects::Mass::State::Valid) {
if(_massManager->hangar(i).state() == Mass::State::Valid) {
if(ImGui::SmallButton(ICON_FA_SEARCH)) {
_currentMass = &_massManager->hangar(i);
_uiState = UiState::MassViewer;
@ -498,10 +550,9 @@ Application::drawMassManager() {
ImGui::SameLine(0.0f, 2.0f);
if(drawUnsafeWidget(ImGui::SmallButton, ICON_FA_TRASH_ALT)) {
mass_to_delete = i;
ImGui::OpenPopup("Confirmation##DeleteMassConfirmation");
ImGui::OpenPopup(mass_deletion_popup_ID);
}
drawTooltip("Delete");
drawDeleteMassPopup(mass_to_delete);
ImGui::PopID();
}
}
@ -511,6 +562,7 @@ Application::drawMassManager() {
drawDeleteMassPopup(mass_to_delete);
static ImGuiID staged_mass_deletion_popup_ID = drawDeleteStagedMassPopup("");
static Containers::StringView staged_mass_to_delete;
if(ImGui::BeginTable("##StagingArea", 2,
@ -527,33 +579,32 @@ Application::drawMassManager() {
ImGui::TextUnformatted("Staging area");
ImGui::SameLine();
if(ImGui::SmallButton(ICON_FA_FOLDER_OPEN " Open staging folder")) {
openUri(Utility::Path::toNativeSeparators(conf().directories().staging));
openUri(Utility::Path::toNativeSeparators(_stagingDir));
}
for(const auto& mass : _stagedMassManager->stagedMasses()) {
for(const auto& pair : _massManager->stagedMasses()) {
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
Containers::String staged_formatted = Utility::format("{} ({})", mass.name, mass.filename);
Containers::String staged_formatted = Utility::format("{} ({})", pair.second, pair.first);
ImGui::Selectable(staged_formatted.data());
if((ImGui::CalcTextSize(staged_formatted.data()).x + ImGui::GetStyle().FramePadding.x) > ImGui::GetContentRegionAvail().x) {
drawTooltip(staged_formatted.data());
}
if(ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceNoHoldToOpenOthers)) {
ImGui::SetDragDropPayload("StagedMass", &(mass.filename), sizeof(Containers::String));
ImGui::SetDragDropPayload("StagedMass", &(pair.first), sizeof(Containers::String));
ImGui::Text("%s - Staged", mass.name.cbegin());
ImGui::Text("%s - Staged", pair.second.data());
ImGui::EndDragDropSource();
}
ImGui::TableSetColumnIndex(1);
ImGui::PushID(mass.filename.data());
ImGui::PushID(pair.first.data());
if(ImGui::SmallButton(ICON_FA_TRASH_ALT)) {
staged_mass_to_delete = mass.filename;
ImGui::OpenPopup("Confirmation##DeleteStagedMassConfirmation");
staged_mass_to_delete = pair.first;
ImGui::OpenPopup(staged_mass_deletion_popup_ID);
}
drawTooltip("Delete");
drawDeleteStagedMassPopup(staged_mass_to_delete);
ImGui::PopID();
}
@ -563,7 +614,7 @@ Application::drawMassManager() {
if(const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("Mass")) {
if(payload->DataSize != sizeof(int)) {
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Fatal error",
"payload->DataSize != sizeof(int) in Application::drawMassManager()",
"payload->DataSize != sizeof(int) in SaveTool::drawMassManager()",
window());
exit(EXIT_FAILURE);
}
@ -581,28 +632,27 @@ Application::drawMassManager() {
drawDeleteStagedMassPopup(staged_mass_to_delete);
}
void
Application::drawDeleteMassPopup(int mass_index) {
auto SaveTool::drawDeleteMassPopup(int mass_index) -> ImGuiID {
if(!ImGui::BeginPopupModal("Confirmation##DeleteMassConfirmation", nullptr,
ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove))
{
return;
return ImGui::GetID("Confirmation##DeleteMassConfirmation");
}
Containers::ScopeGuard guard{ImGui::EndPopup};
if(_massManager->hangar(mass_index).state() == GameObjects::Mass::State::Empty) {
if(_massManager->hangar(mass_index).state() == Mass::State::Empty) {
ImGui::CloseCurrentPopup();
return;
ImGui::EndPopup();
return 0;
}
if(_gameState != GameState::NotRunning) {
ImGui::CloseCurrentPopup();
return;
ImGui::EndPopup();
return 0;
}
ImGui::PushTextWrapPos(float(windowSize().x()) * 0.40f);
if(_massManager->hangar(mass_index).state() == GameObjects::Mass::State::Invalid) {
if(_massManager->hangar(mass_index).state() == Mass::State::Invalid) {
ImGui::Text("Are you sure you want to delete the invalid M.A.S.S. data in hangar %.2i ? This operation is irreversible.",
mass_index + 1);
}
@ -632,19 +682,22 @@ Application::drawDeleteMassPopup(int mass_index) {
ImGui::EndTable();
}
ImGui::EndPopup();
return 0;
}
void
Application::drawDeleteStagedMassPopup(Containers::StringView filename) {
auto SaveTool::drawDeleteStagedMassPopup(Containers::StringView filename) -> ImGuiID {
if(!ImGui::BeginPopupModal("Confirmation##DeleteStagedMassConfirmation", nullptr,
ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove))
{
return;
return ImGui::GetID("Confirmation##DeleteStagedMassConfirmation");
}
ImGui::PushTextWrapPos(float(windowSize().x()) * 0.40f);
ImGui::Text("Are you sure you want to delete the staged M.A.S.S. named %s ? This operation is irreversible.",
_stagedMassManager->at(filename).filename.cbegin());
_massManager->stagedMasses().at(filename).data());
ImGui::PopTextWrapPos();
if(ImGui::BeginTable("##DeleteStagedMassLayout", 2)) {
@ -655,9 +708,8 @@ Application::drawDeleteStagedMassPopup(Containers::StringView filename) {
ImGui::TableSetColumnIndex(1);
if(ImGui::Button("Yes")) {
if(!_stagedMassManager->remove(filename)) {
_queue.addToast(Toast::Type::Error,
"Couldn't delete the staged M.A.S.S. at " + filename + ": " + _stagedMassManager->lastError());
if(!_massManager->deleteStagedMass(filename)) {
_queue.addToast(Toast::Type::Error, _massManager->lastError());
}
ImGui::CloseCurrentPopup();
}
@ -670,6 +722,6 @@ Application::drawDeleteStagedMassPopup(Containers::StringView filename) {
}
ImGui::EndPopup();
}
return 0;
}

View file

@ -1,5 +1,5 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2024 Guillaume Jacquemin
// Copyright (C) 2021-2022 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
@ -19,22 +19,15 @@
#include <Magnum/ImGuiIntegration/Integration.h>
#include <imgui_internal.h>
#include "../Configuration/Configuration.h"
#include "../FontAwesome/IconsFontAwesome5.h"
#include "../GameData/Accessories.h"
#include "../Maps/Accessories.h"
#define STYLENAMES_DEFINITION
#include "../GameData/StyleNames.h"
#include "../ImportExport/Export.h"
#include "../Maps/StyleNames.h"
#include "Application.h"
#include "SaveTool.h"
namespace mbst {
void
Application::drawMassViewer() {
if(!_currentMass || _currentMass->state() != GameObjects::Mass::State::Valid) {
void SaveTool::drawMassViewer() {
if(!_currentMass || _currentMass->state() != Mass::State::Valid) {
_currentMass = nullptr;
_currentWeapon = nullptr;
_uiState = UiState::MainManager;
@ -43,7 +36,7 @@ Application::drawMassViewer() {
}
ImGui::SetNextWindowPos({0.0f, ImGui::GetItemRectSize().y}, ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2{Vector2{float(windowSize().x()), float(windowSize().y()) - ImGui::GetItemRectSize().y} / dpiScaling()},
ImGui::SetNextWindowSize({Float(windowSize().x()), Float(windowSize().y()) - ImGui::GetItemRectSize().y},
ImGuiCond_Always);
if(!ImGui::Begin("##MassViewer", nullptr,
ImGuiWindowFlags_NoDecoration|ImGuiWindowFlags_NoMove|
@ -55,7 +48,7 @@ Application::drawMassViewer() {
if(ImGui::BeginChild("##MassInfo",
{0.0f, 0.0f},
ImGuiChildFlags_Border, ImGuiWindowFlags_MenuBar))
true, ImGuiWindowFlags_MenuBar))
{
if(ImGui::BeginMenuBar()) {
if(ImGui::BeginTable("##MassViewerMenuTable", 4)) {
@ -91,9 +84,8 @@ Application::drawMassViewer() {
_jointsDirty = false;
_stylesDirty = false;
_eyeFlareDirty = false;
_selectedArmourSlot = Containers::NullOpt;
_selectedArmourDecals = Containers::StaticArray<38, std::int32_t>{ValueInit};
_selectedArmourAccessories = Containers::StaticArray<38, std::int32_t>{ValueInit};
_selectedArmourDecals = Containers::StaticArray<38, Int>{ValueInit};
_selectedArmourAccessories = Containers::StaticArray<38, Int>{ValueInit};
_selectedBLPlacement = 0;
_selectedWeaponPart = 0;
_selectedWeaponDecal = 0;
@ -130,13 +122,6 @@ Application::drawMassViewer() {
ImGui::EndTabItem();
}
if(_currentMass->bulletLauncherAttachmentStyle() != GameObjects::BulletLauncherAttachmentStyle::NotFound &&
ImGui::BeginTabItem("Bullet launcher attachment"))
{
drawBLAttachment();
ImGui::EndTabItem();
}
if(ImGui::BeginTabItem("Custom armour styles")) {
drawCustomArmourStyles();
ImGui::EndTabItem();
@ -147,7 +132,7 @@ Application::drawMassViewer() {
ImGui::EndTabItem();
}
if(!_currentMass->globalStyles().isEmpty() && ImGui::BeginTabItem("Global styles")) {
if(_currentMass->globalStyles().size() != 0 && ImGui::BeginTabItem("Global styles")) {
drawGlobalStyles();
ImGui::EndTabItem();
}
@ -168,9 +153,8 @@ Application::drawMassViewer() {
ImGui::End();
}
void
Application::drawGlobalStyles() {
if(!_currentMass || _currentMass->state() != GameObjects::Mass::State::Valid) {
void SaveTool::drawGlobalStyles() {
if(!_currentMass || _currentMass->state() != Mass::State::Valid) {
return;
}
@ -181,9 +165,10 @@ Application::drawGlobalStyles() {
ImGui::TextWrapped("In-game values are multiplied by 100. For example, 0.500 here is equal to 50 in-game.");
for(std::uint32_t i = 0; i < _currentMass->globalStyles().size(); i++) {
for(UnsignedInt i = 0; i < _currentMass->globalStyles().size(); i++) {
ImGui::PushID(int(i));
auto result = drawCustomStyle(_currentMass->globalStyles()[i]);
DCSResult result;
result = drawCustomStyle(_currentMass->globalStyles()[i]);
switch(result) {
case DCS_ResetStyle:
_currentMass->getGlobalStyles();
@ -204,9 +189,8 @@ Application::drawGlobalStyles() {
ImGui::EndChild();
}
void
Application::drawTuning() {
if(!_currentMass || _currentMass->state() != GameObjects::Mass::State::Valid) {
void SaveTool::drawTuning() {
if(!_currentMass || _currentMass->state() != Mass::State::Valid) {
return;
}
@ -237,7 +221,7 @@ Application::drawTuning() {
ImGui::TableNextColumn();
ImGui::TextUnformatted("Gears");
for(std::uint32_t i = 0; i < _currentMass->gears().size(); i++) {
for(UnsignedInt i = 0; i < _currentMass->gears().size(); i++) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%i", _currentMass->gears()[i]);
@ -263,7 +247,7 @@ Application::drawTuning() {
ImGui::TableNextColumn();
ImGui::TextUnformatted("Modules");
for(std::uint32_t i = 0; i < _currentMass->modules().size(); i++) {
for(UnsignedInt i = 0; i < _currentMass->modules().size(); i++) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%i", _currentMass->modules()[i]);
@ -289,7 +273,7 @@ Application::drawTuning() {
ImGui::TableNextColumn();
ImGui::TextUnformatted("Techs");
for(std::uint32_t i = 0; i < _currentMass->techs().size(); i++) {
for(UnsignedInt i = 0; i < _currentMass->techs().size(); i++) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("%i", _currentMass->techs()[i]);
@ -301,9 +285,8 @@ Application::drawTuning() {
ImGui::EndTable();
}
Application::DCSResult
Application::drawCustomStyle(GameObjects::CustomStyle& style) {
if(!_currentMass || _currentMass->state() != GameObjects::Mass::State::Valid) {
auto SaveTool::drawCustomStyle(CustomStyle& style) -> DCSResult {
if(!_currentMass || _currentMass->state() != Mass::State::Valid) {
return DCS_Fail;
}
@ -313,15 +296,13 @@ Application::drawCustomStyle(GameObjects::CustomStyle& style) {
DCSResult return_value = DCS_Fail;
if(!ImGui::BeginChild("##CustomStyle", {}, ImGuiChildFlags_Border|ImGuiChildFlags_AutoResizeY,
ImGuiWindowFlags_MenuBar))
{
if(!ImGui::BeginChild("##CustomStyle", {0.0f, 244.0f}, true, ImGuiWindowFlags_MenuBar)) {
ImGui::EndChild();
return DCS_Fail;
}
if(ImGui::BeginMenuBar()) {
ImGui::TextUnformatted(style.name.cbegin(), style.name.cend());
ImGui::TextUnformatted(style.name.data());
static Containers::StaticArray<33, char> name_buf{ValueInit};
if(ImGui::SmallButton(ICON_FA_EDIT " Rename")) {
@ -335,18 +316,6 @@ Application::drawCustomStyle(GameObjects::CustomStyle& style) {
style.name = name_buf.data();
}
if(ImGui::SmallButton(ICON_FA_FILE_EXPORT " Export")) {
if(!ImportExport::exportStyle(_currentMass->name(), style)) {
_queue.addToast(Toast::Type::Error, ImportExport::lastExportError());
}
else {
_queue.addToast(Toast::Type::Success, "Style exported successfully.");
}
}
if(drawUnsafeWidget(ImGui::SmallButton, ICON_FA_FILE_IMPORT " Import")) {
// TODO: implement once the style manager is ready.
}
ImGui::EndMenuBar();
}
@ -421,14 +390,13 @@ Application::drawCustomStyle(GameObjects::CustomStyle& style) {
return return_value;
}
void
Application::drawDecalEditor(GameObjects::Decal& decal) {
void SaveTool::drawDecalEditor(Decal& decal) {
ImGui::Text("ID: %i", decal.id);
if(ImGui::BeginTable("##DecalTable", conf().advancedMode() ? 2 : 1, ImGuiTableFlags_BordersInnerV)) {
if(ImGui::BeginTable("##DecalTable", _advancedMode ? 2 : 1, ImGuiTableFlags_BordersInnerV)) {
ImGui::TableSetupColumn("##Normal", ImGuiTableColumnFlags_WidthStretch);
if(conf().advancedMode()) {
if(_advancedMode) {
ImGui::TableSetupColumn("##Advanced", ImGuiTableColumnFlags_WidthStretch);
}
@ -452,17 +420,12 @@ Application::drawDecalEditor(GameObjects::Decal& decal) {
ImGui::SameLine();
drawHelpMarker("Right-click for more option, click the coloured square for the full picker.");
std::visit(
[this](auto& arg){
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Vector2>) {
drawVector2Slider("##Offset", arg, Vector2{0.0f}, Vector2{1.0f}, "X: %.3f", "Y: %.3f");
}
else if constexpr (std::is_same_v<T, Vector2d>) {
drawVector2dSlider("##Offset", arg, Vector2d{0.0}, Vector2d{1.0}, "X: %.3f", "Y: %.3f");
}
}, decal.offset
);
ImGui::PushMultiItemsWidths(2, ImGui::CalcItemWidth());
ImGui::SliderFloat("##OffsetX", &decal.offset.x(), 0.0f, 1.0f, "X: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderFloat("##OffsetY", &decal.offset.y(), 0.0f, 1.0f, "Y: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine();
drawHelpMarker("0.0 = -100 in-game\n1.0 = 100 in-game");
@ -479,7 +442,7 @@ Application::drawDecalEditor(GameObjects::Decal& decal) {
ImGui::Checkbox("##Wrap", &decal.wrap);
ImGui::EndGroup();
if(conf().advancedMode()) {
if(_advancedMode) {
ImGui::TableNextColumn();
ImGui::TextColored(ImColor(255, 255, 0), ICON_FA_EXCLAMATION_TRIANGLE);
@ -495,49 +458,34 @@ Application::drawDecalEditor(GameObjects::Decal& decal) {
ImGui::SameLine();
ImGui::BeginGroup();
ImGui::PushItemWidth(-1.0f);
std::visit(
[this](auto& arg){
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Vector3>) {
drawVector3Drag("##Position", arg, 1.0f, Vector3{-FLT_MAX}, Vector3{FLT_MAX}, "X: %.3f",
"Y: %.3f", "Z: %.3f");
}
else if constexpr (std::is_same_v<T, Vector3d>) {
drawVector3dDrag("##Position", arg, 1.0f, Vector3d{-DBL_MAX}, Vector3d{DBL_MAX}, "X: %.3f",
"Y: %.3f", "Z: %.3f");
}
}, decal.position
);
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::DragFloat("##PosX", &decal.position.x(), 1.0f, -FLT_MAX, +FLT_MAX, "X: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragFloat("##PosY", &decal.position.y(), 1.0f, -FLT_MAX, +FLT_MAX, "Y: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragFloat("##PosZ", &decal.position.z(), 1.0f, -FLT_MAX, +FLT_MAX, "Z: %.3f");
ImGui::PopItemWidth();
std::visit(
[this](auto& arg){
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Vector3>) {
drawVector3Drag("##U", arg, 1.0f, Vector3{-FLT_MAX}, Vector3{FLT_MAX}, "X: %.3f", "Y: %.3f",
"Z: %.3f");
}
else if constexpr (std::is_same_v<T, Vector3d>) {
drawVector3dDrag("##U", arg, 1.0f, Vector3d{-DBL_MAX}, Vector3d{DBL_MAX}, "X: %.3f", "Y: %.3f",
"Z: %.3f");
}
}, decal.uAxis
);
std::visit(
[this](auto& arg){
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Vector3>) {
drawVector3Drag("##V", arg, 1.0f, Vector3{-FLT_MAX}, Vector3{FLT_MAX}, "X: %.3f", "Y: %.3f",
"Z: %.3f");
}
else if constexpr (std::is_same_v<T, Vector3d>) {
drawVector3dDrag("##V", arg, 1.0f, Vector3d{-DBL_MAX}, Vector3d{DBL_MAX}, "X: %.3f", "Y: %.3f",
"Z: %.3f");
}
}, decal.vAxis
);
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::DragFloat("##UX", &decal.uAxis.x(), 1.0f, -FLT_MAX, +FLT_MAX, "X: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragFloat("##UY", &decal.uAxis.y(), 1.0f, -FLT_MAX, +FLT_MAX, "Y: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragFloat("##UZ", &decal.uAxis.z(), 1.0f, -FLT_MAX, +FLT_MAX, "Z: %.3f");
ImGui::PopItemWidth();
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::DragFloat("##VX", &decal.vAxis.x(), 1.0f, -FLT_MAX, +FLT_MAX, "X: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragFloat("##VY", &decal.vAxis.y(), 1.0f, -FLT_MAX, +FLT_MAX, "Y: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragFloat("##VZ", &decal.vAxis.z(), 1.0f, -FLT_MAX, +FLT_MAX, "Z: %.3f");
ImGui::PopItemWidth();
ImGui::EndGroup();
}
@ -546,13 +494,12 @@ Application::drawDecalEditor(GameObjects::Decal& decal) {
}
}
void
Application::drawAccessoryEditor(GameObjects::Accessory& accessory, Containers::ArrayView<GameObjects::CustomStyle> style_view) {
void SaveTool::drawAccessoryEditor(Accessory& accessory, Containers::ArrayView<CustomStyle> style_view) {
if(accessory.id < 1) {
ImGui::TextUnformatted("Accessory: <none>");
}
else if(GameData::accessories.find(accessory.id) != GameData::accessories.cend()) {
ImGui::Text("Accessory #%.4i - %s", accessory.id, GameData::accessories.at(accessory.id).name.data());
else if(accessories.find(accessory.id) != accessories.cend()) {
ImGui::Text("Accessory #%.4i - %s", accessory.id, accessories.at(accessory.id).name.data());
}
else {
ImGui::Text("Accessory #%i", accessory.id);
@ -561,8 +508,8 @@ Application::drawAccessoryEditor(GameObjects::Accessory& accessory, Containers::
ImGui::SameLine();
static std::int32_t tab = 0;
static Containers::Optional<GameData::Accessory::Size> size = Containers::NullOpt;
static Int tab = 0;
static Containers::Optional<AccessorySize> size = Containers::NullOpt;
if(ImGui::SmallButton("Change")) {
ImGui::OpenPopup("##AccessoryPopup");
if(accessory.id >= 3000) {
@ -585,7 +532,7 @@ Application::drawAccessoryEditor(GameObjects::Accessory& accessory, Containers::
"L",
"XL"
};
static const float selectable_width = 90.0f;
static const Float selectable_width = 90.0f;
ImGui::PushStyleVar(ImGuiStyleVar_SelectableTextAlign, {0.5f, 0.0f});
if(ImGui::Selectable("Primitives", tab == 0, ImGuiSelectableFlags_DontClosePopups, {selectable_width, 0.0f})) {
@ -620,53 +567,53 @@ Application::drawAccessoryEditor(GameObjects::Accessory& accessory, Containers::
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
if(ImGui::Selectable("S", size && *size == GameData::Accessory::Size::S, ImGuiSelectableFlags_DontClosePopups, {70.0f, 0.0f})) {
if(ImGui::Selectable("S", size && *size == AccessorySize::S, ImGuiSelectableFlags_DontClosePopups, {70.0f, 0.0f})) {
if(!size) {
size.emplace();
}
*size = GameData::Accessory::Size::S;
*size = AccessorySize::S;
}
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
if(ImGui::Selectable("M", size && *size == GameData::Accessory::Size::M, ImGuiSelectableFlags_DontClosePopups, {70.0f, 0.0f})) {
if(ImGui::Selectable("M", size && *size == AccessorySize::M, ImGuiSelectableFlags_DontClosePopups, {70.0f, 0.0f})) {
if(!size) {
size.emplace();
}
*size = GameData::Accessory::Size::M;
*size = AccessorySize::M;
}
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
if(ImGui::Selectable("L", size && *size == GameData::Accessory::Size::L, ImGuiSelectableFlags_DontClosePopups, {70.0f, 0.0f})) {
if(ImGui::Selectable("L", size && *size == AccessorySize::L, ImGuiSelectableFlags_DontClosePopups, {70.0f, 0.0f})) {
if(!size) {
size.emplace();
}
*size = GameData::Accessory::Size::L;
*size = AccessorySize::L;
}
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
if(ImGui::Selectable("XL", size && *size == GameData::Accessory::Size::XL, ImGuiSelectableFlags_DontClosePopups, {70.0f, 0.0f})) {
if(ImGui::Selectable("XL", size && *size == AccessorySize::XL, ImGuiSelectableFlags_DontClosePopups, {70.0f, 0.0f})) {
if(!size) {
size.emplace();
}
*size = GameData::Accessory::Size::XL;
*size = AccessorySize::XL;
}
ImGui::PopStyleVar();
ImGui::Separator();
if(ImGui::BeginListBox("##AccessoryListbox", {-1.0f, 0.0f})) {
for(const auto& [id, acc] : GameData::accessories) {
if(id >= tab * 1000 && id < ((tab + 1) * 1000) && (!size || *size == acc.size)) {
if(ImGui::Selectable(Utility::format("#{:.4d} - {} ({})", id, acc.name, size_labels[acc.size]).data(),
id == accessory.id))
for(const auto& acc : accessories) {
if(acc.first >= tab * 1000 && acc.first < ((tab + 1) * 1000) && (!size || *size == acc.second.size)) {
if(ImGui::Selectable(Utility::format("#{:.4d} - {} ({})", acc.first, acc.second.name, size_labels[acc.second.size]).data(),
acc.first == accessory.id))
{
accessory.id = id;
accessory.id = acc.first;
accessory.attachIndex = 0;
}
if(id == accessory.id) {
if(acc.first == accessory.id) {
ImGui::SetItemDefaultFocus();
}
}
@ -688,11 +635,11 @@ Application::drawAccessoryEditor(GameObjects::Accessory& accessory, Containers::
ImGui::BeginGroup();
drawAlignedText("Styles:");
if(conf().advancedMode()) {
if(_advancedMode) {
drawAlignedText("Base position:");
}
drawAlignedText("Position offset:");
if(conf().advancedMode()) {
if(_advancedMode) {
drawAlignedText("Base rotation:");
}
drawAlignedText("Rotation offset:");
@ -704,7 +651,7 @@ Application::drawAccessoryEditor(GameObjects::Accessory& accessory, Containers::
ImGui::BeginGroup();
ImGui::PushMultiItemsWidths(2, ImGui::CalcItemWidth());
if(ImGui::BeginCombo("##Style1", getStyleName(accessory.styles[0], style_view).data())) {
for(const auto& style : GameData::builtin_style_names) {
for(const auto& style : style_names) {
if(ImGui::Selectable(getStyleName(style.first, style_view).data(), accessory.styles[0] == style.first)) {
accessory.styles[0] = style.first;
}
@ -715,7 +662,7 @@ Application::drawAccessoryEditor(GameObjects::Accessory& accessory, Containers::
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
if(ImGui::BeginCombo("##Style2", getStyleName(accessory.styles[1], style_view).data())) {
for(const auto& style : GameData::builtin_style_names) {
for(const auto& style : style_names) {
if(ImGui::Selectable(getStyleName(style.first, style_view).data(), accessory.styles[1] == style.first)) {
accessory.styles[1] = style.first;
}
@ -725,86 +672,67 @@ Application::drawAccessoryEditor(GameObjects::Accessory& accessory, Containers::
}
ImGui::PopItemWidth();
if(conf().advancedMode()) {
std::visit(
[this](auto& arg){
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Vector3>) {
drawVector3Drag("##RelativePosition", arg, 1.0f, Vector3{-FLT_MAX}, Vector3{FLT_MAX}, "X: %.3f",
"Y: %.3f", "Z: %.3f");
}
else if constexpr (std::is_same_v<T, Vector3d>) {
drawVector3dDrag("##RelativePosition", arg, 1.0f, Vector3d{-DBL_MAX}, Vector3d{DBL_MAX}, "X: %.3f",
"Y: %.3f", "Z: %.3f");
}
}, accessory.relativePosition
);
if(_advancedMode) {
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::DragFloat("##PosX", &accessory.relativePosition.x(), 1.0f, -FLT_MAX, +FLT_MAX, "X: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragFloat("##PosY", &accessory.relativePosition.y(), 1.0f, -FLT_MAX, +FLT_MAX, "Y: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragFloat("##PosZ", &accessory.relativePosition.z(), 1.0f, -FLT_MAX, +FLT_MAX, "Z: %.3f");
ImGui::PopItemWidth();
}
std::visit(
[this](auto& arg){
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Vector3>) {
drawVector3Slider("##OffsetPosition", arg, Vector3{-500.0f}, Vector3{500.0f}, "X: %.3f", "Y: %.3f",
"Z: %.3f");
}
else if constexpr (std::is_same_v<T, Vector3d>) {
drawVector3dSlider("##OffsetPosition", arg, Vector3d{-500.0}, Vector3d{500.0}, "X: %.3f", "Y: %.3f",
"Z: %.3f");
}
}, accessory.relativePositionOffset
);
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::SliderFloat("##PosOffsetX", &accessory.relativePositionOffset.x(), -500.0f, +500.0f, "X: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderFloat("##PosOffsetY", &accessory.relativePositionOffset.y(), -500.0f, +500.0f, "Y: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderFloat("##PosOffsetZ", &accessory.relativePositionOffset.z(), -500.0f, +500.0f, "Z: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine();
drawHelpMarker("+/-500.0 = +/-250 in-game");
if(conf().advancedMode()) {
std::visit(
[this](auto& arg){
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Vector3>) {
drawVector3Drag("##RelativeRotation", arg, 1.0f, Vector3{-FLT_MAX}, Vector3{FLT_MAX}, "Roll: %.3f",
"Yaw: %.3f", "Pitch: %.3f");
}
else if constexpr (std::is_same_v<T, Vector3d>) {
drawVector3dDrag("##RelativeRotation", arg, 1.0f, Vector3d{-DBL_MAX}, Vector3d{DBL_MAX},
"Roll: %.3f", "Yaw: %.3f", "Pitch: %.3f");
}
}, accessory.relativePosition
);
if(_advancedMode) {
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::DragFloat("##RotX", &accessory.relativeRotation.x(), 1.0f, -FLT_MAX, +FLT_MAX, "Roll: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragFloat("##RotY", &accessory.relativeRotation.y(), 1.0f, -FLT_MAX, +FLT_MAX, "Yaw: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragFloat("##RotZ", &accessory.relativeRotation.z(), 1.0f, -FLT_MAX, +FLT_MAX, "Pitch: %.3f");
ImGui::PopItemWidth();
}
std::visit(
[this](auto& arg){
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Vector3>) {
drawVector3Slider("##OffsetRotation", arg, Vector3{-180.0f}, Vector3{180.0f}, "Roll: %.3f",
"Yaw: %.3f", "Pitch: %.3f");
}
else if constexpr (std::is_same_v<T, Vector3d>) {
drawVector3dSlider("##OffsetRotation", arg, Vector3d{-180.0}, Vector3d{180.0}, "Roll: %.3f",
"Yaw: %.3f", "Pitch: %.3f");
}
}, accessory.relativeRotationOffset
);
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::SliderFloat("##RotOffsetX", &accessory.relativeRotationOffset.x(), -180.0f, +180.0f, "Roll: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderFloat("##RotOffsetY", &accessory.relativeRotationOffset.y(), -180.0f, +180.0f, "Yaw: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderFloat("##RotOffsetZ", &accessory.relativeRotationOffset.z(), -180.0f, +180.0f, "Pitch: %.3f");
ImGui::PopItemWidth();
std::visit(
[this](auto& arg){
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, Vector3>) {
drawVector3Slider("##Scale", arg, Vector3{-3.0f}, Vector3{3.0f}, "X: %.3f", "Y: %.3f", "Z: %.3f");
}
else if constexpr (std::is_same_v<T, Vector3d>) {
drawVector3dSlider("##Scale", arg, Vector3d{-3.0}, Vector3d{3.0}, "X: %.3f", "Y: %.3f", "Z: %.3f");
}
}, accessory.localScale
);
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::SliderFloat("##ScaleX", &accessory.localScale.x(), -3.0f, +3.0f, "X: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderFloat("##ScaleY", &accessory.localScale.y(), -3.0f, +3.0f, "Y: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderFloat("##ScaleZ", &accessory.localScale.z(), -3.0f, +3.0f, "Z: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine();
drawHelpMarker("+/-3.0 = +/-150 in-game");
ImGui::EndGroup();
}
Containers::StringView
Application::getStyleName(std::int32_t id, Containers::ArrayView<GameObjects::CustomStyle> view) {
auto SaveTool::getStyleName(Int id, Containers::ArrayView<CustomStyle> view) -> Containers::StringView {
if(id >= 0 && id <= 15) {
return view[id].name;
}
@ -812,8 +740,6 @@ Application::getStyleName(std::int32_t id, Containers::ArrayView<GameObjects::Cu
return _currentMass->globalStyles()[id - 50].name;
}
else {
return GameData::builtin_style_names.at(id);
return style_names.at(id);
}
}
}

View file

@ -0,0 +1,325 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2022 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 "../FontAwesome/IconsFontAwesome5.h"
#include "../Maps/ArmourSets.h"
#include "../Maps/StyleNames.h"
#include "SaveTool.h"
void SaveTool::drawArmour() {
if(!_currentMass || _currentMass->state() != Mass::State::Valid) {
return;
}
if(ImGui::Button(ICON_FA_UNDO_ALT " Reset all")) {
_currentMass->getArmourParts();
_currentMass->getBulletLauncherAttachments();
}
if(!ImGui::BeginChild("##ArmourParts", {0.0f, 0.0f}, true)) {
ImGui::EndChild();
return;
}
static Containers::StringView slot_labels[] = {
#define c(enumerator, strenum, name) name,
#include "../Maps/ArmourSlots.hpp"
#undef c
};
for(UnsignedInt i = 0; i < _currentMass->armourParts().size(); i++) {
ImGui::PushID(int(i));
auto& part = _currentMass->armourParts()[i];
static char header[129] = {'\0'};
std::memset(header, '\0', 129);
if(armour_sets.find(part.id) != armour_sets.cend()) {
std::snprintf(header, 128, "%s: %s###%u", slot_labels[UnsignedInt(part.slot)].data(), armour_sets.at(part.id).name.data(), UnsignedInt(part.slot));
}
else {
std::snprintf(header, 128, "%s: %i###%u", slot_labels[UnsignedInt(part.slot)].data(), part.id, UnsignedInt(part.slot));
}
if(ImGui::CollapsingHeader(header)) {
ImGui::BeginGroup();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x * 0.491f);
if(ImGui::BeginListBox("##ChangePart")) {
if(std::strncmp("Neck", slot_labels[UnsignedInt(part.slot)].data(), 4) != 0) {
for(auto& set : armour_sets) {
if(ImGui::Selectable(set.second.name.data(), set.first == part.id, ImGuiSelectableFlags_SpanAvailWidth)) {
part.id = set.first;
}
}
}
else {
for(auto& set : armour_sets) {
if(!set.second.neck_compatible) {
continue;
}
if(ImGui::Selectable(set.second.name.data(), set.first == part.id, ImGuiSelectableFlags_SpanAvailWidth)) {
part.id = set.first;
}
}
}
ImGui::EndListBox();
}
ImGui::EndGroup();
ImGui::SameLine();
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
ImGui::SameLine();
ImGui::BeginGroup();
ImGui::TextUnformatted("Styles:");
for(Int j = 0; j < 4; j++) {
drawAlignedText("Slot %d:", j + 1);
ImGui::SameLine();
ImGui::PushID(j);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - 2.0f);
if(ImGui::BeginCombo("##Style", getStyleName(part.styles[j], _currentMass->armourCustomStyles()).data())) {
for(const auto& style : style_names) {
if(ImGui::Selectable(getStyleName(style.first, _currentMass->armourCustomStyles()).data(), part.styles[j] == style.first)) {
part.styles[j] = style.first;
}
}
ImGui::EndCombo();
}
ImGui::PopID();
}
ImGui::EndGroup();
ImGui::Separator();
ImGui::PushID("Decal");
drawAlignedText("Showing/editing decal");
for(UnsignedInt j = 0; j < part.decals.size(); j++) {
ImGui::SameLine();
ImGui::RadioButton(std::to_string(j + 1).c_str(), &_selectedArmourDecals[i], int(j));
}
drawDecalEditor(part.decals[_selectedArmourDecals[i]]);
ImGui::PopID();
if(part.accessories.size() != 0) {
ImGui::Separator();
ImGui::PushID("Accessory");
drawAlignedText("Showing/editing accessory");
for(UnsignedInt j = 0; j < part.accessories.size(); j++) {
ImGui::SameLine();
ImGui::RadioButton(std::string{char(65 + j)}.c_str(), &_selectedArmourAccessories[i], int(j));
}
drawAccessoryEditor(part.accessories[_selectedArmourAccessories[i]], _currentMass->armourCustomStyles());
ImGui::PopID();
}
ImGui::Separator();
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) {
_modifiedBySaveTool = true;
if(!_currentMass->writeArmourPart(part.slot)) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
}
}
ImGui::PopID();
}
if(_currentMass->bulletLauncherAttachmentStyle() != BulletLauncherAttachmentStyle::NotFound &&
ImGui::CollapsingHeader("Bullet launcher placement"))
{
drawAlignedText("Attachment style:"_s);
ImGui::SameLine();
ImGui::RadioButton("Active one",
_currentMass->bulletLauncherAttachmentStyle() == BulletLauncherAttachmentStyle::ActiveOne);
ImGui::SameLine();
ImGui::RadioButton("Active one per slot",
_currentMass->bulletLauncherAttachmentStyle() == BulletLauncherAttachmentStyle::ActiveOnePerSlot);
ImGui::SameLine();
ImGui::RadioButton("All equipped",
_currentMass->bulletLauncherAttachmentStyle() == BulletLauncherAttachmentStyle::AllEquipped);
ImGui::Separator();
drawAlignedText("Launcher slot:");
ImGui::SameLine();
ImGui::RadioButton("1", &_selectedBLPlacement, 0);
ImGui::SameLine();
ImGui::RadioButton("2", &_selectedBLPlacement, 1);
ImGui::SameLine();
ImGui::RadioButton("3", &_selectedBLPlacement, 2);
ImGui::SameLine();
ImGui::RadioButton("4", &_selectedBLPlacement, 3);
auto& placement = _currentMass->bulletLauncherAttachments()[_selectedBLPlacement];
static const Containers::StringView socket_labels[] = {
#define c(enumerator, enumstr, name) name,
#include "../Maps/BulletLauncherSockets.hpp"
#undef c
};
drawAlignedText("Socket:");
ImGui::SameLine();
if(ImGui::BeginCombo("##Socket", socket_labels[UnsignedInt(placement.socket)].data())) {
for(UnsignedInt i = 0; i < (sizeof(socket_labels) / sizeof(socket_labels[0])); i++) {
if(ImGui::Selectable(socket_labels[i].data(), i == UnsignedInt(placement.socket), ImGuiSelectableFlags_SpanAvailWidth)) {
placement.socket = static_cast<BulletLauncherSocket>(i);
}
}
ImGui::EndCombo();
}
if(placement.socket != BulletLauncherSocket::Auto) {
ImGui::BeginGroup();
drawAlignedText("Relative position:");
drawAlignedText("Offset position:");
drawAlignedText("Relative rotation:");
drawAlignedText("Offset rotation:");
drawAlignedText("Scale:");
ImGui::EndGroup();
ImGui::SameLine();
ImGui::BeginGroup();
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::DragFloat("##RelPosX", &placement.relativeLocation.x(), 1.0f, -FLT_MAX, +FLT_MAX, "X: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragFloat("##RelPosY", &placement.relativeLocation.y(), 1.0f, -FLT_MAX, +FLT_MAX, "Y: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragFloat("##RelPosZ", &placement.relativeLocation.z(), 1.0f, -FLT_MAX, +FLT_MAX, "Z: %.3f");
ImGui::PopItemWidth();
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::SliderFloat("##OffPosX", &placement.offsetLocation.x(), -500.0f, +500.0f, "X: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderFloat("##OffPosY", &placement.offsetLocation.y(), -500.0f, +500.0f, "Y: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderFloat("##OffPosZ", &placement.offsetLocation.z(), -500.0f, +500.0f, "Z: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine();
drawHelpMarker("+/-500.0 = +/-250 in-game");
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::DragFloat("##RotX", &placement.relativeRotation.x(), 1.0f, -FLT_MAX, +FLT_MAX, "Roll: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragFloat("##RotY", &placement.relativeRotation.y(), 1.0f, -FLT_MAX, +FLT_MAX, "Yaw: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::DragFloat("##RotZ", &placement.relativeRotation.z(), 1.0f, -FLT_MAX, +FLT_MAX, "Pitch: %.3f");
ImGui::PopItemWidth();
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::SliderFloat("##RotOffsetZ", &placement.offsetRotation.z(), -180.0f, +180.0f, "Roll: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderFloat("##RotOffsetX", &placement.offsetRotation.x(), -30.0f, +30.0f, "Pitch: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderFloat("##RotOffsetY", &placement.offsetRotation.y(), -30.0f, +30.0f, "Yaw: %.3f");
ImGui::PopItemWidth();
ImGui::PushMultiItemsWidths(3, ImGui::CalcItemWidth());
ImGui::SliderFloat("##ScaleX", &placement.relativeScale.x(), 0.5f, 1.5f, "X: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderFloat("##ScaleY", &placement.relativeScale.y(), 0.5f, 1.5f, "Y: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::SliderFloat("##ScaleZ", &placement.relativeScale.z(), 0.5f, 1.5f, "Z: %.3f");
ImGui::PopItemWidth();
ImGui::SameLine();
drawHelpMarker("0.5 = 50 in-game\n1.5 = 150 in-game");
ImGui::EndGroup();
}
_modifiedBySaveTool = true;
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); }) &&
!_currentMass->writeBulletLauncherAttachments())
{
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
}
ImGui::EndChild();
}
void SaveTool::drawCustomArmourStyles() {
if(!_currentMass || _currentMass->state() != Mass::State::Valid) {
return;
}
if(!ImGui::BeginChild("##ArmourStyles")) {
ImGui::EndChild();
return;
}
ImGui::TextWrapped("In-game values are multiplied by 100. For example, 0.500 here is equal to 50 in-game.");
for(UnsignedInt i = 0; i < _currentMass->armourCustomStyles().size(); i++) {
ImGui::PushID(int(i));
DCSResult result;
result = drawCustomStyle(_currentMass->armourCustomStyles()[i]);
switch(result) {
case DCS_ResetStyle:
_currentMass->getArmourCustomStyles();
break;
case DCS_Save:
_modifiedBySaveTool = true;
if(!_currentMass->writeArmourCustomStyle(i)) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;
default:
break;
}
ImGui::PopID();
}
ImGui::EndChild();
}

View file

@ -0,0 +1,315 @@
// MassBuilderSaveTool
// Copyright (C) 2021-2022 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 "../FontAwesome/IconsFontAwesome5.h"
#include "../Maps/StyleNames.h"
#include "SaveTool.h"
void SaveTool::drawFrameInfo() {
if(!_currentMass || _currentMass->state() != Mass::State::Valid) {
return;
}
if(!ImGui::BeginChild("##FrameInfo")) {
ImGui::EndChild();
return;
}
ImGui::BeginGroup();
if(ImGui::BeginChild("##JointSliders", {(ImGui::GetContentRegionAvail().x / 2.0f) - (ImGui::GetStyle().WindowPadding.x / 2.0f), 300.0f}, true, ImGuiWindowFlags_MenuBar)) {
if(ImGui::BeginMenuBar()) {
ImGui::TextUnformatted("Joint sliders");
ImGui::EndMenuBar();
}
drawJointSliders();
}
ImGui::EndChild();
if(ImGui::BeginChild("##FrameStyles", {(ImGui::GetContentRegionAvail().x / 2.0f) - (ImGui::GetStyle().WindowPadding.x / 2.0f), 0.0f}, true, ImGuiWindowFlags_MenuBar)) {
if(ImGui::BeginMenuBar()) {
ImGui::TextUnformatted("Frame styles");
ImGui::EndMenuBar();
}
drawFrameStyles();
}
ImGui::EndChild();
ImGui::EndGroup();
ImGui::SameLine();
if(ImGui::BeginChild("##EyeFlare", {0.0f, 0.0f}, true, ImGuiWindowFlags_MenuBar)) {
if(ImGui::BeginMenuBar()) {
ImGui::TextUnformatted("Eye flare colour");
drawHelpMarker("Right-click the picker for more options.", 250.0f);
ImGui::EndMenuBar();
}
drawEyeColourPicker();
}
ImGui::EndChild();
ImGui::EndChild();
}
void SaveTool::drawJointSliders() {
if(!_currentMass || _currentMass->state() != Mass::State::Valid) {
return;
}
ImGui::TextWrapped("In-game values are multiplied by 100.\nFor example, 0.500 here is equal to 50 in-game.");
if(ImGui::BeginTable("##JointSliderTable", 2, ImGuiTableFlags_Borders)) {
ImGui::TableSetupColumn("##SliderLabel", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("##Sliders", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
drawAlignedText("Neck");
ImGui::TableSetColumnIndex(1);
ImGui::SetNextItemWidth(-1.0f);
if(ImGui::SliderFloat("##NeckSlider", &_currentMass->jointSliders().neck, 0.0f, 1.0f)) {
_jointsDirty = true;
}
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
drawAlignedText("Body");
ImGui::TableSetColumnIndex(1);
ImGui::SetNextItemWidth(-1.0f);
if(ImGui::SliderFloat("##BodySlider", &_currentMass->jointSliders().body, 0.0f, 1.0f)) {
_jointsDirty = true;
}
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
drawAlignedText("Shoulders");
ImGui::TableSetColumnIndex(1);
ImGui::SetNextItemWidth(-1.0f);
if(ImGui::SliderFloat("##ShouldersSlider", &_currentMass->jointSliders().shoulders, 0.0f, 1.0f)) {
_jointsDirty = true;
}
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
drawAlignedText("Hips");
ImGui::TableSetColumnIndex(1);
ImGui::SetNextItemWidth(-1.0f);
if(ImGui::SliderFloat("##HipsSlider", &_currentMass->jointSliders().hips, 0.0f, 1.0f)) {
_jointsDirty = true;
}
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
drawAlignedText("Arms");
ImGui::TableSetColumnIndex(1);
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{2.0f, 1.0f});
if(ImGui::BeginTable("##UpperLowerArmsLayoutTable", 2)) {
ImGui::TableSetupColumn("##UpperArms", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("##LowerArms", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(-1.0f);
if(ImGui::SliderFloat("##UpperArmsSlider", &_currentMass->jointSliders().upperArms, 0.0f, 1.0f, "Upper: %.3f")) {
_jointsDirty = true;
}
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(-1.0f);
if(ImGui::SliderFloat("##LowerArmsSlider", &_currentMass->jointSliders().lowerArms, 0.0f, 1.0f, "Lower: %.3f")) {
_jointsDirty = true;
}
ImGui::EndTable();
}
ImGui::PopStyleVar();
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
drawAlignedText("Legs");
ImGui::TableSetColumnIndex(1);
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{2.0f, 1.0f});
if(ImGui::BeginTable("##UpperLowerLegsLayoutTable", 2)) {
ImGui::TableSetupColumn("##UpperLegs", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("##LowerLegs", ImGuiTableColumnFlags_WidthStretch);
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(-1.0f);
if(ImGui::SliderFloat("##UpperLegsSlider", &_currentMass->jointSliders().upperLegs, 0.0f, 1.0f, "Upper: %.3f")) {
_jointsDirty = true;
}
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(-1.0f);
if(ImGui::SliderFloat("##LowerLegsSlider", &_currentMass->jointSliders().lowerLegs, 0.0f, 1.0f, "Lower: %.3f")) {
_jointsDirty = true;
}
ImGui::EndTable();
}
ImGui::PopStyleVar();
ImGui::EndTable();
}
if(!_jointsDirty) {
ImGui::BeginDisabled();
ImGui::Button(ICON_FA_SAVE " Save");
ImGui::SameLine();
ImGui::Button(ICON_FA_UNDO " Reset");
ImGui::EndDisabled();
}
else {
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) {
_modifiedBySaveTool = true;
if(!_currentMass->writeJointSliders()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
_jointsDirty = false;
}
ImGui::SameLine();
if(ImGui::Button(ICON_FA_UNDO " Reset")) {
_currentMass->getJointSliders();
_jointsDirty = false;
}
}
}
void SaveTool::drawFrameStyles() {
if(!_currentMass || _currentMass->state() != Mass::State::Valid) {
return;
}
for(Int i = 0; i < 4; i++) {
drawAlignedText("Slot %d:", i + 1);
ImGui::SameLine();
ImGui::PushID(i);
if(ImGui::BeginCombo("##Style", getStyleName(_currentMass->frameStyles()[i], _currentMass->frameCustomStyles()).data())) {
for(const auto& style : style_names) {
if(ImGui::Selectable(getStyleName(style.first, _currentMass->frameCustomStyles()).data(), _currentMass->frameStyles()[i] == style.first)) {
_currentMass->frameStyles()[i] = style.first;
_stylesDirty = true;
}
}
ImGui::EndCombo();
}
ImGui::PopID();
}
if(!_stylesDirty) {
ImGui::BeginDisabled();
ImGui::Button(ICON_FA_SAVE " Save");
ImGui::SameLine();
ImGui::Button(ICON_FA_UNDO " Reset");
ImGui::EndDisabled();
}
else {
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) {
_modifiedBySaveTool = true;
if(!_currentMass->writeFrameStyles()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
_stylesDirty = false;
}
ImGui::SameLine();
if(ImGui::Button(ICON_FA_UNDO " Reset")) {
_currentMass->getFrameStyles();
_stylesDirty = false;
}
}
}
void SaveTool::drawEyeColourPicker() {
if(!_currentMass || _currentMass->state() != Mass::State::Valid) {
return;
}
if(ImGui::ColorPicker3("##EyeFlarePicker", &_currentMass->eyeFlareColour().x())) {
_eyeFlareDirty = true;
}
if(!_eyeFlareDirty) {
ImGui::BeginDisabled();
ImGui::Button(ICON_FA_SAVE " Save");
ImGui::SameLine();
ImGui::Button(ICON_FA_UNDO " Reset");
ImGui::EndDisabled();
}
else {
if(drawUnsafeWidget([]{ return ImGui::Button(ICON_FA_SAVE " Save"); })) {
_modifiedBySaveTool = true;
if(!_currentMass->writeEyeFlareColour()) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
_eyeFlareDirty = false;
}
ImGui::SameLine();
if(ImGui::Button(ICON_FA_UNDO " Reset")) {
_currentMass->getEyeFlareColour();
_eyeFlareDirty = false;
}
}
}
void SaveTool::drawCustomFrameStyles() {
if(!_currentMass || _currentMass->state() != Mass::State::Valid) {
return;
}
if(!ImGui::BeginChild("##FrameStyles")) {
ImGui::EndChild();
return;
}
ImGui::TextWrapped("In-game values are multiplied by 100. For example, 0.500 here is equal to 50 in-game.");
for(UnsignedInt i = 0; i < _currentMass->frameCustomStyles().size(); i++) {
ImGui::PushID(int(i));
DCSResult result;
result = drawCustomStyle(_currentMass->frameCustomStyles()[i]);
switch(result) {
case DCS_ResetStyle:
_currentMass->getFrameCustomStyles();
break;
case DCS_Save:
_modifiedBySaveTool = true;
if(!_currentMass->writeFrameCustomStyle(i)) {
_modifiedBySaveTool = false;
_queue.addToast(Toast::Type::Error, _currentMass->lastError());
}
break;
default:
break;
}
ImGui::PopID();
}
ImGui::EndChild();
}

Some files were not shown because too many files have changed in this diff Show more