diff --git a/.clangd b/.clangd new file mode 100644 index 000000000..e95990a8f --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + CompilationDatabase: build/ diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a918d2518..e18eb2846 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,44 +10,49 @@ jobs: matrix: include: - configuration: Debug - cc: gcc-12 - cxx: g++-12 + cc: gcc-14 + cxx: g++-14 - configuration: Release - cc: gcc-12 - cxx: g++-12 + cc: gcc-14 + cxx: g++-14 - configuration: Debug - cc: clang-15 - cxx: clang++-15 + cc: clang-18 + cxx: clang++-18 - configuration: Release - cc: clang-15 - cxx: clang++-15 - runs-on: ubuntu-22.04 + cc: clang-18 + cxx: clang++-18 + runs-on: ubuntu-24.04 env: CC: ${{ matrix.cc }} CXX: ${{ matrix.cxx }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: fetch-depth: 0 - submodules: recursive + - name: init submodules (shallow) + run: git submodule update --init --recursive --depth 1 - name: update apt package list run: sudo apt-get update -y - name: install apt packages run: sudo apt-get install -y - gcc-12 - g++-12 - clang-15 - libboost-dev - libboost-filesystem-dev - libboost-program-options-dev + gcc-14 + g++-14 + clang-18 + ccache libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libglew-dev zlib1g-dev libpng-dev + - name: restore ccache + uses: actions/cache@v5 + with: + path: ~/.cache/ccache + key: ccache-${{ runner.os }}-${{ env.CC }}-${{ matrix.configuration }}-${{ github.sha }} + restore-keys: ccache-${{ runner.os }}-${{ env.CC }}-${{ matrix.configuration }}- - name: install node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v6 with: node-version: 18 cache: npm @@ -66,7 +71,7 @@ jobs: run: npm run package - name: restore protobuf id: cache-protobuf-restore - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v5 with: path: libs/_protobuf-install key: ${{ runner.os }}-${{ env.CC }}-protobuf @@ -75,7 +80,7 @@ jobs: run: ./build-protobuf.sh - name: save protobuf id: cache-protobuf-save - uses: actions/cache/save@v3 + uses: actions/cache/save@v5 with: path: libs/_protobuf-install key: ${{ steps.cache-protobuf-restore.outputs.cache-primary-key }} @@ -83,8 +88,8 @@ jobs: run: | mkdir build pushd build - cmake -DCMAKE_BUILD_TYPE=${{ matrix.configuration }} .. - make -j 2 + cmake -DCMAKE_BUILD_TYPE=${{ matrix.configuration }} -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache .. + make -j 4 popd - name: test rwe run: ./build/rwe_test @@ -92,18 +97,15 @@ jobs: strategy: matrix: configuration: [Debug, Release] - runs-on: windows-2022 + runs-on: windows-2025-vs2026 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: fetch-depth: 0 - submodules: recursive - - name: install python - uses: actions/setup-python@v4 - with: - python-version: '3.10' + - name: init submodules (shallow) + run: git submodule update --init --recursive --depth 1 - name: install node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v6 with: node-version: 18 cache: npm @@ -121,27 +123,35 @@ jobs: working-directory: launcher run: npm run package - run: python3 fetch-msvc-libs.py + - name: install tools + run: choco install sccache nsis -y + - name: restore sccache + uses: actions/cache@v5 + with: + path: ~\AppData\Local\Mozilla\sccache + key: sccache-${{ runner.os }}-msvc-${{ matrix.configuration }}-${{ github.sha }} + restore-keys: sccache-${{ runner.os }}-msvc-${{ matrix.configuration }}- - name: build rwe run: | mkdir build cd build - cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=${{ matrix.configuration }} .. - cmake --build . --config ${{ matrix.configuration }} --parallel 2 + cmake -G "Visual Studio 18 2026" -A x64 -DCMAKE_BUILD_TYPE=${{ matrix.configuration }} -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache .. + cmake --build . --config ${{ matrix.configuration }} --parallel 4 cd .. - name: test rwe working-directory: build run: .\${{ matrix.configuration }}\rwe_test.exe - name: package rwe working-directory: build - run: cmake --build . --target package --config ${{ matrix.configuration }} --parallel 2 + run: cmake --build . --target package --config ${{ matrix.configuration }} --parallel 4 - name: upload zip artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v6 with: name: RWE Zip ${{ runner.os }} MSVC ${{ matrix.configuration }} path: build/dist/RobotWarEngine-*-Windows-${{ matrix.configuration }}.zip if-no-files-found: error - name: upload installer artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v6 with: name: RWE Installer ${{ runner.os }} MSVC ${{ matrix.configuration }} path: build/dist/RobotWarEngine-*-Windows-${{ matrix.configuration }}.exe @@ -150,14 +160,15 @@ jobs: strategy: matrix: configuration: [Debug, Release] - runs-on: windows-2022 + runs-on: windows-2025 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 with: fetch-depth: 0 - submodules: recursive + - name: init submodules (shallow) + run: git submodule update --init --recursive --depth 1 - name: install node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v6 with: node-version: 18 cache: npm @@ -174,6 +185,8 @@ jobs: - name: package launcher working-directory: launcher run: npm run package + - name: install nsis + run: choco install nsis -y - uses: msys2/setup-msys2@v2 with: msystem: MINGW64 @@ -185,18 +198,25 @@ jobs: make unzip mingw-w64-x86_64-toolchain - mingw-w64-x86_64-boost mingw-w64-x86_64-SDL2 mingw-w64-x86_64-SDL2_image mingw-w64-x86_64-SDL2_mixer mingw-w64-x86_64-glew mingw-w64-x86_64-smpeg2 + mingw-w64-x86_64-libmodplug mingw-w64-x86_64-zlib mingw-w64-x86_64-libpng mingw-w64-x86_64-readline + mingw-w64-x86_64-ccache + - name: restore ccache + uses: actions/cache@v5 + with: + path: ~/.cache/ccache + key: ccache-${{ runner.os }}-mingw64-${{ matrix.configuration }}-${{ github.sha }} + restore-keys: ccache-${{ runner.os }}-mingw64-${{ matrix.configuration }}- - name: restore protobuf id: cache-protobuf-restore - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v5 with: path: libs/_protobuf-install key: ${{ runner.os }}-mingw64-protobuf-${{ hashFiles('.git/modules/libs/protobuf/HEAD') }} @@ -206,7 +226,7 @@ jobs: shell: msys2 {0} - name: save protobuf id: cache-protobuf-save - uses: actions/cache/save@v3 + uses: actions/cache/save@v5 with: path: libs/_protobuf-install key: ${{ steps.cache-protobuf-restore.outputs.cache-primary-key }} @@ -214,8 +234,8 @@ jobs: run: | mkdir build pushd build - cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=${{ matrix.configuration }} .. - make -j 2 + cmake -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=${{ matrix.configuration }} -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache .. + make -j 4 popd shell: msys2 {0} - name: test rwe @@ -227,13 +247,13 @@ jobs: run: make package shell: msys2 {0} - name: upload zip artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v6 with: name: RWE Zip ${{ runner.os }} MinGW64 ${{ matrix.configuration }} path: build/dist/RobotWarEngine-*-Windows-${{ matrix.configuration }}.zip if-no-files-found: error - name: upload installer artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v6 with: name: RWE Installer ${{ runner.os }} MinGW64 ${{ matrix.configuration }} path: build/dist/RobotWarEngine-*-Windows-${{ matrix.configuration }}.exe diff --git a/.gitignore b/.gitignore index eb9ad245c..728b9efe1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /libs/_msvc/ /libs/_protobuf-install/ +/.cache/ /.idea/ /.vs/ diff --git a/.gitmodules b/.gitmodules index 194516bf4..c0f5384d2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ -[submodule "libs/spdlog"] - path = libs/spdlog - url = https://github.com/gabime/spdlog.git [submodule "libs/rapidcheck"] path = libs/rapidcheck - url = https://github.com/MHeasell/rapidcheck.git + url = https://github.com/emil-e/rapidcheck.git [submodule "libs/protobuf"] path = libs/protobuf url = https://github.com/google/protobuf.git @@ -19,3 +16,6 @@ [submodule "libs/utfcpp"] path = libs/utfcpp url = https://github.com/nemtrif/utfcpp.git +[submodule "libs/asio"] + path = libs/asio + url = https://github.com/chriskohlhoff/asio.git diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..eeb195915 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,79 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Robot War Engine (RWE) is an open-source real-time strategy game engine with high compatibility for Total Annihilation data files. It consists of a C++17 core engine and a TypeScript/Electron launcher application. Active priority is simplifying dependencies and improving new developer onboarding. + +## Build Commands + +### C++ Engine (from repo root) + +```bash +# First time setup (submodules + protobuf) +git submodule update --init --recursive +cd libs && ./build-protobuf.sh && cd .. + +# Build (Linux/macOS) +mkdir build && cd build +cmake .. -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Debug +make -j$(nproc) + +# Run unit tests +./build/rwe_test + +# Run a single test by name (Catch2 syntax) +./build/rwe_test "test name pattern" +./build/rwe_test "[tag]" +``` + +### Launcher (from `launcher/` directory) + +```bash +npm ci +npm run tsc # Type check +npm test # Jest tests +npm run lint # ESLint +npm run server # Webpack dev server (hot reload) +npm start # Launch Electron app (needs RWE_HOME env var) +npm run master-server # Local multiplayer master server +npm run package # Package for distribution +``` + +## Architecture + +### Core Engine (`src/rwe/`) + +The engine is built as a static library `librwe` linked by multiple executables (`rwe`, `rwe_bridge`, `rwe_test`, and various format test tools). + +Key subsystems: + +- **sim/** - Deterministic game simulation (units, weapons, projectiles, terrain, resources). Uses fixed-point math types (`SimScalar`, `SimVector`, `SimAngle`) for cross-platform determinism. +- **scene/** - Scene state machine: MainMenuScene → LoadingScene → GameScene. +- **render/** - OpenGL 3.0+ rendering pipeline with GLSL shaders (in `shaders/`). +- **cob/** - Virtual machine executing Total Annihilation's COB unit behavior scripts. CobThread runs concurrent script coroutines within CobExecutionContext. +- **io/** - Parsers for TA file formats: HPI (archives), GAF (sprites), TDF (config), 3DO (models), COB (scripts), FBI (units), TNT (terrain), PCX (images), OTA (maps), GUI (layouts). +- **vfs/** - Virtual file system abstracting over HPI archives and directories. +- **pathfinding/** - A* pathfinding with octile distance on grids. +- **proto/** - Protocol buffer networking (defined in `proto/network.proto`). +- **geometry/** / **math/** - Linear algebra and spatial primitives. +- **collections/** - Custom data structures (MinHeap, VectorMap). + +### Launcher (`launcher/src/`) + +Electron app with React/Redux for the multiplayer lobby. Communicates with the engine via `rwe_bridge` (JSON IPC). Contains `launcher/`, `master-server/`, `game-server/`, and `common/` modules. + +## Code Conventions + +- All C++ code is in the `rwe::` namespace +- Formatting enforced by `.clang-format`: Allman brace style, 4-space indent, no column limit, C++17 standard +- Test files live alongside source: `src/rwe/[subsystem]/[Component].test.cpp` +- Strong typing via opaque ID types: `UnitId`, `PlayerId`, `ProjectileId` (see `OpaqueId`) +- Variant-based state machines for unit behavior and navigation goals +- Error handling uses `Result` types rather than exceptions +- Version derived from git tags (format: `v#.#.#`) + +## CI + +GitHub Actions runs Linux (gcc-12, clang-15) and Windows (MSVC 2022, MinGW64) builds in both Debug and Release configurations. diff --git a/CMakeLists.txt b/CMakeLists.txt index 643a91182..8e3732229 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,14 @@ -cmake_minimum_required(VERSION 3.11) +cmake_minimum_required(VERSION 3.16) + +# Allow submodules with older cmake_minimum_required to configure under CMake 4.x +if(CMAKE_VERSION VERSION_GREATER_EQUAL "4.0") + set(CMAKE_POLICY_VERSION_MINIMUM 3.5) +endif() + +# Generate compile_commands.json for clangd-based editor integration +# (VS Code, Zed, etc.). The .clangd file in the repo root points +# editors to look for it in the build/ directory. +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Type of build" FORCE) @@ -27,14 +37,12 @@ endif() project("Robot War Engine" VERSION ${CMAKE_MATCH_1}) enable_testing() -set(RC_ENABLE_CATCH ON CACHE BOOL "Enables Catch support in RapidCheck" FORCE) -set(RC_ENABLE_BOOST ON CACHE BOOL "Enables Boost support in RapidCheck" FORCE) -# rapidcheck relies on result_of, which is deprecated in favour of invoke_result. -# MSVC hides it from their stdlib unless you supply this option. -if(MSVC) - add_definitions(-D_HAS_DEPRECATED_RESULT_OF) -endif() +add_subdirectory("libs/catch2") + +set(RC_ENABLE_CATCH OFF CACHE BOOL "Enables Catch support in RapidCheck" FORCE) +set(RC_ENABLE_BOOST OFF CACHE BOOL "Enables Boost support in RapidCheck" FORCE) add_subdirectory("libs/rapidcheck") +add_subdirectory("libs/rapidcheck/extras/catch") set(JSON_BuildTests OFF CACHE INTERNAL "") set(JSON_Install OFF CACHE INTERNAL "") @@ -74,7 +82,7 @@ endfunction() set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules") if(NOT MSVC) - set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD 20) endif() find_package(OpenGL REQUIRED) @@ -92,15 +100,6 @@ if(MSVC) set(GLEW_LIBRARIES "${CMAKE_SOURCE_DIR}/libs/_msvc/glew-2.1.0/lib/Release/x64/glew32.lib") set(GLEW_INCLUDE_DIRS "libs/_msvc/glew-2.1.0/include") - set(Boost_INCLUDE_DIRS "libs/_msvc/boost_1_74_0") - if(CMAKE_BUILD_TYPE STREQUAL "Debug") - set(Boost_LIBRARIES "${CMAKE_SOURCE_DIR}/libs/_msvc/boost_1_74_0/lib64-msvc-14.2/libboost_filesystem-vc142-mt-gd-x64-1_74.lib;${CMAKE_SOURCE_DIR}/libs/_msvc/boost_1_74_0/lib64-msvc-14.2/libboost_system-vc142-mt-gd-x64-1_74.lib;${CMAKE_SOURCE_DIR}/libs/_msvc/boost_1_74_0/lib64-msvc-14.2/libboost_program_options-vc142-mt-gd-x64-1_74.lib") - else() - set(Boost_LIBRARIES "${CMAKE_SOURCE_DIR}/libs/_msvc/boost_1_74_0/lib64-msvc-14.2/libboost_filesystem-vc142-mt-x64-1_74.lib;${CMAKE_SOURCE_DIR}/libs/_msvc/boost_1_74_0/lib64-msvc-14.2/libboost_system-vc142-mt-x64-1_74.lib;${CMAKE_SOURCE_DIR}/libs/_msvc/boost_1_74_0/lib64-msvc-14.2/libboost_program_options-vc142-mt-x64-1_74.lib") - endif() - add_definitions(-DBOOST_DATE_TIME_NO_LIB) - add_definitions(-DBOOST_REGEX_NO_LIB) - set(SDL2_DLL "${CMAKE_SOURCE_DIR}/libs/_msvc/SDL2-2.0.7/lib/x64/SDL2.dll") set(SDL2_LIBRARY "${CMAKE_SOURCE_DIR}/libs/_msvc/SDL2-2.0.7/lib/x64/SDL2.lib;${CMAKE_SOURCE_DIR}/libs/_msvc/SDL2-2.0.7/lib/x64/SDL2main.lib") set(SDL2_INCLUDE_DIR "libs/_msvc/SDL2-2.0.7/include") @@ -131,21 +130,23 @@ if(MSVC) set(VORBISFILE_DLL "${CMAKE_SOURCE_DIR}/libs/_msvc/extras/libvorbisfile-3.dll") set(WEBP_DLL "${CMAKE_SOURCE_DIR}/libs/_msvc/extras/libwebp-7.dll") else() - set(Protobuf_LIBRARY "${CMAKE_SOURCE_DIR}/libs/_protobuf-install/lib/libprotobuf.a") - set(Protobuf_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/libs/_protobuf-install/include") - if(WIN32) - set(Protobuf_PROTOC_EXECUTABLE "${CMAKE_SOURCE_DIR}/libs/_protobuf-install/bin/protoc.exe") + if(EXISTS "${CMAKE_SOURCE_DIR}/libs/_protobuf-install/lib/libprotobuf.a") + set(Protobuf_LIBRARY "${CMAKE_SOURCE_DIR}/libs/_protobuf-install/lib/libprotobuf.a") + set(Protobuf_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/libs/_protobuf-install/include") + if(WIN32) + set(Protobuf_PROTOC_EXECUTABLE "${CMAKE_SOURCE_DIR}/libs/_protobuf-install/bin/protoc.exe") + else() + set(Protobuf_PROTOC_EXECUTABLE "${CMAKE_SOURCE_DIR}/libs/_protobuf-install/bin/protoc") + endif() else() - set(Protobuf_PROTOC_EXECUTABLE "${CMAKE_SOURCE_DIR}/libs/_protobuf-install/bin/protoc") + find_package(Protobuf REQUIRED) + set(Protobuf_LIBRARY protobuf::libprotobuf) + set(Protobuf_INCLUDE_DIR ${Protobuf_INCLUDE_DIRS}) endif() find_package(GLEW REQUIRED) find_windows_dll(GLEW_DLL "glew32.dll") - set(Boost_USE_STATIC_LIBS ON) - set(Boost_NO_BOOST_CMAKE ON) - find_package(Boost 1.54.0 REQUIRED COMPONENTS filesystem program_options) - find_package(SDL2 REQUIRED) find_windows_dll(SDL2_DLL "SDL2.dll") find_package(SDL2_image REQUIRED) @@ -194,13 +195,6 @@ else() find_windows_dll(LCMS2_DLL "liblcms2-2.dll") endif() -# We should have std::chrono everywhere, -# but boost sometimes fails to detect this (e.g. with clang 5 on Ubuntu) -# and falls back to using boost::chrono. -# This enables the use of std::chrono explicitly -# in boost.asio. -add_definitions(-DBOOST_ASIO_HAS_STD_CHRONO) - set(Protobuf_USE_STATIC_LIBS ON) find_package(Protobuf REQUIRED) @@ -620,8 +614,7 @@ endif() add_library(librwe STATIC ${SOURCE_FILES} ${PROTO_SOURCE_FILES} ${PROTO_HEADER_FILES}) set_target_properties(librwe PROPERTIES PREFIX "") if(MSVC) - target_compile_options(librwe PUBLIC "/std:c++17" "/EHsc") - add_definitions(-D_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS) + target_compile_options(librwe PUBLIC "/std:c++20" "/EHsc") else() target_compile_options(librwe PUBLIC "-Wall" "-Wextra") endif() @@ -629,15 +622,30 @@ target_include_directories(librwe PUBLIC "src") configure_file("src/rwe/config.h.in" "config/rwe/config.h" @ONLY) target_include_directories(librwe PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/config") -target_include_directories(librwe PUBLIC ${Boost_INCLUDE_DIRS}) -target_link_libraries(librwe ${Boost_LIBRARIES}) +target_include_directories(librwe PUBLIC "libs/asio/asio/include") +add_definitions(-DASIO_STANDALONE) if(WIN32) - # winsock needed for boost asio on windows + # winsock needed for asio on windows target_link_libraries(librwe ws2_32) endif() target_include_directories(librwe PUBLIC "libs/utfcpp/source") -target_include_directories(librwe PUBLIC "libs/spdlog/include") + +target_precompile_headers(librwe PRIVATE + + + + + + + + + + + + + +) target_link_libraries(librwe OpenGL::GL) @@ -645,8 +653,13 @@ target_copy_file(librwe ${GLEW_DLL}) if(MSVC) target_link_libraries(librwe ${GLEW_LIBRARIES}) target_include_directories(librwe PUBLIC ${GLEW_INCLUDE_DIRS}) -else() +elseif(TARGET GLEW::glew) + target_link_libraries(librwe GLEW::glew) +elseif(TARGET GLEW::GLEW) target_link_libraries(librwe GLEW::GLEW) +else() + target_link_libraries(librwe ${GLEW_LIBRARIES}) + target_include_directories(librwe PUBLIC ${GLEW_INCLUDE_DIRS}) endif() target_copy_file(librwe ${SDL2_DLL}) @@ -704,7 +717,21 @@ target_copy_file(librwe ${VORBIS_DLL}) target_copy_file(librwe ${VORBISFILE_DLL}) target_copy_file(librwe ${WEBP_DLL}) -target_link_libraries(librwe ${Protobuf_LIBRARIES}) +if(TARGET protobuf::libprotobuf) + message(STATUS "Using protobuf::libprotobuf imported target") + target_link_libraries(librwe protobuf::libprotobuf) + # protobuf v32+ depends on abseil; link via pkg-config to propagate to consumers of this static lib + find_package(PkgConfig) + if(PkgConfig_FOUND) + pkg_check_modules(ABSL IMPORTED_TARGET absl_log_internal_check_op) + if(ABSL_FOUND) + target_link_libraries(librwe PkgConfig::ABSL) + endif() + endif() +else() + message(STATUS "Using Protobuf_LIBRARIES: ${Protobuf_LIBRARIES}") + target_link_libraries(librwe ${Protobuf_LIBRARIES}) +endif() target_include_directories(librwe PUBLIC ${Protobuf_INCLUDE_DIRS}) target_include_directories(librwe PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) # for generated headers @@ -719,6 +746,17 @@ add_custom_command(TARGET librwe PRE_BUILD add_executable(rwe src/main.cpp) target_link_libraries(rwe librwe) +if(UNIX AND NOT APPLE) + file(WRITE "${CMAKE_BINARY_DIR}/run.sh" "#!/bin/bash +# The devbox-built binary links against Nix's libglvnd, which can't find +# the system GPU driver (e.g. Mesa). Prepend system lib paths so it's +# discovered. Not needed on macOS, which uses Apple's OpenGL framework +# directly rather than libglvnd. +LD_LIBRARY_PATH=/usr/lib:/usr/lib64:/usr/lib/x86_64-linux-gnu:\$LD_LIBRARY_PATH exec ./rwe \"\$@\" +") + file(CHMOD "${CMAKE_BINARY_DIR}/run.sh" PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) +endif() + add_executable(hpi_test src/hpi_test.cpp) target_link_libraries(hpi_test librwe) @@ -729,7 +767,6 @@ add_executable(gaf_test src/gaf_test.cpp) target_link_libraries(gaf_test librwe) target_link_libraries(gaf_test ${PNG_LIBRARIES}) target_include_directories(gaf_test PUBLIC ${PNG_INCLUDE_DIRS}) -target_include_directories(gaf_test PUBLIC libs/png++-0.2.9) if(WIN32) target_compile_definitions(gaf_test PRIVATE __STDC_LIB_EXT1__=1) endif() @@ -738,7 +775,6 @@ add_executable(pcx_test src/pcx_test.cpp) target_link_libraries(pcx_test librwe) target_link_libraries(pcx_test ${PNG_LIBRARIES}) target_include_directories(pcx_test PUBLIC ${PNG_INCLUDE_DIRS}) -target_include_directories(pcx_test PUBLIC libs/png++-0.2.9) if(WIN32) target_compile_definitions(pcx_test PRIVATE __STDC_LIB_EXT1__=1) endif() @@ -747,7 +783,6 @@ add_executable(tnt_test src/tnt_test.cpp) target_link_libraries(tnt_test librwe) target_link_libraries(tnt_test ${PNG_LIBRARIES}) target_include_directories(tnt_test PUBLIC ${PNG_INCLUDE_DIRS}) -target_include_directories(tnt_test PUBLIC libs/png++-0.2.9) if(WIN32) target_compile_definitions(tnt_test PRIVATE __STDC_LIB_EXT1__=1) endif() @@ -762,7 +797,6 @@ add_executable(fnt_test src/fnt_test.cpp) target_link_libraries(fnt_test librwe) target_link_libraries(fnt_test ${PNG_LIBRARIES}) target_include_directories(fnt_test PUBLIC ${PNG_INCLUDE_DIRS}) -target_include_directories(fnt_test PUBLIC libs/png++-0.2.9) if(WIN32) target_compile_definitions(fnt_test PRIVATE __STDC_LIB_EXT1__=1) endif() @@ -771,7 +805,6 @@ add_executable(texture_test src/texture_test.cpp) target_link_libraries(texture_test librwe) target_link_libraries(texture_test ${PNG_LIBRARIES}) target_include_directories(texture_test PUBLIC ${PNG_INCLUDE_DIRS}) -target_include_directories(texture_test PUBLIC libs/png++-0.2.9) if(WIN32) target_compile_definitions(texture_test PRIVATE __STDC_LIB_EXT1__=1) endif() @@ -781,7 +814,6 @@ target_link_libraries(rwe_bridge PRIVATE librwe) target_link_libraries(rwe_bridge PRIVATE nlohmann_json::nlohmann_json) target_link_libraries(rwe_bridge PRIVATE ${PNG_LIBRARIES}) target_include_directories(rwe_bridge PUBLIC ${PNG_INCLUDE_DIRS}) -target_include_directories(rwe_bridge PUBLIC libs/png++-0.2.9) if(WIN32) target_compile_definitions(rwe_bridge PRIVATE __STDC_LIB_EXT1__=1) endif() @@ -825,15 +857,17 @@ set(TEST_FILES src/rwe/sim/SimVector.test.cpp src/rwe/sim/UnitState_util.test.cpp src/rwe/sim/util.test.cpp + src/rwe/util/OpaqueArgs.test.cpp src/rwe/util/Result.test.cpp + src/rwe/util/SimpleLogger.test.cpp src/rwe/util/rwe_string.test.cpp ) -add_executable(rwe_test src/test.cpp ${TEST_FILES}) -target_include_directories(rwe_test PUBLIC ${PROJECT_SOURCE_DIR}/libs/catch2/single_include) +add_executable(rwe_test ${TEST_FILES}) +target_link_libraries(rwe_test Catch2::Catch2WithMain) target_link_libraries(rwe_test rapidcheck_catch) -target_link_libraries(rwe_test rapidcheck_boost) target_link_libraries(rwe_test librwe) +target_precompile_headers(rwe_test REUSE_FROM librwe) add_test(NAME rwe_test COMMAND rwe_test) install(TARGETS rwe rwe_bridge diff --git a/README.md b/README.md index da29f6083..c6fe1cd62 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,14 @@ To find the files, click "Environment: RWE\_COMPILER=MSYS; Configuration: Releas RWE is currently only available on Windows, however the code is also built and tested on Linux. Official Linux binaries will be available when the project reaches a stable version. +MacOS should work via the devbox build below, tho it's not regularly tested. Source code is hosted on Github: https://github.com/MHeasell/rwe -## How to Install - +## How to Install +Windows: 1. Create the folder `%AppData%/RWE/Data` and copy your TA data files to it (.hpi, .ufo, rev31.gp3, etc.) 2. Run rwe.exe (if you used the installer, RWE will be in your start menu items) @@ -67,8 +68,6 @@ First fetch the source code: cd rwe git submodule update --init --recursive -Then follow the instructions for your platform. - ### Windows with Visual Studio Get the RWE MSVC libraries bundle. @@ -96,7 +95,6 @@ Choose to run at the end of install, and in the terminal that opens install the pacman -S git make unzip mingw-w64-x86_64-cmake mingw-w64-x86_64-toolchain pacman -S \ - mingw-w64-x86_64-boost \ mingw-w64-x86_64-SDL2 \ mingw-w64-x86_64-SDL2_image \ mingw-w64-x86_64-SDL2_mixer \ @@ -134,8 +132,44 @@ Install Visual Studio Code, and open it. Under extensions, search for C/C++, and File > Open Folder, choose the root directory of the RWE repository (where CMakeLists.txt is). At the bottom of the VS Code window you should see CMake: [Debug]: Ready and probably No Kit Selected. Click No Kit Selected to choose which compiler to use. VS Code should auto-detect compilers on your machine, so if none are listed here, install Visual Studio or MSYS2 first and try again. Once a toolset is selected, CMake will configure itself for the project, with its output in the OUTPUT window. When that's done, you should be able to build by clicking the Build button there on the bottom or hit F7. -### Ubuntu - +### Linux/macOS +You can build without TA game assets, but to run the game rwe will need to know where they are. +rwe looks in $HOME/.rwe/Data by default. +> After building, you can also override it at runtime, e.g. `./rwe --data-path "$HOME/src/TA/Total Annihilation"` + +To copy TA data files (.hpi, .ufo, .ccx, rev31.gp3, etc.) in the default dir: +```bash +mkdir -p $HOME/.rwe/Data +cp /path/to/totala/*.hpi $HOME/.rwe/Data +cp /path/to/totala/*.ufo $HOME/.rwe/Data +cp /path/to/totala/*.ccx $HOME/.rwe/Data +cp /path/to/totala/*.gpf $HOME/.rwe/Data +cp /path/to/totala/*.gp3 $HOME/.rwe/Data +``` +Or, symlink: `ln -s "/path/to/totala/" ~/.rwe/Data` + +#### Devbox +The easiest way to get a working build environment is with [Devbox](https://www.jetify.com/devbox), +which uses Nix to provide dependencies automatically. +```bash +curl -fsSL https://get.jetify.com/devbox | bash # installs devbox (one-time) +# From the rwe repo base dir: +devbox shell # Enters the dev environment - uses devbox.json to pull in dependencies. May take a while the first time. +mkdir build && cd build +cmake .. -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Debug +make -j$(nproc 2>/dev/null || sysctl -n hw.ncpu) # -j isn't necessary, but builds with multiple threads and will reduce build time +./rwe_test # run tests, of course +# run the game- this should work for MacOS, tho in Linux the devbox/nix build may have a quirk that gives you OpenGL related errors on launch +./rwe +# If you see "Could not get EGL display" or other OpenGL related errors on launch, try this instead of `./rwe` +# This script just runs rwe with LD_LIBRARY_PATH set to a good guess of which dirs your video drivers exist in. +./run.sh +``` + + +#### Ubuntu +> Note: these steps may be a bit out of date, but the Devbox build above should work fine on Ubuntu. +> If you want a native build environment, take a look at devbox.json to get an idea of the dependencies required. Install the necessary packages: sudo add-apt-repository ppa:ubuntu-toolchain-r/test @@ -143,9 +177,6 @@ Install the necessary packages: sudo apt-get install \ gcc-7 \ g++-7 \ - libboost-dev \ - libboost-filesystem-dev \ - libboost-program-options-dev \ libsdl2-dev \ libsdl2-image-dev \ libsdl2-mixer-dev \ @@ -163,7 +194,6 @@ Here's how you might install CMake: export PATH=$(pwd)/cmake-3.8.2-Linux-x86_64/bin:$PATH Compile protobuf: - cd /path/to/rwe cd libs ./build-protobuf.sh @@ -174,24 +204,11 @@ Now build the code: mkdir build cd build export CC=gcc-7 CXX=g++-7 - cmake .. -G 'Unix Makefiles' -DCMAKE_EXPORT_COMPILE_COMMANDS=1 + cmake .. -G 'Unix Makefiles' make -The -DCMAKE_EXPORT_COMPILE_COMMANDS=1 is optional. It generates compile_commands.json which some VS Code plugins like clangd can read in order to automatically configure themselves for the project. Note if LLVM/clang is installed, export CC=clang CXX=clang++ should also work. -Install some TA data files (.hpi, .ufo, .ccx, rev31.gp3, etc.) -to your local data directory: - - mkdir -p $HOME/.rwe/Data - cp /path/to/totala/*.hpi $HOME/.rwe/Data - cp /path/to/totala/*.ufo $HOME/.rwe/Data - cp /path/to/totala/*.ccx $HOME/.rwe/Data - cp /path/to/totala/*.gpf $HOME/.rwe/Data - cp /path/to/totala/*.gp3 $HOME/.rwe/Data - -Alternatively you can symlink `.rwe/Data` to your TA directory. - Finally, launch RWE from the top-level project directory: cd /path/to/rwe diff --git a/devbox.json b/devbox.json new file mode 100644 index 000000000..716747b18 --- /dev/null +++ b/devbox.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.16.0/.schema/devbox.schema.json", + "packages": [ + "cmake", + "protobuf", + "SDL2", + "SDL2_image", + "SDL2_mixer", + "glew", + "libpng", + "zlib", + "pkg-config", + "git", + "libX11.dev" + ], + "shell": { + "init_hook": [ + "echo 'RWE devbox shell ready.'" + ], + "scripts": { + "build": [ + "mkdir -p build && cd build && cmake .. -G 'Unix Makefiles' -DCMAKE_BUILD_TYPE=Debug && make -j$(sysctl -n hw.ncpu 2>/dev/null || nproc)" + ], + "test": [ + "./build/rwe_test" + ] + } + } +} diff --git a/devbox.lock b/devbox.lock new file mode 100644 index 000000000..805ff46e9 --- /dev/null +++ b/devbox.lock @@ -0,0 +1,154 @@ +{ + "lockfile_version": "1", + "packages": { + "SDL2": { + "resolved": "github:NixOS/nixpkgs/724cf38d99ba81fbb4a347081db93e2e3a9bc2ae?narHash=sha256-MpAKyXfJRDTgRU33Hja%2BG%2B3h9ywLAJJNRq4Pjbb4dQs%3D#SDL2", + "source": "nixpkg", + "systems": { + "x86_64-darwin": { + "outputs": [ + { + "path": "/nix/store/b6c8nbfs0awmdblgjaajsrvwd0m47r3p-sdl2-compat-2.32.62", + "default": true + } + ] + } + } + }, + "SDL2_image": { + "resolved": "github:NixOS/nixpkgs/724cf38d99ba81fbb4a347081db93e2e3a9bc2ae?narHash=sha256-MpAKyXfJRDTgRU33Hja%2BG%2B3h9ywLAJJNRq4Pjbb4dQs%3D#SDL2_image", + "source": "nixpkg", + "systems": { + "x86_64-darwin": { + "outputs": [ + { + "path": "/nix/store/9kkyzjl728ijx8bvy95vvj0b0c2c8zp6-SDL2_image-2.8.8", + "default": true + } + ] + } + } + }, + "SDL2_mixer": { + "resolved": "github:NixOS/nixpkgs/724cf38d99ba81fbb4a347081db93e2e3a9bc2ae?narHash=sha256-MpAKyXfJRDTgRU33Hja%2BG%2B3h9ywLAJJNRq4Pjbb4dQs%3D#SDL2_mixer", + "source": "nixpkg", + "systems": { + "x86_64-darwin": { + "outputs": [ + { + "path": "/nix/store/n0ij8v60ck36rni9gfia25cgn0sjf4ia-SDL2_mixer-2.8.1", + "default": true + } + ] + } + } + }, + "cmake": { + "resolved": "github:NixOS/nixpkgs/724cf38d99ba81fbb4a347081db93e2e3a9bc2ae?narHash=sha256-MpAKyXfJRDTgRU33Hja%2BG%2B3h9ywLAJJNRq4Pjbb4dQs%3D#cmake", + "source": "nixpkg", + "systems": { + "x86_64-darwin": { + "outputs": [ + { + "path": "/nix/store/1cyc0yckxw16x6mma6jbcp70c7nv8i8i-cmake-4.1.2", + "default": true + } + ] + } + } + }, + "git": { + "resolved": "github:NixOS/nixpkgs/724cf38d99ba81fbb4a347081db93e2e3a9bc2ae?narHash=sha256-MpAKyXfJRDTgRU33Hja%2BG%2B3h9ywLAJJNRq4Pjbb4dQs%3D#git", + "source": "nixpkg" + }, + "github:NixOS/nixpkgs/nixpkgs-unstable": { + "last_modified": "2026-02-19T06:30:45Z", + "resolved": "github:NixOS/nixpkgs/724cf38d99ba81fbb4a347081db93e2e3a9bc2ae?lastModified=1771482645&narHash=sha256-MpAKyXfJRDTgRU33Hja%2BG%2B3h9ywLAJJNRq4Pjbb4dQs%3D" + }, + "glew": { + "resolved": "github:NixOS/nixpkgs/724cf38d99ba81fbb4a347081db93e2e3a9bc2ae?narHash=sha256-MpAKyXfJRDTgRU33Hja%2BG%2B3h9ywLAJJNRq4Pjbb4dQs%3D#glew", + "source": "nixpkg", + "systems": { + "x86_64-darwin": { + "outputs": [ + { + "name": "bin", + "path": "/nix/store/48z1blr66jpdkycxanw1jqxh4g1xygai-glew-2.2.0-bin", + "default": true + } + ] + } + } + }, + "libX11.dev": { + "resolved": "github:NixOS/nixpkgs/724cf38d99ba81fbb4a347081db93e2e3a9bc2ae?narHash=sha256-MpAKyXfJRDTgRU33Hja%2BG%2B3h9ywLAJJNRq4Pjbb4dQs%3D#libX11.dev", + "source": "nixpkg" + }, + "libpng": { + "resolved": "github:NixOS/nixpkgs/724cf38d99ba81fbb4a347081db93e2e3a9bc2ae?narHash=sha256-MpAKyXfJRDTgRU33Hja%2BG%2B3h9ywLAJJNRq4Pjbb4dQs%3D#libpng", + "source": "nixpkg", + "systems": { + "x86_64-darwin": { + "outputs": [ + { + "name": "man", + "path": "/nix/store/np3ii7yhkmgfl4d6rhmgdkb2lsq04lyk-libpng-apng-1.6.53-man", + "default": true + }, + { + "path": "/nix/store/zmz6mlp67gxiw51l3q8gv6x68lm1nanp-libpng-apng-1.6.53", + "default": true + } + ] + } + } + }, + "pkg-config": { + "resolved": "github:NixOS/nixpkgs/724cf38d99ba81fbb4a347081db93e2e3a9bc2ae?narHash=sha256-MpAKyXfJRDTgRU33Hja%2BG%2B3h9ywLAJJNRq4Pjbb4dQs%3D#pkg-config", + "source": "nixpkg", + "systems": { + "x86_64-darwin": { + "outputs": [ + { + "name": "man", + "path": "/nix/store/m3xvrpz0p7nnx6ck748ryyhvj50hjjz5-pkg-config-wrapper-0.29.2-man", + "default": true + }, + { + "path": "/nix/store/hxzn7zygy0lxw4s89i86wsa5k0nhwjwk-pkg-config-wrapper-0.29.2", + "default": true + } + ] + } + } + }, + "protobuf": { + "resolved": "github:NixOS/nixpkgs/724cf38d99ba81fbb4a347081db93e2e3a9bc2ae?narHash=sha256-MpAKyXfJRDTgRU33Hja%2BG%2B3h9ywLAJJNRq4Pjbb4dQs%3D#protobuf", + "source": "nixpkg", + "systems": { + "x86_64-darwin": { + "outputs": [ + { + "path": "/nix/store/hks7kpdlgx8v4n75yfq3lla03myyfhf2-protobuf-32.1", + "default": true + } + ] + } + } + }, + "zlib": { + "resolved": "github:NixOS/nixpkgs/724cf38d99ba81fbb4a347081db93e2e3a9bc2ae?narHash=sha256-MpAKyXfJRDTgRU33Hja%2BG%2B3h9ywLAJJNRq4Pjbb4dQs%3D#zlib", + "source": "nixpkg", + "systems": { + "x86_64-darwin": { + "outputs": [ + { + "path": "/nix/store/x76b36x85dy7q9bqnj5m5i1dzqsrahl9-zlib-1.3.1", + "default": true + } + ] + } + } + } + } +} diff --git a/fetch-msvc-libs.py b/fetch-msvc-libs.py index f6d532520..62726dc30 100644 --- a/fetch-msvc-libs.py +++ b/fetch-msvc-libs.py @@ -49,7 +49,7 @@ def log(msg): os.chdir("libs") sha256hash = binascii.unhexlify(sha256hash_hex) - bundle_url = "https://rwe.michaelheasell.com/bundles/" + bundle_name + bundle_url = "https://github.com/MHeasell/rwe/releases/download/msvc-libs-v6/" + bundle_name log("Deleting old files...") try: diff --git a/libs/asio b/libs/asio new file mode 160000 index 000000000..bd500f0a0 --- /dev/null +++ b/libs/asio @@ -0,0 +1 @@ +Subproject commit bd500f0a018db9a845ebaaed5c0318343ae9f497 diff --git a/libs/build-protobuf.sh b/libs/build-protobuf.sh index a3cd00724..916d7d14e 100755 --- a/libs/build-protobuf.sh +++ b/libs/build-protobuf.sh @@ -3,17 +3,31 @@ set -euo pipefail install_dir="${PWD}/_protobuf-install" +path_marker="$install_dir/build_path" pushd protobuf source_protobuf_version="$(git rev-parse HEAD)" built_protobuf_version="" +built_path="" if [ -f "$install_dir/done" ]; then built_protobuf_version="$(cat "$install_dir/done")" fi +if [ -f "$path_marker" ]; then + built_path="$(cat "$path_marker")" +fi + +if [ "$built_path" != "$install_dir" ]; then + echo "repo path has changed (was: ${built_path:-}, now: $install_dir)" + echo "cleaning protobuf source tree" + git clean -fdx + rm -rf "$install_dir" + built_protobuf_version="" +fi + if [ "$source_protobuf_version" != "$built_protobuf_version" ]; then echo "built protobuf is a different version than source" echo "source protobuf: $source_protobuf_version" @@ -25,6 +39,7 @@ if [ "$source_protobuf_version" != "$built_protobuf_version" ]; then make -j`nproc` make install echo "$source_protobuf_version" > "$install_dir/done" + echo "$install_dir" > "$path_marker" echo "finished building protobuf: $source_protobuf_version" else echo "built protobuf matches source protobuf, skipping protobuf build" diff --git a/libs/catch2 b/libs/catch2 index 182c910b4..29c9844f6 160000 --- a/libs/catch2 +++ b/libs/catch2 @@ -1 +1 @@ -Subproject commit 182c910b4b63ff587a3440e08f84f70497e49a81 +Subproject commit 29c9844f688acb27c87338c39cd186ebfe41aa19 diff --git a/libs/json b/libs/json index db78ac1d7..55f93686c 160000 --- a/libs/json +++ b/libs/json @@ -1 +1 @@ -Subproject commit db78ac1d7716f56fc9f1b030b715f872f93964e4 +Subproject commit 55f93686c01528224f448c19128836e7df245f72 diff --git a/libs/rapidcheck b/libs/rapidcheck index a5724ea5b..b96a4e626 160000 --- a/libs/rapidcheck +++ b/libs/rapidcheck @@ -1 +1 @@ -Subproject commit a5724ea5b0b00147109b0605c377f1e54c353ba2 +Subproject commit b96a4e626ef4c7348dcd16c500353c2f997a9f3f diff --git a/libs/spdlog b/libs/spdlog deleted file mode 160000 index 4fba14c79..000000000 --- a/libs/spdlog +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4fba14c79f356ae48d6141c561bf9fd7ba33fabd diff --git a/libs/utfcpp b/libs/utfcpp index 79835a5fa..6bbbaccab 160000 --- a/libs/utfcpp +++ b/libs/utfcpp @@ -1 +1 @@ -Subproject commit 79835a5fa57271f07a90ed36123e30ae9741178e +Subproject commit 6bbbaccabb1440d87acf5abfa3ed1ac604bfbe1a diff --git a/shaders/basicColor.frag b/shaders/basicColor.frag index d9c9f4ffc..0bffb6a79 100644 --- a/shaders/basicColor.frag +++ b/shaders/basicColor.frag @@ -1,4 +1,4 @@ -#version 130 +#version 150 in vec3 fragColor; out vec4 outColor; diff --git a/shaders/basicColor.vert b/shaders/basicColor.vert index f2db8c400..6c3a8b044 100644 --- a/shaders/basicColor.vert +++ b/shaders/basicColor.vert @@ -1,4 +1,4 @@ -#version 130 +#version 150 uniform mat4 mvpMatrix; diff --git a/shaders/basicTexture.frag b/shaders/basicTexture.frag index a27abfc9c..9cdaf7ebc 100644 --- a/shaders/basicTexture.frag +++ b/shaders/basicTexture.frag @@ -1,4 +1,4 @@ -#version 130 +#version 150 in vec2 fragTexCoord; out vec4 outColor; diff --git a/shaders/basicTexture.vert b/shaders/basicTexture.vert index ab601e970..222cbb62d 100644 --- a/shaders/basicTexture.vert +++ b/shaders/basicTexture.vert @@ -1,4 +1,4 @@ -#version 130 +#version 150 uniform mat4 mvpMatrix; diff --git a/shaders/flashEffect.frag b/shaders/flashEffect.frag index e1f2c159d..5ac4c36fe 100644 --- a/shaders/flashEffect.frag +++ b/shaders/flashEffect.frag @@ -1,4 +1,4 @@ -#version 130 +#version 150 in vec2 fragTexCoord; out vec4 outColor; diff --git a/shaders/flashEffect.vert b/shaders/flashEffect.vert index ab601e970..222cbb62d 100644 --- a/shaders/flashEffect.vert +++ b/shaders/flashEffect.vert @@ -1,4 +1,4 @@ -#version 130 +#version 150 uniform mat4 mvpMatrix; diff --git a/shaders/mapTerrain.frag b/shaders/mapTerrain.frag index 0330cd4c5..0858d30f3 100644 --- a/shaders/mapTerrain.frag +++ b/shaders/mapTerrain.frag @@ -1,4 +1,4 @@ -#version 130 +#version 150 in vec3 fragTexCoord; out vec4 outColor; diff --git a/shaders/mapTerrain.vert b/shaders/mapTerrain.vert index 34d13ff74..392d7fe94 100644 --- a/shaders/mapTerrain.vert +++ b/shaders/mapTerrain.vert @@ -1,4 +1,4 @@ -#version 130 +#version 150 uniform mat4 mvpMatrix; diff --git a/shaders/unitBuild.frag b/shaders/unitBuild.frag index 25c6f2931..309f8fd6b 100644 --- a/shaders/unitBuild.frag +++ b/shaders/unitBuild.frag @@ -1,4 +1,4 @@ -#version 130 +#version 150 in vec2 fragTexCoord; in float height; diff --git a/shaders/unitBuild.vert b/shaders/unitBuild.vert index 1237ac4c6..07fa00860 100644 --- a/shaders/unitBuild.vert +++ b/shaders/unitBuild.vert @@ -1,4 +1,4 @@ -#version 130 +#version 150 uniform mat4 mvpMatrix; uniform mat4 modelMatrix; diff --git a/shaders/unitShadow.frag b/shaders/unitShadow.frag index af37405e2..83342f8b0 100644 --- a/shaders/unitShadow.frag +++ b/shaders/unitShadow.frag @@ -1,4 +1,4 @@ -#version 130 +#version 150 in vec2 fragTexCoord; in float height; diff --git a/shaders/unitShadow.vert b/shaders/unitShadow.vert index ec09eeb3f..0d7f2ed91 100644 --- a/shaders/unitShadow.vert +++ b/shaders/unitShadow.vert @@ -1,4 +1,4 @@ -#version 130 +#version 150 uniform mat4 vpMatrix; uniform mat4 modelMatrix; diff --git a/shaders/unitTexture.frag b/shaders/unitTexture.frag index 6d8144468..4d7f3d427 100644 --- a/shaders/unitTexture.frag +++ b/shaders/unitTexture.frag @@ -1,4 +1,4 @@ -#version 130 +#version 150 in vec2 fragTexCoord; in float height; diff --git a/shaders/unitTexture.vert b/shaders/unitTexture.vert index 1237ac4c6..07fa00860 100644 --- a/shaders/unitTexture.vert +++ b/shaders/unitTexture.vert @@ -1,4 +1,4 @@ -#version 130 +#version 150 uniform mat4 mvpMatrix; uniform mat4 modelMatrix; diff --git a/shaders/worldPost.frag b/shaders/worldPost.frag index c1178c026..2464a2124 100644 --- a/shaders/worldPost.frag +++ b/shaders/worldPost.frag @@ -1,4 +1,4 @@ -#version 130 +#version 150 in vec2 fragTexCoord; out vec4 outColor; diff --git a/shaders/worldPost.vert b/shaders/worldPost.vert index e472d57fa..dd7fd2b3b 100644 --- a/shaders/worldPost.vert +++ b/shaders/worldPost.vert @@ -1,4 +1,4 @@ -#version 130 +#version 150 in vec3 position; in vec2 texCoord; diff --git a/src/bridge.cpp b/src/bridge.cpp index 367732f55..9d3db6e64 100644 --- a/src/bridge.cpp +++ b/src/bridge.cpp @@ -1,9 +1,10 @@ #include -#include -#include +#include +#include +#include #include #include -#include +#include #include #include #include @@ -15,7 +16,7 @@ #include #include -namespace fs = boost::filesystem; +namespace fs = std::filesystem; using json = nlohmann::json; @@ -113,18 +114,7 @@ void writeGetModeListSuccess(const std::vector>& modes) namespace rwe { - void loadPalette(std::istream& in, png::rgb_pixel* buffer) - { - for (unsigned int i = 0; i < 256; ++i) - { - in.read(reinterpret_cast(&(buffer[i].red)), 1); - in.read(reinterpret_cast(&(buffer[i].green)), 1); - in.read(reinterpret_cast(&(buffer[i].blue)), 1); - in.seekg(1, std::ios::cur); // skip alpha - } - } - - void extractMinimap(CompositeVirtualFileSystem& vfs, const png::rgb_pixel* palette, const std::string& source, const std::string& mapName, const std::string& outputPath) + void extractMinimap(CompositeVirtualFileSystem& vfs, const RgbPixel* palette, const std::string& source, const std::string& mapName, const std::string& outputPath) { auto tntData = vfs.readFileFromSource(source, "maps/" + mapName + ".tnt"); @@ -133,19 +123,18 @@ namespace rwe throw std::runtime_error("map tnt not found!"); } - boost::interprocess::bufferstream tntStream(tntData->data(), tntData->size()); + rwe::SpanStream tntStream(tntData->data(), tntData->size()); TntArchive tnt(&tntStream); auto minimap = tnt.readMinimap(); - png::image image(minimap.width, minimap.height); - for (png::uint_32 y = 0; y < image.get_height(); ++y) + PngImage image(minimap.width, minimap.height); + for (uint32_t y = 0; y < minimap.height; ++y) { - for (png::uint_32 x = 0; x < image.get_width(); ++x) + for (uint32_t x = 0; x < minimap.width; ++x) { auto b = static_cast(minimap.data[(y * minimap.width) + x]); assert(b >= 0 && b < 256); - auto px = palette[b]; - image[y][x] = px; + image.at(x, y) = palette[b]; } } @@ -172,8 +161,8 @@ std::optional getMinimap(rwe::CompositeVirtualFileSystem& vfs, cons return std::nullopt; } - boost::interprocess::bufferstream paletteBuffer(paletteBytes->data(), paletteBytes->size()); - png::rgb_pixel palette[256]; + rwe::SpanStream paletteBuffer(paletteBytes->data(), paletteBytes->size()); + rwe::RgbPixel palette[256]; rwe::loadPalette(paletteBuffer, palette); auto output = fs::temp_directory_path(); diff --git a/src/fnt_test.cpp b/src/fnt_test.cpp index c86d9f63d..fbc940425 100644 --- a/src/fnt_test.cpp +++ b/src/fnt_test.cpp @@ -2,9 +2,9 @@ #include #include #include -#include #include #include +#include #include @@ -14,7 +14,7 @@ void renderFontFile(std::istream& in, std::ostream& out) std::vector v(512); - png::image image(256, 512); + rwe::PngImage image(256, 512); for (unsigned int i = 0; i < 256; ++i) { auto charX = (i % 16) * 16; @@ -34,12 +34,12 @@ void renderFontFile(std::istream& in, std::ostream& out) } auto val = (static_cast(v[j / 8u]) >> (7u - (j % 8u))) & 1u; - auto pxVal = val ? 255 : 0; - image[charY + dy][charX + dx] = png::rgb_pixel(pxVal, pxVal, pxVal); + auto pxVal = static_cast(val ? 255 : 0); + image.at(charX + dx, charY + dy) = rwe::RgbPixel{pxVal, pxVal, pxVal}; } } - image.write_stream(out); + image.writeStream(out); } int main(int argc, char* argv[]) diff --git a/src/gaf_test.cpp b/src/gaf_test.cpp index 214f0b921..f880f096f 100644 --- a/src/gaf_test.cpp +++ b/src/gaf_test.cpp @@ -1,27 +1,14 @@ -#include +#include +#include #include #include #include #include -#include #include +#include #include -namespace fs = boost::filesystem; - -void loadPalette(const std::string& filename, png::rgb_pixel* buffer) -{ - std::ifstream in(filename, std::ios::binary); - - for (unsigned int i = 0; i < 256; ++i) - { - in.read(reinterpret_cast(&(buffer[i].red)), 1); - in.read(reinterpret_cast(&(buffer[i].green)), 1); - in.read(reinterpret_cast(&(buffer[i].blue)), 1); - in.seekg(1, std::ios::cur); // skip alpha - } -} - +namespace fs = std::filesystem; int listCommand(const std::string& filename) { @@ -95,14 +82,14 @@ class InspectAdapter : public rwe::GafReaderAdapter class GafAdapter : public rwe::GafReaderAdapter { private: - png::rgb_pixel* palette; + rwe::RgbPixel* palette; std::size_t frameCount; std::unique_ptr currentFrame; rwe::GafFrameData currentFrameHeader; fs::path destPath; public: - explicit GafAdapter(png::rgb_pixel* palette, const std::string& destPath) : palette(palette), frameCount(0), currentFrame(), destPath(destPath) {} + explicit GafAdapter(rwe::RgbPixel* palette, const std::string& destPath) : palette(palette), frameCount(0), currentFrame(), destPath(destPath) {} void beginFrame(const rwe::GafFrameEntry& entry, const rwe::GafFrameData& header) override { std::cout << "Beginning frame " << frameCount << std::endl; @@ -141,15 +128,14 @@ class GafAdapter : public rwe::GafReaderAdapter void endFrame() override { - png::image image(currentFrameHeader.width, currentFrameHeader.height); - for (png::uint_32 y = 0; y < image.get_height(); ++y) + rwe::PngImage image(currentFrameHeader.width, currentFrameHeader.height); + for (uint32_t y = 0; y < currentFrameHeader.height; ++y) { - for (png::uint_32 x = 0; x < image.get_width(); ++x) + for (uint32_t x = 0; x < currentFrameHeader.width; ++x) { auto b = static_cast(currentFrame[(y * currentFrameHeader.width) + x]); assert(b >= 0 && b < 256); - auto px = palette[b]; - image[y][x] = px; + image.at(x, y) = palette[b]; } } @@ -165,8 +151,8 @@ int extractAllCommand(const std::string& palettePath, const std::string& gafPath { std::cout << "Palette file: " << palettePath << std::endl; - png::rgb_pixel palette[256]; - loadPalette(palettePath, palette); + rwe::RgbPixel palette[256]; + rwe::loadPalette(palettePath, palette); std::cout << "GAF archive: " << gafPath << std::endl; std::ifstream file(gafPath, std::ios::binary); @@ -201,8 +187,8 @@ int extractCommand(const std::string& palettePath, const std::string& gafPath, c { std::cout << "Palette file: " << palettePath << std::endl; - png::rgb_pixel palette[256]; - loadPalette(palettePath, palette); + rwe::RgbPixel palette[256]; + rwe::loadPalette(palettePath, palette); std::cout << "GAF archive: " << gafPath << std::endl; std::ifstream file(gafPath, std::ios::binary); diff --git a/src/hpi_test.cpp b/src/hpi_test.cpp index 0f2292d7f..92719d211 100644 --- a/src/hpi_test.cpp +++ b/src/hpi_test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -6,7 +6,7 @@ #include #include -namespace fs = boost::filesystem; +namespace fs = std::filesystem; std::string schemeName(rwe::HpiArchive::File::CompressionScheme scheme) { diff --git a/src/main.cpp b/src/main.cpp index 22c8bd795..dbbb841f0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,5 @@ #include -#include -#include +#include #include #include #include @@ -27,12 +26,12 @@ #include #include #include +#include #include +#include #include -#include -namespace fs = boost::filesystem; -namespace po = boost::program_options; +namespace fs = std::filesystem; namespace rwe { @@ -79,13 +78,12 @@ namespace rwe } } - Result createOpenGlContext(SdlContext* sdlContext, SDL_Window* window, spdlog::logger& logger, const OpenGlVersionInfo& requiredVersion) + Result createOpenGlContext(SdlContext* sdlContext, SDL_Window* window, const OpenGlVersionInfo& requiredVersion) { - logger.info( - "Requesting OpenGL version {0}.{1}, {2} profile", - requiredVersion.version.majorVersion, - requiredVersion.version.minorVersion, - getOpenGlProfileName(requiredVersion.profile)); + LOG_INFO << "Requesting OpenGL version " + << requiredVersion.version.majorVersion << "." + << requiredVersion.version.minorVersion << ", " + << getOpenGlProfileName(requiredVersion.profile) << " profile"; if (sdlContext->glSetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, requiredVersion.version.majorVersion) != 0) { @@ -120,14 +118,14 @@ namespace rwe return Ok(std::move(glContext)); }; - int run(spdlog::logger& logger, const std::vector& searchPath, const PathMapping& pathMapping, const std::optional& gameParameters, unsigned int desiredWindowWidth, unsigned int desiredWindowHeight, bool fullscreen, const std::string& imGuiIniPath, GlobalConfig& globalConfig) + int run(const std::vector& searchPath, const PathMapping& pathMapping, const std::optional& gameParameters, unsigned int desiredWindowWidth, unsigned int desiredWindowHeight, bool fullscreen, const std::string& imGuiIniPath, GlobalConfig& globalConfig) { - logger.info(ProjectNameVersion); - logger.info("Current directory: {0}", fs::current_path().string()); + LOG_INFO << ProjectNameVersion; + LOG_INFO << "Current directory: " << fs::current_path().string(); TimeService timeService(getTimestamp()); - logger.info("Initializing SDL"); + LOG_INFO << "Initializing SDL"; SdlContextManager sdlManager; // Set a reasonable number of audio channels @@ -191,13 +189,13 @@ namespace rwe sdlContext->getWindowSize(window.get(), &windowWidth, &windowHeight); Viewport viewport(0, 0, windowWidth, windowHeight); - logger.info("Initializing OpenGL context"); + LOG_INFO << "Initializing OpenGL context"; - auto glContextResult = createOpenGlContext(sdlContext, window.get(), logger, OpenGlVersionInfo(3, 2, OpenGlProfile::Core)); + auto glContextResult = createOpenGlContext(sdlContext, window.get(), OpenGlVersionInfo(3, 2, OpenGlProfile::Core)); if (!glContextResult) { - logger.error("Failed to create preferred OpenGL context: {0}", glContextResult.getErr()); - glContextResult = createOpenGlContext(sdlContext, window.get(), logger, OpenGlVersionInfo(3, 0, OpenGlProfile::Compatibility)); + LOG_ERROR << "Failed to create preferred OpenGL context: " << glContextResult.getErr(); + glContextResult = createOpenGlContext(sdlContext, window.get(), OpenGlVersionInfo(3, 0, OpenGlProfile::Compatibility)); if (!glContextResult) { throw std::runtime_error(glContextResult.getErr()); @@ -213,29 +211,29 @@ namespace rwe doGlewInit(); // log opengl context info - logger.info("OpenGL version: {0}", glGetString(GL_VERSION)); - logger.info("OpenGL vendor: {0}", glGetString(GL_VENDOR)); - logger.info("OpenGL renderer: {0}", glGetString(GL_RENDERER)); - logger.info("OpenGL shading language version: {0}", glGetString(GL_SHADING_LANGUAGE_VERSION)); - logger.debug("OpenGL extensions:"); + LOG_INFO << "OpenGL version: " << glGetString(GL_VERSION); + LOG_INFO << "OpenGL vendor: " << glGetString(GL_VENDOR); + LOG_INFO << "OpenGL renderer: " << glGetString(GL_RENDERER); + LOG_INFO << "OpenGL shading language version: " << glGetString(GL_SHADING_LANGUAGE_VERSION); + LOG_DEBUG << "OpenGL extensions:"; int openGlExtensionCount; glGetIntegerv(GL_NUM_EXTENSIONS, &openGlExtensionCount); for (int i = 0; i < openGlExtensionCount; ++i) { - logger.debug(" {0}", glGetStringi(GL_EXTENSIONS, i)); + LOG_DEBUG << " " << glGetStringi(GL_EXTENSIONS, i); } - logger.info("Initializing Dear ImGui"); + LOG_INFO << "Initializing Dear ImGui"; ImGuiContext imGuiContext(imGuiIniPath, window.get(), glContext.get()); - logger.info("Initializing virtual file system"); + LOG_INFO << "Initializing virtual file system"; CompositeVirtualFileSystem vfs; for (const auto& path : searchPath) { addToVfs(vfs, path.string()); } - logger.info("Loading palette"); + LOG_INFO << "Loading palette"; auto paletteBytes = vfs.readFile("palettes/PALETTE.PAL"); if (!paletteBytes) { @@ -248,7 +246,7 @@ namespace rwe throw std::runtime_error("Couldn't read palette"); } - logger.info("Loading GUI palette"); + LOG_INFO << "Loading GUI palette"; auto guiPaletteBytes = vfs.readFile("palettes/GUIPAL.PAL"); if (!guiPaletteBytes) { @@ -261,7 +259,7 @@ namespace rwe throw std::runtime_error("Couldn't read GUI palette"); } - logger.info("Initializing services"); + LOG_INFO << "Initializing services"; GraphicsContext graphics; graphics.enableCulling(); graphics.enableBlending(); @@ -273,7 +271,7 @@ namespace rwe AudioService audioService(sdlContext, sdlManager.getSdlMixerContext(), &vfs); // load sound definitions - logger.info("Loading global sound definitions"); + LOG_INFO << "Loading global sound definitions"; auto allSoundBytes = vfs.readFile("gamedata/ALLSOUND.TDF"); if (!allSoundBytes) { @@ -283,7 +281,7 @@ namespace rwe std::string allSoundString(allSoundBytes->data(), allSoundBytes->size()); auto allSoundTdf = parseTdfFromString(allSoundString); - logger.info("Loading cursors"); + LOG_INFO << "Loading cursors"; Cursors cursors; cursors[*CursorType::Normal] = textureService.getGafEntry("anims/CURSORS.GAF", "cursornormal"); cursors[*CursorType::Select] = textureService.getGafEntry("anims/CURSORS.GAF", "cursorselect"); @@ -299,7 +297,7 @@ namespace rwe SceneManager sceneManager(sdlContext, window.get(), &graphics, &timeService, &imGuiContext, &cursor, &globalConfig, UiRenderService(&graphics, &shaders, &viewport), &viewport); - logger.info("Loading side data"); + LOG_INFO << "Loading side data"; auto sideDataBytes = vfs.readFile("gamedata/SIDEDATA.TDF"); if (!sideDataBytes) { @@ -335,29 +333,29 @@ namespace rwe if (gameParameters) { - logger.info("Launching into game on map: {0}", gameParameters->mapName); + LOG_INFO << "Launching into game on map: " << gameParameters->mapName; auto scene = std::make_unique( sceneContext, &allSoundTdf, AudioService::LoopToken(), *gameParameters); - sceneManager.setNextScene(std::move(scene)); + sceneManager.setNextScene(std::shared_ptr(std::move(scene))); } else { - logger.info("Launching into the main menu"); + LOG_INFO << "Launching into the main menu"; auto scene = std::make_unique( sceneContext, &allSoundTdf, viewport.width(), viewport.height()); - sceneManager.setNextScene(std::move(scene)); + sceneManager.setNextScene(std::shared_ptr(std::move(scene))); } - logger.info("Entering main loop"); + LOG_INFO << "Entering main loop"; sceneManager.execute(); - logger.info("Finished main loop, exiting"); + LOG_INFO << "Finished main loop, exiting"; return 0; } @@ -445,12 +443,12 @@ namespace rwe } } -auto createLogger(const fs::path& logFile) +std::shared_ptr createLogger(const fs::path& logFile) { - return spdlog::basic_logger_mt("rwe", logFile.string(), true); + return std::make_shared(logFile.string(), true); } -auto createLoggerInDir(const fs::path& logDir) +std::shared_ptr createLoggerInDir(const fs::path& logDir) { for (int i = 0; i < 3; ++i) { @@ -466,9 +464,9 @@ auto createLoggerInDir(const fs::path& logDir) try { - return spdlog::basic_logger_mt("rwe", logPath.string(), true); + return std::make_shared(logPath.string(), true); } - catch (const spdlog::spdlog_ex& ex) + catch (const std::exception&) { } } @@ -520,78 +518,48 @@ int main(int argc, char* argv[]) fs::path imGuiIniFilePath(*localDataPath); imGuiIniFilePath /= "imgui.ini"; - po::options_description desc("Allowed options"); - - // clang-format off - desc.add_options() - ("help", "produce help message") - ("log", po::value(), "Sets the log output file path") - ("state-log", po::value(), "Sets the output file for sim-state logs. This is a desync debugging feature.") - ("width", po::value()->default_value(800), "Sets the window width in pixels") - ("height", po::value()->default_value(600), "Sets the window height in pixels") - ("fullscreen", po::bool_switch(), "Starts the application in fullscreen mode") - ("interface-mode", po::value()->default_value("left-click"), "left-click or right-click") - ("data-path", po::value>(), "Sets the location(s) to search for game data") - ("map", po::value(), "If given, launches straight into a game on the given map") - ("port", po::value()->default_value("1337"), "Network port to bind to") - ("player", po::value>(), "type;side;color") - ("dir-ai", po::value()->default_value("ai"), "AI directory name") - ("dir-anims", po::value()->default_value("anims"), "anims directory name") - ("dir-bitmaps", po::value()->default_value("bitmaps"), "bitmaps directory name") - ("dir-camps", po::value()->default_value("camps"), "campaigns directory name") - ("dir-downloads", po::value()->default_value("downloads"), "downloads directory name") - ("dir-features", po::value()->default_value("features"), "features directory name") - ("dir-fonts", po::value()->default_value("fonts"), "fonts directory name") - ("dir-gamedata", po::value()->default_value("gamedata"), "gamedata directory name") - ("dir-guis", po::value()->default_value("guis"), "GUIs directory name") - ("dir-maps", po::value()->default_value("maps"), "maps directory name") - ("dir-objects3d", po::value()->default_value("objects3d"), "3D objects directory name") - ("dir-palettes", po::value()->default_value("palettes"), "palettes directory name") - ("dir-scripts", po::value()->default_value("scripts"), "scripts directory name") - ("dir-sounds", po::value()->default_value("sounds"), "sounds directory name") - ("dir-textures", po::value()->default_value("textures"), "textures directory name") - ("dir-unitpics", po::value()->default_value("unitpics"), "unitpics directory name") - ("dir-units", po::value()->default_value("units"), "units directory name") - ("dir-weapons", po::value()->default_value("weapons"), "weapons directory name"); - // clang-format on - - po::variables_map vm; - po::store(po::parse_command_line(argc, argv, desc), vm); - { - std::ifstream configFileStream(configFilePath.string(), std::ios::binary); - if (configFileStream.is_open()) - { - po::store(po::parse_config_file(configFileStream, desc), vm); - } - } - po::notify(vm); - - if (vm.count("help")) - { - std::cout << desc << std::endl; + rwe::OpaqueArgs args; + args.parse(argc, argv); + args.parseConfig(configFilePath.string()); + + if (args.isHelpRequested()) + { + std::cout << "Usage: rwe [options]\n" + << " --help Show this message\n" + << " --log Log output file path\n" + << " --state-log Sim-state log file (desync debugging)\n" + << " --width Window width (default: 800)\n" + << " --height Window height (default: 600)\n" + << " --fullscreen Start in fullscreen mode\n" + << " --interface-mode left-click or right-click (default: left-click)\n" + << " --data-path Game data search path (repeatable)\n" + << " --map Launch directly into a game on this map\n" + << " --port Network port (default: 1337)\n" + << " --player Player spec: name;type;side;color (repeatable)\n" + << " --dir- Override directory name for a data category\n" + << std::endl; return 0; } - auto logger = vm.count("log") ? createLogger(fs::path(vm["log"].as())) : createLoggerInDir(*localDataPath); - logger->set_level(spdlog::level::debug); - logger->flush_on(spdlog::level::debug); // always flush + auto logger = args.contains("log") ? createLogger(fs::path(args.getString("log"))) : createLoggerInDir(*localDataPath); + rwe::setGlobalLogger(logger); try { rwe::GlobalConfig config; - config.leftClickInterfaceMode = vm["interface-mode"].as() != "right-click"; + config.leftClickInterfaceMode = args.getString("interface-mode", "left-click") != "right-click"; std::optional gameParameters; - if (vm.count("map")) + if (args.contains("map")) { - const auto& mapName = vm["map"].as(); - const auto& players = vm["player"].as>(); + const auto& mapName = args.getString("map"); + const auto& players = args.getMulti("player"); gameParameters = rwe::GameParameters{mapName, 0}; - if (vm.count("state-log")) + if (args.contains("state-log")) { - gameParameters->stateLogFile = vm["state-log"].as(); + gameParameters->stateLogFile = args.getString("state-log"); } - gameParameters->localNetworkPort = vm["port"].as(); + gameParameters->localNetworkPort = args.getString("port", "1337"); unsigned int playerIndex = 0; if (players.size() > 10) { @@ -606,44 +574,44 @@ int main(int argc, char* argv[]) std::vector gameDataPaths; - if (vm.count("data-path")) + auto dataPaths = args.getMulti("data-path"); + if (!dataPaths.empty()) { - const auto& paths = vm["data-path"].as>(); - gameDataPaths.insert(gameDataPaths.end(), paths.begin(), paths.end()); + gameDataPaths.insert(gameDataPaths.end(), dataPaths.begin(), dataPaths.end()); } else { gameDataPaths.emplace_back(*localDataPath) /= "Data"; } - auto screenWidth = vm["width"].as(); - auto screenHeight = vm["height"].as(); - auto fullscreen = vm["fullscreen"].as(); + auto screenWidth = args.getUint("width", 800); + auto screenHeight = args.getUint("height", 600); + auto fullscreen = args.getBool("fullscreen"); auto pathMapping = constructDefaultPathMapping(); - pathMapping.ai = vm["dir-ai"].as(); - pathMapping.anims = vm["dir-anims"].as(); - pathMapping.bitmaps = vm["dir-bitmaps"].as(); - pathMapping.camps = vm["dir-camps"].as(); - pathMapping.downloads = vm["dir-features"].as(); - pathMapping.fonts = vm["dir-fonts"].as(); - pathMapping.gamedata = vm["dir-gamedata"].as(); - pathMapping.guis = vm["dir-guis"].as(); - pathMapping.maps = vm["dir-maps"].as(); - pathMapping.objects3d = vm["dir-objects3d"].as(); - pathMapping.palettes = vm["dir-palettes"].as(); - pathMapping.scripts = vm["dir-scripts"].as(); - pathMapping.sounds = vm["dir-sounds"].as(); - pathMapping.textures = vm["dir-textures"].as(); - pathMapping.unitpics = vm["dir-unitpics"].as(); - pathMapping.units = vm["dir-units"].as(); - pathMapping.weapons = vm["dir-weapons"].as(); - - return rwe::run(*logger, gameDataPaths, pathMapping, gameParameters, screenWidth, screenHeight, fullscreen, imGuiIniFilePath.string(), config); + pathMapping.ai = args.getString("dir-ai", "ai"); + pathMapping.anims = args.getString("dir-anims", "anims"); + pathMapping.bitmaps = args.getString("dir-bitmaps", "bitmaps"); + pathMapping.camps = args.getString("dir-camps", "camps"); + pathMapping.downloads = args.getString("dir-downloads", "downloads"); + pathMapping.fonts = args.getString("dir-fonts", "fonts"); + pathMapping.gamedata = args.getString("dir-gamedata", "gamedata"); + pathMapping.guis = args.getString("dir-guis", "guis"); + pathMapping.maps = args.getString("dir-maps", "maps"); + pathMapping.objects3d = args.getString("dir-objects3d", "objects3d"); + pathMapping.palettes = args.getString("dir-palettes", "palettes"); + pathMapping.scripts = args.getString("dir-scripts", "scripts"); + pathMapping.sounds = args.getString("dir-sounds", "sounds"); + pathMapping.textures = args.getString("dir-textures", "textures"); + pathMapping.unitpics = args.getString("dir-unitpics", "unitpics"); + pathMapping.units = args.getString("dir-units", "units"); + pathMapping.weapons = args.getString("dir-weapons", "weapons"); + + return rwe::run(gameDataPaths, pathMapping, gameParameters, screenWidth, screenHeight, fullscreen, imGuiIniFilePath.string(), config); } catch (const std::exception& e) { - logger->critical(e.what()); + LOG_CRITICAL << e.what(); throw; } } diff --git a/src/pcx_test.cpp b/src/pcx_test.cpp index e4cdc78a9..ef1bf9922 100644 --- a/src/pcx_test.cpp +++ b/src/pcx_test.cpp @@ -1,14 +1,8 @@ #include -#include #include +#include #include -class PaletteReadingException : public std::runtime_error -{ -public: - explicit PaletteReadingException(const char* message) : runtime_error(message) {} -}; - int convert(const std::string& inFile, const std::string& outFile) { std::ifstream in(inFile, std::ios::binary | std::ios::ate); @@ -28,14 +22,14 @@ int convert(const std::string& inFile, const std::string& outFile) auto width = decoder.getWidth(); auto height = decoder.getHeight(); - png::image image(width, height); - for (png::uint_32 y = 0; y < height; ++y) + rwe::PngImage image(width, height); + for (uint32_t y = 0; y < height; ++y) { - for (png::uint_32 x = 0; x < width; ++x) + for (uint32_t x = 0; x < width; ++x) { auto b = static_cast(decodedData[(y * width) + x]); auto px = palette[b]; - image[y][x] = png::rgb_pixel(px.red, px.green, px.blue); + image.at(x, y) = rwe::RgbPixel{px.red, px.green, px.blue}; } } diff --git a/src/rwe/BoxTreeSplit.h b/src/rwe/BoxTreeSplit.h index 8f0ca9853..3ea442810 100644 --- a/src/rwe/BoxTreeSplit.h +++ b/src/rwe/BoxTreeSplit.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include diff --git a/src/rwe/BoxTreeSplit.test.cpp b/src/rwe/BoxTreeSplit.test.cpp index 6e36a2d8a..032646323 100644 --- a/src/rwe/BoxTreeSplit.test.cpp +++ b/src/rwe/BoxTreeSplit.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include namespace rwe diff --git a/src/rwe/LoadingNetworkService.cpp b/src/rwe/LoadingNetworkService.cpp index 02021fd0a..34731c236 100644 --- a/src/rwe/LoadingNetworkService.cpp +++ b/src/rwe/LoadingNetworkService.cpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include namespace rwe { @@ -24,8 +24,9 @@ namespace rwe { std::scoped_lock lock(mutex); - // boost guarantees that resolve returns non-empty - remoteEndpoints.emplace_back(playerIndex, *resolver.resolve(boost::asio::ip::udp::resolver::query(host, port)), Status::Loading); + // asio guarantees that resolve returns non-empty + auto results = resolver.resolve(host, port); + remoteEndpoints.emplace_back(playerIndex, results.begin()->endpoint(), Status::Loading); } void LoadingNetworkService::setDoneLoading() @@ -58,8 +59,8 @@ namespace rwe { try { - spdlog::get("rwe")->debug("Opening listen socket on port {}", port); - auto endpoint = boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v6(), std::stoi(port)); + LOG_DEBUG << "Opening listen socket on port " << port; + auto endpoint = asio::ip::udp::endpoint(asio::ip::udp::v6(), std::stoi(port)); socket.open(endpoint.protocol()); socket.bind(endpoint); @@ -71,33 +72,33 @@ namespace rwe } catch (const std::exception& e) { - spdlog::get("rwe")->error("Network thread died with error: {0}", e.what()); + LOG_ERROR << "Network thread died with error: " << e.what(); } } - void LoadingNetworkService::onReceive(const boost::system::error_code& error, std::size_t bytesTransferred) + void LoadingNetworkService::onReceive(const asio::error_code& error, std::size_t bytesTransferred) { if (error) { - spdlog::get("rwe")->error("Received network error from {0} port {1}: {2}", currentRemoteEndpoint.address().to_string(), currentRemoteEndpoint.port(), error.message()); + LOG_ERROR << "Received network error from " << currentRemoteEndpoint.address().to_string() << " port " << currentRemoteEndpoint.port() << ": " << error.message(); return; } std::scoped_lock lock(mutex); - spdlog::get("rwe")->debug("Received network message from {0} {1}, size {2}", currentRemoteEndpoint.address().to_string(), currentRemoteEndpoint.port(), bytesTransferred); + LOG_DEBUG << "Received network message from " << currentRemoteEndpoint.address().to_string() << " " << currentRemoteEndpoint.port() << ", size " << bytesTransferred; auto it = std::find_if(remoteEndpoints.begin(), remoteEndpoints.end(), [this](const auto& p) { return p.endpoint == currentRemoteEndpoint; }); if (it == remoteEndpoints.end()) { // message from some unknown address, ignore - spdlog::get("rwe")->debug("Sender was unknown, aborting"); + LOG_DEBUG << "Sender was unknown, aborting"; return; } - spdlog::get("rwe")->debug("Sender was recognised"); + LOG_DEBUG << "Sender was recognised"; if (bytesTransferred < 4) { - spdlog::get("rwe")->error("Received message is too short, ignoring"); + LOG_ERROR << "Received message is too short, ignoring"; return; } @@ -105,7 +106,7 @@ namespace rwe auto computedCrc = computeCrc(receiveBuffer.data(), bytesTransferred - 4); if (receivedCrc != computedCrc) { - spdlog::get("rwe")->error("Message CRC incorrect, ignoring"); + LOG_ERROR << "Message CRC incorrect, ignoring"; return; } @@ -114,7 +115,7 @@ namespace rwe if (!message.has_loading_status()) { - spdlog::get("rwe")->debug("Sender is already in game!"); + LOG_DEBUG << "Sender is already in game!"; it->status = Status::Ready; return; } @@ -122,11 +123,11 @@ namespace rwe switch (message.loading_status().status()) { case proto::LoadingStatusMessage_Status_Loading: - spdlog::get("rwe")->debug("Sender is loading"); + LOG_DEBUG << "Sender is loading"; it->status = Status::Loading; break; case proto::LoadingStatusMessage_Status_Ready: - spdlog::get("rwe")->debug("Sender is ready"); + LOG_DEBUG << "Sender is ready"; it->status = Status::Ready; break; default: @@ -137,7 +138,7 @@ namespace rwe void LoadingNetworkService::notifyStatus() { std::scoped_lock lock(mutex); - spdlog::get("rwe")->debug("Notifying peers about loading status"); + LOG_DEBUG << "Notifying peers about loading status"; proto::NetworkMessage outerMessage; @@ -146,11 +147,11 @@ namespace rwe switch (loadingStatus) { case Status::Loading: - spdlog::get("rwe")->debug("we are loading"); + LOG_DEBUG << "we are loading"; innerMessage.set_status(proto::LoadingStatusMessage_Status_Loading); break; case Status::Ready: - spdlog::get("rwe")->debug("we are ready"); + LOG_DEBUG << "we are ready"; innerMessage.set_status(proto::LoadingStatusMessage_Status_Ready); break; default: @@ -158,7 +159,7 @@ namespace rwe } } - auto messageSize = outerMessage.ByteSize(); + auto messageSize = outerMessage.ByteSizeLong(); if (messageSize > getSize(sendBuffer) - 4) { throw std::runtime_error("Message to be sent was bigger than buffer size"); @@ -173,19 +174,19 @@ namespace rwe for (const auto& p : remoteEndpoints) { - spdlog::get("rwe")->debug("Sending notification to {0} {1}, size {2}", p.endpoint.address().to_string(), p.endpoint.port(), outerMessage.ByteSize()); - socket.send_to(boost::asio::buffer(sendBuffer.data(), messageSize + 4), p.endpoint); + LOG_DEBUG << "Sending notification to " << p.endpoint.address().to_string() << " " << p.endpoint.port() << ", size " << outerMessage.ByteSizeLong(); + socket.send_to(asio::buffer(sendBuffer.data(), messageSize + 4), p.endpoint); } } void LoadingNetworkService::notifyStatusLoop() { notifyStatus(); - notifyTimer.expires_from_now(std::chrono::milliseconds(100)); - notifyTimer.async_wait([this](const boost::system::error_code& error) { + notifyTimer.expires_after(std::chrono::milliseconds(100)); + notifyTimer.async_wait([this](const asio::error_code& error) { if (error) { - spdlog::get("rwe")->error("Received error from network notify timer: {0}", error.message()); + LOG_ERROR << "Received error from network notify timer: " << error.message(); return; } notifyStatusLoop(); @@ -196,14 +197,14 @@ namespace rwe { // go back to waiting for the next message socket.async_receive_from( - boost::asio::buffer(receiveBuffer.data(), receiveBuffer.size()), + asio::buffer(receiveBuffer.data(), receiveBuffer.size()), currentRemoteEndpoint, [this](const auto& error, const auto& bytesTransferred) { onReceive(error, bytesTransferred); startListening(); }); } - boost::asio::ip::udp::endpoint LoadingNetworkService::getEndpoint(int playerIndex) + asio::ip::udp::endpoint LoadingNetworkService::getEndpoint(int playerIndex) { auto it = std::find_if(remoteEndpoints.begin(), remoteEndpoints.end(), [&](const auto& e) { return e.playerIndex == playerIndex; }); if (it == remoteEndpoints.end()) diff --git a/src/rwe/LoadingNetworkService.h b/src/rwe/LoadingNetworkService.h index 4d78d89cc..4da764592 100644 --- a/src/rwe/LoadingNetworkService.h +++ b/src/rwe/LoadingNetworkService.h @@ -2,8 +2,7 @@ #include #include -#include -#include // not in asio.hpp in old boost versions +#include #include #include #include @@ -26,9 +25,9 @@ namespace rwe struct PlayerInfo { int playerIndex; - boost::asio::ip::udp::endpoint endpoint; + asio::ip::udp::endpoint endpoint; Status status; - PlayerInfo(int playerIndex, const boost::asio::ip::udp::endpoint& endpoint, Status status) : playerIndex(playerIndex), endpoint(endpoint), status(status) {} + PlayerInfo(int playerIndex, const asio::ip::udp::endpoint& endpoint, Status status) : playerIndex(playerIndex), endpoint(endpoint), status(status) {} }; private: @@ -40,13 +39,13 @@ namespace rwe std::vector remoteEndpoints; // state owned by the worker thread - boost::asio::io_service ioContext; - boost::asio::ip::udp::resolver resolver; - boost::asio::ip::udp::socket socket; + asio::io_context ioContext; + asio::ip::udp::resolver resolver; + asio::ip::udp::socket socket; std::array sendBuffer; std::array receiveBuffer; - boost::asio::ip::udp::endpoint currentRemoteEndpoint; - boost::asio::steady_timer notifyTimer; + asio::ip::udp::endpoint currentRemoteEndpoint; + asio::steady_timer notifyTimer; public: LoadingNetworkService(); @@ -55,7 +54,7 @@ namespace rwe void addEndpoint(int playerIndex, const std::string& host, const std::string& port); - boost::asio::ip::udp::endpoint getEndpoint(int playerIndex); + asio::ip::udp::endpoint getEndpoint(int playerIndex); void setDoneLoading(); @@ -68,7 +67,7 @@ namespace rwe private: void run(const std::string& port); - void onReceive(const boost::system::error_code& error, std::size_t bytesTransferred); + void onReceive(const asio::error_code& error, std::size_t bytesTransferred); void notifyStatus(); diff --git a/src/rwe/LoadingScene.cpp b/src/rwe/LoadingScene.cpp index 7c5cc4ae4..a49d8ca0b 100644 --- a/src/rwe/LoadingScene.cpp +++ b/src/rwe/LoadingScene.cpp @@ -1,5 +1,6 @@ #include "LoadingScene.h" -#include +#include +#include #include #include #include @@ -130,7 +131,7 @@ namespace rwe } networkService.start(gameParameters.localNetworkPort); - sceneContext.sceneManager->setNextScene(createGameScene(gameParameters.mapName, gameParameters.schemaIndex)); + sceneContext.sceneManager->setNextScene(std::shared_ptr(createGameScene(gameParameters.mapName, gameParameters.schemaIndex))); // wait for other players before starting networkService.setDoneLoading(); @@ -332,7 +333,7 @@ namespace rwe throw std::runtime_error("Failed to load map bytes"); } - boost::interprocess::bufferstream tntStream(tntBytes->data(), tntBytes->size()); + rwe::SpanStream tntStream(tntBytes->data(), tntBytes->size()); TntArchive tnt(&tntStream); auto tileTextures = getTileTextures(tnt); diff --git a/src/rwe/LoadingScene_util.cpp b/src/rwe/LoadingScene_util.cpp index 05b0070a1..c24b758ed 100644 --- a/src/rwe/LoadingScene_util.cpp +++ b/src/rwe/LoadingScene_util.cpp @@ -1,6 +1,7 @@ #include "LoadingScene_util.h" +#include -#include +#include #include namespace rwe @@ -30,7 +31,7 @@ namespace rwe throw std::runtime_error("File in listing could not be read: " + scriptName); } - boost::interprocess::bufferstream s(bytes->data(), bytes->size()); + rwe::SpanStream s(bytes->data(), bytes->size()); auto cob = parseCob(s); auto scriptNameWithoutExtension = scriptName.substr(0, scriptName.size() - 4); diff --git a/src/rwe/MainMenuModel.cpp b/src/rwe/MainMenuModel.cpp index 5ff4b6534..72dd53fe9 100644 --- a/src/rwe/MainMenuModel.cpp +++ b/src/rwe/MainMenuModel.cpp @@ -1,4 +1,5 @@ #include "MainMenuModel.h" +#include namespace rwe { diff --git a/src/rwe/MainMenuScene.cpp b/src/rwe/MainMenuScene.cpp index 6ade2cd58..a4f9f76ce 100644 --- a/src/rwe/MainMenuScene.cpp +++ b/src/rwe/MainMenuScene.cpp @@ -1,4 +1,5 @@ #include "MainMenuScene.h" +#include #include #include #include @@ -187,12 +188,18 @@ namespace rwe sceneContext.sceneManager->requestExit(); } - std::optional matchesPlayer(const std::string& format, const std::string& input) + std::optional matchesPlayer(const std::string& pattern, const std::string& input) { // FIXME: this should probably be a regex match instead of this crude brute-force search for (int i = 0; i < 10; ++i) { - if (input == fmt::format(format, i)) + std::string candidate = pattern; + auto pos = candidate.find("{0}"); + if (pos != std::string::npos) + { + candidate.replace(pos, 3, std::to_string(i)); + } + if (input == candidate) { return i; } @@ -713,7 +720,7 @@ namespace rwe std::move(bgm), params); - sceneContext.sceneManager->setNextScene(std::move(scene)); + sceneContext.sceneManager->setNextScene(std::shared_ptr(std::move(scene))); } Point MainMenuScene::toScaledCoordinates(int x, int y) const diff --git a/src/rwe/MeshService.cpp b/src/rwe/MeshService.cpp index 6ae0b5900..ce57c11e0 100644 --- a/src/rwe/MeshService.cpp +++ b/src/rwe/MeshService.cpp @@ -1,5 +1,5 @@ #include "MeshService.h" -#include +#include #include #include #include @@ -30,7 +30,7 @@ namespace rwe throw std::runtime_error("Failed to load object bytes: " + name); } - boost::interprocess::bufferstream s(bytes->data(), bytes->size()); + rwe::SpanStream s(bytes->data(), bytes->size()); auto objects = parse3doObjects(s, s.tellg()); assert(objects.size() == 1); auto selectionMesh = selectionMeshFrom3do(*graphics, objects.front()); @@ -53,7 +53,7 @@ namespace rwe throw std::runtime_error("Failed to load object bytes: " + name); } - boost::interprocess::bufferstream s(bytes->data(), bytes->size()); + rwe::SpanStream s(bytes->data(), bytes->size()); auto objects = parse3doObjects(s, s.tellg()); assert(objects.size() == 1); diff --git a/src/rwe/RadiansAngle.cpp b/src/rwe/RadiansAngle.cpp index 931871707..4ed2a8386 100644 --- a/src/rwe/RadiansAngle.cpp +++ b/src/rwe/RadiansAngle.cpp @@ -1,4 +1,5 @@ #include "RadiansAngle.h" +#include #include #include diff --git a/src/rwe/TextureService.cpp b/src/rwe/TextureService.cpp index ff77ffa34..694c0d09f 100644 --- a/src/rwe/TextureService.cpp +++ b/src/rwe/TextureService.cpp @@ -1,5 +1,6 @@ #include "TextureService.h" -#include +#include +#include #include #include #include @@ -110,7 +111,7 @@ namespace rwe return std::nullopt; } - boost::interprocess::bufferstream gafStream(gafBytes->data(), gafBytes->size()); + rwe::SpanStream gafStream(gafBytes->data(), gafBytes->size()); GafArchive gafArchive(&gafStream); auto gafEntry = gafArchive.findEntry(normEntryName); @@ -239,7 +240,7 @@ namespace rwe throw std::runtime_error("map tnt not found!"); } - boost::interprocess::bufferstream tntStream(tntData->data(), tntData->size()); + rwe::SpanStream tntStream(tntData->data(), tntData->size()); TntArchive tnt(&tntStream); auto minimap = tnt.readMinimap(); @@ -274,7 +275,7 @@ namespace rwe throw std::runtime_error("font not found!"); } - boost::interprocess::bufferstream fntStream(fntBytes->data(), fntBytes->size()); + rwe::SpanStream fntStream(fntBytes->data(), fntBytes->size()); FntArchive fnt(&fntStream); auto series = std::make_shared(); diff --git a/src/rwe/UiRenderService.h b/src/rwe/UiRenderService.h index f1d032fc5..c10248be2 100644 --- a/src/rwe/UiRenderService.h +++ b/src/rwe/UiRenderService.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include diff --git a/src/rwe/Viewport.test.cpp b/src/rwe/Viewport.test.cpp index 5827489e9..94da4cd20 100644 --- a/src/rwe/Viewport.test.cpp +++ b/src/rwe/Viewport.test.cpp @@ -1,4 +1,5 @@ -#include +#include +#include #include namespace rwe @@ -12,7 +13,7 @@ namespace rwe Viewport vp(0, 0, 8, 6); auto v = vp.toClipSpace(2, 2); REQUIRE(v.x == -0.5f); - REQUIRE(v.y == Approx(0.33333333)); + REQUIRE(v.y == Catch::Approx(0.33333333)); } } diff --git a/src/rwe/atlas_util.cpp b/src/rwe/atlas_util.cpp index 07d8a9d0d..7c8af841e 100644 --- a/src/rwe/atlas_util.cpp +++ b/src/rwe/atlas_util.cpp @@ -1,5 +1,6 @@ #include "atlas_util.h" -#include +#include +#include #include #include #include @@ -94,7 +95,7 @@ namespace rwe throw std::runtime_error("textures/LOGOS.GAF could not be read"); } - boost::interprocess::bufferstream stream(bytes->data(), bytes->size()); + rwe::SpanStream stream(bytes->data(), bytes->size()); GafArchive gaf(&stream); std::vector>> entries; @@ -191,7 +192,7 @@ namespace rwe throw std::runtime_error("File in listing could not be read: " + gafName); } - boost::interprocess::bufferstream stream(bytes->data(), bytes->size()); + rwe::SpanStream stream(bytes->data(), bytes->size()); GafArchive gaf(&stream); diff --git a/src/rwe/cob/CobEnvironment.cpp b/src/rwe/cob/CobEnvironment.cpp index 3b1827615..30d6add39 100644 --- a/src/rwe/cob/CobEnvironment.cpp +++ b/src/rwe/cob/CobEnvironment.cpp @@ -1,4 +1,5 @@ #include "CobEnvironment.h" +#include #include namespace rwe diff --git a/src/rwe/cob/cob_util.test.cpp b/src/rwe/cob/cob_util.test.cpp index 75a56d893..6870e5697 100644 --- a/src/rwe/cob/cob_util.test.cpp +++ b/src/rwe/cob/cob_util.test.cpp @@ -1,21 +1,10 @@ -#include +#include +#include #include #include #include #include -namespace Catch -{ - namespace Detail - { - std::ostream& operator<<(std::ostream& os, const Approx& a) - { - os << a.toString(); - return os; - } - } -} - namespace rwe { TEST_CASE("cob_util") @@ -65,11 +54,11 @@ namespace rwe SECTION("toRadians") { - REQUIRE(toRadians(CobAngle(0)).value == Approx(0.0f)); - REQUIRE(toRadians(CobAngle(8192)).value == Approx(Pif / 4.0f)); - REQUIRE(toRadians(CobAngle(16384)).value == Approx(Pif / 2.0f)); - REQUIRE(toRadians(CobAngle(32768)).value == Approx(-Pif)); - REQUIRE(toRadians(CobAngle(49152)).value == Approx(-Pif / 2.0f)); + REQUIRE(toRadians(CobAngle(0)).value == Catch::Approx(0.0f)); + REQUIRE(toRadians(CobAngle(8192)).value == Catch::Approx(Pif / 4.0f)); + REQUIRE(toRadians(CobAngle(16384)).value == Catch::Approx(Pif / 2.0f)); + REQUIRE(toRadians(CobAngle(32768)).value == Catch::Approx(-Pif)); + REQUIRE(toRadians(CobAngle(49152)).value == Catch::Approx(-Pif / 2.0f)); } SECTION("toCobAngle") diff --git a/src/rwe/collections/MinHeap.test.cpp b/src/rwe/collections/MinHeap.test.cpp index bc391b445..8bd2bdd8a 100644 --- a/src/rwe/collections/MinHeap.test.cpp +++ b/src/rwe/collections/MinHeap.test.cpp @@ -1,4 +1,5 @@ #include +#include namespace std { @@ -9,10 +10,8 @@ namespace std } } -#include -#include +#include #include -#include #include #include #include @@ -24,9 +23,12 @@ namespace std template struct hash> { - std::size_t operator()(const std::pair& f) const noexcept + std::size_t operator()(const std::pair& p) const noexcept { - return boost::hash>()(f); + std::size_t seed = 0; + rwe::hashCombine(seed, std::hash{}(p.first)); + rwe::hashCombine(seed, std::hash{}(p.second)); + return seed; } }; } diff --git a/src/rwe/collections/VectorMap.h b/src/rwe/collections/VectorMap.h index 08523907d..d063d3dab 100644 --- a/src/rwe/collections/VectorMap.h +++ b/src/rwe/collections/VectorMap.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include diff --git a/src/rwe/collections/VectorMap.test.cpp b/src/rwe/collections/VectorMap.test.cpp index a6fdf14ab..6dcc8a03e 100644 --- a/src/rwe/collections/VectorMap.test.cpp +++ b/src/rwe/collections/VectorMap.test.cpp @@ -1,4 +1,5 @@ -#include +#include +#include #include #include #include diff --git a/src/rwe/game/GameMediaDatabase.h b/src/rwe/game/GameMediaDatabase.h index 84e350496..14e10bdfd 100644 --- a/src/rwe/game/GameMediaDatabase.h +++ b/src/rwe/game/GameMediaDatabase.h @@ -1,9 +1,8 @@ #pragma once -#include -#include #include #include +#include #include #include #include @@ -44,8 +43,8 @@ namespace rwe std::size_t operator()(const std::pair& key) const { std::size_t seed = 0; - boost::hash_combine(seed, toUpper(key.first)); - boost::hash_combine(seed, toUpper(key.second)); + hashCombine(seed, std::hash{}(toUpper(key.first))); + hashCombine(seed, std::hash{}(toUpper(key.second))); return seed; } }; diff --git a/src/rwe/game/GameNetworkService.cpp b/src/rwe/game/GameNetworkService.cpp index 8a5f3c145..301b8dc83 100644 --- a/src/rwe/game/GameNetworkService.cpp +++ b/src/rwe/game/GameNetworkService.cpp @@ -1,5 +1,5 @@ #include "GameNetworkService.h" -#include +#include #include #include #include @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include namespace rwe @@ -43,7 +43,7 @@ namespace rwe void GameNetworkService::submitCommands(SceneTime currentSceneTime, const GameNetworkService::CommandSet& commands) { - ioContext.post([this, currentSceneTime, commands]() { + asio::post(ioContext,[this, currentSceneTime, commands]() { this->currentSceneTime = currentSceneTime; for (auto& e : endpoints) { @@ -54,7 +54,7 @@ namespace rwe void GameNetworkService::submitGameHash(GameHash hash) { - ioContext.post([this, hash]() { + asio::post(ioContext,[this, hash]() { for (auto& e : endpoints) { e.hashSendBuffer.push_back(hash); @@ -65,7 +65,7 @@ namespace rwe SceneTime GameNetworkService::estimateAvergeSceneTime(SceneTime localSceneTime) { std::promise result; - ioContext.post([this, localSceneTime, &result]() { + asio::post(ioContext,[this, localSceneTime, &result]() { auto time = getTimestamp(); auto otherTimes = choose(endpoints, [](const auto& e) { return e.lastKnownSceneTime; }); @@ -79,7 +79,7 @@ namespace rwe float GameNetworkService::getMaxAverageRttMillis() { std::promise result; - ioContext.post([this, &result]() { + asio::post(ioContext,[this, &result]() { auto maxRtt = 0.0f; for (const auto& e : endpoints) { @@ -99,7 +99,7 @@ namespace rwe { try { - auto endpoint = boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v6(), port); + auto endpoint = asio::ip::udp::endpoint(asio::ip::udp::v6(), port); socket.open(endpoint.protocol()); socket.bind(endpoint); @@ -111,14 +111,14 @@ namespace rwe } catch (const std::exception& e) { - spdlog::get("rwe")->error("Network thread died with error: {0}", e.what()); + LOG_ERROR << "Network thread died with error: " << e.what(); } } void GameNetworkService::listenForNextMessage() { socket.async_receive_from( - boost::asio::buffer(receiveBuffer.data(), receiveBuffer.size()), + asio::buffer(receiveBuffer.data(), receiveBuffer.size()), currentRemoteEndpoint, [this](const auto& error, const auto& bytesTransferred) { receive(error, bytesTransferred); @@ -171,11 +171,11 @@ namespace rwe void GameNetworkService::sendLoop() { sendToAll(); - sendTimer.expires_from_now(std::chrono::milliseconds(100)); - sendTimer.async_wait([this](const boost::system::error_code& error) { + sendTimer.expires_after(std::chrono::milliseconds(100)); + sendTimer.async_wait([this](const asio::error_code& error) { if (error) { - spdlog::get("rwe")->error("Boost error while waiting on timer: {}", error.message()); + LOG_ERROR << "Error while waiting on timer: " << error.message(); return; } @@ -194,7 +194,7 @@ namespace rwe void GameNetworkService::send(GameNetworkService::EndpointInfo& endpoint) { auto packetId = uniform_dist(gen); - spdlog::get("rwe")->debug("Sending packet ID {} to endpoint: {}:{}", packetId, endpoint.endpoint.address().to_string(), endpoint.endpoint.port()); + LOG_DEBUG << "Sending packet ID " << packetId << " to endpoint: " << endpoint.endpoint.address().to_string() << ":" << endpoint.endpoint.port(); std::chrono::milliseconds delay(0); auto sendTime = getTimestamp(); if (endpoint.lastReceiveTime) @@ -203,7 +203,7 @@ namespace rwe } auto message = createProtoMessage(packetId, localPlayerId, currentSceneTime, endpoint.nextCommandToSend, endpoint.nextCommandToReceive, endpoint.nextHashToSend, endpoint.nextHashToReceive, delay, endpoint.sendBuffer, endpoint.hashSendBuffer); - auto messageSize = message.ByteSize(); + auto messageSize = message.ByteSizeLong(); if (messageSize > getSize(sendBuffer) - 4) { throw std::runtime_error("Message to be sent was bigger than buffer size"); @@ -216,7 +216,7 @@ namespace rwe // throw in a CRC to verify the message writeInt(&sendBuffer[messageSize], computeCrc(sendBuffer.data(), messageSize)); - socket.send_to(boost::asio::buffer(sendBuffer.data(), messageSize + 4), endpoint.endpoint); + socket.send_to(asio::buffer(sendBuffer.data(), messageSize + 4), endpoint.endpoint); auto nextSequenceNumber = SequenceNumber(endpoint.nextCommandToSend.value + (endpoint.sendBuffer.size())); if (endpoint.sendTimes.empty() || endpoint.sendTimes.back().first < nextSequenceNumber) @@ -225,25 +225,25 @@ namespace rwe } } - void GameNetworkService::receive(const boost::system::error_code& error, std::size_t receivedBytes) + void GameNetworkService::receive(const asio::error_code& error, std::size_t receivedBytes) { if (error) { - spdlog::get("rwe")->error("Boost error on receive: {}", error.message()); + LOG_ERROR << "Error on receive: " << error.message(); return; } auto receiveTime = getTimestamp(); - spdlog::get("rwe")->debug("Received {} bytes from endpoint: {}:{}", receivedBytes, currentRemoteEndpoint.address().to_string(), currentRemoteEndpoint.port()); + LOG_DEBUG << "Received " << receivedBytes << " bytes from endpoint: " << currentRemoteEndpoint.address().to_string() << ":" << currentRemoteEndpoint.port(); if (receivedBytes == receiveBuffer.size()) { - spdlog::get("rwe")->warn("Received {} bytes, which filled the entire message buffer!!", receivedBytes); + LOG_WARN << "Received " << receivedBytes << " bytes, which filled the entire message buffer!!"; } if (receivedBytes < 4) { - spdlog::get("rwe")->error("Received message is too short, ignoring", receivedBytes); + LOG_ERROR << "Received message is too short (" << receivedBytes << " bytes), ignoring"; return; } @@ -251,7 +251,7 @@ namespace rwe if (endpointIt == endpoints.end()) { // message was from some unknown address, ignore it - spdlog::get("rwe")->debug("Unknown address, ignoring"); + LOG_DEBUG << "Unknown address, ignoring"; return; } @@ -259,7 +259,7 @@ namespace rwe auto computedCrc = computeCrc(receiveBuffer.data(), receivedBytes - 4); if (receivedCrc != computedCrc) { - spdlog::get("rwe")->error("Message CRC incorrect, ignoring"); + LOG_ERROR << "Message CRC incorrect, ignoring"; return; } @@ -268,7 +268,7 @@ namespace rwe if (!outerMessage.has_game_update()) { // message wasn't a game update, ignore it - spdlog::get("rwe")->debug("Not game update, ignoring"); + LOG_DEBUG << "Not game update, ignoring"; return; } @@ -276,23 +276,19 @@ namespace rwe const auto& message = outerMessage.game_update(); - spdlog::get("rwe")->debug("Packet received with ID {}", message.packet_id()); + LOG_DEBUG << "Packet received with ID " << message.packet_id(); if (message.player_id() != endpoint.playerId.value) { - spdlog::get("rwe")->error("Player {} endpoint sent wrong player ID: {}", endpoint.playerId.value, message.player_id()); + LOG_ERROR << "Player " << endpoint.playerId.value << " endpoint sent wrong player ID: " << message.player_id(); return; } - spdlog::get("rwe")->debug("Received ack to {0} and {1} commands starting at {2}", message.next_command_set_to_receive(), message.command_set_size(), message.next_command_set_to_send()); + LOG_DEBUG << "Received ack to " << message.next_command_set_to_receive() << " and " << message.command_set_size() << " commands starting at " << message.next_command_set_to_send(); SequenceNumber newNextCommandToSend(message.next_command_set_to_receive()); if (newNextCommandToSend.value > endpoint.nextCommandToSend.value + endpoint.sendBuffer.size()) { - spdlog::get("rwe")->error( - "Remote acked up to {0}, but we are at {1} and command buffer contains {2} elements", - newNextCommandToSend.value, - endpoint.nextCommandToSend.value, - endpoint.sendBuffer.size()); + LOG_ERROR << "Remote acked up to " << newNextCommandToSend.value << ", but we are at " << endpoint.nextCommandToSend.value << " and command buffer contains " << endpoint.sendBuffer.size() << " elements"; } while (newNextCommandToSend > endpoint.nextCommandToSend && !endpoint.sendBuffer.empty()) { @@ -312,19 +308,19 @@ namespace rwe roundTripTime = roundTripTime > ackDelay ? roundTripTime - ackDelay : std::chrono::milliseconds(0); auto rttMillis = std::chrono::duration_cast(roundTripTime).count(); endpoint.averageRoundTripTime = ema(rttMillis, endpoint.averageRoundTripTime, 0.1f); - spdlog::get("rwe")->debug("Average RTT: {0}ms", endpoint.averageRoundTripTime); + LOG_DEBUG << "Average RTT: " << endpoint.averageRoundTripTime << "ms"; } auto extraFrames = static_cast((endpoint.averageRoundTripTime / 2.0f) * SimTicksPerSecond / 1000.0f); endpoint.lastKnownSceneTime = std::make_pair(SceneTime(message.current_scene_time() + extraFrames), receiveTime); - spdlog::get("rwe")->debug("Estimated peer scene time: {0}", endpoint.lastKnownSceneTime->first.value); + LOG_DEBUG << "Estimated peer scene time: " << endpoint.lastKnownSceneTime->first.value; SequenceNumber firstCommandNumber(message.next_command_set_to_send()); if (firstCommandNumber > endpoint.nextCommandToReceive) { // message starts with commands too far in the future, ignore it. // FIXME: this should probably be an error as it shouldn't ever happen - spdlog::get("rwe")->error("First command number in message was too high! Expecting no more than {0}, received {1}", endpoint.nextCommandToReceive.value, firstCommandNumber.value); + LOG_ERROR << "First command number in message was too high! Expecting no more than " << endpoint.nextCommandToReceive.value << ", received " << firstCommandNumber.value; return; } @@ -346,11 +342,7 @@ namespace rwe GameTime newNextHashToSend(message.next_game_hash_to_receive()); if (newNextHashToSend > endpoint.nextHashToSend + GameTime(endpoint.hashSendBuffer.size())) { - spdlog::get("rwe")->error( - "Remote acked up to {0}, but we are at {1} and hash buffer contains {2} elements", - newNextHashToSend.value, - endpoint.nextHashToSend.value, - endpoint.hashSendBuffer.size()); + LOG_ERROR << "Remote acked up to " << newNextHashToSend.value << ", but we are at " << endpoint.nextHashToSend.value << " and hash buffer contains " << endpoint.hashSendBuffer.size() << " elements"; } while (newNextHashToSend > endpoint.nextHashToSend && !endpoint.hashSendBuffer.empty()) { @@ -363,7 +355,7 @@ namespace rwe { // message starts with hashes too far in the future, ignore it. // FIXME: this should probably be an error as it shouldn't ever happen - spdlog::get("rwe")->error("First game hash time in message was too high! Expecting no more than {0}, received {1}", endpoint.nextHashToReceive.value, firstGameHashTime.value); + LOG_ERROR << "First game hash time in message was too high! Expecting no more than " << endpoint.nextHashToReceive.value << ", received " << firstGameHashTime.value; return; } diff --git a/src/rwe/game/GameNetworkService.h b/src/rwe/game/GameNetworkService.h index 4da5ae7d6..7dbc8c542 100644 --- a/src/rwe/game/GameNetworkService.h +++ b/src/rwe/game/GameNetworkService.h @@ -1,7 +1,6 @@ #pragma once -#include -#include // not in asio.hpp in old boost versions +#include #include #include #include @@ -28,7 +27,7 @@ namespace rwe struct EndpointInfo { PlayerId playerId; - boost::asio::ip::udp::endpoint endpoint; + asio::ip::udp::endpoint endpoint; SequenceNumber nextCommandToSend{0}; SequenceNumber nextCommandToReceive{0}; @@ -67,7 +66,7 @@ namespace rwe */ float averageRoundTripTime{0}; - EndpointInfo(const PlayerId& playerId, const boost::asio::ip::udp::endpoint& endpoint) + EndpointInfo(const PlayerId& playerId, const asio::ip::udp::endpoint& endpoint) : playerId(playerId), endpoint(endpoint) { } @@ -82,16 +81,16 @@ namespace rwe std::thread networkThread; - boost::asio::io_service ioContext; - boost::asio::ip::udp::resolver resolver; - boost::asio::ip::udp::socket socket; - boost::asio::steady_timer sendTimer; + asio::io_context ioContext; + asio::ip::udp::resolver resolver; + asio::ip::udp::socket socket; + asio::steady_timer sendTimer; std::vector endpoints; std::array sendBuffer; std::array receiveBuffer; - boost::asio::ip::udp::endpoint currentRemoteEndpoint; + asio::ip::udp::endpoint currentRemoteEndpoint; PlayerCommandService* const playerCommandService; @@ -132,6 +131,6 @@ namespace rwe void send(EndpointInfo& endpoint); - void receive(const boost::system::error_code& error, std::size_t receivedBytes); + void receive(const asio::error_code& error, std::size_t receivedBytes); }; } diff --git a/src/rwe/game/GameScene.cpp b/src/rwe/game/GameScene.cpp index 9b9528afc..fd7d24371 100644 --- a/src/rwe/game/GameScene.cpp +++ b/src/rwe/game/GameScene.cpp @@ -1,6 +1,5 @@ #include "GameScene.h" #include -#include #include #include #include @@ -14,7 +13,7 @@ #include #include #include -#include +#include namespace rwe { @@ -523,7 +522,7 @@ namespace rwe auto worldToMinimap = worldToMinimapMatrix(simulation.terrain, minimapRect); // draw minimap dots - for (const auto& unit : (simulation.units | boost::adaptors::map_values)) + for (const auto& [_, unit] : simulation.units) { auto minimapPos = worldToMinimap * simVectorToFloat(unit.position); minimapPos.x = std::floor(minimapPos.x); @@ -754,7 +753,7 @@ namespace rwe auto seaLevel = simulation.terrain.getSeaLevel(); UnitShadowMeshBatch unitShadowMeshBatch; - for (const auto& unit : (simulation.units | boost::adaptors::map_values)) + for (const auto& [_, unit] : simulation.units) { const auto& unitDefinition = simulation.unitDefinitions.at(unit.unitType); const auto& modelDefinition = simulation.unitModelDefinitions.at(unitDefinition.objectName); @@ -766,7 +765,7 @@ namespace rwe } drawUnitShadow(gameMediaDatabase, viewProjectionMatrix, unit, unitDefinition, modelDefinition, interpolationFraction, simScalarToFloat(groundHeight), unitTextureAtlas.get(), unitTeamTextureAtlases, unitShadowMeshBatch); } - for (const auto& feature : (simulation.features | boost::adaptors::map_values)) + for (const auto& [_, feature] : simulation.features) { const auto& position = feature.position; auto groundHeight = simulation.terrain.getHeightAt(position.x, position.z); @@ -782,13 +781,13 @@ namespace rwe sceneContext.graphics->enableDepthBuffer(); UnitMeshBatch unitMeshBatch; - for (const auto& unit : (simulation.units | boost::adaptors::map_values)) + for (const auto& [_, unit] : simulation.units) { const auto& unitDefinition = simulation.unitDefinitions.at(unit.unitType); const auto& unitModelDefinition = simulation.unitModelDefinitions.at(unitDefinition.objectName); drawUnit(gameMediaDatabase, viewProjectionMatrix, unit, unitDefinition, unitModelDefinition, getPlayer(unit.owner).color, interpolationFraction, unitTextureAtlas.get(), unitTeamTextureAtlases, unitMeshBatch); } - for (const auto& feature : (simulation.features | boost::adaptors::map_values)) + for (const auto& [_, feature] : simulation.features) { drawMeshFeature(simulation.unitModelDefinitions, gameMediaDatabase, viewProjectionMatrix, feature, unitTextureAtlas.get(), unitTeamTextureAtlases, unitMeshBatch); } @@ -820,7 +819,7 @@ namespace rwe sceneContext.graphics->disableDepthTest(); ColoredMeshBatch nanoLinesBatch; - for (const auto& unit : (simulation.units | boost::adaptors::map_values)) + for (const auto& [_, unit] : simulation.units) { if (auto nanolatheTarget = unit.getActiveNanolatheTarget()) { @@ -904,7 +903,7 @@ namespace rwe if (healthBarsVisible) { - for (const UnitState& unit : (simulation.units | boost::adaptors::map_values)) + for (const auto& [_, unit] : simulation.units) { if (!unit.isOwnedBy(localPlayerId)) { @@ -1983,7 +1982,7 @@ namespace rwe auto bufferedCommandCount = playerCommandService->bufferedCommandCount(localPlayerId); - spdlog::get("rwe")->debug("Buffer levels (real/target) {0}/{1}", bufferedCommandCount, targetCommandBufferSize); + LOG_DEBUG << "Buffer levels (real/target) " << bufferedCommandCount << "/" << targetCommandBufferSize; // If we have too many commands buffered, // defer submitting commands this frame @@ -2335,7 +2334,7 @@ namespace rwe auto playerCommands = playerCommandService->tryPopCommands(); if (!playerCommands) { - spdlog::get("rwe")->error("Blocked waiting for player commands"); + LOG_ERROR << "Blocked waiting for player commands"; return; } @@ -3325,7 +3324,7 @@ namespace rwe bool GameScene::matchesWithSidePrefix(const std::string& suffix, const std::string& value) const { - for (const auto& side : (*sceneContext.sideData | boost::adaptors::map_values)) + for (const auto& [_, side] : *sceneContext.sideData) { if (side.namePrefix + suffix == value) { diff --git a/src/rwe/game/GameScene.h b/src/rwe/game/GameScene.h index 07f1ce0c7..6bde0ff1e 100644 --- a/src/rwe/game/GameScene.h +++ b/src/rwe/game/GameScene.h @@ -1,7 +1,5 @@ #pragma once -#include -#include #include #include #include @@ -542,7 +540,7 @@ namespace rwe template std::optional> findWithSidePrefix(UiPanel& p, const std::string& name) { - for (const auto& side : (*sceneContext.sideData | boost::adaptors::map_values)) + for (const auto& [_, side] : *sceneContext.sideData) { auto control = p.find(side.namePrefix + name); if (control) diff --git a/src/rwe/game/GameScene_util.cpp b/src/rwe/game/GameScene_util.cpp index 18ffd5b32..f1deba89c 100644 --- a/src/rwe/game/GameScene_util.cpp +++ b/src/rwe/game/GameScene_util.cpp @@ -1,7 +1,6 @@ #include "GameScene_util.h" #include -#include #include namespace rwe diff --git a/src/rwe/game/PlayerCommandService.cpp b/src/rwe/game/PlayerCommandService.cpp index 0ef713414..63313fe1e 100644 --- a/src/rwe/game/PlayerCommandService.cpp +++ b/src/rwe/game/PlayerCommandService.cpp @@ -1,4 +1,5 @@ #include "PlayerCommandService.h" +#include namespace rwe { diff --git a/src/rwe/game/dump_util.test.cpp b/src/rwe/game/dump_util.test.cpp index 80421e871..a613aebb3 100644 --- a/src/rwe/game/dump_util.test.cpp +++ b/src/rwe/game/dump_util.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include namespace rwe diff --git a/src/rwe/geometry/BoundingBox3f.test.cpp b/src/rwe/geometry/BoundingBox3f.test.cpp index a0fb1d5c3..4e8e403bd 100644 --- a/src/rwe/geometry/BoundingBox3f.test.cpp +++ b/src/rwe/geometry/BoundingBox3f.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include diff --git a/src/rwe/geometry/BoundingBox3x.h b/src/rwe/geometry/BoundingBox3x.h index 09c048660..017fc8798 100644 --- a/src/rwe/geometry/BoundingBox3x.h +++ b/src/rwe/geometry/BoundingBox3x.h @@ -1,6 +1,7 @@ #pragma once #include +#include namespace rwe { diff --git a/src/rwe/geometry/Circle2f.test.cpp b/src/rwe/geometry/Circle2f.test.cpp index 5088fc86d..90fa63040 100644 --- a/src/rwe/geometry/Circle2f.test.cpp +++ b/src/rwe/geometry/Circle2f.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include diff --git a/src/rwe/geometry/CollisionMesh.test.cpp b/src/rwe/geometry/CollisionMesh.test.cpp index bb3c9bdfc..0980073c2 100644 --- a/src/rwe/geometry/CollisionMesh.test.cpp +++ b/src/rwe/geometry/CollisionMesh.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include diff --git a/src/rwe/geometry/Plane3f.test.cpp b/src/rwe/geometry/Plane3f.test.cpp index b373e5df5..f2fcfec12 100644 --- a/src/rwe/geometry/Plane3f.test.cpp +++ b/src/rwe/geometry/Plane3f.test.cpp @@ -1,4 +1,5 @@ -#include +#include +#include #include #include #include @@ -86,13 +87,13 @@ namespace rwe // not strictly required by implementation // (could use b or c as the point) // but no better way to verify at the moment - REQUIRE(p.point.x == Approx(0.0f)); - REQUIRE(p.point.y == Approx(0.0f)); - REQUIRE(p.point.z == Approx(0.0f)); + REQUIRE(p.point.x == Catch::Approx(0.0f)); + REQUIRE(p.point.y == Catch::Approx(0.0f)); + REQUIRE(p.point.z == Catch::Approx(0.0f)); - REQUIRE(p.normal.x == Approx(0.0f)); - REQUIRE(p.normal.y == Approx(0.0f)); - REQUIRE(p.normal.z == Approx(1.0f)); + REQUIRE(p.normal.x == Catch::Approx(0.0f)); + REQUIRE(p.normal.y == Catch::Approx(0.0f)); + REQUIRE(p.normal.z == Catch::Approx(1.0f)); } } } diff --git a/src/rwe/geometry/Ray3f.test.cpp b/src/rwe/geometry/Ray3f.test.cpp index 75f9aecd5..c9764fd2f 100644 --- a/src/rwe/geometry/Ray3f.test.cpp +++ b/src/rwe/geometry/Ray3f.test.cpp @@ -1,4 +1,5 @@ -#include +#include +#include #include #include @@ -10,9 +11,9 @@ namespace rwe { Ray3f r(Vector3f(3.0f, 10.0f, 4.0f), Vector3f(0.0f, -2.0f, 0.0f)); auto i = r.pointAt(3.0f); - REQUIRE(i.x == Approx(3.0f)); - REQUIRE(i.y == Approx(4.0f)); - REQUIRE(i.z == Approx(4.0f)); + REQUIRE(i.x == Catch::Approx(3.0f)); + REQUIRE(i.y == Catch::Approx(4.0f)); + REQUIRE(i.z == Catch::Approx(4.0f)); } } diff --git a/src/rwe/geometry/Rectangle2f.test.cpp b/src/rwe/geometry/Rectangle2f.test.cpp index a8509baee..ce84dcadd 100644 --- a/src/rwe/geometry/Rectangle2f.test.cpp +++ b/src/rwe/geometry/Rectangle2f.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/rwe/geometry/Triangle3f.test.cpp b/src/rwe/geometry/Triangle3f.test.cpp index 9aea37d6b..44576a8fb 100644 --- a/src/rwe/geometry/Triangle3f.test.cpp +++ b/src/rwe/geometry/Triangle3f.test.cpp @@ -1,4 +1,5 @@ -#include +#include +#include #include #include #include @@ -18,9 +19,9 @@ namespace rwe Vector3f(0.0f, 2.0f, 0.0f)); Vector3f bary = tri.toBarycentric(p); - REQUIRE(bary.x == Approx(0.5f)); - REQUIRE(bary.y == Approx(0.5f)); - REQUIRE(bary.z == Approx(0.0f)); + REQUIRE(bary.x == Catch::Approx(0.5f)); + REQUIRE(bary.y == Catch::Approx(0.5f)); + REQUIRE(bary.z == Catch::Approx(0.0f)); } SECTION("barycentric conversion test 2") @@ -32,9 +33,9 @@ namespace rwe Vector3f(0.0f, 2.0f, 0.0f)); Vector3f bary = tri.toBarycentric(p); - REQUIRE(bary.x == Approx(0.5f)); - REQUIRE(bary.y == Approx(0.25f)); - REQUIRE(bary.z == Approx(0.25f)); + REQUIRE(bary.x == Catch::Approx(0.5f)); + REQUIRE(bary.y == Catch::Approx(0.25f)); + REQUIRE(bary.z == Catch::Approx(0.25f)); } SECTION("barycentric conversion test 3") @@ -46,9 +47,9 @@ namespace rwe Vector3f(0.5f, 0.5f, 0.5f)); Vector3f bary = tri.toBarycentric(p); - REQUIRE(bary.x == Approx(5.5f)); - REQUIRE(bary.y == Approx(-10.0f)); - REQUIRE(bary.z == Approx(5.5f)); + REQUIRE(bary.x == Catch::Approx(5.5f)); + REQUIRE(bary.y == Catch::Approx(-10.0f)); + REQUIRE(bary.z == Catch::Approx(5.5f)); } } @@ -63,9 +64,9 @@ namespace rwe Vector3f(0.0f, 2.0f, 0.0f)); Vector3f cart = tri.toCartesian(p); - REQUIRE(cart.x == Approx(1.0f)); - REQUIRE(cart.y == Approx(0.0f)); - REQUIRE(cart.z == Approx(0.0f)); + REQUIRE(cart.x == Catch::Approx(1.0f)); + REQUIRE(cart.y == Catch::Approx(0.0f)); + REQUIRE(cart.z == Catch::Approx(0.0f)); } SECTION("test 2") @@ -77,9 +78,9 @@ namespace rwe Vector3f(0.0f, 2.0f, 0.0f)); Vector3f cart = tri.toCartesian(p); - REQUIRE(cart.x == Approx(0.5f)); - REQUIRE(cart.y == Approx(0.5f)); - REQUIRE(cart.z == Approx(0.0f)); + REQUIRE(cart.x == Catch::Approx(0.5f)); + REQUIRE(cart.y == Catch::Approx(0.5f)); + REQUIRE(cart.z == Catch::Approx(0.0f)); } SECTION("test 3") @@ -91,9 +92,9 @@ namespace rwe Vector3f(0.5f, 0.5f, 0.5f)); Vector3f cart = tri.toCartesian(p); - REQUIRE(cart.x == Approx(-5.0f)); - REQUIRE(cart.y == Approx(0.5f)); - REQUIRE(cart.z == Approx(5.0f)); + REQUIRE(cart.x == Catch::Approx(-5.0f)); + REQUIRE(cart.y == Catch::Approx(0.5f)); + REQUIRE(cart.z == Catch::Approx(5.0f)); } } @@ -110,7 +111,7 @@ namespace rwe Vector3f(0.0f, 0.0f, -1.0f)); auto intersect = tri.intersect(r); REQUIRE(intersect); - REQUIRE(*intersect == Approx(10.0f)); + REQUIRE(*intersect == Catch::Approx(10.0f)); } SECTION("hits at the corner of the triangle") @@ -124,7 +125,7 @@ namespace rwe Vector3f(0.0f, 0.0f, -1.0f)); auto intersect = tri.intersect(r); REQUIRE(intersect); - REQUIRE(*intersect == Approx(10.0f)); + REQUIRE(*intersect == Catch::Approx(10.0f)); } SECTION("misses just below the corner of the triangle") @@ -202,9 +203,9 @@ namespace rwe Vector3f(0.0f, 1.0f, 0.0f)); auto intersect = tri.intersectLine(Vector3f(0.0f, 0.0f, 10.0f), Vector3f(0.0f, 0.0f, -10.0f)); REQUIRE(intersect); - REQUIRE(intersect->x == Approx(0.0f)); - REQUIRE(intersect->y == Approx(0.0f)); - REQUIRE(intersect->z == Approx(0.0f)); + REQUIRE(intersect->x == Catch::Approx(0.0f)); + REQUIRE(intersect->y == Catch::Approx(0.0f)); + REQUIRE(intersect->z == Catch::Approx(0.0f)); } SECTION("works for a line in the other direction") @@ -215,9 +216,9 @@ namespace rwe Vector3f(0.0f, 1.0f, 0.0f)); auto intersect = tri.intersectLine(Vector3f(0.0f, 0.0f, -10.0f), Vector3f(0.0f, 0.0f, 10.0f)); REQUIRE(intersect); - REQUIRE(intersect->x == Approx(0.0f)); - REQUIRE(intersect->y == Approx(0.0f)); - REQUIRE(intersect->z == Approx(0.0f)); + REQUIRE(intersect->x == Catch::Approx(0.0f)); + REQUIRE(intersect->y == Catch::Approx(0.0f)); + REQUIRE(intersect->z == Catch::Approx(0.0f)); } SECTION("hits at the corner of the triangle") @@ -228,9 +229,9 @@ namespace rwe Vector3f(0.0f, 1.0f, 0.0f)); auto intersect = tri.intersectLine(Vector3f(-1.0f, -1.0f, 10.0f), Vector3f(-1.0f, -1.0f, -10.0f)); REQUIRE(intersect); - REQUIRE(intersect->x == Approx(-1.0f)); - REQUIRE(intersect->y == Approx(-1.0f)); - REQUIRE(intersect->z == Approx(0.0f)); + REQUIRE(intersect->x == Catch::Approx(-1.0f)); + REQUIRE(intersect->y == Catch::Approx(-1.0f)); + REQUIRE(intersect->z == Catch::Approx(0.0f)); } SECTION("misses just below the corner of the triangle") @@ -266,13 +267,13 @@ namespace rwe Vector3f(0, 1, 0)); Plane3f p = tri.toPlane(); - REQUIRE(p.point.x == Approx(-1.0f)); - REQUIRE(p.point.y == Approx(-1.0f)); - REQUIRE(p.point.z == Approx(0.0f)); + REQUIRE(p.point.x == Catch::Approx(-1.0f)); + REQUIRE(p.point.y == Catch::Approx(-1.0f)); + REQUIRE(p.point.z == Catch::Approx(0.0f)); - REQUIRE(p.normal.x == Approx(0.0f)); - REQUIRE(p.normal.y == Approx(0.0f)); - REQUIRE(p.normal.z == Approx(4.0f)); + REQUIRE(p.normal.x == Catch::Approx(0.0f)); + REQUIRE(p.normal.y == Catch::Approx(0.0f)); + REQUIRE(p.normal.z == Catch::Approx(4.0f)); } } } diff --git a/src/rwe/grid/DiscreteRect.h b/src/rwe/grid/DiscreteRect.h index a4933e3a6..ae77d6f80 100644 --- a/src/rwe/grid/DiscreteRect.h +++ b/src/rwe/grid/DiscreteRect.h @@ -1,7 +1,8 @@ #pragma once -#include +#include #include +#include #include #include @@ -49,7 +50,7 @@ namespace rwe } int bottom() const { - return y + width; + return y + height; } /** @@ -111,10 +112,10 @@ namespace std std::size_t operator()(const rwe::DiscreteRect& r) const noexcept { std::size_t seed = 0; - boost::hash_combine(seed, r.x); - boost::hash_combine(seed, r.y); - boost::hash_combine(seed, r.width); - boost::hash_combine(seed, r.height); + rwe::hashCombine(seed, std::hash{}(r.x)); + rwe::hashCombine(seed, std::hash{}(r.y)); + rwe::hashCombine(seed, std::hash{}(r.width)); + rwe::hashCombine(seed, std::hash{}(r.height)); return seed; } }; diff --git a/src/rwe/grid/DiscreteRect.test.cpp b/src/rwe/grid/DiscreteRect.test.cpp index dcb0e99c5..218a35914 100644 --- a/src/rwe/grid/DiscreteRect.test.cpp +++ b/src/rwe/grid/DiscreteRect.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -289,6 +289,15 @@ namespace rwe } } + SECTION(".bottom") + { + SECTION("returns y + height for non-square rects") + { + DiscreteRect r(1, 2, 3, 5); + REQUIRE(r.bottom() == 7); + } + } + SECTION(".translate") { SECTION("translates the rectangle") diff --git a/src/rwe/grid/EightWayDirection.cpp b/src/rwe/grid/EightWayDirection.cpp index e8d6d51c0..6c35c36b7 100644 --- a/src/rwe/grid/EightWayDirection.cpp +++ b/src/rwe/grid/EightWayDirection.cpp @@ -1,5 +1,8 @@ #include "EightWayDirection.h" +#include +#include #include +#include namespace rwe { diff --git a/src/rwe/grid/EightWayDirection.test.cpp b/src/rwe/grid/EightWayDirection.test.cpp index 1021cb4a5..a0142e7e6 100644 --- a/src/rwe/grid/EightWayDirection.test.cpp +++ b/src/rwe/grid/EightWayDirection.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include namespace rwe diff --git a/src/rwe/grid/Grid.test.cpp b/src/rwe/grid/Grid.test.cpp index 0e6e5d5bc..434110629 100644 --- a/src/rwe/grid/Grid.test.cpp +++ b/src/rwe/grid/Grid.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/rwe/grid/Point.cpp b/src/rwe/grid/Point.cpp index d8dbb5bde..0c1763590 100644 --- a/src/rwe/grid/Point.cpp +++ b/src/rwe/grid/Point.cpp @@ -1,4 +1,5 @@ #include "Point.h" +#include namespace rwe { @@ -40,11 +41,6 @@ namespace rwe return *this; } - std::size_t hash_value(const Point& p) - { - return std::hash()(p); - } - int Point::maxSingleDimensionDistance(const Point& rhs) const { auto delta = *this - rhs; diff --git a/src/rwe/grid/Point.h b/src/rwe/grid/Point.h index 891c7e14f..b2cef47ca 100644 --- a/src/rwe/grid/Point.h +++ b/src/rwe/grid/Point.h @@ -1,6 +1,7 @@ #pragma once -#include +#include +#include namespace rwe { @@ -37,14 +38,9 @@ namespace std std::size_t operator()(const rwe::Point& v) const noexcept { std::size_t seed = 0; - boost::hash_combine(seed, v.x); - boost::hash_combine(seed, v.y); + rwe::hashCombine(seed, std::hash{}(v.x)); + rwe::hashCombine(seed, std::hash{}(v.y)); return seed; } }; } - -namespace rwe -{ - std::size_t hash_value(const Point& p); -} diff --git a/src/rwe/grid/Point.test.cpp b/src/rwe/grid/Point.test.cpp index eaee1dd15..9b559315a 100644 --- a/src/rwe/grid/Point.test.cpp +++ b/src/rwe/grid/Point.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include diff --git a/src/rwe/io/featuretdf/io.test.cpp b/src/rwe/io/featuretdf/io.test.cpp index 350aaec35..8062c5a39 100644 --- a/src/rwe/io/featuretdf/io.test.cpp +++ b/src/rwe/io/featuretdf/io.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include diff --git a/src/rwe/io/gui/gui.test.cpp b/src/rwe/io/gui/gui.test.cpp index a868eb19f..ec646f5c8 100644 --- a/src/rwe/io/gui/gui.test.cpp +++ b/src/rwe/io/gui/gui.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/rwe/io/hpi/hpi_util.cpp b/src/rwe/io/hpi/hpi_util.cpp index 7d3d5aa7f..ac5b58c05 100644 --- a/src/rwe/io/hpi/hpi_util.cpp +++ b/src/rwe/io/hpi/hpi_util.cpp @@ -1,4 +1,5 @@ #include "hpi_util.h" +#include #include #include diff --git a/src/rwe/io/ota/ota.test.cpp b/src/rwe/io/ota/ota.test.cpp index 9c6be8dc7..1e4767339 100644 --- a/src/rwe/io/ota/ota.test.cpp +++ b/src/rwe/io/ota/ota.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/rwe/io/sidedatatdf/SideData.test.cpp b/src/rwe/io/sidedatatdf/SideData.test.cpp index 4bcefc7d7..0f03820b7 100644 --- a/src/rwe/io/sidedatatdf/SideData.test.cpp +++ b/src/rwe/io/sidedatatdf/SideData.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/rwe/io/tdf/ListTdfAdapter.test.cpp b/src/rwe/io/tdf/ListTdfAdapter.test.cpp index c7dc32b58..9a712c1e2 100644 --- a/src/rwe/io/tdf/ListTdfAdapter.test.cpp +++ b/src/rwe/io/tdf/ListTdfAdapter.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/rwe/io/tdf/NetSchemaTdfAdapter.test.cpp b/src/rwe/io/tdf/NetSchemaTdfAdapter.test.cpp index 64ced68fd..3dca08baf 100644 --- a/src/rwe/io/tdf/NetSchemaTdfAdapter.test.cpp +++ b/src/rwe/io/tdf/NetSchemaTdfAdapter.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/rwe/io/tdf/SimpleTdfAdapter.test.cpp b/src/rwe/io/tdf/SimpleTdfAdapter.test.cpp index da09fb390..d86fb913c 100644 --- a/src/rwe/io/tdf/SimpleTdfAdapter.test.cpp +++ b/src/rwe/io/tdf/SimpleTdfAdapter.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/rwe/io/tdf/TdfBlock.test.cpp b/src/rwe/io/tdf/TdfBlock.test.cpp index b7dd76920..398b6e2af 100644 --- a/src/rwe/io/tdf/TdfBlock.test.cpp +++ b/src/rwe/io/tdf/TdfBlock.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/rwe/io/tnt/TntArchive.cpp b/src/rwe/io/tnt/TntArchive.cpp index 5c4923141..22a3031c2 100644 --- a/src/rwe/io/tnt/TntArchive.cpp +++ b/src/rwe/io/tnt/TntArchive.cpp @@ -1,4 +1,5 @@ #include "TntArchive.h" +#include #include #include diff --git a/src/rwe/ip_util.test.cpp b/src/rwe/ip_util.test.cpp index e85abb47e..a88433753 100644 --- a/src/rwe/ip_util.test.cpp +++ b/src/rwe/ip_util.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include using namespace std::string_literals; diff --git a/src/rwe/math/Matrix4f.test.cpp b/src/rwe/math/Matrix4f.test.cpp index 41879b3f7..c3fe5ce15 100644 --- a/src/rwe/math/Matrix4f.test.cpp +++ b/src/rwe/math/Matrix4f.test.cpp @@ -1,4 +1,5 @@ -#include +#include +#include #include #include #include @@ -66,25 +67,25 @@ namespace rwe Matrix4f b = Matrix4f::inverseOrthographicProjection(-40.0f, 60.0f, -30.0f, 10.0f, 2.0f, 200.0f); Matrix4f c = b * a; - REQUIRE(c.data[0] == Approx(1.0f)); - REQUIRE(c.data[1] == Approx(0.0f)); - REQUIRE(c.data[2] == Approx(0.0f)); - REQUIRE(c.data[3] == Approx(0.0f)); - - REQUIRE(c.data[4] == Approx(0.0f)); - REQUIRE(c.data[5] == Approx(1.0f)); - REQUIRE(c.data[6] == Approx(0.0f)); - REQUIRE(c.data[7] == Approx(0.0f)); - - REQUIRE(c.data[8] == Approx(0.0f)); - REQUIRE(c.data[9] == Approx(0.0f)); - REQUIRE(c.data[10] == Approx(1.0f)); - REQUIRE(c.data[11] == Approx(0.0f)); - - REQUIRE(c.data[12] == Approx(0.0f)); - REQUIRE(c.data[13] == Approx(0.0f)); - REQUIRE(c.data[14] == Approx(0.0f)); - REQUIRE(c.data[15] == Approx(1.0f)); + REQUIRE(c.data[0] == Catch::Approx(1.0f)); + REQUIRE(c.data[1] == Catch::Approx(0.0f)); + REQUIRE(c.data[2] == Catch::Approx(0.0f)); + REQUIRE(c.data[3] == Catch::Approx(0.0f)); + + REQUIRE(c.data[4] == Catch::Approx(0.0f)); + REQUIRE(c.data[5] == Catch::Approx(1.0f)); + REQUIRE(c.data[6] == Catch::Approx(0.0f)); + REQUIRE(c.data[7] == Catch::Approx(0.0f)); + + REQUIRE(c.data[8] == Catch::Approx(0.0f)); + REQUIRE(c.data[9] == Catch::Approx(0.0f)); + REQUIRE(c.data[10] == Catch::Approx(1.0f)); + REQUIRE(c.data[11] == Catch::Approx(0.0f)); + + REQUIRE(c.data[12] == Catch::Approx(0.0f)); + REQUIRE(c.data[13] == Catch::Approx(0.0f)); + REQUIRE(c.data[14] == Catch::Approx(0.0f)); + REQUIRE(c.data[15] == Catch::Approx(1.0f)); } } @@ -96,25 +97,25 @@ namespace rwe Matrix4f b = Matrix4f::cabinetProjection(0.0f, -0.5f); Matrix4f c = b * a; - REQUIRE(c.data[0] == Approx(1.0f)); - REQUIRE(c.data[1] == Approx(0.0f)); - REQUIRE(c.data[2] == Approx(0.0f)); - REQUIRE(c.data[3] == Approx(0.0f)); - - REQUIRE(c.data[4] == Approx(0.0f)); - REQUIRE(c.data[5] == Approx(1.0f)); - REQUIRE(c.data[6] == Approx(0.0f)); - REQUIRE(c.data[7] == Approx(0.0f)); - - REQUIRE(c.data[8] == Approx(0.0f)); - REQUIRE(c.data[9] == Approx(0.0f)); - REQUIRE(c.data[10] == Approx(1.0f)); - REQUIRE(c.data[11] == Approx(0.0f)); - - REQUIRE(c.data[12] == Approx(0.0f)); - REQUIRE(c.data[13] == Approx(0.0f)); - REQUIRE(c.data[14] == Approx(0.0f)); - REQUIRE(c.data[15] == Approx(1.0f)); + REQUIRE(c.data[0] == Catch::Approx(1.0f)); + REQUIRE(c.data[1] == Catch::Approx(0.0f)); + REQUIRE(c.data[2] == Catch::Approx(0.0f)); + REQUIRE(c.data[3] == Catch::Approx(0.0f)); + + REQUIRE(c.data[4] == Catch::Approx(0.0f)); + REQUIRE(c.data[5] == Catch::Approx(1.0f)); + REQUIRE(c.data[6] == Catch::Approx(0.0f)); + REQUIRE(c.data[7] == Catch::Approx(0.0f)); + + REQUIRE(c.data[8] == Catch::Approx(0.0f)); + REQUIRE(c.data[9] == Catch::Approx(0.0f)); + REQUIRE(c.data[10] == Catch::Approx(1.0f)); + REQUIRE(c.data[11] == Catch::Approx(0.0f)); + + REQUIRE(c.data[12] == Catch::Approx(0.0f)); + REQUIRE(c.data[13] == Catch::Approx(0.0f)); + REQUIRE(c.data[14] == Catch::Approx(0.0f)); + REQUIRE(c.data[15] == Catch::Approx(1.0f)); } } @@ -125,9 +126,9 @@ namespace rwe Matrix4f m = Matrix4f::translation(Vector3f(3.0f, 4.0f, 5.0f)); Vector3f v(11.0f, 15.0f, 19.0f); auto u = m * v; - REQUIRE(u.x == Approx(14.0f)); - REQUIRE(u.y == Approx(19.0f)); - REQUIRE(u.z == Approx(24.0f)); + REQUIRE(u.x == Catch::Approx(14.0f)); + REQUIRE(u.y == Catch::Approx(19.0f)); + REQUIRE(u.z == Catch::Approx(24.0f)); } } @@ -188,9 +189,9 @@ namespace rwe auto m = Matrix4f::rotationAxisAngle(Vector3f(0.0f, 1.0f, 0.0f), Pif / 2.0f); auto v = Vector3f(5.0f, 0.0f, 3.0f); auto v2 = m * v; - REQUIRE(v2.x == Approx(3.0f)); - REQUIRE(v2.y == Approx(0.0f)); - REQUIRE(v2.z == Approx(-5.0f)); + REQUIRE(v2.x == Catch::Approx(3.0f)); + REQUIRE(v2.y == Catch::Approx(0.0f)); + REQUIRE(v2.z == Catch::Approx(-5.0f)); } } } diff --git a/src/rwe/math/Vector2f.test.cpp b/src/rwe/math/Vector2f.test.cpp index da7a6455b..4d6b5025a 100644 --- a/src/rwe/math/Vector2f.test.cpp +++ b/src/rwe/math/Vector2f.test.cpp @@ -1,4 +1,5 @@ -#include +#include +#include #include #include @@ -12,31 +13,31 @@ namespace rwe { Vector2f a(1.0f, 0.0f); Vector2f b(1.0f, 0.0f); - REQUIRE(a.angleTo(b) == Approx(0.0f)); + REQUIRE(a.angleTo(b) == Catch::Approx(0.0f)); } SECTION("works for perpendicular vectors") { Vector2f a(1.0f, 0.0f); Vector2f b(0.0f, 1.0f); - REQUIRE(a.angleTo(b) == Approx(Pif / 2.0f)); + REQUIRE(a.angleTo(b) == Catch::Approx(Pif / 2.0f)); } SECTION("works for perpendicular vectors with negative angle") { Vector2f a(0.0f, 1.0f); Vector2f b(1.0f, 0.0f); - REQUIRE(a.angleTo(b) == Approx(-Pif / 2.0f)); + REQUIRE(a.angleTo(b) == Catch::Approx(-Pif / 2.0f)); } SECTION("works for opposite vectors") { Vector2f a(0.0f, 1.0f); Vector2f b(0.0f, -1.0f); - REQUIRE(a.angleTo(b) == Approx(-Pif)); + REQUIRE(a.angleTo(b) == Catch::Approx(-Pif)); } SECTION("works for vectors not of the same length") { Vector2f a(0.0f, 1.0f); Vector2f b(-1.0f, 1.0f); - REQUIRE(a.angleTo(b) == Approx(Pif / 4.0f)); + REQUIRE(a.angleTo(b) == Catch::Approx(Pif / 4.0f)); } } } diff --git a/src/rwe/math/Vector3f.cpp b/src/rwe/math/Vector3f.cpp index 57b537f28..300666ce0 100644 --- a/src/rwe/math/Vector3f.cpp +++ b/src/rwe/math/Vector3f.cpp @@ -1,4 +1,5 @@ #include "Vector3f.h" +#include #include diff --git a/src/rwe/math/Vector3f.test.cpp b/src/rwe/math/Vector3f.test.cpp index 0c0273c66..2b40b4c96 100644 --- a/src/rwe/math/Vector3f.test.cpp +++ b/src/rwe/math/Vector3f.test.cpp @@ -1,4 +1,5 @@ -#include +#include +#include #include #include #include @@ -53,9 +54,9 @@ namespace rwe { Vector3f v(3.0, 4.0, 0.0); Vector3f n = v.normalized(); - REQUIRE(n.x == Approx(0.6f)); - REQUIRE(n.y == Approx(0.8f)); - REQUIRE(n.z == Approx(0.0f)); + REQUIRE(n.x == Catch::Approx(0.6f)); + REQUIRE(n.y == Catch::Approx(0.8f)); + REQUIRE(n.z == Catch::Approx(0.0f)); } } @@ -65,9 +66,9 @@ namespace rwe { Vector3f v(3.0, 4.0, 0.0); Vector3f n = v.normalizedOr(Vector3f(1.0f, 0.0f, 0.0f)); - REQUIRE(n.x == Approx(0.6f)); - REQUIRE(n.y == Approx(0.8f)); - REQUIRE(n.z == Approx(0.0f)); + REQUIRE(n.x == Catch::Approx(0.6f)); + REQUIRE(n.y == Catch::Approx(0.8f)); + REQUIRE(n.z == Catch::Approx(0.0f)); } SECTION("when length is zero, returns the default value") { @@ -153,21 +154,21 @@ namespace rwe Vector3f a(1.0f, 0.0f, 0.0f); Vector3f b(1.0f, 0.0f, 0.0f); Vector3f n(0.0f, 0.0f, 1.0f); - REQUIRE(angleTo(a, b, n) == Approx(0.0f)); + REQUIRE(angleTo(a, b, n) == Catch::Approx(0.0f)); } SECTION("Y") { Vector3f a(0.0f, 1.0f, 0.0f); Vector3f b(0.0f, 1.0f, 0.0f); Vector3f n(1.0f, 0.0f, 0.0f); - REQUIRE(angleTo(a, b, n) == Approx(0.0f)); + REQUIRE(angleTo(a, b, n) == Catch::Approx(0.0f)); } SECTION("Z") { Vector3f a(0.0f, 0.0f, 1.0f); Vector3f b(0.0f, 0.0f, 1.0f); Vector3f n(0.0f, 1.0f, 0.0f); - REQUIRE(angleTo(a, b, n) == Approx(0.0f)); + REQUIRE(angleTo(a, b, n) == Catch::Approx(0.0f)); } } SECTION("works for perpendicular vectors") @@ -177,21 +178,21 @@ namespace rwe Vector3f a(1.0f, 0.0f, 0.0f); Vector3f b(0.0f, 1.0f, 0.0f); Vector3f n(0.0f, 0.0f, 1.0f); - REQUIRE(angleTo(a, b, n) == Approx(Pif / 2.0f)); + REQUIRE(angleTo(a, b, n) == Catch::Approx(Pif / 2.0f)); } SECTION("Z -> X") { Vector3f a(0.0f, 0.0f, 1.0f); Vector3f b(1.0f, 0.0f, 0.0f); Vector3f n(0.0f, 1.0f, 0.0f); - REQUIRE(angleTo(a, b, n) == Approx(Pif / 2.0f)); + REQUIRE(angleTo(a, b, n) == Catch::Approx(Pif / 2.0f)); } SECTION("Y -> Z") { Vector3f a(0.0f, 1.0f, 0.0f); Vector3f b(0.0f, 0.0f, 1.0f); Vector3f n(1.0f, 0.0f, 0.0f); - REQUIRE(angleTo(a, b, n) == Approx(Pif / 2.0f)); + REQUIRE(angleTo(a, b, n) == Catch::Approx(Pif / 2.0f)); } } SECTION("works for perpendicular vectors with negative angle") @@ -201,21 +202,21 @@ namespace rwe Vector3f a(0.0f, 1.0f, 0.0f); Vector3f b(1.0f, 0.0f, 0.0f); Vector3f n(0.0f, 0.0f, 1.0f); - REQUIRE(angleTo(a, b, n) == Approx(-Pif / 2.0f)); + REQUIRE(angleTo(a, b, n) == Catch::Approx(-Pif / 2.0f)); } SECTION("X -> Z") { Vector3f a(1.0f, 0.0f, 0.0f); Vector3f b(0.0f, 0.0f, 1.0f); Vector3f n(0.0f, 1.0f, 0.0f); - REQUIRE(angleTo(a, b, n) == Approx(-Pif / 2.0f)); + REQUIRE(angleTo(a, b, n) == Catch::Approx(-Pif / 2.0f)); } SECTION("Z -> Y") { Vector3f a(0.0f, 0.0f, 1.0f); Vector3f b(0.0f, 1.0f, 0.0f); Vector3f n(1.0f, 0.0f, 0.0f); - REQUIRE(angleTo(a, b, n) == Approx(-Pif / 2.0f)); + REQUIRE(angleTo(a, b, n) == Catch::Approx(-Pif / 2.0f)); } } SECTION("works for opposite vectors") @@ -225,7 +226,7 @@ namespace rwe Vector3f a(1.0f, 0.0f, 0.0f); Vector3f b(-1.0f, 0.0f, 0.0f); Vector3f n(0.0f, 0.0f, 1.0f); - REQUIRE(angleTo(a, b, n) == Approx(-Pif)); + REQUIRE(angleTo(a, b, n) == Catch::Approx(-Pif)); } SECTION("Y") @@ -233,7 +234,7 @@ namespace rwe Vector3f a(0.0f, 1.0f, 0.0f); Vector3f b(0.0f, -1.0f, 0.0f); Vector3f n(1.0f, 0.0f, 0.0f); - REQUIRE(angleTo(a, b, n) == Approx(-Pif)); + REQUIRE(angleTo(a, b, n) == Catch::Approx(-Pif)); } SECTION("Z") @@ -241,7 +242,7 @@ namespace rwe Vector3f a(0.0f, 0.0f, 1.0f); Vector3f b(0.0f, 0.0f, -1.0f); Vector3f n(0.0f, 1.0f, 0.0f); - REQUIRE(angleTo(a, b, n) == Approx(-Pif)); + REQUIRE(angleTo(a, b, n) == Catch::Approx(-Pif)); } } SECTION("works for vectors not of the same length") @@ -249,7 +250,7 @@ namespace rwe Vector3f a(0.0f, 1.0f, 0.0f); Vector3f b(-1.0f, 1.0f, 0.0f); Vector3f n(-1.0f, 1.0f, 1.0f); - REQUIRE(angleTo(a, b, n) == Approx(Pif / 4.0f)); + REQUIRE(angleTo(a, b, n) == Catch::Approx(Pif / 4.0f)); } SECTION("it doesnt emit nan") { @@ -259,7 +260,7 @@ namespace rwe Vector3f a(171.99025, -0.00849914551, -38.2367249); Vector3f b(171.99025, 0, -38.2367249); Vector3f n(38.2367249, -0, 171.99025); - REQUIRE(angleTo(a, b, n) == Approx(0.0f)); + REQUIRE(angleTo(a, b, n) == Catch::Approx(0.0f)); } } diff --git a/src/rwe/math/rwe_math.test.cpp b/src/rwe/math/rwe_math.test.cpp index d52992025..2384c69b8 100644 --- a/src/rwe/math/rwe_math.test.cpp +++ b/src/rwe/math/rwe_math.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/rwe/network_util.cpp b/src/rwe/network_util.cpp index dbbaf76ec..1ee28a6a7 100644 --- a/src/rwe/network_util.cpp +++ b/src/rwe/network_util.cpp @@ -1,8 +1,33 @@ #include "network_util.h" -#include +#include namespace rwe { + // CRC32 lookup table (polynomial 0xEDB88320, same as Boost.CRC / zlib) + static uint32_t makeCrc32Entry(uint32_t index) + { + uint32_t crc = index; + for (int j = 0; j < 8; ++j) + { + crc = (crc >> 1) ^ (0xEDB88320u & (-(crc & 1u))); + } + return crc; + } + + struct Crc32Table + { + uint32_t entries[256]; + Crc32Table() + { + for (uint32_t i = 0; i < 256; ++i) + { + entries[i] = makeCrc32Entry(i); + } + } + }; + + static const Crc32Table crc32Table; + float ema(float val, float average, float alpha) { return (alpha * val) + ((1.0f - alpha) * average); @@ -23,10 +48,12 @@ namespace rwe } unsigned int computeCrc(const char* buffer, unsigned int size) { - // throw in a CRC to verify the message - boost::crc_32_type crc; - crc.process_bytes(buffer, size); - return crc.checksum(); + uint32_t crc = 0xFFFFFFFFu; + for (unsigned int i = 0; i < size; ++i) + { + crc = crc32Table.entries[(crc ^ static_cast(buffer[i])) & 0xFFu] ^ (crc >> 8u); + } + return crc ^ 0xFFFFFFFFu; } } diff --git a/src/rwe/network_util.test.cpp b/src/rwe/network_util.test.cpp index 6a48f28c8..f0a316436 100644 --- a/src/rwe/network_util.test.cpp +++ b/src/rwe/network_util.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -23,4 +23,25 @@ namespace rwe RC_ASSERT(result == i); }); } + + TEST_CASE("computeCrc") + { + SECTION("empty input") + { + REQUIRE(computeCrc("", 0) == 0x00000000u); + } + + SECTION("known CRC32 values") + { + // CRC32 of "123456789" is 0xCBF43926 + const char* input = "123456789"; + REQUIRE(computeCrc(input, 9) == 0xCBF43926u); + } + + SECTION("single byte") + { + // CRC32 of "a" is 0xE8B7BE43 + REQUIRE(computeCrc("a", 1) == 0xE8B7BE43u); + } + } } diff --git a/src/rwe/pathfinding/AStarPathFinder.h b/src/rwe/pathfinding/AStarPathFinder.h index 9f9c4f101..c11856f32 100644 --- a/src/rwe/pathfinding/AStarPathFinder.h +++ b/src/rwe/pathfinding/AStarPathFinder.h @@ -1,9 +1,10 @@ #pragma once #include +#include #include #include -#include +#include #include #include @@ -66,7 +67,7 @@ namespace rwe if (isGoal(current.vertex)) { - spdlog::get("rwe")->debug("Found goal after visiting {0} vertices", openListPopsPerformed); + LOG_DEBUG << "Found goal after visiting " << openListPopsPerformed << " vertices"; return AStarPathInfo{AStarPathType::Complete, walkPath(current), std::move(closedVertices)}; } @@ -88,7 +89,7 @@ namespace rwe } } - spdlog::get("rwe")->debug("Failed to find goal, visited {0} vertices", openListPopsPerformed); + LOG_DEBUG << "Failed to find goal, visited " << openListPopsPerformed << " vertices"; return AStarPathInfo{AStarPathType::Partial, walkPath(*(closestVertex->second)), std::move(closedVertices)}; } diff --git a/src/rwe/pathfinding/pathfinding_utils.test.cpp b/src/rwe/pathfinding/pathfinding_utils.test.cpp index 426f67967..b0f494387 100644 --- a/src/rwe/pathfinding/pathfinding_utils.test.cpp +++ b/src/rwe/pathfinding/pathfinding_utils.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/rwe/sim/GameHash_util.test.cpp b/src/rwe/sim/GameHash_util.test.cpp index cf614ffc0..1aeef2e8e 100644 --- a/src/rwe/sim/GameHash_util.test.cpp +++ b/src/rwe/sim/GameHash_util.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/rwe/sim/GameSimulation.cpp b/src/rwe/sim/GameSimulation.cpp index c900baffe..aa7b48109 100644 --- a/src/rwe/sim/GameSimulation.cpp +++ b/src/rwe/sim/GameSimulation.cpp @@ -1,4 +1,5 @@ #include "GameSimulation.h" +#include #include #include #include diff --git a/src/rwe/sim/SimAngle.test.cpp b/src/rwe/sim/SimAngle.test.cpp index 698673fb1..b85a5bb03 100644 --- a/src/rwe/sim/SimAngle.test.cpp +++ b/src/rwe/sim/SimAngle.test.cpp @@ -1,4 +1,5 @@ -#include +#include +#include #include #include #include @@ -40,9 +41,9 @@ namespace rwe SECTION("converts SimAngle to radians") { REQUIRE(toRadians(SimAngle(0)) == RadiansAngle(0.0f)); - REQUIRE(toRadians(SimAngle(16384)).value == Approx(Pif / 2.0f)); - REQUIRE(toRadians(SimAngle(32768)).value == Approx(-Pif)); - REQUIRE(toRadians(SimAngle(49152)).value == Approx(-Pif / 2.0f)); + REQUIRE(toRadians(SimAngle(16384)).value == Catch::Approx(Pif / 2.0f)); + REQUIRE(toRadians(SimAngle(32768)).value == Catch::Approx(-Pif)); + REQUIRE(toRadians(SimAngle(49152)).value == Catch::Approx(-Pif / 2.0f)); } rc::prop("fromRadians inverts toRadians", [](SimAngle a) { diff --git a/src/rwe/sim/SimVector.test.cpp b/src/rwe/sim/SimVector.test.cpp index 106b54577..1180ef272 100644 --- a/src/rwe/sim/SimVector.test.cpp +++ b/src/rwe/sim/SimVector.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include namespace rwe diff --git a/src/rwe/sim/UnitBehaviorService_util.cpp b/src/rwe/sim/UnitBehaviorService_util.cpp index f01d8fdd6..f60806f2b 100644 --- a/src/rwe/sim/UnitBehaviorService_util.cpp +++ b/src/rwe/sim/UnitBehaviorService_util.cpp @@ -1,4 +1,5 @@ #include "UnitBehaviorService_util.h" +#include #include diff --git a/src/rwe/sim/UnitState.h b/src/rwe/sim/UnitState.h index 20a25bdfc..6f0f25e9e 100644 --- a/src/rwe/sim/UnitState.h +++ b/src/rwe/sim/UnitState.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include diff --git a/src/rwe/sim/UnitState_util.test.cpp b/src/rwe/sim/UnitState_util.test.cpp index 6672fd77b..45a87e8f9 100644 --- a/src/rwe/sim/UnitState_util.test.cpp +++ b/src/rwe/sim/UnitState_util.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include namespace rwe diff --git a/src/rwe/sim/util.test.cpp b/src/rwe/sim/util.test.cpp index d04f2b840..7d29b5f45 100644 --- a/src/rwe/sim/util.test.cpp +++ b/src/rwe/sim/util.test.cpp @@ -1,4 +1,5 @@ -#include +#include +#include #include namespace rwe @@ -73,9 +74,9 @@ namespace rwe // still introduces a bit of fp error due to conversion to radians auto barExpectedPosition = SimVector(31_ss, 22_ss, -7_ss); auto barActualPosition = getPieceTransform("bar", modelDef, pieces) * SimVector(0_ss, 0_ss, 0_ss); - REQUIRE(barActualPosition.x.value == Approx(barExpectedPosition.x.value)); - REQUIRE(barActualPosition.y.value == Approx(barExpectedPosition.y.value)); - REQUIRE(barActualPosition.z.value == Approx(barExpectedPosition.z.value)); + REQUIRE(barActualPosition.x.value == Catch::Approx(barExpectedPosition.x.value)); + REQUIRE(barActualPosition.y.value == Catch::Approx(barExpectedPosition.y.value)); + REQUIRE(barActualPosition.z.value == Catch::Approx(barExpectedPosition.z.value)); } } } diff --git a/src/rwe/ui/UiFactory.cpp b/src/rwe/ui/UiFactory.cpp index 4a2100f56..af1624474 100644 --- a/src/rwe/ui/UiFactory.cpp +++ b/src/rwe/ui/UiFactory.cpp @@ -1,4 +1,5 @@ #include "UiFactory.h" +#include #include #include diff --git a/src/rwe/ui/UiListBox.cpp b/src/rwe/ui/UiListBox.cpp index 860034b4c..62a199a26 100644 --- a/src/rwe/ui/UiListBox.cpp +++ b/src/rwe/ui/UiListBox.cpp @@ -1,4 +1,5 @@ #include "UiListBox.h" +#include namespace rwe { diff --git a/src/rwe/ui/UiPanel.cpp b/src/rwe/ui/UiPanel.cpp index 9bb55753e..d5900406c 100644 --- a/src/rwe/ui/UiPanel.cpp +++ b/src/rwe/ui/UiPanel.cpp @@ -1,4 +1,5 @@ #include "UiPanel.h" +#include #include namespace rwe diff --git a/src/rwe/ui/UiPanel.h b/src/rwe/ui/UiPanel.h index eb36ccb1c..6ecd02334 100644 --- a/src/rwe/ui/UiPanel.h +++ b/src/rwe/ui/UiPanel.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include diff --git a/src/rwe/ui/UiScrollBar.cpp b/src/rwe/ui/UiScrollBar.cpp index d5bfcf3d0..728ce53a0 100644 --- a/src/rwe/ui/UiScrollBar.cpp +++ b/src/rwe/ui/UiScrollBar.cpp @@ -1,4 +1,5 @@ #include "UiScrollBar.h" +#include namespace rwe { diff --git a/src/rwe/ui/UiSurface.cpp b/src/rwe/ui/UiSurface.cpp index aee27573e..9bfd8b9c2 100644 --- a/src/rwe/ui/UiSurface.cpp +++ b/src/rwe/ui/UiSurface.cpp @@ -1,4 +1,5 @@ #include "UiSurface.h" +#include namespace rwe { diff --git a/src/rwe/util.cpp b/src/rwe/util.cpp index 1ba184966..9a297fbe8 100644 --- a/src/rwe/util.cpp +++ b/src/rwe/util.cpp @@ -5,7 +5,7 @@ namespace rwe { #ifdef RWE_PLATFORM_WINDOWS - std::optional getLocalDataPath() + std::optional getLocalDataPath() { auto appData = std::getenv("APPDATA"); if (appData == nullptr) @@ -13,7 +13,7 @@ namespace rwe return std::nullopt; } - boost::filesystem::path path(appData); + std::filesystem::path path(appData); path /= "RWE"; return path; @@ -21,7 +21,7 @@ namespace rwe #endif #ifdef RWE_PLATFORM_LINUX - std::optional getLocalDataPath() + std::optional getLocalDataPath() { auto home = std::getenv("HOME"); if (home == nullptr) @@ -29,14 +29,14 @@ namespace rwe return std::nullopt; } - boost::filesystem::path path(home); + std::filesystem::path path(home); path /= ".rwe"; return path; } #endif - std::optional getSearchPath() + std::optional getSearchPath() { auto path = getLocalDataPath(); if (!path) diff --git a/src/rwe/util.h b/src/rwe/util.h index e4d3994da..a0f9db30f 100644 --- a/src/rwe/util.h +++ b/src/rwe/util.h @@ -1,12 +1,12 @@ #pragma once -#include +#include #include namespace rwe { - std::optional getLocalDataPath(); - std::optional getSearchPath(); + std::optional getLocalDataPath(); + std::optional getSearchPath(); float toleranceToRadians(unsigned int angle); } diff --git a/src/rwe/util/OpaqueArgs.h b/src/rwe/util/OpaqueArgs.h new file mode 100644 index 000000000..2d8131109 --- /dev/null +++ b/src/rwe/util/OpaqueArgs.h @@ -0,0 +1,186 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace rwe +{ + // Simple command-line and config-file argument parser. + // Supports --key value, --key=value, --flag (bool), and multi-value keys. + // Config files use key=value format, one per line; # comments are ignored. + // Command-line args take precedence over config file values. + class OpaqueArgs + { + private: + std::map> values; + bool helpRequested{false}; + + public: + void parse(int argc, char* argv[]) + { + for (int i = 1; i < argc; ++i) + { + std::string arg(argv[i]); + if (arg.size() < 3 || arg[0] != '-' || arg[1] != '-') + { + throw std::runtime_error("Unexpected argument: " + arg); + } + arg = arg.substr(2); + + if (arg == "help") + { + helpRequested = true; + continue; + } + + // --key=value form + auto eqPos = arg.find('='); + if (eqPos != std::string::npos) + { + auto key = arg.substr(0, eqPos); + auto val = arg.substr(eqPos + 1); + values[key].push_back(val); + continue; + } + + // --flag (no next arg or next arg starts with --) + if (i + 1 >= argc || (argv[i + 1][0] == '-' && argv[i + 1][1] == '-')) + { + values[arg].emplace_back("true"); + continue; + } + + // --key value form + ++i; + values[arg].push_back(argv[i]); + } + } + + void parseConfig(const std::string& filePath) + { + std::ifstream file(filePath); + if (!file.is_open()) + { + return; + } + parseConfig(file); + } + + void parseConfig(std::istream& stream) + { + std::string line; + while (std::getline(stream, line)) + { + // trim leading whitespace + auto start = line.find_first_not_of(" \t\r\n"); + if (start == std::string::npos) + { + continue; + } + line = line.substr(start); + + // skip comments and empty lines + if (line.empty() || line[0] == '#') + { + continue; + } + + auto eqPos = line.find('='); + if (eqPos == std::string::npos) + { + throw std::runtime_error("Invalid config line (no '='): " + line); + } + + auto key = line.substr(0, eqPos); + auto val = line.substr(eqPos + 1); + + // trim trailing whitespace from key + auto keyEnd = key.find_last_not_of(" \t"); + if (keyEnd != std::string::npos) + { + key = key.substr(0, keyEnd + 1); + } + + // trim leading whitespace from value + auto valStart = val.find_first_not_of(" \t"); + if (valStart != std::string::npos) + { + val = val.substr(valStart); + } + else + { + val = ""; + } + + // Only set from config if not already set by command line + if (values.find(key) == values.end()) + { + values[key].push_back(val); + } + } + } + + bool isHelpRequested() const + { + return helpRequested; + } + + bool contains(const std::string& key) const + { + return values.find(key) != values.end(); + } + + std::string getString(const std::string& key, const std::string& defaultValue) const + { + auto it = values.find(key); + if (it == values.end() || it->second.empty()) + { + return defaultValue; + } + return it->second.back(); + } + + std::string getString(const std::string& key) const + { + auto it = values.find(key); + if (it == values.end() || it->second.empty()) + { + throw std::runtime_error("Missing required argument: --" + key); + } + return it->second.back(); + } + + unsigned int getUint(const std::string& key, unsigned int defaultValue) const + { + auto it = values.find(key); + if (it == values.end() || it->second.empty()) + { + return defaultValue; + } + return static_cast(std::stoul(it->second.back())); + } + + bool getBool(const std::string& key) const + { + auto it = values.find(key); + if (it == values.end() || it->second.empty()) + { + return false; + } + return it->second.back() == "true" || it->second.back() == "1"; + } + + std::vector getMulti(const std::string& key) const + { + auto it = values.find(key); + if (it == values.end()) + { + return {}; + } + return it->second; + } + }; +} diff --git a/src/rwe/util/OpaqueArgs.test.cpp b/src/rwe/util/OpaqueArgs.test.cpp new file mode 100644 index 000000000..2c22d05af --- /dev/null +++ b/src/rwe/util/OpaqueArgs.test.cpp @@ -0,0 +1,212 @@ +#include +#include +#include + +namespace rwe +{ + // Helper to build argc/argv from a vector of strings. + // The first element should be the program name. + struct ArgBuilder + { + std::vector storage; + std::vector ptrs; + + ArgBuilder(std::initializer_list args) : storage(args) + { + for (auto& s : storage) + { + ptrs.push_back(s.data()); + } + } + + int argc() const { return static_cast(ptrs.size()); } + char** argv() { return ptrs.data(); } + }; + + TEST_CASE("OpaqueArgs") + { + SECTION("parses --key value pairs") + { + ArgBuilder args{"rwe", "--width", "1024", "--height", "768"}; + OpaqueArgs oa; + oa.parse(args.argc(), args.argv()); + REQUIRE(oa.getUint("width", 800) == 1024); + REQUIRE(oa.getUint("height", 600) == 768); + } + + SECTION("parses --key=value pairs") + { + ArgBuilder args{"rwe", "--width=1024", "--height=768"}; + OpaqueArgs oa; + oa.parse(args.argc(), args.argv()); + REQUIRE(oa.getUint("width", 800) == 1024); + REQUIRE(oa.getUint("height", 600) == 768); + } + + SECTION("returns defaults for missing keys") + { + ArgBuilder args{"rwe"}; + OpaqueArgs oa; + oa.parse(args.argc(), args.argv()); + REQUIRE(oa.getUint("width", 800) == 800); + REQUIRE(oa.getString("port", "1337") == "1337"); + REQUIRE(oa.getBool("fullscreen") == false); + } + + SECTION("parses bool flags") + { + ArgBuilder args{"rwe", "--fullscreen"}; + OpaqueArgs oa; + oa.parse(args.argc(), args.argv()); + REQUIRE(oa.getBool("fullscreen") == true); + } + + SECTION("bool flag followed by another flag") + { + ArgBuilder args{"rwe", "--fullscreen", "--width", "1024"}; + OpaqueArgs oa; + oa.parse(args.argc(), args.argv()); + REQUIRE(oa.getBool("fullscreen") == true); + REQUIRE(oa.getUint("width", 800) == 1024); + } + + SECTION("multi-value args accumulate") + { + ArgBuilder args{"rwe", "--data-path", "/foo", "--data-path", "/bar"}; + OpaqueArgs oa; + oa.parse(args.argc(), args.argv()); + auto paths = oa.getMulti("data-path"); + REQUIRE(paths.size() == 2); + REQUIRE(paths[0] == "/foo"); + REQUIRE(paths[1] == "/bar"); + } + + SECTION("--help sets help flag") + { + ArgBuilder args{"rwe", "--help"}; + OpaqueArgs oa; + oa.parse(args.argc(), args.argv()); + REQUIRE(oa.isHelpRequested()); + } + + SECTION("getString throws on missing required key") + { + ArgBuilder args{"rwe"}; + OpaqueArgs oa; + oa.parse(args.argc(), args.argv()); + REQUIRE_THROWS_AS(oa.getString("map"), std::runtime_error); + } + + SECTION("contains returns true for present keys") + { + ArgBuilder args{"rwe", "--map", "coast"}; + OpaqueArgs oa; + oa.parse(args.argc(), args.argv()); + REQUIRE(oa.contains("map")); + REQUIRE(!oa.contains("log")); + } + + SECTION("throws on unexpected argument without --") + { + ArgBuilder args{"rwe", "bogus"}; + OpaqueArgs oa; + REQUIRE_THROWS_AS(oa.parse(args.argc(), args.argv()), std::runtime_error); + } + + SECTION("parses config file") + { + std::istringstream config( + "width=1024\n" + "height=768\n" + "fullscreen=true\n"); + + OpaqueArgs oa; + ArgBuilder args{"rwe"}; + oa.parse(args.argc(), args.argv()); + oa.parseConfig(config); + REQUIRE(oa.getUint("width", 800) == 1024); + REQUIRE(oa.getUint("height", 600) == 768); + REQUIRE(oa.getBool("fullscreen") == true); + } + + SECTION("config file skips comments and blank lines") + { + std::istringstream config( + "# this is a comment\n" + "\n" + "width=1024\n" + " \n" + "# another comment\n"); + + OpaqueArgs oa; + ArgBuilder args{"rwe"}; + oa.parse(args.argc(), args.argv()); + oa.parseConfig(config); + REQUIRE(oa.getUint("width", 800) == 1024); + } + + SECTION("command line takes precedence over config file") + { + std::istringstream config("width=640\nheight=480\n"); + + ArgBuilder args{"rwe", "--width", "1024"}; + OpaqueArgs oa; + oa.parse(args.argc(), args.argv()); + oa.parseConfig(config); + REQUIRE(oa.getUint("width", 800) == 1024); + REQUIRE(oa.getUint("height", 600) == 480); + } + + SECTION("config file trims whitespace around key and value") + { + std::istringstream config(" width = 1024 \n"); + + OpaqueArgs oa; + ArgBuilder args{"rwe"}; + oa.parse(args.argc(), args.argv()); + oa.parseConfig(config); + REQUIRE(oa.getUint("width", 800) == 1024); + } + + SECTION("config file throws on line without =") + { + std::istringstream config("badline\n"); + + OpaqueArgs oa; + ArgBuilder args{"rwe"}; + oa.parse(args.argc(), args.argv()); + REQUIRE_THROWS_AS(oa.parseConfig(config), std::runtime_error); + } + + SECTION("player args accumulate via multi") + { + ArgBuilder args{"rwe", "--map", "coast", + "--player", "Player1;human;arm;blue", + "--player", "Player2;computer;core;red"}; + OpaqueArgs oa; + oa.parse(args.argc(), args.argv()); + auto players = oa.getMulti("player"); + REQUIRE(players.size() == 2); + REQUIRE(players[0] == "Player1;human;arm;blue"); + REQUIRE(players[1] == "Player2;computer;core;red"); + } + + SECTION("getString with default returns last value for multi-value key") + { + ArgBuilder args{"rwe", "--interface-mode", "left-click", "--interface-mode", "right-click"}; + OpaqueArgs oa; + oa.parse(args.argc(), args.argv()); + REQUIRE(oa.getString("interface-mode", "left-click") == "right-click"); + } + + SECTION("all dir-* options work") + { + ArgBuilder args{"rwe", "--dir-ai", "myai", "--dir-maps", "mymaps"}; + OpaqueArgs oa; + oa.parse(args.argc(), args.argv()); + REQUIRE(oa.getString("dir-ai", "ai") == "myai"); + REQUIRE(oa.getString("dir-maps", "maps") == "mymaps"); + REQUIRE(oa.getString("dir-sounds", "sounds") == "sounds"); + } + } +} diff --git a/src/rwe/util/Result.test.cpp b/src/rwe/util/Result.test.cpp index 1e228fa6c..02d8333dc 100644 --- a/src/rwe/util/Result.test.cpp +++ b/src/rwe/util/Result.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include diff --git a/src/rwe/util/SimpleLogger.h b/src/rwe/util/SimpleLogger.h new file mode 100644 index 000000000..8cac2d00b --- /dev/null +++ b/src/rwe/util/SimpleLogger.h @@ -0,0 +1,200 @@ +#pragma once + +// Minimal logging replacement for spdlog. +// This project only used spdlog for basic file logging with timestamps, +// so a full logging framework was unnecessary overhead and was blocking +// the C++20 upgrade. If more advanced logging features are needed in the +// future (async logging, log rotation, multiple sinks, etc.), consider +// re-evaluating spdlog or another logging library. +// +// Usage: +// LOG_INFO << "Loaded " << count << " units from " << path; +// LOG_DEBUG << "Position: " << x << ", " << y; +// LOG_ERROR << "Failed to open file: " << filename; +// +// Log level is controlled at compile time via RWE_LOG_LEVEL. +// Set it in CMake (e.g. -DRWE_LOG_LEVEL=1) to strip lower levels +// from the binary entirely. Default is 0 (everything enabled). +// +// 0 = Debug (all messages) +// 1 = Info +// 2 = Warn +// 3 = Error +// 4 = Critical +// 5 = Off (no messages) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Compile-time log level. Statements below this level are dead code +// and will be eliminated by the compiler. +#ifndef RWE_LOG_LEVEL +#define RWE_LOG_LEVEL 0 +#endif + +#define RWE_LEVEL_DEBUG 0 +#define RWE_LEVEL_INFO 1 +#define RWE_LEVEL_WARN 2 +#define RWE_LEVEL_ERROR 3 +#define RWE_LEVEL_CRITICAL 4 +#define RWE_LEVEL_OFF 5 + +namespace rwe +{ + enum class LogLevel + { + Debug, + Info, + Warn, + Error, + Critical + }; + + class SimpleLogger + { + private: + std::ofstream file; + std::mutex mutex; + + static const char* levelString(LogLevel lvl) + { + switch (lvl) + { + case LogLevel::Debug: return "debug"; + case LogLevel::Info: return "info"; + case LogLevel::Warn: return "warn"; + case LogLevel::Error: return "error"; + case LogLevel::Critical: return "critical"; + } + return "unknown"; + } + + static std::string timestamp() + { + auto now = std::chrono::system_clock::now(); + auto time = std::chrono::system_clock::to_time_t(now); + auto ms = std::chrono::duration_cast( + now.time_since_epoch()) % 1000; + + std::tm tm{}; +#ifdef _WIN32 + localtime_s(&tm, &time); +#else + localtime_r(&time, &tm); +#endif + + std::ostringstream oss; + oss << std::put_time(&tm, "%Y-%m-%d %H:%M:%S") + << '.' << std::setfill('0') << std::setw(3) << ms.count(); + return oss.str(); + } + + public: + SimpleLogger(const std::string& filePath, bool truncate) + : file(filePath, truncate ? std::ios::trunc : std::ios::app) + { + if (!file.is_open()) + { + throw std::runtime_error("Failed to open log file: " + filePath); + } + } + + void write(LogLevel msgLevel, const std::string& msg) + { + auto line = "[" + timestamp() + "] [" + levelString(msgLevel) + "] " + msg + "\n"; + std::lock_guard lock(mutex); + file << line; + file.flush(); + } + }; + + // RAII stream that accumulates a log message via operator<< + // and writes it to the logger when destroyed. + class LogStream + { + private: + SimpleLogger& logger; + LogLevel level; + std::ostringstream oss; + + public: + LogStream(SimpleLogger& logger, LogLevel level) + : logger(logger), level(level) {} + + ~LogStream() + { + logger.write(level, oss.str()); + } + + LogStream(const LogStream&) = delete; + LogStream& operator=(const LogStream&) = delete; + LogStream(LogStream&&) = default; + + template + LogStream& operator<<(const T& value) + { + oss << value; + return *this; + } + }; + + // Global logger instance, set once at startup. + inline std::shared_ptr& globalLogger() + { + static std::shared_ptr instance; + return instance; + } + + inline void setGlobalLogger(std::shared_ptr logger) + { + globalLogger() = std::move(logger); + } + + inline SimpleLogger& getLogger() + { + auto& logger = globalLogger(); + assert(logger && "Logger not initialized"); + return *logger; + } +} + +// Logging macros. Enabled levels expand to a plain LogStream expression. +// Disabled levels use if(true){}else to create a dead branch that the +// compiler eliminates entirely while still type-checking the << chain. +#if RWE_LOG_LEVEL <= RWE_LEVEL_DEBUG +#define LOG_DEBUG rwe::LogStream(rwe::getLogger(), rwe::LogLevel::Debug) +#else +#define LOG_DEBUG if (true) {} else rwe::LogStream(rwe::getLogger(), rwe::LogLevel::Debug) +#endif + +#if RWE_LOG_LEVEL <= RWE_LEVEL_INFO +#define LOG_INFO rwe::LogStream(rwe::getLogger(), rwe::LogLevel::Info) +#else +#define LOG_INFO if (true) {} else rwe::LogStream(rwe::getLogger(), rwe::LogLevel::Info) +#endif + +#if RWE_LOG_LEVEL <= RWE_LEVEL_WARN +#define LOG_WARN rwe::LogStream(rwe::getLogger(), rwe::LogLevel::Warn) +#else +#define LOG_WARN if (true) {} else rwe::LogStream(rwe::getLogger(), rwe::LogLevel::Warn) +#endif + +#if RWE_LOG_LEVEL <= RWE_LEVEL_ERROR +#define LOG_ERROR rwe::LogStream(rwe::getLogger(), rwe::LogLevel::Error) +#else +#define LOG_ERROR if (true) {} else rwe::LogStream(rwe::getLogger(), rwe::LogLevel::Error) +#endif + +#if RWE_LOG_LEVEL <= RWE_LEVEL_CRITICAL +#define LOG_CRITICAL rwe::LogStream(rwe::getLogger(), rwe::LogLevel::Critical) +#else +#define LOG_CRITICAL if (true) {} else rwe::LogStream(rwe::getLogger(), rwe::LogLevel::Critical) +#endif diff --git a/src/rwe/util/SimpleLogger.test.cpp b/src/rwe/util/SimpleLogger.test.cpp new file mode 100644 index 000000000..952299dd7 --- /dev/null +++ b/src/rwe/util/SimpleLogger.test.cpp @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace rwe +{ + static std::vector readLines(const std::string& path) + { + std::ifstream f(path); + std::vector lines; + std::string line; + while (std::getline(f, line)) + { + lines.push_back(line); + } + return lines; + } + + // Strip the timestamp prefix "[YYYY-MM-DD HH:MM:SS.mmm] " from a log line + // so we can assert on the rest deterministically. + static std::string stripTimestamp(const std::string& line) + { + auto pos = line.find("] "); + if (pos == std::string::npos) + { + return line; + } + return line.substr(pos + 2); + } + + static void initTestLogger(const std::string& path) + { + auto logger = std::make_shared(path, true); + setGlobalLogger(logger); + } + + TEST_CASE("SimpleLogger") + { + const std::string path = (std::filesystem::temp_directory_path() / "rwe_test_logger.log").string(); + + SECTION("writes log lines via macros") + { + initTestLogger(path); + + LOG_INFO << "Hello world"; + LOG_DEBUG << "Value is " << 42; + LOG_ERROR << "Error in " << "module" << ": " << "bad thing"; + LOG_WARN << "No args here"; + + // flush by replacing global logger + setGlobalLogger(nullptr); + + auto lines = readLines(path); + REQUIRE(lines.size() == 4); + REQUIRE(stripTimestamp(lines[0]) == "[info] Hello world"); + REQUIRE(stripTimestamp(lines[1]) == "[debug] Value is 42"); + REQUIRE(stripTimestamp(lines[2]) == "[error] Error in module: bad thing"); + REQUIRE(stripTimestamp(lines[3]) == "[warn] No args here"); + + std::remove(path.c_str()); + } + + SECTION("truncate mode overwrites existing file") + { + initTestLogger(path); + LOG_INFO << "first run"; + setGlobalLogger(nullptr); + + initTestLogger(path); + LOG_INFO << "second run"; + setGlobalLogger(nullptr); + + auto lines = readLines(path); + REQUIRE(lines.size() == 1); + REQUIRE(stripTimestamp(lines[0]) == "[info] second run"); + + std::remove(path.c_str()); + } + + SECTION("append mode preserves existing content") + { + { + auto logger = std::make_shared(path, true); + setGlobalLogger(logger); + LOG_INFO << "first run"; + } + { + auto logger = std::make_shared(path, false); + setGlobalLogger(logger); + LOG_INFO << "second run"; + } + setGlobalLogger(nullptr); + + auto lines = readLines(path); + REQUIRE(lines.size() == 2); + REQUIRE(stripTimestamp(lines[0]) == "[info] first run"); + REQUIRE(stripTimestamp(lines[1]) == "[info] second run"); + + std::remove(path.c_str()); + } + + SECTION("handles multiple stream insertions") + { + initTestLogger(path); + LOG_INFO << "alpha" << " then " << "beta"; + setGlobalLogger(nullptr); + + auto lines = readLines(path); + REQUIRE(lines.size() == 1); + REQUIRE(stripTimestamp(lines[0]) == "[info] alpha then beta"); + + std::remove(path.c_str()); + } + + SECTION("handles utf-8 content") + { + initTestLogger(path); + LOG_INFO << "Japanese: \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"; + LOG_INFO << "Emoji: \xf0\x9f\x8e\xae\xf0\x9f\x8e\xaf"; + LOG_INFO << "Mixed: hello \xc3\xa9\xc3\xa0\xc3\xbc world"; + LOG_INFO << "Format with utf-8: " << "user" << " said \xc2\xab" << "\xc3\xa7" "a marche\xc2\xbb"; + setGlobalLogger(nullptr); + + auto lines = readLines(path); + REQUIRE(lines.size() == 4); + REQUIRE(stripTimestamp(lines[0]) == "[info] Japanese: \xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e"); + REQUIRE(stripTimestamp(lines[1]) == "[info] Emoji: \xf0\x9f\x8e\xae\xf0\x9f\x8e\xaf"); + REQUIRE(stripTimestamp(lines[2]) == "[info] Mixed: hello \xc3\xa9\xc3\xa0\xc3\xbc world"); + REQUIRE(stripTimestamp(lines[3]) == "[info] Format with utf-8: user said \xc2\xab\xc3\xa7" "a marche\xc2\xbb"); + + std::remove(path.c_str()); + } + + SECTION("timestamp format is correct") + { + initTestLogger(path); + LOG_INFO << "check timestamp"; + setGlobalLogger(nullptr); + + auto lines = readLines(path); + REQUIRE(lines.size() == 1); + // Verify timestamp matches [YYYY-MM-DD HH:MM:SS.mmm] pattern + REQUIRE(lines[0][0] == '['); + REQUIRE(lines[0][5] == '-'); + REQUIRE(lines[0][8] == '-'); + REQUIRE(lines[0][11] == ' '); + REQUIRE(lines[0][14] == ':'); + REQUIRE(lines[0][17] == ':'); + REQUIRE(lines[0][20] == '.'); + REQUIRE(lines[0][24] == ']'); + + std::remove(path.c_str()); + } + + SECTION("logs numeric types correctly") + { + initTestLogger(path); + LOG_INFO << "int=" << 42 << " float=" << 3.14 << " negative=" << -1; + setGlobalLogger(nullptr); + + auto lines = readLines(path); + REQUIRE(lines.size() == 1); + REQUIRE(stripTimestamp(lines[0]) == "[info] int=42 float=3.14 negative=-1"); + + std::remove(path.c_str()); + } + } +} diff --git a/src/rwe/util/SpanStream.h b/src/rwe/util/SpanStream.h new file mode 100644 index 000000000..eb5c37959 --- /dev/null +++ b/src/rwe/util/SpanStream.h @@ -0,0 +1,74 @@ +#pragma once + +#include + +namespace rwe +{ + class SpanStreamBuf : public std::streambuf + { + public: + SpanStreamBuf() = default; + + SpanStreamBuf(const char* data, std::size_t size) + { + reset(data, size); + } + + void reset(const char* data, std::size_t size) + { + auto p = const_cast(data); + setg(p, p, p + size); + } + + protected: + std::streampos seekoff(std::streamoff off, std::ios_base::seekdir dir, std::ios_base::openmode which) override + { + if (!(which & std::ios_base::in)) + { + return std::streampos(-1); + } + + char* newPos; + switch (dir) + { + case std::ios_base::beg: + newPos = eback() + off; + break; + case std::ios_base::cur: + newPos = gptr() + off; + break; + case std::ios_base::end: + newPos = egptr() + off; + break; + default: + return std::streampos(-1); + } + + if (newPos < eback() || newPos > egptr()) + { + return std::streampos(-1); + } + + setg(eback(), newPos, egptr()); + return std::streampos(newPos - eback()); + } + + std::streampos seekpos(std::streampos pos, std::ios_base::openmode which) override + { + return seekoff(std::streamoff(pos), std::ios_base::beg, which); + } + }; + + class SpanStream : public std::istream + { + private: + SpanStreamBuf buf; + + public: + SpanStream(const char* data, std::size_t size) + : std::istream(nullptr), buf(data, size) + { + rdbuf(&buf); + } + }; +} diff --git a/src/rwe/util/SpanStream.test.cpp b/src/rwe/util/SpanStream.test.cpp new file mode 100644 index 000000000..7f4cf23f9 --- /dev/null +++ b/src/rwe/util/SpanStream.test.cpp @@ -0,0 +1,108 @@ +#include +#include +#include + +namespace rwe +{ + TEST_CASE("SpanStream") + { + const char data[] = "Hello, world!"; + auto size = sizeof(data) - 1; // exclude null terminator + + SECTION("reads data sequentially") + { + SpanStream stream(data, size); + char buf[5]; + stream.read(buf, 5); + REQUIRE(!stream.fail()); + REQUIRE(std::string(buf, 5) == "Hello"); + } + + SECTION("reports failure when reading past end") + { + SpanStream stream(data, size); + char buf[20]; + stream.read(buf, 20); + REQUIRE(stream.fail()); + } + + SECTION("tellg reports current position") + { + SpanStream stream(data, size); + REQUIRE(stream.tellg() == 0); + char buf[5]; + stream.read(buf, 5); + REQUIRE(stream.tellg() == 5); + } + + SECTION("seekg to absolute position") + { + SpanStream stream(data, size); + stream.seekg(7); + REQUIRE(!stream.fail()); + char buf[5]; + stream.read(buf, 5); + REQUIRE(!stream.fail()); + REQUIRE(std::string(buf, 5) == "world"); + } + + SECTION("seekg relative to current position") + { + SpanStream stream(data, size); + char buf[5]; + stream.read(buf, 5); + stream.seekg(2, std::ios_base::cur); + REQUIRE(!stream.fail()); + stream.read(buf, 5); + REQUIRE(!stream.fail()); + REQUIRE(std::string(buf, 5) == "world"); + } + + SECTION("seekg relative to end") + { + SpanStream stream(data, size); + stream.seekg(-6, std::ios_base::end); + REQUIRE(!stream.fail()); + char buf[5]; + stream.read(buf, 5); + REQUIRE(!stream.fail()); + REQUIRE(std::string(buf, 5) == "orld!"); + } + + SECTION("seekg to beginning after reading") + { + SpanStream stream(data, size); + char buf[5]; + stream.read(buf, 5); + stream.seekg(0); + REQUIRE(!stream.fail()); + stream.read(buf, 5); + REQUIRE(std::string(buf, 5) == "Hello"); + } + + SECTION("seekg past end fails") + { + SpanStream stream(data, size); + stream.seekg(100); + REQUIRE(stream.fail()); + } + + SECTION("seekg before beginning fails") + { + SpanStream stream(data, size); + stream.seekg(-1); + REQUIRE(stream.fail()); + } + + SECTION("works with binary data containing null bytes") + { + const char binData[] = {'\x00', '\x01', '\x02', '\x03', '\x04'}; + SpanStream stream(binData, 5); + stream.seekg(2); + char val; + stream.read(&val, 1); + REQUIRE(!stream.fail()); + REQUIRE(val == '\x02'); + } + } +} diff --git a/src/rwe/util/hash_combine.h b/src/rwe/util/hash_combine.h new file mode 100644 index 000000000..9dee56d67 --- /dev/null +++ b/src/rwe/util/hash_combine.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +namespace rwe +{ + // boost::hash_combine algorithm. The magic constant is the golden + // ratio's fixed-point representation (2^32 / phi), which spreads + // sequential inputs across the hash space to minimize collisions. + inline void hashCombine(std::size_t& seed, std::size_t value) + { + seed ^= value + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } +} diff --git a/src/rwe/util/png_write.h b/src/rwe/util/png_write.h new file mode 100644 index 000000000..cc498d40c --- /dev/null +++ b/src/rwe/util/png_write.h @@ -0,0 +1,154 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace rwe +{ + struct RgbPixel + { + uint8_t r; + uint8_t g; + uint8_t b; + }; + + class PngImage + { + public: + uint32_t width; + uint32_t height; + std::vector pixels; + + PngImage(uint32_t width, uint32_t height) + : width(width), height(height), pixels(width * height) + { + } + + RgbPixel& at(uint32_t x, uint32_t y) + { + return pixels[y * width + x]; + } + + void write(const std::string& filename) const + { + FILE* fp = fopen(filename.c_str(), "wb"); + if (!fp) + { + throw std::runtime_error("Failed to open file for writing: " + filename); + } + + png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png) + { + fclose(fp); + throw std::runtime_error("Failed to create PNG write struct"); + } + + png_infop info = png_create_info_struct(png); + if (!info) + { + png_destroy_write_struct(&png, nullptr); + fclose(fp); + throw std::runtime_error("Failed to create PNG info struct"); + } + + if (setjmp(png_jmpbuf(png))) + { + png_destroy_write_struct(&png, &info); + fclose(fp); + throw std::runtime_error("Error during PNG write"); + } + + png_init_io(png, fp); + writeRows(png, info); + png_destroy_write_struct(&png, &info); + fclose(fp); + } + + void writeStream(std::ostream& out) const + { + png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png) + { + throw std::runtime_error("Failed to create PNG write struct"); + } + + png_infop info = png_create_info_struct(png); + if (!info) + { + png_destroy_write_struct(&png, nullptr); + throw std::runtime_error("Failed to create PNG info struct"); + } + + if (setjmp(png_jmpbuf(png))) + { + png_destroy_write_struct(&png, &info); + throw std::runtime_error("Error during PNG write"); + } + + png_set_write_fn( + png, + &out, + [](png_structp pngPtr, png_bytep data, png_size_t length) { + auto* stream = static_cast(png_get_io_ptr(pngPtr)); + stream->write(reinterpret_cast(data), length); + }, + nullptr); + + writeRows(png, info); + png_destroy_write_struct(&png, &info); + } + + private: + void writeRows(png_structp png, png_infop info) const + { + png_set_IHDR( + png, + info, + width, + height, + 8, + PNG_COLOR_TYPE_RGB, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + png_write_info(png, info); + + for (uint32_t y = 0; y < height; ++y) + { + png_write_row(png, reinterpret_cast(const_cast(&pixels[y * width]))); + } + + png_write_end(png, nullptr); + } + }; + + inline void loadPalette(const std::string& filename, RgbPixel* buffer) + { + std::ifstream in(filename, std::ios::binary); + for (unsigned int i = 0; i < 256; ++i) + { + in.read(reinterpret_cast(&(buffer[i].r)), 1); + in.read(reinterpret_cast(&(buffer[i].g)), 1); + in.read(reinterpret_cast(&(buffer[i].b)), 1); + in.seekg(1, std::ios::cur); // skip alpha + } + } + + inline void loadPalette(std::istream& in, RgbPixel* buffer) + { + for (unsigned int i = 0; i < 256; ++i) + { + in.read(reinterpret_cast(&(buffer[i].r)), 1); + in.read(reinterpret_cast(&(buffer[i].g)), 1); + in.read(reinterpret_cast(&(buffer[i].b)), 1); + in.seekg(1, std::ios::cur); // skip alpha + } + } +} diff --git a/src/rwe/util/range_util.h b/src/rwe/util/range_util.h index 2e76600ba..15f716bca 100644 --- a/src/rwe/util/range_util.h +++ b/src/rwe/util/range_util.h @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace rwe { @@ -8,8 +8,8 @@ namespace rwe auto choose(Range r, Chooser c) { return r - | boost::adaptors::transformed(c) - | boost::adaptors::filtered([](const auto& e) { return e.has_value(); }) - | boost::adaptors::transformed([](const auto& e) { return *e; }); + | std::views::transform(c) + | std::views::filter([](const auto& e) { return e.has_value(); }) + | std::views::transform([](const auto& e) { return *e; }); } } diff --git a/src/rwe/util/rwe_string.test.cpp b/src/rwe/util/rwe_string.test.cpp index f92f61b33..635dcd3c3 100644 --- a/src/rwe/util/rwe_string.test.cpp +++ b/src/rwe/util/rwe_string.test.cpp @@ -1,4 +1,4 @@ -#include +#include #include diff --git a/src/rwe/vertex_height.cpp b/src/rwe/vertex_height.cpp index dba18b0f2..b2398b9f4 100644 --- a/src/rwe/vertex_height.cpp +++ b/src/rwe/vertex_height.cpp @@ -1,4 +1,5 @@ #include "vertex_height.h" +#include namespace rwe { diff --git a/src/rwe/vfs/CompositeVirtualFileSystem.cpp b/src/rwe/vfs/CompositeVirtualFileSystem.cpp index 67e9ba3ab..e5180e994 100644 --- a/src/rwe/vfs/CompositeVirtualFileSystem.cpp +++ b/src/rwe/vfs/CompositeVirtualFileSystem.cpp @@ -1,12 +1,12 @@ #include "CompositeVirtualFileSystem.h" -#include +#include #include #include #include #include #include -namespace fs = boost::filesystem; +namespace fs = std::filesystem; namespace rwe { @@ -144,7 +144,7 @@ namespace rwe } } - void addToVfs(CompositeVirtualFileSystem& vfs, const boost::filesystem::path& searchPath) + void addToVfs(CompositeVirtualFileSystem& vfs, const std::filesystem::path& searchPath) { std::vector hpiExtensions{".hpi", ".ufo", ".ccx", ".gpf", ".gp3"}; @@ -157,7 +157,7 @@ namespace rwe } } - CompositeVirtualFileSystem constructVfs(const boost::filesystem::path& searchPath) + CompositeVirtualFileSystem constructVfs(const std::filesystem::path& searchPath) { CompositeVirtualFileSystem vfs; addToVfs(vfs, searchPath); diff --git a/src/rwe/vfs/CompositeVirtualFileSystem.h b/src/rwe/vfs/CompositeVirtualFileSystem.h index d46a7d73c..b404f80d7 100644 --- a/src/rwe/vfs/CompositeVirtualFileSystem.h +++ b/src/rwe/vfs/CompositeVirtualFileSystem.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include #include @@ -38,6 +38,6 @@ namespace rwe }; - void addToVfs(CompositeVirtualFileSystem& vfs, const boost::filesystem::path& searchPath); - CompositeVirtualFileSystem constructVfs(const boost::filesystem::path& searchPath); + void addToVfs(CompositeVirtualFileSystem& vfs, const std::filesystem::path& searchPath); + CompositeVirtualFileSystem constructVfs(const std::filesystem::path& searchPath); } diff --git a/src/rwe/vfs/DirectoryFileSystem.cpp b/src/rwe/vfs/DirectoryFileSystem.cpp index b29b58fcc..4ea316c18 100644 --- a/src/rwe/vfs/DirectoryFileSystem.cpp +++ b/src/rwe/vfs/DirectoryFileSystem.cpp @@ -1,10 +1,11 @@ #include "DirectoryFileSystem.h" +#include #include #include #include -namespace fs = boost::filesystem; +namespace fs = std::filesystem; namespace rwe { @@ -20,8 +21,8 @@ namespace rwe * If we naively try to follow file references on a case-sensitive filesystem * we may fail to find the file we wanted. */ - std::optional findPathCaseInsensitive( - const boost::filesystem::path& root, const boost::filesystem::path& path) + std::optional findPathCaseInsensitive( + const std::filesystem::path& root, const std::filesystem::path& path) { fs::path basePath; @@ -52,7 +53,7 @@ namespace rwe { } - DirectoryFileSystem::DirectoryFileSystem(const boost::filesystem::path& path) + DirectoryFileSystem::DirectoryFileSystem(const std::filesystem::path& path) : path(path), pathString(this->path.string()) { } @@ -141,7 +142,7 @@ namespace rwe for (; it != end; ++it) { const auto& e = *it; - if (e.status().type() == fs::file_type::directory_file) + if (e.status().type() == fs::file_type::directory) { // recurse into directory auto innerDirectoryName = e.path().filename(); diff --git a/src/rwe/vfs/DirectoryFileSystem.h b/src/rwe/vfs/DirectoryFileSystem.h index 618369185..74bbf56e6 100644 --- a/src/rwe/vfs/DirectoryFileSystem.h +++ b/src/rwe/vfs/DirectoryFileSystem.h @@ -2,19 +2,19 @@ #include -#include +#include namespace rwe { class DirectoryFileSystem final : public LeafVirtualFileSystem { private: - boost::filesystem::path path; + std::filesystem::path path; std::string pathString; public: explicit DirectoryFileSystem(const std::string& path); - explicit DirectoryFileSystem(const boost::filesystem::path& path); + explicit DirectoryFileSystem(const std::filesystem::path& path); public: const std::string& getPath() const override; diff --git a/src/test.cpp b/src/test.cpp deleted file mode 100644 index 4ed06df1f..000000000 --- a/src/test.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#define CATCH_CONFIG_MAIN -#include diff --git a/src/texture_test.cpp b/src/texture_test.cpp index 71cf052c6..d1854063c 100644 --- a/src/texture_test.cpp +++ b/src/texture_test.cpp @@ -1,8 +1,7 @@ -#include -#include +#include #include #include -#include +#include #include #include #include @@ -26,7 +25,10 @@ namespace std { std::size_t operator()(const rwe::FrameId& f) const noexcept { - return boost::hash()(f); + std::size_t seed = 0; + rwe::hashCombine(seed, std::hash{}(f.first)); + rwe::hashCombine(seed, std::hash{}(f.second)); + return seed; } }; } @@ -113,7 +115,7 @@ namespace rwe throw std::runtime_error("File in listing could not be read: " + gafName); } - boost::interprocess::bufferstream stream(bytes->data(), bytes->size()); + rwe::SpanStream stream(bytes->data(), bytes->size()); GafArchive gaf(&stream); for (const auto& e : gaf.entries()) @@ -163,15 +165,15 @@ namespace rwe void dumpImage(const Grid& g, const std::string& outFile) { - auto width = static_cast(g.getWidth()); - auto height = static_cast(g.getHeight()); - png::image image(width, height); - for (png::uint_32 y = 0; y < height; ++y) + auto width = static_cast(g.getWidth()); + auto height = static_cast(g.getHeight()); + PngImage image(width, height); + for (uint32_t y = 0; y < height; ++y) { - for (png::uint_32 x = 0; x < width; ++x) + for (uint32_t x = 0; x < width; ++x) { Color px = g.get(x, y); - image[y][x] = png::rgb_pixel(px.r, px.g, px.b); + image.at(x, y) = RgbPixel{px.r, px.g, px.b}; } } diff --git a/src/tnt_test.cpp b/src/tnt_test.cpp index 5a3ee32d2..f43ec81dd 100644 --- a/src/tnt_test.cpp +++ b/src/tnt_test.cpp @@ -1,26 +1,13 @@ -#include +#include #include #include #include -#include #include #include +#include #include -namespace fs = boost::filesystem; - -void loadPalette(const std::string& filename, png::rgb_pixel* buffer) -{ - std::ifstream in(filename, std::ios::binary); - - for (unsigned int i = 0; i < 256; ++i) - { - in.read(reinterpret_cast(&(buffer[i].red)), 1); - in.read(reinterpret_cast(&(buffer[i].green)), 1); - in.read(reinterpret_cast(&(buffer[i].blue)), 1); - in.seekg(1, std::ios::cur); // skip alpha - } -} +namespace fs = std::filesystem; int featuresCommand(const std::string& tntPath) { @@ -42,8 +29,8 @@ int featuresCommand(const std::string& tntPath) int tilesCommand(const std::string& palettePath, const std::string& tntPath, const std::string& outputPath) { - png::rgb_pixel palette[256]; - loadPalette(palettePath, palette); + rwe::RgbPixel palette[256]; + rwe::loadPalette(palettePath, palette); std::ifstream file(tntPath, std::ios::binary); if (!file.is_open()) @@ -58,15 +45,13 @@ int tilesCommand(const std::string& palettePath, const std::string& tntPath, con int i = 0; tnt.readTiles([&palette, &i, &outPath](const char* tileData) { - png::image image(32, 32); - for (png::uint_32 y = 0; y < image.get_height(); ++y) + rwe::PngImage image(32, 32); + for (uint32_t y = 0; y < 32; ++y) { - for (png::uint_32 x = 0; x < image.get_width(); ++x) + for (uint32_t x = 0; x < 32; ++x) { auto b = static_cast(tileData[(y * 32) + x]); - assert(b >= 0 && b < 256); - auto px = palette[b]; - image[y][x] = px; + image.at(x, y) = palette[b]; } } @@ -102,8 +87,8 @@ int infoCommand(const std::string& tntPath) int minimapCommand(const std::string& palettePath, const std::string& tntPath, const std::string& outputPath) { - png::rgb_pixel palette[256]; - loadPalette(palettePath, palette); + rwe::RgbPixel palette[256]; + rwe::loadPalette(palettePath, palette); std::ifstream file(tntPath, std::ios::binary); if (!file.is_open()) @@ -115,15 +100,13 @@ int minimapCommand(const std::string& palettePath, const std::string& tntPath, c rwe::TntArchive tnt(&file); auto minimap = tnt.readMinimap(); - png::image image(minimap.width, minimap.height); - for (png::uint_32 y = 0; y < image.get_height(); ++y) + rwe::PngImage image(minimap.width, minimap.height); + for (uint32_t y = 0; y < minimap.height; ++y) { - for (png::uint_32 x = 0; x < image.get_width(); ++x) + for (uint32_t x = 0; x < minimap.width; ++x) { auto b = static_cast(minimap.data[(y * minimap.width) + x]); - assert(b >= 0 && b < 256); - auto px = palette[b]; - image[y][x] = px; + image.at(x, y) = palette[b]; } }