diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 5d66d0fd4..4314436e5 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -7,7 +7,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install tar curl zip unzip bash-completion build-essential ripgrep htop \ ninja-build ccache g++ mold gdb clang-format clang-tidy \ rpm pkg-config cmake git gettext libsdl2-dev libsdl2-image-dev libsodium-dev \ - libpng-dev libbz2-dev libfmt-dev libgtest-dev libgmock-dev libbenchmark-dev zsh \ + libpng-dev libbz2-dev libgtest-dev libgmock-dev libbenchmark-dev zsh \ qtbase5-dev qt6-base-dev ristretto \ && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/lists/* diff --git a/.github/workflows/Android.yml b/.github/workflows/Android.yml index 72339cc40..ffc85584c 100644 --- a/.github/workflows/Android.yml +++ b/.github/workflows/Android.yml @@ -44,8 +44,8 @@ jobs: uses: actions/cache@v6 with: path: android-project/app/.cxx - key: ${{ github.workflow }}-v5-${{ github.sha }} - restore-keys: ${{ github.workflow }}-v5- + key: ${{ github.workflow }}-v6-${{ github.sha }} + restore-keys: ${{ github.workflow }}-v6- - name: Build working-directory: ${{github.workspace}} diff --git a/.github/workflows/Linux_aarch64.yml b/.github/workflows/Linux_aarch64.yml index c29817fab..8cf4aed61 100644 --- a/.github/workflows/Linux_aarch64.yml +++ b/.github/workflows/Linux_aarch64.yml @@ -40,20 +40,19 @@ jobs: uses: actions/cache@v6 with: path: build - key: ${{ github.workflow }}-v8-${{ github.sha }} - restore-keys: ${{ github.workflow }}-v8- + key: ${{ github.workflow }}-v9-${{ github.sha }} + restore-keys: ${{ github.workflow }}-v9- - name: Build working-directory: ${{github.workspace}} shell: bash env: CMAKE_BUILD_TYPE: ${{github.event_name == 'release' && 'Release' || 'RelWithDebInfo'}} - # We set DEVILUTIONX_SYSTEM_LIBFMT=OFF because its soversion changes frequently. # We set DEVILUTIONX_SYSTEM_LIBSODIUM=OFF because its soversion changes frequently. # We set DEVILUTIONX_SYSTEM_BZIP2=OFF because Fedora and Debian do not agree on how to link it. run: | cmake -S. -Bbuild -DCMAKE_TOOLCHAIN_FILE=../CMake/platforms/aarch64-linux-gnu.toolchain.cmake \ - -DCMAKE_BUILD_TYPE=${{env.CMAKE_BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=/usr -DCPACK=ON -DDEVILUTIONX_SYSTEM_LIBFMT=OFF \ + -DCMAKE_BUILD_TYPE=${{env.CMAKE_BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=/usr -DCPACK=ON \ -DDEVILUTIONX_SYSTEM_LIBSODIUM=OFF -DDEVILUTIONX_SYSTEM_BZIP2=OFF && \ cmake --build build -j $(getconf _NPROCESSORS_ONLN) --target package diff --git a/.github/workflows/Linux_x86.yml b/.github/workflows/Linux_x86.yml index b71307b8c..c2126a5e2 100644 --- a/.github/workflows/Linux_x86.yml +++ b/.github/workflows/Linux_x86.yml @@ -40,21 +40,20 @@ jobs: uses: actions/cache@v6 with: path: build - key: ${{ github.workflow }}-v7-${{ github.sha }} - restore-keys: ${{ github.workflow }}-v7- + key: ${{ github.workflow }}-v8-${{ github.sha }} + restore-keys: ${{ github.workflow }}-v8- - name: Build working-directory: ${{github.workspace}} shell: bash env: CMAKE_BUILD_TYPE: ${{github.event_name == 'release' && 'Release' || 'RelWithDebInfo'}} - # We set DEVILUTIONX_SYSTEM_LIBFMT=OFF because its soversion changes frequently. # We set DEVILUTIONX_SYSTEM_LIBSODIUM=OFF because its soversion changes frequently. # We set DEVILUTIONX_SYSTEM_BZIP2=OFF because Fedora and Debian do not agree on how to link it. run: | cmake -S. -Bbuild -DCMAKE_TOOLCHAIN_FILE=../CMake/platforms/linux_i386.toolchain.cmake \ -DCMAKE_BUILD_TYPE=${{env.CMAKE_BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=/usr -DCPACK=ON \ - -DBUILD_TESTING=OFF -DDEVILUTIONX_SYSTEM_LIBFMT=OFF -DDEVILUTIONX_SYSTEM_LIBSODIUM=OFF \ + -DBUILD_TESTING=OFF -DDEVILUTIONX_SYSTEM_LIBSODIUM=OFF \ -DDEVILUTIONX_SYSTEM_BZIP2=OFF && \ cmake --build build -j $(getconf _NPROCESSORS_ONLN) --target package diff --git a/.github/workflows/Linux_x86_64.yml b/.github/workflows/Linux_x86_64.yml index 5bdbe7f37..35deca520 100644 --- a/.github/workflows/Linux_x86_64.yml +++ b/.github/workflows/Linux_x86_64.yml @@ -40,20 +40,19 @@ jobs: uses: actions/cache@v6 with: path: build - key: ${{ github.workflow }}-v7-${{ github.sha }} - restore-keys: ${{ github.workflow }}-v7- + key: ${{ github.workflow }}-v8-${{ github.sha }} + restore-keys: ${{ github.workflow }}-v8- - name: Build working-directory: ${{github.workspace}} shell: bash env: CMAKE_BUILD_TYPE: ${{github.event_name == 'release' && 'Release' || 'RelWithDebInfo'}} - # We set DEVILUTIONX_SYSTEM_LIBFMT=OFF because its soversion changes frequently. # We set DEVILUTIONX_SYSTEM_LIBSODIUM=OFF because its soversion changes frequently. # We set DEVILUTIONX_SYSTEM_BZIP2=OFF because Fedora and Debian do not agree on how to link it. run: | cmake -S. -Bbuild -DCMAKE_BUILD_TYPE=${{env.CMAKE_BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=/usr -DCPACK=ON \ - -DDISCORD_INTEGRATION=ON -DBUILD_TESTING=OFF -DDEVILUTIONX_SYSTEM_LIBFMT=OFF \ + -DDISCORD_INTEGRATION=ON -DBUILD_TESTING=OFF \ -DDEVILUTIONX_SYSTEM_LIBSODIUM=OFF -DDEVILUTIONX_SYSTEM_BZIP2=OFF && \ cmake --build build -j $(getconf _NPROCESSORS_ONLN) --target package diff --git a/.github/workflows/Linux_x86_64_SDL1.yml b/.github/workflows/Linux_x86_64_SDL1.yml index 451378af9..eb06aeb16 100644 --- a/.github/workflows/Linux_x86_64_SDL1.yml +++ b/.github/workflows/Linux_x86_64_SDL1.yml @@ -29,14 +29,14 @@ jobs: - name: Create Build Environment run: > sudo apt-get update && - sudo apt-get install -y cmake file g++ gettext git libfmt-dev libsdl1.2-dev libsodium-dev libpng-dev libbz2-dev rpm smpq + sudo apt-get install -y cmake file g++ gettext git libsdl1.2-dev libsodium-dev libpng-dev libbz2-dev rpm smpq - name: Cache CMake build folder uses: actions/cache@v6 with: path: build - key: ${{ github.workflow }}-v3-${{ github.sha }} - restore-keys: ${{ github.workflow }}-v3- + key: ${{ github.workflow }}-v4-${{ github.sha }} + restore-keys: ${{ github.workflow }}-v4- - name: Configure CMake shell: bash diff --git a/.github/workflows/Linux_x86_64_SDL3_test.yml b/.github/workflows/Linux_x86_64_SDL3_test.yml index 1c11b7947..052e67656 100644 --- a/.github/workflows/Linux_x86_64_SDL3_test.yml +++ b/.github/workflows/Linux_x86_64_SDL3_test.yml @@ -30,14 +30,14 @@ jobs: - name: Install dependencies run: | sudo apt-get update -y - sudo apt-get install -y cmake curl g++ git libgtest-dev libgmock-dev libbenchmark-dev libfmt-dev libsodium-dev libpng-dev libbz2-dev libasound2-dev libxcursor-dev libxi-dev libxrandr-dev libxss-dev libxtst-dev libxkbcommon-dev wget + sudo apt-get install -y cmake curl g++ git libgtest-dev libgmock-dev libbenchmark-dev libsodium-dev libpng-dev libbz2-dev libasound2-dev libxcursor-dev libxi-dev libxrandr-dev libxss-dev libxtst-dev libxkbcommon-dev wget - name: Cache CMake build folder uses: actions/cache@v6 with: path: build - key: ${{ github.workflow }}-v4-${{ github.sha }} - restore-keys: ${{ github.workflow }}-v4- + key: ${{ github.workflow }}-v5-${{ github.sha }} + restore-keys: ${{ github.workflow }}-v5- # We specify `-DDEVILUTIONX_SYSTEM_BENCHMARK=OFF` to work around the following error: # lto1: fatal error: bytecode stream in file ‘/usr/lib/x86_64-linux-gnu/libbenchmark_main.a’ generated with LTO version 11.2 instead of the expected 11.3 diff --git a/.github/workflows/Linux_x86_64_test.yml b/.github/workflows/Linux_x86_64_test.yml index 2b41197a4..1159967d0 100644 --- a/.github/workflows/Linux_x86_64_test.yml +++ b/.github/workflows/Linux_x86_64_test.yml @@ -30,14 +30,14 @@ jobs: - name: Install dependencies run: | sudo apt-get update -y - sudo apt-get install -y cmake curl g++ git lcov libgtest-dev libgmock-dev libbenchmark-dev libfmt-dev libsdl2-dev libsodium-dev libpng-dev libbz2-dev wget + sudo apt-get install -y cmake curl g++ git lcov libgtest-dev libgmock-dev libbenchmark-dev libsdl2-dev libsodium-dev libpng-dev libbz2-dev wget - name: Cache CMake build folder uses: actions/cache@v6 with: path: build - key: ${{ github.workflow }}-v3-${{ github.sha }} - restore-keys: ${{ github.workflow }}-v3- + key: ${{ github.workflow }}-v4-${{ github.sha }} + restore-keys: ${{ github.workflow }}-v4- # We specify `-DDEVILUTIONX_SYSTEM_BENCHMARK=OFF` to work around the following error: # lto1: fatal error: bytecode stream in file ‘/usr/lib/x86_64-linux-gnu/libbenchmark_main.a’ generated with LTO version 11.2 instead of the expected 11.3 diff --git a/.github/workflows/Windows_MSVC_x64.yml b/.github/workflows/Windows_MSVC_x64.yml index c8461e7ec..4374d83ea 100644 --- a/.github/workflows/Windows_MSVC_x64.yml +++ b/.github/workflows/Windows_MSVC_x64.yml @@ -67,7 +67,6 @@ jobs: path: | build-ninja-vcpkg-relwithdebinfo/devilutionx.exe build-ninja-vcpkg-relwithdebinfo/bz2.dll - build-ninja-vcpkg-relwithdebinfo/fmt.dll build-ninja-vcpkg-relwithdebinfo/libpng16.dll build-ninja-vcpkg-relwithdebinfo/libsodium.dll build-ninja-vcpkg-relwithdebinfo/SDL2.dll diff --git a/.github/workflows/clang-tidy-check.yml b/.github/workflows/clang-tidy-check.yml index 9391ee92f..cd50ed79a 100644 --- a/.github/workflows/clang-tidy-check.yml +++ b/.github/workflows/clang-tidy-check.yml @@ -25,7 +25,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update -y - sudo apt-get install -y cmake curl g++ git libgtest-dev libgmock-dev libbenchmark-dev libfmt-dev libsdl2-dev libsodium-dev libpng-dev libbz2-dev wget + sudo apt-get install -y cmake curl g++ git libgtest-dev libgmock-dev libbenchmark-dev libsdl2-dev libsodium-dev libpng-dev libbz2-dev wget - name: CMake Configure run: cmake -S. -Bbuild diff --git a/.github/workflows/iOS.yml b/.github/workflows/iOS.yml index 9043de453..94838396f 100644 --- a/.github/workflows/iOS.yml +++ b/.github/workflows/iOS.yml @@ -39,8 +39,8 @@ jobs: uses: actions/cache@v6 with: path: build - key: ${{ github.workflow }}-v5-${{ github.sha }} - restore-keys: ${{ github.workflow }}-v5- + key: ${{ github.workflow }}-v6-${{ github.sha }} + restore-keys: ${{ github.workflow }}-v6- - name: Configure CMake # Use a bash shell so we can use the same syntax for environment variable diff --git a/.github/workflows/macOS_arm64.yml b/.github/workflows/macOS_arm64.yml index 0cb79237b..c08986c1f 100644 --- a/.github/workflows/macOS_arm64.yml +++ b/.github/workflows/macOS_arm64.yml @@ -38,8 +38,8 @@ jobs: uses: actions/cache@v6 with: path: build - key: ${{ github.workflow }}-v2-${{ github.sha }} - restore-keys: ${{ github.workflow }}-v2- + key: ${{ github.workflow }}-v3-${{ github.sha }} + restore-keys: ${{ github.workflow }}-v3- - name: Clean previous DMG working-directory: ${{github.workspace}} diff --git a/.github/workflows/macOS_x86_64.yml b/.github/workflows/macOS_x86_64.yml index 8561a7efa..a2399ae21 100644 --- a/.github/workflows/macOS_x86_64.yml +++ b/.github/workflows/macOS_x86_64.yml @@ -38,8 +38,8 @@ jobs: uses: actions/cache@v6 with: path: build - key: ${{ github.workflow }}-v4-${{ github.sha }} - restore-keys: ${{ github.workflow }}-v4- + key: ${{ github.workflow }}-v5-${{ github.sha }} + restore-keys: ${{ github.workflow }}-v5- - name: Clean previous DMG working-directory: ${{github.workspace}} diff --git a/.github/workflows/s390x_qemu_big_endian_tests.yml b/.github/workflows/s390x_qemu_big_endian_tests.yml index a07de35e3..1bb4ef142 100644 --- a/.github/workflows/s390x_qemu_big_endian_tests.yml +++ b/.github/workflows/s390x_qemu_big_endian_tests.yml @@ -36,7 +36,7 @@ jobs: run: > docker run --platform linux/s390x --rm --interactive --mount type=bind,source=$(pwd),target=/host s390x/alpine sh -c " - apk add --update-cache g++ ninja cmake ccache sdl2-dev sdl2_image-dev fmt-dev libpng-dev jpeg-dev bzip2-dev gtest-dev wget && + apk add --update-cache g++ ninja cmake ccache sdl2-dev sdl2_image-dev libpng-dev jpeg-dev bzip2-dev gtest-dev wget && cd /host && export CCACHE_DIR=/host/.ccache && cmake -S. -Bbuild -G Ninja -DNONET=ON -DNOSOUND=ON && diff --git a/.github/workflows/src_dist_release.yml b/.github/workflows/src_dist_release.yml index e3be1abae..a5a1e4cdd 100644 --- a/.github/workflows/src_dist_release.yml +++ b/.github/workflows/src_dist_release.yml @@ -24,7 +24,7 @@ jobs: - name: Create Build Environment run: > sudo apt-get update && - sudo apt-get install -y cmake curl libsdl2-dev libsdl2-image-dev libfmt-dev libsodium-dev libbz2-dev libgtest-dev libgmock-dev libbenchmark-dev git smpq gettext python-is-python3 + sudo apt-get install -y cmake curl libsdl2-dev libsdl2-image-dev libsodium-dev libbz2-dev libgtest-dev libgmock-dev libbenchmark-dev git smpq gettext python-is-python3 - name: Build working-directory: ${{github.workspace}} @@ -55,7 +55,7 @@ jobs: - name: Create Build Environment run: > sudo apt-get update && - sudo apt-get install -y cmake curl libsdl2-dev libsdl2-image-dev libfmt-dev libsodium-dev libbz2-dev libgtest-dev libgmock-dev libbenchmark-dev git smpq gettext python-is-python3 + sudo apt-get install -y cmake curl libsdl2-dev libsdl2-image-dev libsodium-dev libbz2-dev libgtest-dev libgmock-dev libbenchmark-dev git smpq gettext python-is-python3 - name: Build working-directory: ${{github.workspace}} diff --git a/3rdParty/SDL_audiolib/0001-aulib_log.h-Include-missing-fmt-format.h.patch b/3rdParty/SDL_audiolib/0001-aulib_log.h-Include-missing-fmt-format.h.patch deleted file mode 100644 index b56d20ada..000000000 --- a/3rdParty/SDL_audiolib/0001-aulib_log.h-Include-missing-fmt-format.h.patch +++ /dev/null @@ -1,25 +0,0 @@ -From e89e20a71e824a182c1ba519a9f4004686ed2433 Mon Sep 17 00:00:00 2001 -From: Gleb Mazovetskiy -Date: Sun, 28 Jun 2026 22:50:06 +0100 -Subject: [PATCH] aulib_log.h: Include missing - -This include was missing and this started causing an error recently in our CI ---- - src/aulib_log.h | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/src/aulib_log.h b/src/aulib_log.h -index b87ee7d..bfd8652 100644 ---- a/src/aulib_log.h -+++ b/src/aulib_log.h -@@ -2,6 +2,7 @@ - #pragma once - #include "aulib_config.h" - #include -+#include - - namespace aulib { - namespace log { --- -2.53.0 - diff --git a/3rdParty/SDL_audiolib/CMakeLists.txt b/3rdParty/SDL_audiolib/CMakeLists.txt index 54f31629b..b4c0d228b 100644 --- a/3rdParty/SDL_audiolib/CMakeLists.txt +++ b/3rdParty/SDL_audiolib/CMakeLists.txt @@ -6,9 +6,6 @@ else() set(BUILD_SHARED_LIBS ON) endif() -# Will use our `fmt::fmt` target if it exists. -set(WITH_SYSTEM_FMTLIB ON) - # No need for the libsamplerate resampler: set(USE_RESAMP_SRC OFF) @@ -36,13 +33,23 @@ set(USE_DEC_WILDMIDI OFF) set(USE_DEC_ADLMIDI OFF) set(USE_DEC_DRMP3 ON) -find_package(Patch REQUIRED) +# SDL_audiolib only uses fmt in aulib_log.h, which we replace with a +# std::format version below. Pretend that fmt is already provided so that +# SDL_audiolib does not require or build it (linking the empty INTERFACE +# target is a no-op). +set(WITH_SYSTEM_FMTLIB ON) +if(NOT TARGET fmt::fmt) + add_library(devilutionx_fmt_stub INTERFACE) + add_library(fmt::fmt ALIAS devilutionx_fmt_stub) +endif() include(FetchContent) FetchContent_Declare_ExcludeFromAll(SDL_audiolib URL https://github.com/realnc/SDL_audiolib/archive/cc1bb6af8d4cf5e200259072bde1edd1c8c5137e.tar.gz URL_HASH MD5=0e8174264ac9c6b314c6b2d9a5f72efd - PATCH_COMMAND "${Patch_EXECUTABLE}" -p1 -N < "${CMAKE_CURRENT_LIST_DIR}/0001-aulib_log.h-Include-missing-fmt-format.h.patch" || true + # A file replacement instead of a diff: unlike the `patch` tool, this is + # idempotent and works the same on every host platform. + PATCH_COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${CMAKE_CURRENT_LIST_DIR}/aulib_log.h" /src/aulib_log.h ) FetchContent_MakeAvailable_ExcludeFromAll(SDL_audiolib) diff --git a/3rdParty/SDL_audiolib/aulib_log.h b/3rdParty/SDL_audiolib/aulib_log.h new file mode 100644 index 000000000..40cdd556a --- /dev/null +++ b/3rdParty/SDL_audiolib/aulib_log.h @@ -0,0 +1,76 @@ +// This is copyrighted software. More information is at the end of this file. +#pragma once +#include "aulib_config.h" +#include +#include +#include +#include + +namespace aulib { +namespace log { + +template +void debug([[maybe_unused]] std::format_string fmt_str, + [[maybe_unused]] Args &&...args) { +#if AULIB_DEBUG + const std::string message = std::format(fmt_str, std::forward(args)...); + std::fprintf(stderr, "SDL_audiolib debug: %s", message.c_str()); +#endif +} + +template +void debugLn([[maybe_unused]] std::format_string fmt_str, + [[maybe_unused]] Args &&...args) { +#if AULIB_DEBUG + const std::string message = std::format(fmt_str, std::forward(args)...); + std::fprintf(stderr, "SDL_audiolib debug: %s\n", message.c_str()); +#endif +} + +template +void warn(std::format_string fmt_str, Args &&...args) { + const std::string message = std::format(fmt_str, std::forward(args)...); + std::fprintf(stderr, "SDL_audiolib warning: %s", message.c_str()); +} + +template +void warnLn(std::format_string fmt_str, Args &&...args) { + const std::string message = std::format(fmt_str, std::forward(args)...); + std::fprintf(stderr, "SDL_audiolib warning: %s\n", message.c_str()); +} + +template +void info(std::format_string fmt_str, Args &&...args) { + const std::string message = std::format(fmt_str, std::forward(args)...); + std::printf("SDL_audiolib info: %s", message.c_str()); +} + +template +void infoLn(std::format_string fmt_str, Args &&...args) { + const std::string message = std::format(fmt_str, std::forward(args)...); + std::printf("SDL_audiolib info: %s\n", message.c_str()); +} + +} // namespace log +} // namespace aulib + +/* + +Copyright (C) 2022 Nikos Chantziaras. + +This file is part of SDL_audiolib. + +SDL_audiolib is free software: you can redistribute it and/or modify it under +the terms of the GNU Lesser General Public License as published by the Free +Software Foundation, either version 3 of the License, or (at your option) any +later version. + +SDL_audiolib is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. + +You should have received a copy of the GNU Lesser General Public License +along with SDL_audiolib. If not, see . + +*/ diff --git a/3rdParty/libfmt/CMakeLists.txt b/3rdParty/libfmt/CMakeLists.txt deleted file mode 100644 index 2f5be03b5..000000000 --- a/3rdParty/libfmt/CMakeLists.txt +++ /dev/null @@ -1,39 +0,0 @@ -include(functions/FetchContent_ExcludeFromAll_backport) - -if(NOT WIN32 AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} STREQUAL FreeBSD) - # Enable POSIX extensions such as `readlink` and `ftruncate`. - add_definitions(-D_POSIX_C_SOURCE=200809L) -endif() - -# Disable fmt/os.h functionality. -# We do not use it and it is not supported on some systems. -set(FMT_OS OFF) - -if(DEVILUTIONX_STATIC_LIBFMT) - set(BUILD_SHARED_LIBS OFF) -else() - set(BUILD_SHARED_LIBS ON) -endif() -include(FetchContent) -FetchContent_Declare_ExcludeFromAll(libfmt - URL https://github.com/fmtlib/fmt/releases/download/12.0.0/fmt-12.0.0.zip - URL_HASH SHA256=1c32293203449792bf8e94c7f6699c643887e826f2d66a80869b4f279fb07d25 -) -FetchContent_MakeAvailable_ExcludeFromAll(libfmt) - -# We do not use locale-specific features of libfmt and disabling them reduces the size. -target_compile_definitions(fmt PUBLIC FMT_USE_LOCALE=0) - -if(DEVILUTIONX_WINDOWS_NO_WCHAR) - target_compile_definitions(fmt PUBLIC FMT_USE_WRITE_CONSOLE) -endif() - -# Reduces the overall binary size by 8 KiB. -if(TARGET_PLATFORM STREQUAL "rg99") - target_compile_definitions(fmt PUBLIC FMT_BUILTIN_TYPES=0) -endif() - -# https://github.com/fmtlib/fmt/issues/4189 -if(NINTENDO_3DS OR NINTENDO_SWITCH OR VITA) - target_compile_definitions(fmt PUBLIC FMT_USE_FALLBACK_FILE=1) -endif() diff --git a/Brewfile b/Brewfile index d2b950ad7..2f4dd0192 100644 --- a/Brewfile +++ b/Brewfile @@ -1,5 +1,4 @@ brew "cmake" -brew "fmt" brew "sdl2" brew "libsodium" brew "pkg-config" diff --git a/CMake/Dependencies.cmake b/CMake/Dependencies.cmake index c2d2629ed..2488f2c65 100644 --- a/CMake/Dependencies.cmake +++ b/CMake/Dependencies.cmake @@ -179,22 +179,6 @@ else() endif() endif() -if(NOT DEFINED DEVILUTIONX_SYSTEM_LIBFMT) - find_package(fmt 8.0.0 QUIET) - if(fmt_FOUND) - message("-- Found fmt ${fmt_VERSION}") - else() - message("-- Suitable system fmt package not found, will use fmt from source") - set(DEVILUTIONX_SYSTEM_LIBFMT OFF) - endif() -endif() -dependency_options("libfmt" DEVILUTIONX_SYSTEM_LIBFMT ON DEVILUTIONX_STATIC_LIBFMT) -if(DEVILUTIONX_SYSTEM_LIBFMT) - find_package(fmt 8.0.0 REQUIRED) -else() - add_subdirectory(3rdParty/libfmt) -endif() - if(NOT NOSOUND) if(USE_SDL3) if(NOT DEFINED DEVILUTIONX_SYSTEM_SDL_MIXER) diff --git a/CMake/Platforms.cmake b/CMake/Platforms.cmake index 30b5e9400..3bb3718be 100644 --- a/CMake/Platforms.cmake +++ b/CMake/Platforms.cmake @@ -106,8 +106,5 @@ if(CMAKE_SYSTEM_NAME MATCHES "Darwin") # For older macOS, we assume MacPorts because Homebrew only supports newer version if(DARWIN_MAJOR_VERSION VERSION_LESS 11) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/platforms/macports/finders") - - # On MacPorts, libfmt is in a subdirectory: - list(APPEND CMAKE_MODULE_PATH "/opt/local/lib/libfmt11/cmake") endif() endif() diff --git a/CMake/Tests.cmake b/CMake/Tests.cmake index c06cec27c..e0fc44727 100644 --- a/CMake/Tests.cmake +++ b/CMake/Tests.cmake @@ -163,7 +163,6 @@ if(DEVILUTIONX_SCREENSHOT_FORMAT STREQUAL DEVILUTIONX_SCREENSHOT_FORMAT_PNG AND DevilutionX::SDL GTest::gmock GTest::gtest - fmt::fmt tl app_fatal_for_testing language_for_testing diff --git a/CMake/platforms/android.cmake b/CMake/platforms/android.cmake index afc88313d..0bff490eb 100644 --- a/CMake/platforms/android.cmake +++ b/CMake/platforms/android.cmake @@ -18,7 +18,6 @@ set(DEVILUTIONX_SYSTEM_SDL_IMAGE OFF) set(DEVILUTIONX_SYSTEM_SDL_AUDIOLIB OFF) set(DEVILUTIONX_SYSTEM_LIBSODIUM OFF) set(DEVILUTIONX_SYSTEM_LIBPNG OFF) -set(DEVILUTIONX_SYSTEM_LIBFMT OFF) set(DEVILUTIONX_SYSTEM_BZIP2 OFF) # Package the assets with the APK. diff --git a/CMake/platforms/dos.cmake b/CMake/platforms/dos.cmake index 86ea132b6..0971c3fcb 100644 --- a/CMake/platforms/dos.cmake +++ b/CMake/platforms/dos.cmake @@ -7,7 +7,6 @@ set(PACKET_ENCRYPTION OFF) set(DEFAULT_PER_PIXEL_LIGHTING false) set(DEVILUTIONX_SYSTEM_BZIP2 OFF) -set(DEVILUTIONX_SYSTEM_LIBFMT OFF) set(DEVILUTIONX_SYSTEM_ZLIB OFF) set(DEVILUTIONX_STATIC_ZLIB ON) set(USE_SDL3 ON) diff --git a/CMake/platforms/emscripten.cmake b/CMake/platforms/emscripten.cmake index 0e2c1378b..75e8e848e 100644 --- a/CMake/platforms/emscripten.cmake +++ b/CMake/platforms/emscripten.cmake @@ -4,7 +4,6 @@ set(DISABLE_ZERO_TIER ON) set(DISABLE_TCP ON) set(DEVILUTIONX_SYSTEM_SDL_AUDIOLIB OFF) set(DEVILUTIONX_SYSTEM_LIBSODIUM OFF) -set(DEVILUTIONX_SYSTEM_LIBFMT OFF) set(NOEXIT ON) # Emscripten ports do have a bzip2 but it fails to link with this error: # warning: _BZ2_bzDecompress may need to be added to EXPORTED_FUNCTIONS if it arrives from a system library diff --git a/CMake/platforms/ios.cmake b/CMake/platforms/ios.cmake index 08e967d79..caea3efb8 100644 --- a/CMake/platforms/ios.cmake +++ b/CMake/platforms/ios.cmake @@ -10,7 +10,6 @@ set(DEVILUTIONX_SYSTEM_SDL_IMAGE OFF) set(DEVILUTIONX_SYSTEM_SDL_AUDIOLIB OFF) set(DEVILUTIONX_SYSTEM_LIBSODIUM OFF) set(DEVILUTIONX_SYSTEM_LIBPNG OFF) -set(DEVILUTIONX_SYSTEM_LIBFMT OFF) set(NOEXIT ON) diff --git a/CMake/platforms/macos_tiger.cmake b/CMake/platforms/macos_tiger.cmake index 2367ac5a5..01b13e9e4 100644 --- a/CMake/platforms/macos_tiger.cmake +++ b/CMake/platforms/macos_tiger.cmake @@ -8,11 +8,6 @@ set(USE_SDL1 ON) # ZeroTier is yet to be tested. set(DISABLE_ZERO_TIER ON) -# Use vendored libfmt until this issue is resolved: -# https://trac.macports.org/ticket/71503 -set(DEVILUTIONX_SYSTEM_LIBFMT OFF) -set(DEVILUTIONX_STATIC_LIBFMT ON) - # https://trac.macports.org/ticket/71511 set(DEVILUTIONX_SYSTEM_GOOGLETEST OFF) set(DEVILUTIONX_STATIC_GOOGLETEST OFF) diff --git a/CMake/platforms/miyoo_mini.cmake b/CMake/platforms/miyoo_mini.cmake index 95924fa49..3071c7fe4 100644 --- a/CMake/platforms/miyoo_mini.cmake +++ b/CMake/platforms/miyoo_mini.cmake @@ -1,6 +1,5 @@ set(USE_SDL1 ON) set(NONET ON) -set(DEVILUTIONX_SYSTEM_LIBFMT OFF) set(DEVILUTIONX_SYSTEM_LIBSODIUM OFF) set(DEVILUTIONX_SYSTEM_BZIP2 OFF) diff --git a/CMake/platforms/n3ds.cmake b/CMake/platforms/n3ds.cmake index 0125be551..adbf91b78 100644 --- a/CMake/platforms/n3ds.cmake +++ b/CMake/platforms/n3ds.cmake @@ -4,9 +4,7 @@ set(UBSAN OFF) set(BUILD_TESTING OFF) set(BUILD_ASSETS_MPQ OFF) set(DEVILUTIONX_SYSTEM_LIBSODIUM OFF) -set(DEVILUTIONX_SYSTEM_LIBFMT OFF) set(DEVILUTIONX_STATIC_LIBSODIUM ON) -set(DEVILUTIONX_STATIC_LIBFMT ON) set(DISABLE_ZERO_TIER ON) set(MPQFS_FILE_BUFFER_SIZE 32768) set(NOEXIT ON) diff --git a/CMake/platforms/uwp_lib.cmake b/CMake/platforms/uwp_lib.cmake index a9824b7f1..d54afb24d 100644 --- a/CMake/platforms/uwp_lib.cmake +++ b/CMake/platforms/uwp_lib.cmake @@ -11,7 +11,6 @@ set(DEVILUTIONX_SYSTEM_SDL_IMAGE OFF) set(DEVILUTIONX_SYSTEM_SDL_AUDIOLIB OFF) set(DEVILUTIONX_SYSTEM_LIBSODIUM OFF) set(DEVILUTIONX_SYSTEM_LIBPNG OFF) -set(DEVILUTIONX_SYSTEM_LIBFMT OFF) set(DEVILUTIONX_SYSTEM_BZIP2 OFF) set(DEVILUTIONX_SYSTEM_ZLIB OFF) @@ -20,7 +19,6 @@ set(DEVILUTIONX_STATIC_SDL_IMAGE ON) set(DEVILUTIONX_STATIC_SDL_AUDIOLIB ON) set(DEVILUTIONX_STATIC_LIBSODIUM ON) set(DEVILUTIONX_STATIC_LIBPNG ON) -set(DEVILUTIONX_STATIC_LIBFMT ON) set(DEVILUTIONX_STATIC_BZIP2 ON) set(DEVILUTIONX_STATIC_ZLIB ON) diff --git a/CMake/platforms/windows9x.cmake b/CMake/platforms/windows9x.cmake index d38cb8087..8ff4cca10 100644 --- a/CMake/platforms/windows9x.cmake +++ b/CMake/platforms/windows9x.cmake @@ -6,7 +6,6 @@ set(NONET ON) set(DISABLE_ZERO_TIER ON) set(USE_SDL1 ON) set(DEVILUTIONX_SYSTEM_BZIP2 OFF) -set(DEVILUTIONX_SYSTEM_LIBFMT OFF) set(DEVILUTIONX_STATIC_LIBSODIUM OFF) # Compatibility with Windows 9x 8-bit mode and improved performance diff --git a/CMake/platforms/xbox_nxdk.cmake b/CMake/platforms/xbox_nxdk.cmake index c1211bdd8..32886cbfb 100644 --- a/CMake/platforms/xbox_nxdk.cmake +++ b/CMake/platforms/xbox_nxdk.cmake @@ -5,7 +5,6 @@ set(UBSAN OFF) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/xbox_nxdk/finders") set(DEVILUTIONX_SYSTEM_BZIP2 OFF) -set(DEVILUTIONX_SYSTEM_LIBFMT OFF) # nxdk's zlib is built with Z_SOLO, which excludes utility functions like # uncompress() that mpqfs needs for zlib-compressed MPQ sectors. diff --git a/CMakeLists.txt b/CMakeLists.txt index 63b455c0c..85bc30907 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,6 +73,14 @@ if(NOT VERSION_SUFFIX) endif() endif() +# The iOS toolchain file reads DEPLOYMENT_TARGET when it is processed by +# `project()`, so the default must be set before that. +# libc++'s requires the floating-point std::to_chars symbols, +# which are only available in the iOS 16.3+ system libc++. +if(CMAKE_TOOLCHAIN_FILE MATCHES "ios\\.toolchain\\.cmake" AND NOT DEFINED DEPLOYMENT_TARGET) + set(DEPLOYMENT_TARGET "16.3") +endif() + project(DevilutionX VERSION ${VERSION_NUM} LANGUAGES C CXX) @@ -355,7 +363,7 @@ include(Dependencies) # Mixing `-static-libstdc++` with a dynamic C++ dependency causes both copies # to initialize, which is UB and can cause crashes, e.g. in # `std::ios_base_library_init()`. -if(NOT DEVILUTIONX_STATIC_LIBFMT) +if(NOT NOSOUND AND NOT DEVILUTIONX_STATIC_SDL_AUDIOLIB) set(DEVILUTIONX_STATIC_CXX_STDLIB OFF) endif() @@ -448,10 +456,14 @@ if(APPLE) if(IOS) set(MACOSX_BUNDLE_REQUIRED_PLATFORM IPhoneOS) set_target_properties(${BIN_TARGET} PROPERTIES XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2") - set(CMAKE_OSX_DEPLOYMENT_TARGET "9.0") + # libc++'s requires the floating-point std::to_chars symbols, + # which are only available in the iOS 16.3+ system libc++. + set(CMAKE_OSX_DEPLOYMENT_TARGET "16.3") elseif(DARWIN_MAJOR_VERSION GREATER_EQUAL 17) set(MACOSX_BUNDLE_REQUIRED_PLATFORM Carbon) - set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13.0") + # libc++'s requires the floating-point std::to_chars symbols, + # which are only available in the macOS 13.3+ system libc++. + set(CMAKE_OSX_DEPLOYMENT_TARGET "13.3") endif() if(DARWIN_MAJOR_VERSION VERSION_LESS 9) # Finder on OSX Tiger can only handle icns files with up to 128x128 icons. diff --git a/Packaging/OpenDingux/build.sh b/Packaging/OpenDingux/build.sh index 688090455..da81991bb 100755 --- a/Packaging/OpenDingux/build.sh +++ b/Packaging/OpenDingux/build.sh @@ -201,7 +201,6 @@ cmake_configure() { "-DTARGET_PLATFORM=$TARGET" \ -DCMAKE_TOOLCHAIN_FILE="${TOOLCHAIN}/usr/share/buildroot/toolchainfile.cmake" \ -DBUILD_TESTING=OFF \ - -DDEVILUTIONX_SYSTEM_LIBFMT=OFF \ -DDEVILUTIONX_SYSTEM_LIBSODIUM=OFF \ -DDEVILUTIONX_SYSTEM_BZIP2=OFF \ -DSTACK_PROTECTOR=OFF \ diff --git a/Packaging/nix/debian-cross-aarch64-prep.sh b/Packaging/nix/debian-cross-aarch64-prep.sh index c28f10363..d57977d71 100755 --- a/Packaging/nix/debian-cross-aarch64-prep.sh +++ b/Packaging/nix/debian-cross-aarch64-prep.sh @@ -22,7 +22,7 @@ fi PACKAGES=( cmake git smpq gettext dpkg-cross libc-dev-arm64-cross libsdl2-dev:arm64 libsdl2-image-dev:arm64 libsodium-dev:arm64 - libpng-dev:arm64 libbz2-dev:arm64 libfmt-dev:arm64 + libpng-dev:arm64 libbz2-dev:arm64 libspeechd-dev:arm64 ) diff --git a/Packaging/nix/debian-cross-i386-prep.sh b/Packaging/nix/debian-cross-i386-prep.sh index d57aac451..0665ed033 100755 --- a/Packaging/nix/debian-cross-i386-prep.sh +++ b/Packaging/nix/debian-cross-i386-prep.sh @@ -21,7 +21,7 @@ fi PACKAGES=( cmake git smpq gettext libsdl2-dev:i386 libsdl2-image-dev:i386 libsodium-dev:i386 - libpng-dev:i386 libbz2-dev:i386 libfmt-dev:i386 libspeechd-dev:i386 + libpng-dev:i386 libbz2-dev:i386 libspeechd-dev:i386 ) if (( $# < 1 )) || [[ "$1" != --no-gcc ]]; then diff --git a/Packaging/nix/debian-host-prep.sh b/Packaging/nix/debian-host-prep.sh index b46223fb5..81b0000be 100755 --- a/Packaging/nix/debian-host-prep.sh +++ b/Packaging/nix/debian-host-prep.sh @@ -4,7 +4,7 @@ set -x PACKAGES=( rpm pkg-config cmake git smpq gettext libsdl2-dev libsdl2-image-dev libsodium-dev - libpng-dev libbz2-dev libfmt-dev libspeechd-dev + libpng-dev libbz2-dev libspeechd-dev ) if (( $# < 1 )) || [[ "$1" != --no-gcc ]]; then diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt index 74d213d5e..0f75dae74 100644 --- a/Source/CMakeLists.txt +++ b/Source/CMakeLists.txt @@ -200,7 +200,6 @@ add_devilutionx_object_library(libdevilutionx_assets ) target_link_dependencies(libdevilutionx_assets PUBLIC DevilutionX::SDL - fmt::fmt tl libdevilutionx_headless_mode libdevilutionx_game_mode @@ -233,7 +232,6 @@ add_devilutionx_object_library(libdevilutionx_clx_render ) target_link_dependencies(libdevilutionx_clx_render PUBLIC DevilutionX::SDL - fmt::fmt libdevilutionx_light_render libdevilutionx_palette_blending libdevilutionx_strings @@ -262,7 +260,6 @@ target_link_dependencies(libdevilutionx_control tl libdevilutionx_text_input PRIVATE - fmt::fmt libdevilutionx_clx_render libdevilutionx_control_mode libdevilutionx_format_int @@ -404,7 +401,6 @@ add_devilutionx_object_library(libdevilutionx_gendung ) target_link_dependencies(libdevilutionx_gendung PUBLIC DevilutionX::SDL - fmt::fmt tl libdevilutionx_assets libdevilutionx_items @@ -508,7 +504,6 @@ add_devilutionx_object_library(libdevilutionx_lighting ) target_link_dependencies(libdevilutionx_lighting PUBLIC DevilutionX::SDL - fmt::fmt magic_enum::magic_enum tl unordered_dense::unordered_dense @@ -552,7 +547,6 @@ add_devilutionx_object_library(libdevilutionx_ini utils/ini.cpp ) target_link_dependencies(libdevilutionx_ini PUBLIC - fmt::fmt tl unordered_dense::unordered_dense libdevilutionx_strings @@ -566,7 +560,6 @@ target_include_directories(libdevilutionx_log INTERFACE ${PROJECT_SOURCE_DIR}/Source) target_link_libraries(libdevilutionx_log INTERFACE DevilutionX::SDL - fmt::fmt ) target_sources(libdevilutionx_log INTERFACE $) @@ -632,7 +625,6 @@ if(SUPPORTS_MPQ) ) target_link_dependencies(libdevilutionx_mpq PUBLIC DevilutionX::SDL - fmt::fmt mpqfs::mpqfs tl libdevilutionx_file_util @@ -655,7 +647,6 @@ add_devilutionx_object_library(libdevilutionx_options ) target_link_dependencies(libdevilutionx_options PUBLIC DevilutionX::SDL - fmt::fmt magic_enum::magic_enum tl unordered_dense::unordered_dense @@ -695,7 +686,6 @@ add_devilutionx_object_library(libdevilutionx_player target_link_dependencies(libdevilutionx_player PUBLIC DevilutionX::SDL - fmt::fmt magic_enum::magic_enum sol2::sol2 tl @@ -760,7 +750,6 @@ target_link_dependencies(libdevilutionx_text_render PUBLIC libdevilutionx_surface PRIVATE - fmt::fmt unordered_dense::unordered_dense libdevilutionx_clx_render libdevilutionx_game_mode @@ -787,7 +776,6 @@ add_devilutionx_object_library(libdevilutionx_txtdata data/value_reader.cpp ) target_link_dependencies(libdevilutionx_txtdata PUBLIC - fmt::fmt tl libdevilutionx_assets libdevilutionx_parse_int @@ -808,7 +796,6 @@ if(NOSOUND) ) target_link_dependencies(libdevilutionx_sound PUBLIC DevilutionX::SDL - fmt::fmt magic_enum::magic_enum tl unordered_dense::unordered_dense @@ -835,7 +822,6 @@ else() endif() target_link_dependencies(libdevilutionx_sound PUBLIC DevilutionX::SDL - fmt::fmt magic_enum::magic_enum tl unordered_dense::unordered_dense @@ -850,7 +836,6 @@ add_devilutionx_object_library(libdevilutionx_stores ) target_link_dependencies(libdevilutionx_stores PUBLIC DevilutionX::SDL - fmt::fmt tl libdevilutionx_clx_render libdevilutionx_control @@ -863,8 +848,7 @@ add_devilutionx_object_library(libdevilutionx_strings utils/str_cat.cpp utils/str_case.cpp ) -target_link_dependencies(libdevilutionx_strings PRIVATE - fmt::fmt) +target_link_dependencies(libdevilutionx_strings PRIVATE) add_devilutionx_object_library(libdevilutionx_utils_console utils/console.cpp @@ -903,6 +887,11 @@ if(IOS) list(APPEND libdevilutionx_SRCS platform/ios/ios_paths.m) endif() +if(AMIGA AND NOT WARPOS) + # Works around a missing `__cmpdf2` in the hard-float libgcc multilib. + list(APPEND libdevilutionx_SRCS platform/amiga/cmpdf2.c) +endif() + if(NOT DISABLE_DEMOMODE) list(APPEND libdevilutionx_SRCS engine/demomode.cpp) endif() @@ -969,7 +958,6 @@ add_devilutionx_object_library(libdevilutionx ${libdevilutionx_SRCS}) target_include_directories(libdevilutionx PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) target_link_dependencies(libdevilutionx PUBLIC DevilutionX::SDL - fmt::fmt libsmackerdec ${LUA_LIBRARIES} magic_enum::magic_enum diff --git a/Source/DiabloUI/hero/selhero.cpp b/Source/DiabloUI/hero/selhero.cpp index 80de8fc80..c58987950 100644 --- a/Source/DiabloUI/hero/selhero.cpp +++ b/Source/DiabloUI/hero/selhero.cpp @@ -16,9 +16,6 @@ #include #endif -#include -#include - #include "DiabloUI/diabloui.h" #include "DiabloUI/dialogs.h" #include "DiabloUI/multi/selgame.h" @@ -36,6 +33,7 @@ #include "pfile.h" #include "tables/playerdat.hpp" #include "utils/enum_traits.h" +#include "utils/format.hpp" #include "utils/language.h" #include "utils/sdl_geometry.h" #include "utils/str_cat.hpp" @@ -620,7 +618,7 @@ static void UiSelHeroDialog( } else { CopyUtf8(dialogTitle, _("Delete Single Player Hero"), sizeof(dialogTitle)); } - strcpy(dialogText, fmt::format(fmt::runtime(_("Are you sure you want to delete the character \"{:s}\"?")), selhero_heroInfo.name).c_str()); + strcpy(dialogText, FormatRuntime(_("Are you sure you want to delete the character \"{:s}\"?"), selhero_heroInfo.name).c_str()); if (UiSelHeroYesNoDialog(dialogTitle, dialogText)) fnremove(&selhero_heroInfo); diff --git a/Source/DiabloUI/multi/selconn.cpp b/Source/DiabloUI/multi/selconn.cpp index 830cbc4f3..f09e53a81 100644 --- a/Source/DiabloUI/multi/selconn.cpp +++ b/Source/DiabloUI/multi/selconn.cpp @@ -10,9 +10,6 @@ #include #endif -#include -#include - #include "DiabloUI/diabloui.h" #include "DiabloUI/ui_flags.hpp" #include "DiabloUI/ui_item.h" @@ -20,6 +17,7 @@ #include "engine/render/text_render.hpp" #include "multi.h" #include "storm/storm_net.hpp" +#include "utils/format.hpp" #include "utils/language.h" #include "utils/ui_fwd.h" #include "utils/utf8.hpp" @@ -138,7 +136,7 @@ void SelconnFocus(size_t value) break; } - CopyUtf8(selconn_MaxPlayers, fmt::format(fmt::runtime(_("Players Supported: {:d}")), players), sizeof(selconn_MaxPlayers)); + CopyUtf8(selconn_MaxPlayers, FormatRuntime(_("Players Supported: {:d}"), players), sizeof(selconn_MaxPlayers)); CopyUtf8(selconn_Description, WordWrapString(selconn_Description, DESCRIPTION_WIDTH), sizeof(selconn_Description)); } diff --git a/Source/DiabloUI/multi/selgame.cpp b/Source/DiabloUI/multi/selgame.cpp index 00dd905e6..698d0647f 100644 --- a/Source/DiabloUI/multi/selgame.cpp +++ b/Source/DiabloUI/multi/selgame.cpp @@ -17,9 +17,6 @@ #include #endif -#include -#include - #include "DiabloUI/diabloui.h" #include "DiabloUI/hero/selhero.h" #include "DiabloUI/scrollbar.h" @@ -35,6 +32,7 @@ #include "multi.h" #include "options.h" #include "storm/storm_net.hpp" +#include "utils/format.hpp" #include "utils/language.h" #include "utils/str_cat.hpp" #include "utils/ui_fwd.h" @@ -107,7 +105,7 @@ static std::string GetErrorMessageIncompatibility(const GameData &data) else return std::string(_("You need to full version of the game to join this game.")); } - return fmt::format(fmt::runtime(_(/* TRANSLATORS: Error message when somebody tries to join a game running another version. */ "Your version {:s} does not match the host {:d}.{:d}.{:d}.")), PROJECT_VERSION, data.versionMajor, data.versionMinor, data.versionPatch); + return FormatRuntime(_(/* TRANSLATORS: Error message when somebody tries to join a game running another version. */ "Your version {:s} does not match the host {:d}.{:d}.{:d}."), PROJECT_VERSION, data.versionMajor, data.versionMinor, data.versionPatch); } void UiInitGameSelectionList(std::string_view search) @@ -248,7 +246,7 @@ void selgame_GameSelection_Focus(size_t value) difficulty = _("Hell"); break; } - infoString.append(fmt::format(fmt::runtime(_(/* TRANSLATORS: {:s} means: Game Difficulty. */ "Difficulty: {:s}")), difficulty)); + infoString.append(FormatRuntime(_(/* TRANSLATORS: {:s} means: Game Difficulty. */ "Difficulty: {:s}"), difficulty)); infoString += '\n'; switch (gameInfo.gameData.nTickRate) { case 20: @@ -276,9 +274,9 @@ void selgame_GameSelection_Focus(size_t value) } infoString += '\n'; if (gameInfo.peerIsRelayed.value_or(false)) - infoString.append(fmt::format(fmt::runtime(_("Ping: {:d} ms (RELAYED)")), gameInfo.latency.value_or(0))); + infoString.append(FormatRuntime(_("Ping: {:d} ms (RELAYED)"), gameInfo.latency.value_or(0))); else - infoString.append(fmt::format(fmt::runtime(_("Ping: {:d} ms")), gameInfo.latency.value_or(0))); + infoString.append(FormatRuntime(_("Ping: {:d} ms"), gameInfo.latency.value_or(0))); } else { infoString.append(GetErrorMessageIncompatibility(gameInfo.gameData)); } @@ -354,7 +352,7 @@ void selgame_GameSelection_Select(size_t value) break; } case 2: { - selgame_Title = fmt::format(fmt::runtime(_("Join {:s} Games")), _(ConnectionNames[provider])); + selgame_Title = FormatRuntime(_("Join {:s} Games"), _(ConnectionNames[provider])); title = selgame_Title.c_str(); const char *inputHint; diff --git a/Source/appfat.cpp b/Source/appfat.cpp index 18a53e1ba..1b338b09b 100644 --- a/Source/appfat.cpp +++ b/Source/appfat.cpp @@ -18,12 +18,11 @@ #include "utils/sdl_compat.h" #endif -#include - #include "diablo.h" #include "dvlnet/leaveinfo.hpp" #include "multi.h" #include "storm/storm_net.hpp" +#include "utils/format.hpp" #include "utils/language.h" #include "utils/sdl_thread.h" #include "utils/str_cat.hpp" @@ -83,16 +82,16 @@ void ErrDlg(const char *title, std::string_view error, std::string_view logFileP { DisplayFatalErrorAndExit( title, - fmt::format(fmt::runtime(_(/* TRANSLATORS: Error message that displays relevant information for bug report */ "{:s}\n\nThe error occurred at: {:s} line {:d}")), + FormatRuntime(_(/* TRANSLATORS: Error message that displays relevant information for bug report */ "{:s}\n\nThe error occurred at: {:s} line {:d}"), error, logFilePath, logLineNr)); } void InsertCDDlg(std::string_view archiveName) { DisplayFatalErrorAndExit(_("Data File Error"), - fmt::format(fmt::runtime(_("Unable to open main data archive ({:s}).\n" - "\n" - "Make sure that it is in the game folder.")), + FormatRuntime(_("Unable to open main data archive ({:s}).\n" + "\n" + "Make sure that it is in the game folder."), archiveName)); } @@ -100,7 +99,7 @@ void DirErrorDlg(std::string_view error) { DisplayFatalErrorAndExit( _("Read-Only Directory Error"), - fmt::format(fmt::runtime(_(/* TRANSLATORS: Error when Program is not allowed to write data */ "Unable to write to location:\n{:s}")), + FormatRuntime(_(/* TRANSLATORS: Error when Program is not allowed to write data */ "Unable to write to location:\n{:s}"), error)); } diff --git a/Source/automap.cpp b/Source/automap.cpp index c64892ebe..c63063eeb 100644 --- a/Source/automap.cpp +++ b/Source/automap.cpp @@ -8,8 +8,6 @@ #include #include -#include - #include "control/control.hpp" #include "engine/load_file.hpp" #include "engine/palette.h" @@ -21,6 +19,7 @@ #include "player.h" #include "utils/attributes.h" #include "utils/enum_traits.h" +#include "utils/format.hpp" #include "utils/is_of.hpp" #include "utils/language.h" #include "utils/ui_fwd.h" @@ -1467,16 +1466,16 @@ void DrawAutomapText(const Surface &out) std::string description; switch (leveltype) { case DTYPE_NEST: - description = fmt::format(fmt::runtime(_("Level: Nest {:d}")), currlevel - 16); + description = FormatRuntime(_("Level: Nest {:d}"), currlevel - 16); break; case DTYPE_CRYPT: - description = fmt::format(fmt::runtime(_("Level: Crypt {:d}")), currlevel - 20); + description = FormatRuntime(_("Level: Crypt {:d}"), currlevel - 20); break; case DTYPE_TOWN: description = std::string(_("Town")); break; default: - description = fmt::format(fmt::runtime(_("Level: {:d}")), currlevel); + description = FormatRuntime(_("Level: {:d}"), currlevel); break; } drawStringAndAdvanceLine(description); @@ -1495,7 +1494,7 @@ void DrawAutomapText(const Surface &out) break; } - const std::string description = fmt::format(fmt::runtime(_(/* TRANSLATORS: {:s} means: Game Difficulty. */ "Difficulty: {:s}")), difficulty); + const std::string description = FormatRuntime(_(/* TRANSLATORS: {:s} means: Game Difficulty. */ "Difficulty: {:s}"), difficulty); drawStringAndAdvanceLine(description); #ifdef _DEBUG diff --git a/Source/control/control_chat_commands.cpp b/Source/control/control_chat_commands.cpp index 584f5f5b9..c6b71935a 100644 --- a/Source/control/control_chat_commands.cpp +++ b/Source/control/control_chat_commands.cpp @@ -1,14 +1,13 @@ #include "control_chat_commands.hpp" #include "control.hpp" -#include - #include "diablo_msg.hpp" #include "engine/backbuffer_state.hpp" #include "inv.h" #include "levels/setmaps.h" #include "storm/storm_net.hpp" #include "utils/algorithm/container.hpp" +#include "utils/format.hpp" #include "utils/log.hpp" #include "utils/parse_int.hpp" #include "utils/str_case.hpp" @@ -232,15 +231,15 @@ std::string TextCmdPing(const std::string_view parameter) Player &player = *it; DvlNetLatencies latencies = DvlNet_GetLatencies(player.getId()); - StrAppend(ret, fmt::format(fmt::runtime(_(/* TRANSLATORS: {:s} means: Character Name */ "Latency statistics for {:s}:")), player.name())); + StrAppend(ret, FormatRuntime(_(/* TRANSLATORS: {:s} means: Character Name */ "Latency statistics for {:s}:"), player.name())); - StrAppend(ret, "\n", fmt::format(fmt::runtime(_(/* TRANSLATORS: Network connectivity statistics */ "Echo latency: {:d} ms")), latencies.echoLatency)); + StrAppend(ret, "\n", FormatRuntime(_(/* TRANSLATORS: Network connectivity statistics */ "Echo latency: {:d} ms"), latencies.echoLatency)); if (latencies.providerLatency) { if (latencies.isRelayed && *latencies.isRelayed) { - StrAppend(ret, "\n", fmt::format(fmt::runtime(_(/* TRANSLATORS: Network connectivity statistics */ "Provider latency: {:d} ms (Relayed)")), *latencies.providerLatency)); + StrAppend(ret, "\n", FormatRuntime(_(/* TRANSLATORS: Network connectivity statistics */ "Provider latency: {:d} ms (Relayed)"), *latencies.providerLatency)); } else { - StrAppend(ret, "\n", fmt::format(fmt::runtime(_(/* TRANSLATORS: Network connectivity statistics */ "Provider latency: {:d} ms")), *latencies.providerLatency)); + StrAppend(ret, "\n", FormatRuntime(_(/* TRANSLATORS: Network connectivity statistics */ "Provider latency: {:d} ms"), *latencies.providerLatency)); } } diff --git a/Source/control/control_gold.cpp b/Source/control/control_gold.cpp index 969569228..02dd5259a 100644 --- a/Source/control/control_gold.cpp +++ b/Source/control/control_gold.cpp @@ -1,12 +1,11 @@ #include "control.hpp" #include "control_chat.hpp" -#include - #include "DiabloUI/text_input.hpp" #include "engine/render/clx_render.hpp" #include "inv.h" #include "utils/display.h" +#include "utils/format.hpp" #include "utils/format_int.hpp" #include "utils/log.hpp" #include "utils/sdl_compat.h" @@ -56,12 +55,12 @@ void DrawGoldSplit(const Surface &out) const TextInputCursorState &cursor = GoldDropCursor; const int max = GetGoldDropMax(); - const std::string description = fmt::format( - fmt::runtime(ngettext( + const std::string description = FormatRuntime( + ngettext( /* TRANSLATORS: {:s} is a number with separators. Dialog is shown when splitting a stash of Gold.*/ "You have {:s} gold piece. How many do you want to remove?", "You have {:s} gold pieces. How many do you want to remove?", - max)), + max), FormatInteger(max)); // Pre-wrap the string at spaces, otherwise DrawString would hard wrap in the middle of words diff --git a/Source/control/control_infobox.cpp b/Source/control/control_infobox.cpp index 95fa28b49..4f81effef 100644 --- a/Source/control/control_infobox.cpp +++ b/Source/control/control_infobox.cpp @@ -1,8 +1,5 @@ #include "control.hpp" #include "control_panel.hpp" - -#include - #include "controls/control_mode.hpp" #include "engine/render/primitive_render.hpp" #include "inv.h" @@ -14,6 +11,7 @@ #include "qol/xpbar.h" #include "towners.h" #include "utils/algorithm/container.hpp" +#include "utils/format.hpp" #include "utils/format_int.hpp" #include "utils/log.hpp" #include "utils/screen_reader.hpp" @@ -330,7 +328,7 @@ void CheckPanelInfo() InfoString = _("Player attack"); } if (PanBtnHotKey[i] != nullptr) { - AddInfoBoxString(fmt::format(fmt::runtime(_("Hotkey: {:s}")), _(PanBtnHotKey[i]))); + AddInfoBoxString(FormatRuntime(_("Hotkey: {:s}"), _(PanBtnHotKey[i]))); } InfoColor = UiFlags::ColorWhite; MainPanelFlag = true; @@ -349,30 +347,30 @@ void CheckPanelInfo() ? GetOptions().Padmapper.InputNameForAction("DisplaySpells", true) : GetOptions().Keymapper.KeyNameForAction("DisplaySpells"); if (!speedbookKeyName.empty()) { - AddInfoBoxString(fmt::format(fmt::runtime(_("Hotkey: '{:s}'")), speedbookKeyName)); + AddInfoBoxString(FormatRuntime(_("Hotkey: '{:s}'"), speedbookKeyName)); } const Player &myPlayer = *MyPlayer; const SpellID spellId = myPlayer._pRSpell; if (IsValidSpell(spellId)) { switch (myPlayer._pRSplType) { case SpellType::Skill: - AddInfoBoxString(fmt::format(fmt::runtime(_("{:s} Skill")), pgettext("spell", GetSpellData(spellId).sNameText))); + AddInfoBoxString(FormatRuntime(_("{:s} Skill"), pgettext("spell", GetSpellData(spellId).sNameText))); break; case SpellType::Spell: { - AddInfoBoxString(fmt::format(fmt::runtime(_("{:s} Spell")), pgettext("spell", GetSpellData(spellId).sNameText))); + AddInfoBoxString(FormatRuntime(_("{:s} Spell"), pgettext("spell", GetSpellData(spellId).sNameText))); const int spellLevel = myPlayer.GetSpellLevel(spellId); - AddInfoBoxString(spellLevel == 0 ? _("Spell Level 0 - Unusable") : fmt::format(fmt::runtime(_("Spell Level {:d}")), spellLevel)); + AddInfoBoxString(spellLevel == 0 ? _("Spell Level 0 - Unusable") : FormatRuntime(_("Spell Level {:d}"), spellLevel)); } break; case SpellType::Scroll: { - AddInfoBoxString(fmt::format(fmt::runtime(_("Scroll of {:s}")), pgettext("spell", GetSpellData(spellId).sNameText))); + AddInfoBoxString(FormatRuntime(_("Scroll of {:s}"), pgettext("spell", GetSpellData(spellId).sNameText))); const int scrollCount = c_count_if(InventoryAndBeltPlayerItemsRange { myPlayer }, [spellId](const Item &item) { return item.isScrollOf(spellId); }); - AddInfoBoxString(fmt::format(fmt::runtime(ngettext("{:d} Scroll", "{:d} Scrolls", scrollCount)), scrollCount)); + AddInfoBoxString(FormatRuntime(ngettext("{:d} Scroll", "{:d} Scrolls", scrollCount), scrollCount)); } break; case SpellType::Charges: - AddInfoBoxString(fmt::format(fmt::runtime(_("Staff of {:s}")), pgettext("spell", GetSpellData(spellId).sNameText))); - AddInfoBoxString(fmt::format(fmt::runtime(ngettext("{:d} Charge", "{:d} Charges", myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges)), myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges)); + AddInfoBoxString(FormatRuntime(_("Staff of {:s}"), pgettext("spell", GetSpellData(spellId).sNameText))); + AddInfoBoxString(FormatRuntime(ngettext("{:d} Charge", "{:d} Charges", myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges), myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges)); break; case SpellType::Invalid: break; @@ -404,7 +402,7 @@ void DrawInfoBox(const Surface &out) } else if (!myPlayer.HoldItem.isEmpty()) { if (myPlayer.HoldItem._itype == ItemType::Gold) { const int nGold = myPlayer.HoldItem._ivalue; - InfoString = fmt::format(fmt::runtime(ngettext("{:s} gold piece", "{:s} gold pieces", nGold)), FormatInteger(nGold)); + InfoString = FormatRuntime(ngettext("{:s} gold piece", "{:s} gold pieces", nGold), FormatInteger(nGold)); } else if (!myPlayer.CanUseItem(myPlayer.HoldItem)) { InfoString = _("Requirements not met"); } else { @@ -435,8 +433,8 @@ void DrawInfoBox(const Surface &out) InfoColor = UiFlags::ColorWhitegold; const auto &target = *PlayerUnderCursor; InfoString = std::string_view(target._pName); - AddInfoBoxString(fmt::format(fmt::runtime(_("{:s}, Level: {:d}")), target.getClassName(), target.getCharacterLevel())); - AddInfoBoxString(fmt::format(fmt::runtime(_("Hit Points {:d} of {:d}")), target._pHitPoints >> 6, target._pMaxHP >> 6)); + AddInfoBoxString(FormatRuntime(_("{:s}, Level: {:d}"), target.getClassName(), target.getCharacterLevel())); + AddInfoBoxString(FormatRuntime(_("Hit Points {:d} of {:d}"), target._pHitPoints >> 6, target._pMaxHP >> 6)); } if (PortraitIdUnderCursor != -1) { InfoColor = UiFlags::ColorWhitegold; diff --git a/Source/control/control_panel.cpp b/Source/control/control_panel.cpp index 6937cebe8..f7bc23932 100644 --- a/Source/control/control_panel.cpp +++ b/Source/control/control_panel.cpp @@ -3,8 +3,6 @@ #include "control_chat.hpp" #include "control_flasks.hpp" -#include - #include "automap.h" #include "controls/control_mode.hpp" #include "controls/modifier_hints.h" @@ -27,6 +25,7 @@ #include "qol/stash.h" #include "qol/visual_store.h" #include "stores.h" +#include "utils/format.hpp" #include "utils/sdl_compat.h" namespace devilution { @@ -820,12 +819,12 @@ void DrawDeathText(const Surface &out) if (!gbIsMultiplayer) { if (gbValidSaveFile) - text = fmt::format(fmt::runtime(_("Press {} to load last save.")), buttonText); + text = FormatRuntime(_("Press {} to load last save."), buttonText); else - text = fmt::format(fmt::runtime(_("Press {} to return to Main Menu.")), buttonText); + text = FormatRuntime(_("Press {} to return to Main Menu."), buttonText); } else { - text = fmt::format(fmt::runtime(_("Press {} to restart in town.")), buttonText); + text = FormatRuntime(_("Press {} to restart in town."), buttonText); } DrawString(out, text, linePosition, smallTextOptions); } diff --git a/Source/cursor.cpp b/Source/cursor.cpp index 5569a78c2..b2575a698 100644 --- a/Source/cursor.cpp +++ b/Source/cursor.cpp @@ -18,8 +18,6 @@ #include #endif -#include - #include "DiabloUI/diabloui.h" #include "control/control.hpp" #include "controls/control_mode.hpp" @@ -44,6 +42,7 @@ #include "towners.h" #include "track.h" #include "utils/attributes.h" +#include "utils/format.hpp" #include "utils/is_of.hpp" #include "utils/language.h" #include "utils/palette_blending.hpp" @@ -628,7 +627,7 @@ void CheckTown() if (EntranceBoundaryContains(missile.position.tile, cursPosition)) { trigflag = true; InfoString = _("Town Portal"); - AddInfoBoxString(fmt::format(fmt::runtime(_("from {:s}")), Players[missile._misource]._pName)); + AddInfoBoxString(FormatRuntime(_("from {:s}"), Players[missile._misource]._pName)); cursPosition = missile.position.tile; } } diff --git a/Source/data/file.cpp b/Source/data/file.cpp index b5845d50c..6d6c676b5 100644 --- a/Source/data/file.cpp +++ b/Source/data/file.cpp @@ -7,10 +7,9 @@ #include #include -#include - #include "engine/assets.hpp" #include "utils/algorithm/container.hpp" +#include "utils/format.hpp" #include "utils/language.h" namespace devilution { @@ -47,19 +46,19 @@ void DataFile::reportFatalError(Error code, std::string_view fileName) case Error::NotFound: case Error::OpenFailed: case Error::BadRead: - app_fatal(fmt::format(fmt::runtime(_( - /* TRANSLATORS: Error message when a data file is missing or corrupt. Arguments are {file name} */ - "Unable to load data from file {0}")), + app_fatal(FormatRuntime(_( + /* TRANSLATORS: Error message when a data file is missing or corrupt. Arguments are {file name} */ + "Unable to load data from file {0}"), fileName)); case Error::NoContent: - app_fatal(fmt::format(fmt::runtime(_( - /* TRANSLATORS: Error message when a data file is empty or only contains the header row. Arguments are {file name} */ - "{0} is incomplete, please check the file contents.")), + app_fatal(FormatRuntime(_( + /* TRANSLATORS: Error message when a data file is empty or only contains the header row. Arguments are {file name} */ + "{0} is incomplete, please check the file contents."), fileName)); case Error::NotEnoughColumns: - app_fatal(fmt::format(fmt::runtime(_( - /* TRANSLATORS: Error message when a data file doesn't contain the expected columns. Arguments are {file name} */ - "Your {0} file doesn't have the expected columns, please make sure it matches the documented format.")), + app_fatal(FormatRuntime(_( + /* TRANSLATORS: Error message when a data file doesn't contain the expected columns. Arguments are {file name} */ + "Your {0} file doesn't have the expected columns, please make sure it matches the documented format."), fileName)); } } @@ -72,21 +71,21 @@ void DataFile::reportFatalFieldError(DataFileField::Error code, std::string_view } switch (code) { case DataFileField::Error::NotANumber: - app_fatal(fmt::format(fmt::runtime(_( - /* TRANSLATORS: Error message when parsing a data file and a text value is encountered when a number is expected. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} */ - "Non-numeric value {0} for {1} in {2} at row {3} and column {4}")), + app_fatal(FormatRuntime(_( + /* TRANSLATORS: Error message when parsing a data file and a text value is encountered when a number is expected. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} */ + "Non-numeric value {0} for {1} in {2} at row {3} and column {4}"), field.currentValue(), fieldName, fileName, field.row(), field.column()) .append(detailsStr)); case DataFileField::Error::OutOfRange: - app_fatal(fmt::format(fmt::runtime(_( - /* TRANSLATORS: Error message when parsing a data file and we find a number larger than expected. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} */ - "Out of range value {0} for {1} in {2} at row {3} and column {4}")), + app_fatal(FormatRuntime(_( + /* TRANSLATORS: Error message when parsing a data file and we find a number larger than expected. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} */ + "Out of range value {0} for {1} in {2} at row {3} and column {4}"), field.currentValue(), fieldName, fileName, field.row(), field.column()) .append(detailsStr)); case DataFileField::Error::InvalidValue: - app_fatal(fmt::format(fmt::runtime(_( - /* TRANSLATORS: Error message when we find an unrecognised value in a key column. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} */ - "Invalid value {0} for {1} in {2} at row {3} and column {4}")), + app_fatal(FormatRuntime(_( + /* TRANSLATORS: Error message when we find an unrecognised value in a key column. Arguments are {found value}, {column heading}, {file name}, {row/record number}, {column/field number} */ + "Invalid value {0} for {1} in {2} at row {3} and column {4}"), field.currentValue(), fieldName, fileName, field.row(), field.column()) .append(detailsStr)); } diff --git a/Source/data/value_reader.cpp b/Source/data/value_reader.cpp index 6155e3bba..4656cb9c9 100644 --- a/Source/data/value_reader.cpp +++ b/Source/data/value_reader.cpp @@ -1,7 +1,6 @@ #include "data/value_reader.hpp" -#include -#include +#include #include "appfat.h" @@ -17,7 +16,7 @@ ValueReader::ValueReader(DataFile &dataFile, std::string_view filename) DataFileField ValueReader::getValueField(std::string_view expectedKey) { if (it_ == end_) { - app_fatal(fmt::format("Missing field {} in {}", expectedKey, filename_)); + app_fatal(std::format("Missing field {} in {}", expectedKey, filename_)); } DataFileRecord record = *it_; FieldIterator fieldIt = record.begin(); @@ -25,7 +24,7 @@ DataFileField ValueReader::getValueField(std::string_view expectedKey) const std::string_view key = (*fieldIt).value(); if (key != expectedKey) { - app_fatal(fmt::format("Unexpected field in {}: got {}, expected {}", filename_, key, expectedKey)); + app_fatal(std::format("Unexpected field in {}: got {}, expected {}", filename_, key, expectedKey)); } ++fieldIt; diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 49728ba58..3d6bfb95c 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -19,8 +19,6 @@ #endif #endif -#include - #include #include "DiabloUI/selstart.h" @@ -108,6 +106,7 @@ #include "track.h" #include "utils/console.h" #include "utils/display.h" +#include "utils/format.hpp" #include "utils/is_of.hpp" #include "utils/language.h" #include "utils/parse_int.hpp" @@ -1587,17 +1586,17 @@ void TimeoutCursor(bool bTimeout) DvlNetLatencies latencies = DvlNet_GetLatencies(i); - std::string ping = fmt::format( - fmt::runtime(_(/* TRANSLATORS: {:s} means: Character Name */ "Player {:s} is timing out!")), + std::string ping = FormatRuntime( + _(/* TRANSLATORS: {:s} means: Character Name */ "Player {:s} is timing out!"), Players[i].name()); - StrAppend(ping, "\n ", fmt::format(fmt::runtime(_(/* TRANSLATORS: Network connectivity statistics */ "Echo latency: {:d} ms")), latencies.echoLatency)); + StrAppend(ping, "\n ", FormatRuntime(_(/* TRANSLATORS: Network connectivity statistics */ "Echo latency: {:d} ms"), latencies.echoLatency)); if (latencies.providerLatency) { if (latencies.isRelayed && *latencies.isRelayed) { - StrAppend(ping, "\n ", fmt::format(fmt::runtime(_(/* TRANSLATORS: Network connectivity statistics */ "Provider latency: {:d} ms (Relayed)")), *latencies.providerLatency)); + StrAppend(ping, "\n ", FormatRuntime(_(/* TRANSLATORS: Network connectivity statistics */ "Provider latency: {:d} ms (Relayed)"), *latencies.providerLatency)); } else { - StrAppend(ping, "\n ", fmt::format(fmt::runtime(_(/* TRANSLATORS: Network connectivity statistics */ "Provider latency: {:d} ms")), *latencies.providerLatency)); + StrAppend(ping, "\n ", FormatRuntime(_(/* TRANSLATORS: Network connectivity statistics */ "Provider latency: {:d} ms"), *latencies.providerLatency)); } } EventPlrMsg(ping); @@ -2117,8 +2116,8 @@ void InitKeymapActions() N_("Displays game infos."), 'V', [] { - EventPlrMsg(fmt::format( - fmt::runtime(_(/* TRANSLATORS: {:s} means: Project Name, Game Version. */ "{:s} {:s}")), + EventPlrMsg(FormatRuntime( + _(/* TRANSLATORS: {:s} means: Project Name, Game Version. */ "{:s} {:s}"), PROJECT_NAME, PROJECT_VERSION), UiFlags::ColorWhite); @@ -2629,8 +2628,8 @@ void InitPadmapActions() N_("Displays game infos."), ControllerButton_NONE, [] { - EventPlrMsg(fmt::format( - fmt::runtime(_(/* TRANSLATORS: {:s} means: Project Name, Game Version. */ "{:s} {:s}")), + EventPlrMsg(FormatRuntime( + _(/* TRANSLATORS: {:s} means: Project Name, Game Version. */ "{:s} {:s}"), PROJECT_NAME, PROJECT_VERSION), UiFlags::ColorWhite); diff --git a/Source/discord/discord.cpp b/Source/discord/discord.cpp index d21fbccf4..4d885a69c 100644 --- a/Source/discord/discord.cpp +++ b/Source/discord/discord.cpp @@ -14,8 +14,6 @@ #include #include -#include - #include "config.h" #include "levels/gendung.h" #include "levels/setmaps.h" @@ -24,6 +22,7 @@ #include "panels/charpanel.hpp" #include "player.h" #include "tables/playerdat.hpp" +#include "utils/format.hpp" #include "utils/language.h" #include "utils/str_cat.hpp" @@ -94,14 +93,14 @@ std::string GetLocationString() else if (tracked_data.dungeonArea == DTYPE_CRYPT) level -= 20; - return fmt::format(fmt::runtime(_(/* TRANSLATORS: dungeon type and floor number i.e. "Cathedral 3"*/ "{} {}")), dungeonStr, level); + return FormatRuntime(_(/* TRANSLATORS: dungeon type and floor number i.e. "Cathedral 3"*/ "{} {}"), dungeonStr, level); } return dungeonStr; } std::string GetCharacterString() { - return fmt::format(fmt::runtime(_(/* TRANSLATORS: Discord character, i.e. "Lv 6 Warrior" */ "Lv {} {}")), tracked_data.playerLevel, MyPlayer->getClassName()); + return FormatRuntime(_(/* TRANSLATORS: Discord character, i.e. "Lv 6 Warrior" */ "Lv {} {}"), tracked_data.playerLevel, MyPlayer->getClassName()); } std::string GetDetailString() @@ -113,7 +112,7 @@ std::string GetStateString() { constexpr std::array DifficultyStrs = { N_("Normal"), N_("Nightmare"), N_("Hell") }; const std::string_view difficultyStr = _(DifficultyStrs[sgGameInitInfo.nDifficulty]); - return fmt::format(fmt::runtime(_(/* TRANSLATORS: Discord state i.e. "Nightmare difficulty" */ "{} difficulty")), difficultyStr); + return FormatRuntime(_(/* TRANSLATORS: Discord state i.e. "Nightmare difficulty" */ "{} difficulty"), difficultyStr); } std::string GetTooltipString() diff --git a/Source/dvlnet/tcp_client.cpp b/Source/dvlnet/tcp_client.cpp index b8584694d..e8533ee99 100644 --- a/Source/dvlnet/tcp_client.cpp +++ b/Source/dvlnet/tcp_client.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -15,7 +16,6 @@ #endif #include -#include #include "options.h" #include "utils/language.h" @@ -216,7 +216,7 @@ void tcp_client::HandleTcpErrorCode() if (code == PacketError::ErrorCode::DecryptionFailed) RaiseIoHandlerError(_("Server failed to decrypt your packet. Check if you typed the password correctly.")); else - RaiseIoHandlerError(fmt::format("Unknown error code received from server: {:#04x}", pktData[0])); + RaiseIoHandlerError(std::format("Unknown error code received from server: {:#04x}", pktData[0])); } std::expected tcp_client::send(packet &pkt) diff --git a/Source/dvlnet/tcp_client.h b/Source/dvlnet/tcp_client.h index eab94a644..3afb43fba 100644 --- a/Source/dvlnet/tcp_client.h +++ b/Source/dvlnet/tcp_client.h @@ -3,15 +3,6 @@ #include #include -// This header must be included before any 3DS code -// because 3DS SDK defines a macro with the same name -// as an fmt template parameter in some versions of fmt. -// See https://github.com/fmtlib/fmt/issues/3632 -// -// 3DS uses some custom ASIO code that transitively includes -// the 3DS SDK. -#include - #include #include #include diff --git a/Source/dvlnet/tcp_server.h b/Source/dvlnet/tcp_server.h index 71f397391..e3dcbd83d 100644 --- a/Source/dvlnet/tcp_server.h +++ b/Source/dvlnet/tcp_server.h @@ -5,15 +5,6 @@ #include #include -// This header must be included before any 3DS code -// because 3DS SDK defines a macro with the same name -// as an fmt template parameter in some versions of fmt. -// See https://github.com/fmtlib/fmt/issues/3632 -// -// 3DS uses some custom ASIO code that transitively includes -// the 3DS SDK. -#include - #include #include #include diff --git a/Source/engine/assets.cpp b/Source/engine/assets.cpp index 2452a7363..6cc354405 100644 --- a/Source/engine/assets.cpp +++ b/Source/engine/assets.cpp @@ -17,12 +17,11 @@ #include #endif -#include - #include "appfat.h" #include "game_mode.hpp" #include "mods/mod_identity.h" #include "utils/file_util.h" +#include "utils/format.hpp" #include "utils/log.hpp" #include "utils/paths.h" #include "utils/sdl_compat.h" @@ -358,7 +357,7 @@ std::expected LoadIntegralAsset(std::string_view path) std::string FailedToOpenFileErrorMessage(std::string_view path, std::string_view error) { - return fmt::format(fmt::runtime(_("Failed to open file:\n{:s}\n\n{:s}\n\nThe MPQ file(s) might be damaged. Please check the file integrity.")), path, error); + return FormatRuntime(_("Failed to open file:\n{:s}\n\n{:s}\n\nThe MPQ file(s) might be damaged. Please check the file integrity."), path, error); } namespace { diff --git a/Source/engine/assets.hpp b/Source/engine/assets.hpp index 8bb275191..1d3ab5da2 100644 --- a/Source/engine/assets.hpp +++ b/Source/engine/assets.hpp @@ -18,8 +18,6 @@ #include #endif -#include - #include "appfat.h" #include "game_mode.hpp" #include "headless_mode.hpp" diff --git a/Source/engine/render/clx_render.cpp b/Source/engine/render/clx_render.cpp index 39afbde7c..d24fdeae3 100644 --- a/Source/engine/render/clx_render.cpp +++ b/Source/engine/render/clx_render.cpp @@ -7,6 +7,7 @@ #include #include +#include #include "engine/point.hpp" #include "engine/render/blit_impl.hpp" @@ -16,7 +17,6 @@ #include "utils/static_vector.hpp" #ifdef DEBUG_CLX -#include #include "utils/str_cat.hpp" #endif @@ -564,15 +564,20 @@ std::string ClxDescribe(ClxSprite clx) if (IsClxOpaque(control)) { if (IsClxOpaqueFill(control)) { const uint8_t length = GetClxOpaqueFillWidth(control); - out.append(fmt::format("Fill | {:>5} | {:>5} | {}\n", length, 2, src[1])); + out.append(std::format("Fill | {:>5} | {:>5} | {}\n", length, 2, src[1])); ++src; } else { const uint8_t length = GetClxOpaquePixelsWidth(control); - out.append(fmt::format("Pixels | {:>5} | {:>5} | {}\n", length, length + 1, fmt::join(src + 1, src + 1 + length, " "))); + std::string pixels; + for (const uint8_t *it = src + 1, *pixelsEnd = src + 1 + length; it != pixelsEnd; ++it) { + if (!pixels.empty()) pixels += ' '; + StrAppend(pixels, *it); + } + out.append(std::format("Pixels | {:>5} | {:>5} | {}\n", length, length + 1, pixels)); src += length; } } else { - out.append(fmt::format("Transp. | {:>5} | {:>5} |\n", control, 1)); + out.append(std::format("Transp. | {:>5} | {:>5} |\n", control, 1)); } } return out; diff --git a/Source/engine/render/text_render.cpp b/Source/engine/render/text_render.cpp index 83a5af330..4a140f611 100644 --- a/Source/engine/render/text_render.cpp +++ b/Source/engine/render/text_render.cpp @@ -15,8 +15,6 @@ #include #include -#include -#include #include "DiabloUI/ui_flags.hpp" #include "engine/clx_sprite.hpp" @@ -32,6 +30,7 @@ #include "engine/ticks.hpp" #include "game_mode.hpp" #include "utils/algorithm/container.hpp" +#include "utils/format.hpp" #include "utils/is_of.hpp" #include "utils/language.h" #include "utils/log.hpp" @@ -343,7 +342,7 @@ public: } else { if (!args_[*result].HasFormatted()) { const auto fmtStr = positional ? "{}" : std::string_view(rest.data(), fmtLen); - args_[*result].SetFormatted(fmt::format(fmt::runtime(fmtStr), std::get(args_[*result].value()))); + args_[*result].SetFormatted(FormatRuntime(fmtStr, std::get(args_[*result].value()))); } rest.remove_prefix(fmtLen); } diff --git a/Source/engine/render/text_render.hpp b/Source/engine/render/text_render.hpp index 3f454f174..25c4a10c8 100644 --- a/Source/engine/render/text_render.hpp +++ b/Source/engine/render/text_render.hpp @@ -14,8 +14,6 @@ #include #include -#include - #include "DiabloUI/ui_flags.hpp" #include "engine/clx_sprite.hpp" #include "engine/palette.h" @@ -182,7 +180,7 @@ int GetLineWidth(std::string_view text, GameFontTables size = GameFont12, int sp /** * @brief Calculate pixel width of first line of text, respecting kerning - * @param fmt An fmt::format string. + * @param fmt A std::format string. * @param args Format arguments. * @param argsLen Number of format arguments. * @param argsOffset Index of the first unprocessed format argument. @@ -252,7 +250,7 @@ inline void DrawString(const Surface &out, std::string_view text, const Point &p * DrawStringWithColors(out, "Press {} to start", {{"Ⓧ", UiFlags::ColorBlue}}, {.flags = UiFlags::ColorWhite}) * * @param out Output buffer to draw the text on. - * @param fmt An fmt::format string. + * @param fmt A std::format string. * @param args Format arguments. * @param argsLen Number of format arguments. * @param rect Clipping region relative to the output buffer describing where to draw the text and when to wrap long lines. diff --git a/Source/engine/trn.cpp b/Source/engine/trn.cpp index e4121b120..f6fa7503e 100644 --- a/Source/engine/trn.cpp +++ b/Source/engine/trn.cpp @@ -2,8 +2,6 @@ #include -#include - #ifdef _DEBUG #include "debug.h" #endif diff --git a/Source/inv.cpp b/Source/inv.cpp index 5dae0e532..58411ba33 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -16,8 +16,6 @@ #include #endif -#include - #include "DiabloUI/ui_flags.hpp" #include "controls/control_mode.hpp" #include "controls/plrctrls.h" @@ -43,6 +41,7 @@ #include "stores.h" #include "towners.h" #include "utils/display.h" +#include "utils/format.hpp" #include "utils/format_int.hpp" #include "utils/is_of.hpp" #include "utils/language.h" @@ -2002,8 +2001,8 @@ int8_t CheckInvHLight() AddInfoBoxString(_("Fully Repaired")); } else if (pi->_itype == ItemType::Gold) { const int nGold = pi->_ivalue; - InfoString = fmt::format(fmt::runtime(ngettext("{:s} gold piece", "{:s} gold pieces", nGold)), FormatInteger(nGold)); - FloatingInfoString = fmt::format(fmt::runtime(ngettext("{:s} gold piece", "{:s} gold pieces", nGold)), FormatInteger(nGold)); + InfoString = FormatRuntime(ngettext("{:s} gold piece", "{:s} gold pieces", nGold), FormatInteger(nGold)); + FloatingInfoString = FormatRuntime(ngettext("{:s} gold piece", "{:s} gold pieces", nGold), FormatInteger(nGold)); } else { InfoColor = pi->getTextColor(); InfoString = pi->getName(); diff --git a/Source/items.cpp b/Source/items.cpp index 602e7fbf3..c77beb8f5 100644 --- a/Source/items.cpp +++ b/Source/items.cpp @@ -25,9 +25,6 @@ #include #endif -#include -#include - #include "DiabloUI/ui_flags.hpp" #include "control/control.hpp" #include "controls/control_mode.hpp" @@ -81,6 +78,7 @@ #include "tables/spelldat.h" #include "tables/textdat.h" #include "utils/enum_traits.h" +#include "utils/format.hpp" #include "utils/format_int.hpp" #include "utils/is_of.hpp" #include "utils/language.h" @@ -1108,10 +1106,10 @@ std::string GenerateStaffName(const ItemData &baseItemData, SpellID spellId, boo std::string_view baseName = translate ? _(baseItemData.iName) : baseItemData.iName; std::string_view spellName = translate ? pgettext("spell", GetSpellData(spellId).sNameText) : GetSpellData(spellId).sNameText; const std::string_view normalFmt = translate ? pgettext("spell", /* TRANSLATORS: Constructs item names. Format: {Item} of {Spell}. Example: War Staff of Firewall */ "{0} of {1}") : "{0} of {1}"; - std::string name = fmt::format(fmt::runtime(normalFmt), baseName, spellName); + std::string name = FormatRuntime(normalFmt, baseName, spellName); if (!StringInPanel(name.c_str())) { std::string_view shortName = translate ? _(baseItemData.iSName) : baseItemData.iSName; - name = fmt::format(fmt::runtime(normalFmt), shortName, spellName); + name = FormatRuntime(normalFmt, shortName, spellName); } return name; } @@ -1123,10 +1121,10 @@ std::string GenerateStaffNameMagical(const ItemData &baseItemData, SpellID spell std::string_view spellName = translate ? pgettext("spell", GetSpellData(spellId).sNameText) : GetSpellData(spellId).sNameText; std::string_view prefixName = translate ? _(power.PLName) : power.PLName; - std::string identifiedName = fmt::format(fmt::runtime(magicFmt), prefixName, baseName, spellName); + std::string identifiedName = FormatRuntime(magicFmt, prefixName, baseName, spellName); if (forceNameLengthCheck ? *forceNameLengthCheck : !StringInPanel(identifiedName.c_str())) { std::string_view shortName = translate ? _(baseItemData.iSName) : baseItemData.iSName; - identifiedName = fmt::format(fmt::runtime(magicFmt), prefixName, shortName, spellName); + identifiedName = FormatRuntime(magicFmt, prefixName, shortName, spellName); } return identifiedName; } @@ -1158,15 +1156,15 @@ std::string GenerateMagicItemName(const std::string_view &baseNamel, const PLStr { if (pPrefix != nullptr && pSufix != nullptr) { const std::string_view fmt = translate ? _(/* TRANSLATORS: Constructs item names. Format: {Prefix} {Item} of {Suffix}. Example: King's Long Sword of the Whale */ "{0} {1} of {2}") : "{0} {1} of {2}"; - return fmt::format(fmt::runtime(fmt), translate ? _(pPrefix->PLName) : pPrefix->PLName, baseNamel, translate ? _(pSufix->PLName) : pSufix->PLName); + return FormatRuntime(fmt, translate ? _(pPrefix->PLName) : pPrefix->PLName, baseNamel, translate ? _(pSufix->PLName) : pSufix->PLName); } if (pPrefix != nullptr) { const std::string_view fmt = translate ? _(/* TRANSLATORS: Constructs item names. Format: {Prefix} {Item}. Example: King's Long Sword */ "{0} {1}") : "{0} {1}"; - return fmt::format(fmt::runtime(fmt), translate ? _(pPrefix->PLName) : pPrefix->PLName, baseNamel); + return FormatRuntime(fmt, translate ? _(pPrefix->PLName) : pPrefix->PLName, baseNamel); } if (pSufix != nullptr) { const std::string_view fmt = translate ? _(/* TRANSLATORS: Constructs item names. Format: {Item} of {Suffix}. Example: Long Sword of the Whale */ "{0} of {1}") : "{0} of {1}"; - return fmt::format(fmt::runtime(fmt), baseNamel, translate ? _(pSufix->PLName) : pSufix->PLName); + return FormatRuntime(fmt, baseNamel, translate ? _(pSufix->PLName) : pSufix->PLName); } return std::string(baseNamel); @@ -1801,25 +1799,25 @@ void printItemMiscGamepad(const Item &item, bool isOil, bool isCastOnTarget) const std::string_view castButton = GetOptions().Padmapper.InputNameForAction("SpellAction"); if (item._iMiscId == IMISC_MAPOFDOOM) { - AddItemInfoBoxString(fmt::format(fmt::runtime(_("{} to view")), activateButton)); + AddItemInfoBoxString(FormatRuntime(_("{} to view"), activateButton)); } else if (isOil) { PrintItemOil(item._iMiscId); if (!invflag) { AddItemInfoBoxString(_("Open inventory to use")); } else { - AddItemInfoBoxString(fmt::format(fmt::runtime(_("{} to use")), activateButton)); + AddItemInfoBoxString(FormatRuntime(_("{} to use"), activateButton)); } } else if (isCastOnTarget) { - AddItemInfoBoxString(fmt::format(fmt::runtime(_("Select from spell book,\nthen {} to read")), castButton)); + AddItemInfoBoxString(FormatRuntime(_("Select from spell book,\nthen {} to read"), castButton)); } else if (IsAnyOf(item._iMiscId, IMISC_BOOK, IMISC_NOTE, IMISC_SCROLL, IMISC_SCROLLT)) { - AddItemInfoBoxString(fmt::format(fmt::runtime(_("{} to read")), activateButton)); + AddItemInfoBoxString(FormatRuntime(_("{} to read"), activateButton)); } } void PrintItemMisc(const Item &item) { if (item._iMiscId == IMISC_EAR) { - AddItemInfoBoxString(fmt::format(fmt::runtime(pgettext("player", "Level: {:d}")), item._ivalue)); + AddItemInfoBoxString(FormatRuntime(pgettext("player", "Level: {:d}"), item._ivalue)); return; } if (item._iMiscId == IMISC_AURIC) { @@ -1858,11 +1856,11 @@ void PrintItemInfo(const Item &item) if (str != 0 || mag != 0 || dex != 0) { std::string text = std::string(_("Required:")); if (str != 0) - text.append(fmt::format(fmt::runtime(_(" {:d} Str")), str)); + text.append(FormatRuntime(_(" {:d} Str"), str)); if (mag != 0) - text.append(fmt::format(fmt::runtime(_(" {:d} Mag")), mag)); + text.append(FormatRuntime(_(" {:d} Mag"), mag)); if (dex != 0) - text.append(fmt::format(fmt::runtime(_(" {:d} Dex")), dex)); + text.append(FormatRuntime(_(" {:d} Dex"), dex)); AddItemInfoBoxString(text); } } @@ -2218,10 +2216,10 @@ StringOrView GetTranslatedItemName(const Item &item) } if (item._iMiscId == IMISC_BOOK) { const std::string_view spellName = pgettext("spell", GetSpellData(item._iSpell).sNameText); - return fmt::format(fmt::runtime(_(/* TRANSLATORS: {:s} will be a spell name */ "Book of {:s}")), spellName); + return FormatRuntime(_(/* TRANSLATORS: {:s} will be a spell name */ "Book of {:s}"), spellName); } if (item._iMiscId == IMISC_EAR) { - return fmt::format(fmt::runtime(_(/* TRANSLATORS: {:s} will be a Character Name */ "Ear of {:s}")), item._iIName); + return FormatRuntime(_(/* TRANSLATORS: {:s} will be a Character Name */ "Ear of {:s}"), item._iIName); } if (item._iMiscId > IMISC_OILFIRST && item._iMiscId < IMISC_OILLAST) { for (size_t i = 0; i < 10; i++) { @@ -3575,7 +3573,7 @@ void RecreateEar(Item &item, uint16_t ic, uint32_t iseed, uint8_t bCursval, std: { InitializeItem(item, IDI_EAR); - const std::string itemName = fmt::format(fmt::runtime("Ear of {:s}"), heroName); + const std::string itemName = FormatRuntime("Ear of {:s}", heroName); CopyUtf8(item._iName, itemName, ItemNameLength); CopyUtf8(item._iIName, heroName, ItemNameLength); @@ -3827,7 +3825,7 @@ void GetItemStr(Item &item) InfoColor = item.getTextColor(); } else { const int nGold = item._ivalue; - InfoString = fmt::format(fmt::runtime(ngettext("{:s} gold piece", "{:s} gold pieces", nGold)), FormatInteger(nGold)); + InfoString = FormatRuntime(ngettext("{:s} gold piece", "{:s} gold pieces", nGold), FormatInteger(nGold)); } } @@ -3893,87 +3891,87 @@ bool DoOil(Player &player, int cii) switch (plidx) { case IPL_TOHIT: case IPL_TOHIT_CURSE: - return fmt::format(fmt::runtime(_("chance to hit: {:+d}%")), item._iPLToHit); + return FormatRuntime(_("chance to hit: {:+d}%"), item._iPLToHit); case IPL_DAMP: case IPL_DAMP_CURSE: - return fmt::format(fmt::runtime(_(/*xgettext:no-c-format*/ "{:+d}% damage")), item._iPLDam); + return FormatRuntime(_(/*xgettext:no-c-format*/ "{:+d}% damage"), item._iPLDam); case IPL_TOHIT_DAMP: case IPL_TOHIT_DAMP_CURSE: - return fmt::format(fmt::runtime(_("to hit: {:+d}%, {:+d}% damage")), item._iPLToHit, item._iPLDam); + return FormatRuntime(_("to hit: {:+d}%, {:+d}% damage"), item._iPLToHit, item._iPLDam); case IPL_ACP: case IPL_ACP_CURSE: - return fmt::format(fmt::runtime(_(/*xgettext:no-c-format*/ "{:+d}% armor")), item._iPLAC); + return FormatRuntime(_(/*xgettext:no-c-format*/ "{:+d}% armor"), item._iPLAC); case IPL_SETAC: case IPL_AC_CURSE: - return fmt::format(fmt::runtime(_("armor class: {:d}")), item._iAC); + return FormatRuntime(_("armor class: {:d}"), item._iAC); case IPL_FIRERES: case IPL_FIRERES_CURSE: if (item._iPLFR < MaxResistance) - return fmt::format(fmt::runtime(_("Resist Fire: {:+d}%")), item._iPLFR); + return FormatRuntime(_("Resist Fire: {:+d}%"), item._iPLFR); else - return fmt::format(fmt::runtime(_("Resist Fire: {:+d}% MAX")), MaxResistance); + return FormatRuntime(_("Resist Fire: {:+d}% MAX"), MaxResistance); case IPL_LIGHTRES: case IPL_LIGHTRES_CURSE: if (item._iPLLR < MaxResistance) - return fmt::format(fmt::runtime(_("Resist Lightning: {:+d}%")), item._iPLLR); + return FormatRuntime(_("Resist Lightning: {:+d}%"), item._iPLLR); else - return fmt::format(fmt::runtime(_("Resist Lightning: {:+d}% MAX")), MaxResistance); + return FormatRuntime(_("Resist Lightning: {:+d}% MAX"), MaxResistance); case IPL_MAGICRES: case IPL_MAGICRES_CURSE: if (item._iPLMR < MaxResistance) - return fmt::format(fmt::runtime(_("Resist Magic: {:+d}%")), item._iPLMR); + return FormatRuntime(_("Resist Magic: {:+d}%"), item._iPLMR); else - return fmt::format(fmt::runtime(_("Resist Magic: {:+d}% MAX")), MaxResistance); + return FormatRuntime(_("Resist Magic: {:+d}% MAX"), MaxResistance); case IPL_ALLRES: if (item._iPLFR < MaxResistance) - return fmt::format(fmt::runtime(_("Resist All: {:+d}%")), item._iPLFR); + return FormatRuntime(_("Resist All: {:+d}%"), item._iPLFR); else - return fmt::format(fmt::runtime(_("Resist All: {:+d}% MAX")), MaxResistance); + return FormatRuntime(_("Resist All: {:+d}% MAX"), MaxResistance); case IPL_SPLLVLADD: if (item._iSplLvlAdd > 0) - return fmt::format(fmt::runtime(ngettext("spells are increased {:d} level", "spells are increased {:d} levels", item._iSplLvlAdd)), item._iSplLvlAdd); + return FormatRuntime(ngettext("spells are increased {:d} level", "spells are increased {:d} levels", item._iSplLvlAdd), item._iSplLvlAdd); else if (item._iSplLvlAdd < 0) - return fmt::format(fmt::runtime(ngettext("spells are decreased {:d} level", "spells are decreased {:d} levels", -item._iSplLvlAdd)), -item._iSplLvlAdd); + return FormatRuntime(ngettext("spells are decreased {:d} level", "spells are decreased {:d} levels", -item._iSplLvlAdd), -item._iSplLvlAdd); else return _("spell levels unchanged (?)"); case IPL_CHARGES: return _("Extra charges"); case IPL_SPELL: - return fmt::format(fmt::runtime(ngettext("{:d} {:s} charge", "{:d} {:s} charges", item._iMaxCharges)), item._iMaxCharges, pgettext("spell", GetSpellData(item._iSpell).sNameText)); + return FormatRuntime(ngettext("{:d} {:s} charge", "{:d} {:s} charges", item._iMaxCharges), item._iMaxCharges, pgettext("spell", GetSpellData(item._iSpell).sNameText)); case IPL_FIREDAM: if (item._iFMinDam == item._iFMaxDam) - return fmt::format(fmt::runtime(_("Fire hit damage: {:d}")), item._iFMinDam); + return FormatRuntime(_("Fire hit damage: {:d}"), item._iFMinDam); else - return fmt::format(fmt::runtime(_("Fire hit damage: {:d}-{:d}")), item._iFMinDam, item._iFMaxDam); + return FormatRuntime(_("Fire hit damage: {:d}-{:d}"), item._iFMinDam, item._iFMaxDam); case IPL_LIGHTDAM: if (item._iLMinDam == item._iLMaxDam) - return fmt::format(fmt::runtime(_("Lightning hit damage: {:d}")), item._iLMinDam); + return FormatRuntime(_("Lightning hit damage: {:d}"), item._iLMinDam); else - return fmt::format(fmt::runtime(_("Lightning hit damage: {:d}-{:d}")), item._iLMinDam, item._iLMaxDam); + return FormatRuntime(_("Lightning hit damage: {:d}-{:d}"), item._iLMinDam, item._iLMaxDam); case IPL_STR: case IPL_STR_CURSE: - return fmt::format(fmt::runtime(_("{:+d} to strength")), item._iPLStr); + return FormatRuntime(_("{:+d} to strength"), item._iPLStr); case IPL_MAG: case IPL_MAG_CURSE: - return fmt::format(fmt::runtime(_("{:+d} to magic")), item._iPLMag); + return FormatRuntime(_("{:+d} to magic"), item._iPLMag); case IPL_DEX: case IPL_DEX_CURSE: - return fmt::format(fmt::runtime(_("{:+d} to dexterity")), item._iPLDex); + return FormatRuntime(_("{:+d} to dexterity"), item._iPLDex); case IPL_VIT: case IPL_VIT_CURSE: - return fmt::format(fmt::runtime(_("{:+d} to vitality")), item._iPLVit); + return FormatRuntime(_("{:+d} to vitality"), item._iPLVit); case IPL_ATTRIBS: case IPL_ATTRIBS_CURSE: - return fmt::format(fmt::runtime(_("{:+d} to all attributes")), item._iPLStr); + return FormatRuntime(_("{:+d} to all attributes"), item._iPLStr); case IPL_GETHIT_CURSE: case IPL_GETHIT: - return fmt::format(fmt::runtime(_("{:+d} damage from enemies")), item._iPLGetHit); + return FormatRuntime(_("{:+d} damage from enemies"), item._iPLGetHit); case IPL_LIFE: case IPL_LIFE_CURSE: - return fmt::format(fmt::runtime(_("Hit Points: {:+d}")), item._iPLHP >> 6); + return FormatRuntime(_("Hit Points: {:+d}"), item._iPLHP >> 6); case IPL_MANA: case IPL_MANA_CURSE: - return fmt::format(fmt::runtime(_("Mana: {:+d}")), item._iPLMana >> 6); + return FormatRuntime(_("Mana: {:+d}"), item._iPLMana >> 6); case IPL_DUR: return _("high durability"); case IPL_DUR_CURSE: @@ -3981,26 +3979,26 @@ bool DoOil(Player &player, int cii) case IPL_INDESTRUCTIBLE: return _("indestructible"); case IPL_LIGHT: - return fmt::format(fmt::runtime(_(/*xgettext:no-c-format*/ "+{:d}% light radius")), 10 * item._iPLLight); + return FormatRuntime(_(/*xgettext:no-c-format*/ "+{:d}% light radius"), 10 * item._iPLLight); case IPL_LIGHT_CURSE: - return fmt::format(fmt::runtime(_(/*xgettext:no-c-format*/ "-{:d}% light radius")), -10 * item._iPLLight); + return FormatRuntime(_(/*xgettext:no-c-format*/ "-{:d}% light radius"), -10 * item._iPLLight); case IPL_MULT_ARROWS: return _("multiple arrows per shot"); case IPL_FIRE_ARROWS: if (item._iFMinDam == item._iFMaxDam) - return fmt::format(fmt::runtime(_("fire arrows damage: {:d}")), item._iFMinDam); + return FormatRuntime(_("fire arrows damage: {:d}"), item._iFMinDam); else - return fmt::format(fmt::runtime(_("fire arrows damage: {:d}-{:d}")), item._iFMinDam, item._iFMaxDam); + return FormatRuntime(_("fire arrows damage: {:d}-{:d}"), item._iFMinDam, item._iFMaxDam); case IPL_LIGHT_ARROWS: if (item._iLMinDam == item._iLMaxDam) - return fmt::format(fmt::runtime(_("lightning arrows damage {:d}")), item._iLMinDam); + return FormatRuntime(_("lightning arrows damage {:d}"), item._iLMinDam); else - return fmt::format(fmt::runtime(_("lightning arrows damage {:d}-{:d}")), item._iLMinDam, item._iLMaxDam); + return FormatRuntime(_("lightning arrows damage {:d}-{:d}"), item._iLMinDam, item._iLMaxDam); case IPL_FIREBALL: if (item._iFMinDam == item._iFMaxDam) - return fmt::format(fmt::runtime(_("fireball damage: {:d}")), item._iFMinDam); + return FormatRuntime(_("fireball damage: {:d}"), item._iFMinDam); else - return fmt::format(fmt::runtime(_("fireball damage: {:d}-{:d}")), item._iFMinDam, item._iFMaxDam); + return FormatRuntime(_("fireball damage: {:d}-{:d}"), item._iFMinDam, item._iFMaxDam); case IPL_THORNS: return _("attacker takes 1-3 damage"); case IPL_NOMANA: @@ -4048,7 +4046,7 @@ bool DoOil(Player &player, int cii) case IPL_FASTBLOCK: return _("fast block"); case IPL_DAMMOD: - return fmt::format(fmt::runtime(ngettext("adds {:d} point to damage", "adds {:d} points to damage", item._iPLDamMod)), item._iPLDamMod); + return FormatRuntime(ngettext("adds {:d} point to damage", "adds {:d} points to damage", item._iPLDamMod), item._iPLDamMod); case IPL_RNDARROWVEL: return _("fires random speed arrows"); case IPL_SETDAM: @@ -4065,23 +4063,23 @@ bool DoOil(Player &player, int cii) return _("no strength requirement"); case IPL_ADDACLIFE: if (item._iFMinDam == item._iFMaxDam) - return fmt::format(fmt::runtime(_("lightning damage: {:d}")), item._iFMinDam); + return FormatRuntime(_("lightning damage: {:d}"), item._iFMinDam); else - return fmt::format(fmt::runtime(_("lightning damage: {:d}-{:d}")), item._iFMinDam, item._iFMaxDam); + return FormatRuntime(_("lightning damage: {:d}-{:d}"), item._iFMinDam, item._iFMaxDam); case IPL_ADDMANAAC: return _("charged bolts on hits"); case IPL_DEVASTATION: return _("occasional triple damage"); case IPL_DECAY: - return fmt::format(fmt::runtime(_(/*xgettext:no-c-format*/ "decaying {:+d}% damage")), item._iPLDam); + return FormatRuntime(_(/*xgettext:no-c-format*/ "decaying {:+d}% damage"), item._iPLDam); case IPL_PERIL: return _("2x dmg to monst, 1x to you"); case IPL_JESTERS: return std::string(_(/*xgettext:no-c-format*/ "Random 0 - 600% damage")); case IPL_CRYSTALLINE: - return fmt::format(fmt::runtime(_(/*xgettext:no-c-format*/ "low dur, {:+d}% damage")), item._iPLDam); + return FormatRuntime(_(/*xgettext:no-c-format*/ "low dur, {:+d}% damage"), item._iPLDam); case IPL_DOPPELGANGER: - return fmt::format(fmt::runtime(_("to hit: {:+d}%, {:+d}% damage")), item._iPLToHit, item._iPLDam); + return FormatRuntime(_("to hit: {:+d}%, {:+d}% damage"), item._iPLToHit, item._iPLDam); case IPL_ACDEMON: return _("extra AC vs demons"); case IPL_ACUNDEAD: @@ -4132,24 +4130,24 @@ void PrintItemDetails(const Item &item) if (item._iClass == ICLASS_WEAPON) { if (item._iMinDam == item._iMaxDam) { if (item._iMaxDur == DUR_INDESTRUCTIBLE) - AddItemInfoBoxString(fmt::format(fmt::runtime(_("damage: {:d} Indestructible")), item._iMinDam)); + AddItemInfoBoxString(FormatRuntime(_("damage: {:d} Indestructible"), item._iMinDam)); else - AddItemInfoBoxString(fmt::format(fmt::runtime(_(/* TRANSLATORS: Dur: is durability */ "damage: {:d} Dur: {:d}/{:d}")), item._iMinDam, item._iDurability, item._iMaxDur)); + AddItemInfoBoxString(FormatRuntime(_(/* TRANSLATORS: Dur: is durability */ "damage: {:d} Dur: {:d}/{:d}"), item._iMinDam, item._iDurability, item._iMaxDur)); } else { if (item._iMaxDur == DUR_INDESTRUCTIBLE) - AddItemInfoBoxString(fmt::format(fmt::runtime(_("damage: {:d}-{:d} Indestructible")), item._iMinDam, item._iMaxDam)); + AddItemInfoBoxString(FormatRuntime(_("damage: {:d}-{:d} Indestructible"), item._iMinDam, item._iMaxDam)); else - AddItemInfoBoxString(fmt::format(fmt::runtime(_(/* TRANSLATORS: Dur: is durability */ "damage: {:d}-{:d} Dur: {:d}/{:d}")), item._iMinDam, item._iMaxDam, item._iDurability, item._iMaxDur)); + AddItemInfoBoxString(FormatRuntime(_(/* TRANSLATORS: Dur: is durability */ "damage: {:d}-{:d} Dur: {:d}/{:d}"), item._iMinDam, item._iMaxDam, item._iDurability, item._iMaxDur)); } } if (item._iClass == ICLASS_ARMOR) { if (item._iMaxDur == DUR_INDESTRUCTIBLE) - AddItemInfoBoxString(fmt::format(fmt::runtime(_("armor: {:d} Indestructible")), item._iAC)); + AddItemInfoBoxString(FormatRuntime(_("armor: {:d} Indestructible"), item._iAC)); else - AddItemInfoBoxString(fmt::format(fmt::runtime(_(/* TRANSLATORS: Dur: is durability */ "armor: {:d} Dur: {:d}/{:d}")), item._iAC, item._iDurability, item._iMaxDur)); + AddItemInfoBoxString(FormatRuntime(_(/* TRANSLATORS: Dur: is durability */ "armor: {:d} Dur: {:d}/{:d}"), item._iAC, item._iDurability, item._iMaxDur)); } if (item._iMiscId == IMISC_STAFF && item._iMaxCharges != 0) { - AddItemInfoBoxString(fmt::format(fmt::runtime(_("Charges: {:d}/{:d}")), item._iCharges, item._iMaxCharges)); + AddItemInfoBoxString(FormatRuntime(_("Charges: {:d}/{:d}"), item._iCharges, item._iMaxCharges)); } if (item._iPrePower != -1) { AddItemInfoBoxString(PrintItemPower(item._iPrePower, item)); @@ -4173,30 +4171,30 @@ void PrintItemDur(const Item &item) if (item._iClass == ICLASS_WEAPON) { if (item._iMinDam == item._iMaxDam) { if (item._iMaxDur == DUR_INDESTRUCTIBLE) - AddItemInfoBoxString(fmt::format(fmt::runtime(_("damage: {:d} Indestructible")), item._iMinDam)); + AddItemInfoBoxString(FormatRuntime(_("damage: {:d} Indestructible"), item._iMinDam)); else - AddItemInfoBoxString(fmt::format(fmt::runtime(_("damage: {:d} Dur: {:d}/{:d}")), item._iMinDam, item._iDurability, item._iMaxDur)); + AddItemInfoBoxString(FormatRuntime(_("damage: {:d} Dur: {:d}/{:d}"), item._iMinDam, item._iDurability, item._iMaxDur)); } else { if (item._iMaxDur == DUR_INDESTRUCTIBLE) - AddItemInfoBoxString(fmt::format(fmt::runtime(_("damage: {:d}-{:d} Indestructible")), item._iMinDam, item._iMaxDam)); + AddItemInfoBoxString(FormatRuntime(_("damage: {:d}-{:d} Indestructible"), item._iMinDam, item._iMaxDam)); else - AddItemInfoBoxString(fmt::format(fmt::runtime(_("damage: {:d}-{:d} Dur: {:d}/{:d}")), item._iMinDam, item._iMaxDam, item._iDurability, item._iMaxDur)); + AddItemInfoBoxString(FormatRuntime(_("damage: {:d}-{:d} Dur: {:d}/{:d}"), item._iMinDam, item._iMaxDam, item._iDurability, item._iMaxDur)); } if (item._iMiscId == IMISC_STAFF && item._iMaxCharges > 0) { - AddItemInfoBoxString(fmt::format(fmt::runtime(_("Charges: {:d}/{:d}")), item._iCharges, item._iMaxCharges)); + AddItemInfoBoxString(FormatRuntime(_("Charges: {:d}/{:d}"), item._iCharges, item._iMaxCharges)); } if (item._iMagical != ITEM_QUALITY_NORMAL) AddItemInfoBoxString(_("Not Identified")); } if (item._iClass == ICLASS_ARMOR) { if (item._iMaxDur == DUR_INDESTRUCTIBLE) - AddItemInfoBoxString(fmt::format(fmt::runtime(_("armor: {:d} Indestructible")), item._iAC)); + AddItemInfoBoxString(FormatRuntime(_("armor: {:d} Indestructible"), item._iAC)); else - AddItemInfoBoxString(fmt::format(fmt::runtime(_("armor: {:d} Dur: {:d}/{:d}")), item._iAC, item._iDurability, item._iMaxDur)); + AddItemInfoBoxString(FormatRuntime(_("armor: {:d} Dur: {:d}/{:d}"), item._iAC, item._iDurability, item._iMaxDur)); if (item._iMagical != ITEM_QUALITY_NORMAL) AddItemInfoBoxString(_("Not Identified")); if (item._iMiscId == IMISC_STAFF && item._iMaxCharges > 0) { - AddItemInfoBoxString(fmt::format(fmt::runtime(_("Charges: {:d}/{:d}")), item._iCharges, item._iMaxCharges)); + AddItemInfoBoxString(FormatRuntime(_("Charges: {:d}/{:d}"), item._iCharges, item._iMaxCharges)); } } if (IsAnyOf(item._itype, ItemType::Ring, ItemType::Amulet)) diff --git a/Source/levels/themes.cpp b/Source/levels/themes.cpp index 83a0463e5..d6cf9e9e2 100644 --- a/Source/levels/themes.cpp +++ b/Source/levels/themes.cpp @@ -7,8 +7,6 @@ #include -#include - #include "engine/path.h" #include "engine/points_in_rectangle_range.hpp" #include "engine/random.hpp" diff --git a/Source/levels/trigs.cpp b/Source/levels/trigs.cpp index c3b88ae25..bde600d5a 100644 --- a/Source/levels/trigs.cpp +++ b/Source/levels/trigs.cpp @@ -8,8 +8,6 @@ #include #include -#include - #include "control/control.hpp" #include "controls/control_mode.hpp" #include "controls/plrctrls.h" @@ -18,6 +16,7 @@ #include "game_mode.hpp" #include "multi.h" #include "utils/algorithm/container.hpp" +#include "utils/format.hpp" #include "utils/is_of.hpp" #include "utils/language.h" #include "utils/utf8.hpp" @@ -416,7 +415,7 @@ bool ForceL1Trig() for (const uint16_t tileId : L1UpList) { if (dPiece[cursPosition.x][cursPosition.y] == tileId) { if (currlevel > 1) - InfoString = fmt::format(fmt::runtime(_("Up to level {:d}")), currlevel - 1); + InfoString = FormatRuntime(_("Up to level {:d}"), currlevel - 1); else InfoString = _("Up to town"); for (int j = 0; j < numtrigs; j++) { @@ -429,7 +428,7 @@ bool ForceL1Trig() } for (const uint16_t tileId : L1DownList) { if (dPiece[cursPosition.x][cursPosition.y] == tileId) { - InfoString = fmt::format(fmt::runtime(_("Down to level {:d}")), currlevel + 1); + InfoString = FormatRuntime(_("Down to level {:d}"), currlevel + 1); for (int j = 0; j < numtrigs; j++) { if (trigs[j]._tmsg == WM_DIABNEXTLVL) { cursPosition = trigs[j].position; @@ -451,7 +450,7 @@ bool ForceL2Trig() const int dx = std::abs(trigs[j].position.x - cursPosition.x); const int dy = std::abs(trigs[j].position.y - cursPosition.y); if (dx < 4 && dy < 4) { - InfoString = fmt::format(fmt::runtime(_("Up to level {:d}")), currlevel - 1); + InfoString = FormatRuntime(_("Up to level {:d}"), currlevel - 1); cursPosition = trigs[j].position; return true; } @@ -462,7 +461,7 @@ bool ForceL2Trig() for (const uint16_t tileId : L2DownList) { if (dPiece[cursPosition.x][cursPosition.y] == tileId) { - InfoString = fmt::format(fmt::runtime(_("Down to level {:d}")), currlevel + 1); + InfoString = FormatRuntime(_("Down to level {:d}"), currlevel + 1); for (int j = 0; j < numtrigs; j++) { if (trigs[j]._tmsg == WM_DIABNEXTLVL) { cursPosition = trigs[j].position; @@ -497,7 +496,7 @@ bool ForceL3Trig() { for (const uint16_t tileId : L3UpList) { if (dPiece[cursPosition.x][cursPosition.y] == tileId) { - InfoString = fmt::format(fmt::runtime(_("Up to level {:d}")), currlevel - 1); + InfoString = FormatRuntime(_("Up to level {:d}"), currlevel - 1); for (int j = 0; j < numtrigs; j++) { if (trigs[j]._tmsg == WM_DIABPREVLVL) { const int dx = std::abs(trigs[j].position.x - cursPosition.x); @@ -514,7 +513,7 @@ bool ForceL3Trig() if (dPiece[cursPosition.x][cursPosition.y] == tileId || dPiece[cursPosition.x + 1][cursPosition.y] == tileId || dPiece[cursPosition.x + 2][cursPosition.y] == tileId) { - InfoString = fmt::format(fmt::runtime(_("Down to level {:d}")), currlevel + 1); + InfoString = FormatRuntime(_("Down to level {:d}"), currlevel + 1); for (int j = 0; j < numtrigs; j++) { if (trigs[j]._tmsg == WM_DIABNEXTLVL) { cursPosition = trigs[j].position; @@ -549,7 +548,7 @@ bool ForceL4Trig() { for (const uint16_t tileId : L4UpList) { if (dPiece[cursPosition.x][cursPosition.y] == tileId) { - InfoString = fmt::format(fmt::runtime(_("Up to level {:d}")), currlevel - 1); + InfoString = FormatRuntime(_("Up to level {:d}"), currlevel - 1); for (int j = 0; j < numtrigs; j++) { if (trigs[j]._tmsg == WM_DIABPREVLVL) { cursPosition = trigs[j].position; @@ -561,7 +560,7 @@ bool ForceL4Trig() for (const uint16_t tileId : L4DownList) { if (dPiece[cursPosition.x][cursPosition.y] == tileId) { - InfoString = fmt::format(fmt::runtime(_("Down to level {:d}")), currlevel + 1); + InfoString = FormatRuntime(_("Down to level {:d}"), currlevel + 1); for (int j = 0; j < numtrigs; j++) { if (trigs[j]._tmsg == WM_DIABNEXTLVL) { cursPosition = trigs[j].position; @@ -610,7 +609,7 @@ bool ForceHiveTrig() { for (const uint16_t tileId : L6UpList) { if (dPiece[cursPosition.x][cursPosition.y] == tileId) { - InfoString = fmt::format(fmt::runtime(_("Up to Nest level {:d}")), currlevel - 17); + InfoString = FormatRuntime(_("Up to Nest level {:d}"), currlevel - 17); for (int j = 0; j < numtrigs; j++) { if (trigs[j]._tmsg == WM_DIABPREVLVL) { cursPosition = trigs[j].position; @@ -623,7 +622,7 @@ bool ForceHiveTrig() if (dPiece[cursPosition.x][cursPosition.y] == tileId || dPiece[cursPosition.x + 1][cursPosition.y] == tileId || dPiece[cursPosition.x + 2][cursPosition.y] == tileId) { - InfoString = fmt::format(fmt::runtime(_("Down to level {:d}")), currlevel - 15); + InfoString = FormatRuntime(_("Down to level {:d}"), currlevel - 15); for (int j = 0; j < numtrigs; j++) { if (trigs[j]._tmsg == WM_DIABNEXTLVL) { cursPosition = trigs[j].position; @@ -658,7 +657,7 @@ bool ForceCryptTrig() { for (const uint16_t tileId : L5UpList) { if (dPiece[cursPosition.x][cursPosition.y] == tileId) { - InfoString = fmt::format(fmt::runtime(_("Up to Crypt level {:d}")), currlevel - 21); + InfoString = FormatRuntime(_("Up to Crypt level {:d}"), currlevel - 21); for (int j = 0; j < numtrigs; j++) { if (trigs[j]._tmsg == WM_DIABPREVLVL) { cursPosition = trigs[j].position; @@ -673,7 +672,7 @@ bool ForceCryptTrig() } for (const uint16_t tileId : L5DownList) { if (dPiece[cursPosition.x][cursPosition.y] == tileId) { - InfoString = fmt::format(fmt::runtime(_("Down to Crypt level {:d}")), currlevel - 19); + InfoString = FormatRuntime(_("Down to Crypt level {:d}"), currlevel - 19); for (int j = 0; j < numtrigs; j++) { if (trigs[j]._tmsg == WM_DIABNEXTLVL) { cursPosition = trigs[j].position; @@ -721,7 +720,7 @@ bool ForceSKingTrig() { for (const uint16_t tileId : L1UpList) { if (dPiece[cursPosition.x][cursPosition.y] == tileId) { - InfoString = fmt::format(fmt::runtime(_("Back to Level {:d}")), Quests[Q_SKELKING]._qlevel); + InfoString = FormatRuntime(_("Back to Level {:d}"), Quests[Q_SKELKING]._qlevel); cursPosition = trigs[0].position; return true; @@ -735,7 +734,7 @@ bool ForceSChambTrig() { for (const uint16_t tileId : L2DownList) { if (dPiece[cursPosition.x][cursPosition.y] == tileId) { - InfoString = fmt::format(fmt::runtime(_("Back to Level {:d}")), Quests[Q_SCHAMB]._qlevel); + InfoString = FormatRuntime(_("Back to Level {:d}"), Quests[Q_SCHAMB]._qlevel); cursPosition = trigs[0].position; return true; @@ -749,7 +748,7 @@ bool ForcePWaterTrig() { for (const uint16_t tileId : L3DownList) { if (dPiece[cursPosition.x][cursPosition.y] == tileId) { - InfoString = fmt::format(fmt::runtime(_("Back to Level {:d}")), Quests[Q_PWATER]._qlevel); + InfoString = FormatRuntime(_("Back to Level {:d}"), Quests[Q_PWATER]._qlevel); cursPosition = trigs[0].position; return true; diff --git a/Source/lua/modules/i18n.cpp b/Source/lua/modules/i18n.cpp index 9891e2091..615fddc8f 100644 --- a/Source/lua/modules/i18n.cpp +++ b/Source/lua/modules/i18n.cpp @@ -2,10 +2,10 @@ #include -#include #include #include "lua/metadoc.hpp" +#include "utils/format.hpp" #include "utils/language.h" namespace devilution { @@ -19,7 +19,7 @@ sol::table LuaI18nModule(sol::state_view &lua) "Translates the given string", [](const char *key) { return LanguageTranslate(key); }); LuaSetDocFn(table, "plural_translate", "(singular: string, plural: string, count: integer)", "Returns a singular or plural translation for the given keys and count", [](const char *singular, std::string_view plural, int count) { - return fmt::format(fmt::runtime(LanguagePluralTranslate(singular, plural, count)), count); + return FormatRuntime(LanguagePluralTranslate(singular, plural, count), count); }); LuaSetDocFn(table, "particular_translate", "(context: string, text: string)", "Returns the translation for the given context identifier and key.", LanguageParticularTranslate); diff --git a/Source/lua/modules/items.cpp b/Source/lua/modules/items.cpp index f83328aac..e7c6ff498 100644 --- a/Source/lua/modules/items.cpp +++ b/Source/lua/modules/items.cpp @@ -2,7 +2,6 @@ #include -#include #include #include "data/file.hpp" diff --git a/Source/lua/modules/log.cpp b/Source/lua/modules/log.cpp index eea84a90e..638862b94 100644 --- a/Source/lua/modules/log.cpp +++ b/Source/lua/modules/log.cpp @@ -6,12 +6,23 @@ #include #endif -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include +#include "utils/attributes.h" #include "utils/log.hpp" +#include "utils/str_cat.hpp" #ifdef USE_SDL1 #include "utils/sdl2_to_1_2_backports.h" @@ -21,50 +32,113 @@ namespace devilution { namespace { +using LuaFormatArg = std::variant; + +/** + * @brief Formats a message with arguments only known at runtime. + * + * `std::format` has no equivalent of `fmt::dynamic_format_arg_store`, so we + * parse the replacement fields ourselves and format each one individually. + */ +std::expected FormatLuaMessage(std::string_view fmt, std::span args) +{ + std::string result; + result.reserve(fmt.size()); + size_t autoIndex = 0; + bool manualIndexing = false; + bool automaticIndexing = false; + for (size_t i = 0; i < fmt.size(); ++i) { + const char c = fmt[i]; + if (c == '}') { + if (i + 1 < fmt.size() && fmt[i + 1] == '}') { + result += '}'; + ++i; + continue; + } + return std::unexpected("unmatched '}' in format string"); + } + if (c != '{') { + result += c; + continue; + } + if (i + 1 < fmt.size() && fmt[i + 1] == '{') { + result += '{'; + ++i; + continue; + } + const size_t fieldEnd = fmt.find_first_of("{}", i + 1); + if (fieldEnd == std::string_view::npos || fmt[fieldEnd] == '{') + return std::unexpected("unmatched '{' in format string"); + const std::string_view field = fmt.substr(i + 1, fieldEnd - (i + 1)); + const size_t colonPos = field.find(':'); + const std::string_view argId = field.substr(0, colonPos); + size_t argIndex; + if (argId.empty()) { + if (manualIndexing) + return std::unexpected("cannot switch from manual to automatic argument indexing"); + automaticIndexing = true; + argIndex = autoIndex++; + } else { + if (automaticIndexing) + return std::unexpected("cannot switch from automatic to manual argument indexing"); + manualIndexing = true; + const std::from_chars_result parseResult = std::from_chars(argId.data(), argId.data() + argId.size(), argIndex); + if (parseResult.ec != std::errc() || parseResult.ptr != argId.data() + argId.size()) + return std::unexpected(StrCat("invalid argument id \"", argId, "\"")); + } + if (argIndex >= args.size()) + return std::unexpected(StrCat("argument index ", argIndex, " out of range")); + const std::string fieldFmt = colonPos == std::string_view::npos + ? "{}" + : StrCat("{:", field.substr(colonPos + 1), "}"); +#if DVL_EXCEPTIONS + try { +#endif + std::visit([&](const auto &value) { + result += std::vformat(fieldFmt, std::make_format_args(value)); + }, + args[argIndex]); +#if DVL_EXCEPTIONS + } catch (const std::format_error &e) { + return std::unexpected(e.what()); + } +#endif + i = fieldEnd; + } + return result; +} + void LuaLogMessage(LogPriority priority, std::string_view fmt, sol::variadic_args args) { - std::string formatted; - FMT_TRY - { - fmt::dynamic_format_arg_store store; - for (const sol::stack_proxy arg : args) { - switch (arg.get_type()) { - case sol::type::boolean: - store.push_back(arg.as()); - break; - case sol::type::number: - if (lua_isinteger(arg.lua_state(), arg.stack_index())) { - store.push_back(lua_tointeger(arg.lua_state(), arg.stack_index())); - } else { - store.push_back(lua_tonumber(arg.lua_state(), arg.stack_index())); - } - break; - case sol::type::string: - store.push_back(arg.as()); - break; - default: - store.push_back(sol::utility::to_string(sol::stack_object(arg))); - break; + std::vector formatArgs; + formatArgs.reserve(args.size()); + for (const sol::stack_proxy arg : args) { + switch (arg.get_type()) { + case sol::type::boolean: + formatArgs.emplace_back(arg.as()); + break; + case sol::type::number: + if (lua_isinteger(arg.lua_state(), arg.stack_index())) { + formatArgs.emplace_back(lua_tointeger(arg.lua_state(), arg.stack_index())); + } else { + formatArgs.emplace_back(lua_tonumber(arg.lua_state(), arg.stack_index())); } + break; + case sol::type::string: + formatArgs.emplace_back(arg.as()); + break; + default: + formatArgs.emplace_back(sol::utility::to_string(sol::stack_object(arg))); + break; } - formatted = fmt::vformat(fmt, store); } -#if FMT_EXCEPTIONS - FMT_CATCH(const fmt::format_error &e) - { - // e.what() is undefined if exceptions are disabled, so we wrap it - // with an `FMT_EXCEPTIONS` check. - std::string error = e.what(); -#else - FMT_CATCH(const fmt::format_error &) - { - std::string error = "unknown (FMT_EXCEPTIONS disabled)"; -#endif - std::string fullError = StrCat("Format error, fmt: ", fmt, " error: ", error); + const std::expected formatted = FormatLuaMessage(fmt, formatArgs); + if (!formatted.has_value()) { + const std::string fullError = StrCat("Format error, fmt: ", fmt, " error: ", formatted.error()); SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "%s", fullError.c_str()); return; } - SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, static_cast(priority), "%s", formatted.c_str()); + SDL_LogMessage(SDL_LOG_CATEGORY_APPLICATION, static_cast(priority), "%s", formatted->c_str()); } void LuaLogInfo(std::string_view fmt, sol::variadic_args args) diff --git a/Source/lua/modules/monsters.cpp b/Source/lua/modules/monsters.cpp index f648d500d..b0e79ad5b 100644 --- a/Source/lua/modules/monsters.cpp +++ b/Source/lua/modules/monsters.cpp @@ -2,7 +2,6 @@ #include -#include #include #include "data/file.hpp" diff --git a/Source/monster.cpp b/Source/monster.cpp index 1ec207728..e016e306b 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -31,9 +31,6 @@ #include #endif -#include -#include - #include "automap.h" #include "control/control.hpp" #include "crawl.hpp" @@ -96,6 +93,7 @@ #include "utils/endian_swap.hpp" #include "utils/enum_traits.h" #include "utils/file_name_generator.hpp" +#include "utils/format.hpp" #include "utils/is_of.hpp" #include "utils/language.h" #include "utils/log.hpp" @@ -4472,9 +4470,9 @@ void M_FallenFear(Point position) void PrintMonstHistory(int mt) { if (*GetOptions().Gameplay.showMonsterType) { - AddInfoBoxString(fmt::format(fmt::runtime(_("Type: {:s} Kills: {:d}")), GetMonsterTypeText(MonstersData[mt]), MonsterKillCounts[mt])); + AddInfoBoxString(FormatRuntime(_("Type: {:s} Kills: {:d}"), GetMonsterTypeText(MonstersData[mt]), MonsterKillCounts[mt])); } else { - AddInfoBoxString(fmt::format(fmt::runtime(_("Total kills: {:d}")), MonsterKillCounts[mt])); + AddInfoBoxString(FormatRuntime(_("Total kills: {:d}"), MonsterKillCounts[mt])); } if (MonsterKillCounts[mt] >= 30) { @@ -4500,7 +4498,7 @@ void PrintMonstHistory(int mt) minHP = 4 * minHP + hpBonusHell; maxHP = 4 * maxHP + hpBonusHell; } - AddInfoBoxString(fmt::format(fmt::runtime(_("Hit Points: {:d}-{:d}")), minHP, maxHP)); + AddInfoBoxString(FormatRuntime(_("Hit Points: {:d}-{:d}"), minHP, maxHP)); } if (MonsterKillCounts[mt] >= 15) { const int res = (sgGameInitInfo.nDifficulty != DIFF_HELL) ? MonstersData[mt].resistance : MonstersData[mt].resistanceHell; @@ -4535,7 +4533,7 @@ void PrintUniqueHistory() { const Monster &monster = Monsters[pcursmonst]; if (*GetOptions().Gameplay.showMonsterType) { - AddInfoBoxString(fmt::format(fmt::runtime(_("Type: {:s}")), GetMonsterTypeText(monster.data()))); + AddInfoBoxString(FormatRuntime(_("Type: {:s}"), GetMonsterTypeText(monster.data()))); } const int res = monster.resistance & (RESIST_MAGIC | RESIST_FIRE | RESIST_LIGHTNING | IMMUNE_MAGIC | IMMUNE_FIRE | IMMUNE_LIGHTNING); diff --git a/Source/msg.cpp b/Source/msg.cpp index 67b949f33..93599b355 100644 --- a/Source/msg.cpp +++ b/Source/msg.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -18,7 +19,6 @@ #endif #include -#include #if !defined(UNPACKED_MPQS) || !defined(UNPACKED_SAVES) || !defined(NONET) #define USE_PKWARE @@ -58,6 +58,7 @@ #include "tmsg.h" #include "towners.h" #include "utils/endian_swap.hpp" +#include "utils/format.hpp" #include "utils/is_of.hpp" #include "utils/language.h" #include "utils/str_cat.hpp" @@ -86,7 +87,7 @@ namespace devilution { void EventFailedPacket(const char *playerName) { - const std::string message = fmt::format("Player '{}' sent an invalid packet.", playerName); + const std::string message = std::format("Player '{}' sent an invalid packet.", playerName); EventPlrMsg(message); } @@ -2313,7 +2314,7 @@ size_t OnPlayerJoinLevel(const TCmdLocParam2 &message, Player &player) ResetPlayerGFX(player); player.plractive = true; gbActivePlayers++; - EventPlrMsg(fmt::format(fmt::runtime(_("Player '{:s}' (level {:d}) just joined the game")), player._pName, player.getCharacterLevel())); + EventPlrMsg(FormatRuntime(_("Player '{:s}' (level {:d}) just joined the game"), player._pName, player.getCharacterLevel())); } if (player.plractive && &player != MyPlayer) { diff --git a/Source/multi.cpp b/Source/multi.cpp index 344dc8bab..0a81beb06 100644 --- a/Source/multi.cpp +++ b/Source/multi.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #ifdef USE_SDL3 @@ -19,7 +20,6 @@ #endif #include -#include #include "DiabloUI/diabloui.h" #include "diablo.h" @@ -43,6 +43,7 @@ #include "tmsg.h" #include "utils/endian_read.hpp" #include "utils/endian_swap.hpp" +#include "utils/format.hpp" #include "utils/is_of.hpp" #include "utils/language.h" #include "utils/log.hpp" @@ -302,7 +303,7 @@ void PlayerLeftMsg(Player &player, bool left) else LogInfo("Player left the {} game ({}, {}/{} players)", ConnectionNames[provider], reasonDescription, remainingPlayers, MAX_PLRS); } - EventPlrMsg(fmt::format(fmt::runtime(pszFmt), player._pName)); + EventPlrMsg(FormatRuntime(pszFmt, player._pName)); } player.plractive = false; player._pName[0] = '\0'; @@ -589,13 +590,13 @@ DVL_API_FOR_TEST std::string DescribeLeaveReason(leaveinfo_t leaveReason) case leaveinfo_t::LEAVE_DROP: return "connection timeout"; default: - return fmt::format("code 0x{:08X}", static_cast(leaveReason)); + return std::format("code 0x{:08X}", static_cast(leaveReason)); } } std::string FormatGameSeed(const uint32_t gameSeed[4]) { - return fmt::format("{:08X}{:08X}{:08X}{:08X}", + return std::format("{:08X}{:08X}{:08X}{:08X}", gameSeed[0], gameSeed[1], gameSeed[2], gameSeed[3]); } @@ -894,7 +895,7 @@ bool NetInit(bool bSinglePlayer) Player &myPlayer = *MyPlayer; // separator for marking messages from a different game AddMessageToChatLog(_("New Game"), nullptr, UiFlags::ColorRed); - AddMessageToChatLog(fmt::format(fmt::runtime(_("Player '{:s}' (level {:d}) just joined the game")), myPlayer._pName, myPlayer.getCharacterLevel())); + AddMessageToChatLog(FormatRuntime(_("Player '{:s}' (level {:d}) just joined the game"), myPlayer._pName, myPlayer.getCharacterLevel())); // Log join message with seed for joining players (creator already logged it in SNetCreateGame) if (gbIsMultiplayer && !IsLoopback && MyPlayerId != 0) { @@ -960,7 +961,7 @@ void recv_plrinfo(Player &player, const TCmdPlrInfoHdr &header, bool recv) } else { szEvent = _("Player '{:s}' (level {:d}) is already in the game"); } - EventPlrMsg(fmt::format(fmt::runtime(szEvent), player._pName, player.getCharacterLevel())); + EventPlrMsg(FormatRuntime(szEvent, player._pName, player.getCharacterLevel())); SyncInitPlr(player); diff --git a/Source/objects.cpp b/Source/objects.cpp index bc3b4dd20..256174089 100644 --- a/Source/objects.cpp +++ b/Source/objects.cpp @@ -12,9 +12,6 @@ #include -#include -#include - #include "DiabloUI/ui_flags.hpp" #include "automap.h" #include "cursor.h" @@ -47,6 +44,7 @@ #include "track.h" #include "utils/algorithm/container.hpp" #include "utils/endian_swap.hpp" +#include "utils/format.hpp" #include "utils/is_of.hpp" #include "utils/language.h" #include "utils/log.hpp" @@ -4853,7 +4851,7 @@ StringOrView Object::name() const return _("Urn"); case OBJ_SHRINEL: case OBJ_SHRINER: - return fmt::format(fmt::runtime(_(/* TRANSLATORS: {:s} will be a name from the Shrine block above */ "{:s} Shrine")), _(ShrineNames[_oVar1])); + return FormatRuntime(_(/* TRANSLATORS: {:s} will be a name from the Shrine block above */ "{:s} Shrine"), _(ShrineNames[_oVar1])); case OBJ_SKELBOOK: return _("Skeleton Tome"); case OBJ_BOOKSTAND: @@ -4908,12 +4906,12 @@ void GetObjectStr(const Object &object) const ClassAttributes &classAttributes = GetClassAttributes(MyPlayer->_pClass); if (HasAnyOf(classAttributes.classFlags, PlayerClassFlag::TrapSense)) { if (object._oTrapFlag) { - InfoString = fmt::format(fmt::runtime(_(/* TRANSLATORS: {:s} will either be a chest or a door */ "Trapped {:s}")), InfoString.str()); + InfoString = FormatRuntime(_(/* TRANSLATORS: {:s} will either be a chest or a door */ "Trapped {:s}"), InfoString.str()); InfoColor = UiFlags::ColorRed; } } if (object.IsDisabled()) { - InfoString = fmt::format(fmt::runtime(_(/* TRANSLATORS: If user enabled diablo.ini setting "Disable Crippling Shrines" is set to 1; also used for Na-Kruls lever */ "{:s} (disabled)")), InfoString.str()); + InfoString = FormatRuntime(_(/* TRANSLATORS: If user enabled diablo.ini setting "Disable Crippling Shrines" is set to 1; also used for Na-Kruls lever */ "{:s} (disabled)"), InfoString.str()); InfoColor = UiFlags::ColorRed; } } diff --git a/Source/options.cpp b/Source/options.cpp index 629093693..f671937e8 100644 --- a/Source/options.cpp +++ b/Source/options.cpp @@ -27,7 +27,6 @@ #include #endif -#include #include #include "appfat.h" @@ -39,6 +38,7 @@ #include "quick_messages.hpp" #include "utils/algorithm/container.hpp" #include "utils/file_util.h" +#include "utils/format.hpp" #include "utils/ini.hpp" #include "utils/language.h" #include "utils/log.hpp" @@ -994,7 +994,14 @@ void OptionEntryLanguageCode::LoadFromIni(std::string_view category) } } - LogVerbose("Found user preferred locales: {}", fmt::join(locales, ", ")); + if (IsLogLevel(defaultCategory, SDL_LOG_PRIORITY_VERBOSE)) { + std::string joinedLocales; + for (const std::string &locale : locales) { + if (!joinedLocales.empty()) joinedLocales.append(", "); + joinedLocales.append(locale); + } + LogVerbose("Found user preferred locales: {}", joinedLocales); + } for (const auto &locale : locales) { LogVerbose("Trying to load translation: {}", locale); @@ -1180,7 +1187,7 @@ KeymapperOptions::Action::Action(std::string_view key, const char *name, const c , dynamicIndex(index) { if (index != 0) { - dynamicKey = fmt::format(fmt::runtime(std::string_view(key.data(), key.size())), index); + dynamicKey = FormatRuntime(std::string_view(key.data(), key.size()), index); this->key = dynamicKey; } } @@ -1189,7 +1196,7 @@ std::string_view KeymapperOptions::Action::GetName() const { if (dynamicIndex == 0) return _(name); - dynamicName = fmt::format(fmt::runtime(_(name)), dynamicIndex); + dynamicName = FormatRuntime(_(name), dynamicIndex); return dynamicName; } @@ -1358,7 +1365,7 @@ PadmapperOptions::Action::Action(std::string_view key, const char *name, const c , dynamicIndex(index) { if (index != 0) { - dynamicKey = fmt::format(fmt::runtime(std::string_view(key.data(), key.size())), index); + dynamicKey = FormatRuntime(std::string_view(key.data(), key.size()), index); this->key = dynamicKey; } } @@ -1367,7 +1374,7 @@ std::string_view PadmapperOptions::Action::GetName() const { if (dynamicIndex == 0) return _(name); - dynamicName = fmt::format(fmt::runtime(_(name)), dynamicIndex); + dynamicName = FormatRuntime(_(name), dynamicIndex); return dynamicName; } @@ -1643,11 +1650,11 @@ std::string BuildModDescription(const ModManifest &manifest) const bool hasVersion = !manifest.version.empty(); const bool hasAuthor = !manifest.author.empty(); if (hasVersion && hasAuthor) - return fmt::format(fmt::runtime(_("Version {:s} by {:s}")), manifest.version, manifest.author); + return FormatRuntime(_("Version {:s} by {:s}"), manifest.version, manifest.author); if (hasVersion) - return fmt::format(fmt::runtime(_("Version {:s}")), manifest.version); + return FormatRuntime(_("Version {:s}"), manifest.version); if (hasAuthor) - return fmt::format(fmt::runtime(_("By {:s}")), manifest.author); + return FormatRuntime(_("By {:s}"), manifest.author); return {}; } } // namespace diff --git a/Source/pack.cpp b/Source/pack.cpp index d89e3e6c7..3294d6397 100644 --- a/Source/pack.cpp +++ b/Source/pack.cpp @@ -6,8 +6,7 @@ #include "pack.h" #include - -#include +#include #include "engine/random.hpp" #include "game_mode.hpp" @@ -46,7 +45,7 @@ namespace { void EventFailedJoinAttempt(const char *playerName) { - const std::string message = fmt::format("Player '{}' sent invalid player data during attempt to join the game.", playerName); + const std::string message = std::format("Player '{}' sent invalid player data during attempt to join the game.", playerName); EventPlrMsg(message); } diff --git a/Source/panels/charpanel.cpp b/Source/panels/charpanel.cpp index 3ef55f743..8e7e0fc06 100644 --- a/Source/panels/charpanel.cpp +++ b/Source/panels/charpanel.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include "control/control.hpp" diff --git a/Source/panels/spell_book.cpp b/Source/panels/spell_book.cpp index e27c0b5e1..284912f3f 100644 --- a/Source/panels/spell_book.cpp +++ b/Source/panels/spell_book.cpp @@ -5,8 +5,6 @@ #include #include -#include - #include "control/control.hpp" #include "engine/backbuffer_state.hpp" #include "engine/clx_sprite.hpp" @@ -21,6 +19,7 @@ #include "panels/ui_panels.hpp" #include "player.h" #include "tables/spelldat.h" +#include "utils/format.hpp" #include "utils/language.h" #include "utils/status_macros.hpp" @@ -112,9 +111,9 @@ StringOrView GetSpellPowerText(SpellID spell, int spellLevel) return StringOrView {}; } if (spell == SpellID::Healing || spell == SpellID::HealOther) { - return fmt::format(fmt::runtime(_(/* TRANSLATORS: UI constraints, keep short please.*/ "Heals: {:d} - {:d}")), min, max); + return FormatRuntime(_(/* TRANSLATORS: UI constraints, keep short please.*/ "Heals: {:d} - {:d}"), min, max); } - return fmt::format(fmt::runtime(_(/* TRANSLATORS: UI constraints, keep short please.*/ "Damage: {:d} - {:d}")), min, max); + return FormatRuntime(_(/* TRANSLATORS: UI constraints, keep short please.*/ "Damage: {:d} - {:d}"), min, max); } } // namespace @@ -173,16 +172,16 @@ void DrawSpellBook(const Surface &out) break; case SpellType::Charges: { const int charges = player.InvBody[INVLOC_HAND_LEFT]._iCharges; - PrintSBookStr(out, line1, fmt::format(fmt::runtime(ngettext("Staff ({:d} charge)", "Staff ({:d} charges)", charges)), charges)); + PrintSBookStr(out, line1, FormatRuntime(ngettext("Staff ({:d} charge)", "Staff ({:d} charges)", charges), charges)); } break; default: { const int mana = GetManaAmount(player, sn) >> 6; const int lvl = player.GetSpellLevel(sn); - PrintSBookStr(out, line0, fmt::format(fmt::runtime(pgettext(/* TRANSLATORS: UI constraints, keep short please.*/ "spellbook", "Level {:d}")), lvl), UiFlags::AlignRight); + PrintSBookStr(out, line0, FormatRuntime(pgettext(/* TRANSLATORS: UI constraints, keep short please.*/ "spellbook", "Level {:d}"), lvl), UiFlags::AlignRight); if (const StringOrView text = GetSpellPowerText(sn, lvl); !text.empty()) { PrintSBookStr(out, line1, text, UiFlags::AlignRight); } - PrintSBookStr(out, line1, fmt::format(fmt::runtime(pgettext(/* TRANSLATORS: UI constraints, keep short please.*/ "spellbook", "Mana: {:d}")), mana)); + PrintSBookStr(out, line1, FormatRuntime(pgettext(/* TRANSLATORS: UI constraints, keep short please.*/ "spellbook", "Mana: {:d}"), mana)); } break; } } diff --git a/Source/panels/spell_list.cpp b/Source/panels/spell_list.cpp index 4105fbe4d..b01291a3b 100644 --- a/Source/panels/spell_list.cpp +++ b/Source/panels/spell_list.cpp @@ -2,8 +2,6 @@ #include -#include - #include "control/control.hpp" #include "controls/control_mode.hpp" #include "controls/plrctrls.h" @@ -17,6 +15,7 @@ #include "player.h" #include "spells.h" #include "utils/algorithm/container.hpp" +#include "utils/format.hpp" #include "utils/language.h" #include "utils/str_cat.hpp" #include "utils/utf8.hpp" @@ -150,48 +149,48 @@ void DrawSpellList(const Surface &out) case SpellType::Skill: spellColor = PAL16_YELLOW - 46; PrintSBookSpellType(out, spellListItem.location, _("Skill"), spellColor); - InfoString = fmt::format(fmt::runtime(_("{:s} Skill")), pgettext("spell", spellDataItem.sNameText)); + InfoString = FormatRuntime(_("{:s} Skill"), pgettext("spell", spellDataItem.sNameText)); break; case SpellType::Spell: if (!myPlayer.isOnLevel(0)) { spellColor = PAL16_BLUE + 5; } PrintSBookSpellType(out, spellListItem.location, _("Spell"), spellColor); - InfoString = fmt::format(fmt::runtime(_("{:s} Spell")), pgettext("spell", spellDataItem.sNameText)); + InfoString = FormatRuntime(_("{:s} Spell"), pgettext("spell", spellDataItem.sNameText)); if (spellId == SpellID::HolyBolt) { AddInfoBoxString(_("Damages undead only")); } if (spellLevel == 0) AddInfoBoxString(_("Spell Level 0 - Unusable")); else - AddInfoBoxString(fmt::format(fmt::runtime(_("Spell Level {:d}")), spellLevel)); + AddInfoBoxString(FormatRuntime(_("Spell Level {:d}"), spellLevel)); break; case SpellType::Scroll: { if (!myPlayer.isOnLevel(0)) { spellColor = PAL16_RED - 59; } PrintSBookSpellType(out, spellListItem.location, _("Scroll"), spellColor); - InfoString = fmt::format(fmt::runtime(_("Scroll of {:s}")), pgettext("spell", spellDataItem.sNameText)); + InfoString = FormatRuntime(_("Scroll of {:s}"), pgettext("spell", spellDataItem.sNameText)); const int scrollCount = c_count_if(InventoryAndBeltPlayerItemsRange { myPlayer }, [spellId](const Item &item) { return item.isScrollOf(spellId); }); - AddInfoBoxString(fmt::format(fmt::runtime(ngettext("{:d} Scroll", "{:d} Scrolls", scrollCount)), scrollCount)); + AddInfoBoxString(FormatRuntime(ngettext("{:d} Scroll", "{:d} Scrolls", scrollCount), scrollCount)); } break; case SpellType::Charges: { if (!myPlayer.isOnLevel(0)) { spellColor = PAL16_ORANGE + 5; } PrintSBookSpellType(out, spellListItem.location, _("Staff"), spellColor); - InfoString = fmt::format(fmt::runtime(_("Staff of {:s}")), pgettext("spell", spellDataItem.sNameText)); + InfoString = FormatRuntime(_("Staff of {:s}"), pgettext("spell", spellDataItem.sNameText)); int charges = myPlayer.InvBody[INVLOC_HAND_LEFT]._iCharges; - AddInfoBoxString(fmt::format(fmt::runtime(ngettext("{:d} Charge", "{:d} Charges", charges)), charges)); + AddInfoBoxString(FormatRuntime(ngettext("{:d} Charge", "{:d} Charges", charges), charges)); } break; case SpellType::Invalid: break; } std::optional fullHotkeyName = GetHotkeyName(spellId, spellListItem.type); if (fullHotkeyName) { - AddInfoBoxString(fmt::format(fmt::runtime(_("Spell Hotkey {:s}")), *fullHotkeyName)); + AddInfoBoxString(FormatRuntime(_("Spell Hotkey {:s}"), *fullHotkeyName)); } } } diff --git a/Source/platform/amiga/cmpdf2.c b/Source/platform/amiga/cmpdf2.c new file mode 100644 index 000000000..bf4c55ad1 --- /dev/null +++ b/Source/platform/amiga/cmpdf2.c @@ -0,0 +1,18 @@ +/* + * The m68k-amigaos hard-float libgcc multilib omits the software + * double-precision comparison routine `__cmpdf2` (doubles are compared in + * hardware), but its software extended-precision comparison routines + * (xfpgnulib `__cmpxf2`, `__eqxf2`, ...) still call it. + * + * libstdc++'s machinery references the extended-precision routines + * through its `long double` code paths, so linking fails with an undefined + * `__cmpdf2` even though it is never actually called. + */ +int __cmpdf2(double a, double b); + +int __cmpdf2(double a, double b) +{ + if (a < b) return -1; + if (a > b) return 1; + return 0; +} diff --git a/Source/platform/ctr/CMakeLists.txt b/Source/platform/ctr/CMakeLists.txt index 1d1e35390..09f63e2c7 100644 --- a/Source/platform/ctr/CMakeLists.txt +++ b/Source/platform/ctr/CMakeLists.txt @@ -26,7 +26,6 @@ endif() target_link_dependencies(libdevilutionx_ctr PUBLIC DevilutionX::SDL - fmt::fmt 3ds::citro3d 3ds::ctrulib PRIVATE diff --git a/Source/platform/ctr/sockets.cpp b/Source/platform/ctr/sockets.cpp index 4bff0f938..ce57a5a02 100644 --- a/Source/platform/ctr/sockets.cpp +++ b/Source/platform/ctr/sockets.cpp @@ -3,12 +3,6 @@ #include #include -// This header must be included before any 3DS code -// because 3DS SDK defines a macro with the same name -// as an fmt template parameter in some versions of fmt. -// See https://github.com/fmtlib/fmt/issues/3632 -#include - #include <3ds.h> #include "utils/log.hpp" diff --git a/Source/player.cpp b/Source/player.cpp index dfa878cb8..dd4417f3d 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -14,9 +14,6 @@ #include #endif -#include -#include - #include "control/control.hpp" #include "controls/control_mode.hpp" #include "controls/plrctrls.h" @@ -55,6 +52,7 @@ #include "spells.h" #include "stores.h" #include "towners.h" +#include "utils/format.hpp" #include "utils/is_of.hpp" #include "utils/language.h" #include "utils/log.hpp" @@ -2749,7 +2747,7 @@ StartPlayerKill(Player &player, DeathReason deathReason) if (dropEar) { Item ear; InitializeItem(ear, IDI_EAR); - CopyUtf8(ear._iName, fmt::format(fmt::runtime("Ear of {:s}"), player._pName), sizeof(ear._iName)); + CopyUtf8(ear._iName, FormatRuntime("Ear of {:s}", player._pName), sizeof(ear._iName)); CopyUtf8(ear._iIName, player._pName, ItemNameLength); switch (player._pClass) { case HeroClass::Sorcerer: diff --git a/Source/plrmsg.cpp b/Source/plrmsg.cpp index f879d675a..ceb2b4fb5 100644 --- a/Source/plrmsg.cpp +++ b/Source/plrmsg.cpp @@ -15,8 +15,6 @@ #include #endif -#include - #include "control/control.hpp" #include "engine/render/primitive_render.hpp" #include "engine/render/text_render.hpp" @@ -24,6 +22,7 @@ #include "qol/chatlog.h" #include "qol/stash.h" #include "utils/algorithm/container.hpp" +#include "utils/format.hpp" #include "utils/language.h" #include "utils/utf8.hpp" @@ -82,7 +81,7 @@ void SendPlrMsg(Player &player, std::string_view text) { PlayerMessage &message = GetNextMessage(); - const std::string from = fmt::format(fmt::runtime(_("{:s} (lvl {:d}): ")), player._pName, player.getCharacterLevel()); + const std::string from = FormatRuntime(_("{:s} (lvl {:d}): "), player._pName, player.getCharacterLevel()); message.style = UiFlags::ColorWhite; message.time = SDL_GetTicks(); diff --git a/Source/qol/chatlog.cpp b/Source/qol/chatlog.cpp index 218e7bd56..f257ad449 100644 --- a/Source/qol/chatlog.cpp +++ b/Source/qol/chatlog.cpp @@ -9,8 +9,6 @@ #include #include -#include - #include "DiabloUI/ui_flags.hpp" #include "automap.h" #include "chatlog.h" @@ -23,6 +21,7 @@ #include "inv.h" #include "minitext.h" #include "stores.h" +#include "utils/format.hpp" #include "utils/language.h" #include "utils/str_cat.hpp" @@ -127,7 +126,7 @@ void AddMessageToChatLog(std::string_view message, Player *player, UiFlags flags if (player == nullptr) { ChatLogLines.emplace_back(MultiColoredText { "{0} {1}", { { timestamp, UiFlags::ColorRed }, { std::string(message), flags } } }); } else { - std::string playerInfo = fmt::format(fmt::runtime(_("{:s} (lvl {:d}): ")), player->_pName, player->getCharacterLevel()); + std::string playerInfo = FormatRuntime(_("{:s} (lvl {:d}): "), player->_pName, player->getCharacterLevel()); UiFlags nameColor = player == MyPlayer ? UiFlags::ColorWhitegold : UiFlags::ColorBlue; const std::string prefix = timestamp + " - " + playerInfo; const std::string text = WordWrapString(prefix + std::string(message), ContentTextWidth); @@ -167,7 +166,7 @@ void DrawChatLog(const Surface &out) const int sx = uiPosition.x + PaddingLeft; const int sy = uiPosition.y; - DrawString(out, fmt::format(fmt::runtime(_("Chat History (Messages: {:d})")), MessageCounter), + DrawString(out, FormatRuntime(_("Chat History (Messages: {:d})"), MessageCounter), { { sx, sy + PaddingTop + blankLineHeight }, { ContentTextWidth, lineHeight } }, { .flags = (UnreadFlag ? UiFlags::ColorRed : UiFlags::ColorWhitegold) | UiFlags::AlignCenter }); diff --git a/Source/qol/floatingnumbers.cpp b/Source/qol/floatingnumbers.cpp index 23f2139b7..1b42c1195 100644 --- a/Source/qol/floatingnumbers.cpp +++ b/Source/qol/floatingnumbers.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #ifdef USE_SDL3 diff --git a/Source/qol/itemlabels.cpp b/Source/qol/itemlabels.cpp index 05be65dad..b482b670c 100644 --- a/Source/qol/itemlabels.cpp +++ b/Source/qol/itemlabels.cpp @@ -7,8 +7,6 @@ #include #include -#include - #include "control/control.hpp" #include "cursor.h" #include "engine/point.hpp" @@ -20,6 +18,7 @@ #include "qol/stash.h" #include "stores.h" #include "utils/algorithm/container.hpp" +#include "utils/format.hpp" #include "utils/format_int.hpp" #include "utils/language.h" @@ -110,7 +109,7 @@ void AddItemToLabelQueue(int id, Point position) StringOrView textOnGround; if (item._itype == ItemType::Gold) { - textOnGround = fmt::format(fmt::runtime(_("{:s} gold")), FormatInteger(item._ivalue)); + textOnGround = FormatRuntime(_("{:s} gold"), FormatInteger(item._ivalue)); } else { textOnGround = item.getName(); } diff --git a/Source/qol/monhealthbar.cpp b/Source/qol/monhealthbar.cpp index b9fef1a30..74f7a841a 100644 --- a/Source/qol/monhealthbar.cpp +++ b/Source/qol/monhealthbar.cpp @@ -7,8 +7,6 @@ #include -#include - #include "control/control.hpp" #include "cursor.h" #include "engine/clx_sprite.hpp" diff --git a/Source/qol/stash.cpp b/Source/qol/stash.cpp index f6fc38b8b..81c529165 100644 --- a/Source/qol/stash.cpp +++ b/Source/qol/stash.cpp @@ -10,8 +10,6 @@ #include #endif -#include - #include "DiabloUI/text_input.hpp" #include "control/control.hpp" #include "controls/plrctrls.h" diff --git a/Source/qol/xpbar.cpp b/Source/qol/xpbar.cpp index 8be7b27fd..cb1f11c31 100644 --- a/Source/qol/xpbar.cpp +++ b/Source/qol/xpbar.cpp @@ -8,9 +8,6 @@ #include #include -#include -#include - #include "control/control.hpp" #include "engine/clx_sprite.hpp" #include "engine/load_clx.hpp" @@ -20,6 +17,7 @@ #include "game_mode.hpp" #include "options.h" #include "tables/playerdat.hpp" +#include "utils/format.hpp" #include "utils/format_int.hpp" #include "utils/language.h" @@ -137,13 +135,13 @@ bool CheckXPBarInfo() const uint8_t charLevel = player.getCharacterLevel(); - AddInfoBoxString(fmt::format(fmt::runtime(_("Level {:d}")), charLevel)); + AddInfoBoxString(FormatRuntime(_("Level {:d}"), charLevel)); if (player.isMaxCharacterLevel()) { // Show a maximum level indicator for max level players. InfoColor = UiFlags::ColorWhitegold; - AddInfoBoxString(fmt::format(fmt::runtime(_("Experience: {:s}")), FormatInteger(player._pExperience))); + AddInfoBoxString(FormatRuntime(_("Experience: {:s}"), FormatInteger(player._pExperience))); AddInfoBoxString(_("Maximum Level")); return true; @@ -151,10 +149,10 @@ bool CheckXPBarInfo() InfoColor = UiFlags::ColorWhite; - AddInfoBoxString(fmt::format(fmt::runtime(_("Experience: {:s}")), FormatInteger(player._pExperience))); + AddInfoBoxString(FormatRuntime(_("Experience: {:s}"), FormatInteger(player._pExperience))); const uint32_t nextExperienceThreshold = player.getNextExperienceThreshold(); - AddInfoBoxString(fmt::format(fmt::runtime(_("Next Level: {:s}")), FormatInteger(nextExperienceThreshold))); - AddInfoBoxString(fmt::format(fmt::runtime(_("{:s} to Level {:d}")), FormatInteger(nextExperienceThreshold - player._pExperience), charLevel + 1)); + AddInfoBoxString(FormatRuntime(_("Next Level: {:s}"), FormatInteger(nextExperienceThreshold))); + AddInfoBoxString(FormatRuntime(_("{:s} to Level {:d}"), FormatInteger(nextExperienceThreshold - player._pExperience), charLevel + 1)); return true; } diff --git a/Source/quests.cpp b/Source/quests.cpp index 36f1b80c8..47a9c5da9 100644 --- a/Source/quests.cpp +++ b/Source/quests.cpp @@ -7,8 +7,6 @@ #include -#include - #include "DiabloUI/ui_flags.hpp" #include "control/control.hpp" #include "cursor.h" @@ -32,6 +30,7 @@ #include "tables/townerdat.hpp" #include "towners.h" #include "utils/endian_swap.hpp" +#include "utils/format.hpp" #include "utils/is_of.hpp" #include "utils/language.h" #include "utils/utf8.hpp" @@ -354,7 +353,7 @@ bool ForceQuests() const int ql = quest._qslvl - 1; if (EntranceBoundaryContains(quest.position, cursPosition)) { - InfoString = fmt::format(fmt::runtime(_(/* TRANSLATORS: Used for Quest Portals. {:s} is a Map Name */ "To {:s}")), _(QuestTriggerNames[ql])); + InfoString = FormatRuntime(_(/* TRANSLATORS: Used for Quest Portals. {:s} is a Map Name */ "To {:s}"), _(QuestTriggerNames[ql])); cursPosition = quest.position; return true; } diff --git a/Source/stores.cpp b/Source/stores.cpp index 40c957895..017781cdc 100644 --- a/Source/stores.cpp +++ b/Source/stores.cpp @@ -13,8 +13,6 @@ #include #include -#include - #include "controls/control_mode.hpp" #include "controls/plrctrls.h" #include "cursor.h" @@ -34,6 +32,7 @@ #include "qol/visual_store.h" #include "tables/townerdat.hpp" #include "towners.h" +#include "utils/format.hpp" #include "utils/format_int.hpp" #include "utils/language.h" #include "utils/log.hpp" @@ -370,7 +369,7 @@ void PrintStoreItem(const Item &item, int l, UiFlags flags, bool cursIndent = fa if (item._iMiscId == IMISC_STAFF && item._iMaxCharges != 0) { if (!productLine.empty()) productLine.append(_(", ")); - productLine.append(fmt::format(fmt::runtime(_("Charges: {:d}/{:d}")), item._iCharges, item._iMaxCharges)); + productLine.append(FormatRuntime(_("Charges: {:d}/{:d}"), item._iCharges, item._iMaxCharges)); } if (!productLine.empty()) { AddSText(40, l, productLine, flags, false, -1, cursIndent); @@ -380,11 +379,11 @@ void PrintStoreItem(const Item &item, int l, UiFlags flags, bool cursIndent = fa if (item._itype != ItemType::Misc) { if (item._iClass == ICLASS_WEAPON) - productLine = fmt::format(fmt::runtime(_("Damage: {:d}-{:d} ")), item._iMinDam, item._iMaxDam); + productLine = FormatRuntime(_("Damage: {:d}-{:d} "), item._iMinDam, item._iMaxDam); else if (item._iClass == ICLASS_ARMOR) - productLine = fmt::format(fmt::runtime(_("Armor: {:d} ")), item._iAC); + productLine = FormatRuntime(_("Armor: {:d} "), item._iAC); if (item._iMaxDur != DUR_INDESTRUCTIBLE && item._iMaxDur != 0) - productLine += fmt::format(fmt::runtime(_("Dur: {:d}/{:d}")), item._iDurability, item._iMaxDur); + productLine += FormatRuntime(_("Dur: {:d}/{:d}"), item._iDurability, item._iMaxDur); else productLine.append(_("Indestructible")); } @@ -398,11 +397,11 @@ void PrintStoreItem(const Item &item, int l, UiFlags flags, bool cursIndent = fa productLine.append(_(", ")); productLine.append(_("Required:")); if (str != 0) - productLine.append(fmt::format(fmt::runtime(_(" {:d} Str")), str)); + productLine.append(FormatRuntime(_(" {:d} Str"), str)); if (mag != 0) - productLine.append(fmt::format(fmt::runtime(_(" {:d} Mag")), mag)); + productLine.append(FormatRuntime(_(" {:d} Mag"), mag)); if (dex != 0) - productLine.append(fmt::format(fmt::runtime(_(" {:d} Dex")), dex)); + productLine.append(FormatRuntime(_(" {:d} Dex"), dex)); } AddSText(40, l++, productLine, flags, false, -1, cursIndent); } @@ -1207,10 +1206,10 @@ void StartTalk() IsTextFullSize = false; HasScrollbar = false; - AddSText(0, 2, fmt::format(fmt::runtime(_("Talk to {:s}")), _(TownerNames[TownerId])), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); + AddSText(0, 2, FormatRuntime(_("Talk to {:s}"), _(TownerNames[TownerId])), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSLine(5); if (gbIsSpawn) { - AddSText(0, 10, fmt::format(fmt::runtime(_("Talking to {:s}")), _(TownerNames[TownerId])), UiFlags::ColorWhite | UiFlags::AlignCenter, false); + AddSText(0, 10, FormatRuntime(_("Talking to {:s}"), _(TownerNames[TownerId])), UiFlags::ColorWhite | UiFlags::AlignCenter, false); AddSText(0, 12, _("is not available"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); AddSText(0, 14, _("in the shareware"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); AddSText(0, 16, _("version"), UiFlags::ColorWhite | UiFlags::AlignCenter, false); @@ -2471,7 +2470,7 @@ void DrawSText(const Surface &out) } if (RenderGold) { - PrintSString(out, 28, 1, fmt::format(fmt::runtime(_("Your gold: {:s}")), FormatInteger(TotalPlayerGold())).c_str(), UiFlags::ColorWhitegold | UiFlags::AlignRight); + PrintSString(out, 28, 1, FormatRuntime(_("Your gold: {:s}"), FormatInteger(TotalPlayerGold())).c_str(), UiFlags::ColorWhitegold | UiFlags::AlignRight); } if (HasScrollbar) diff --git a/Source/tables/itemdat.cpp b/Source/tables/itemdat.cpp index 699154024..226d8762c 100644 --- a/Source/tables/itemdat.cpp +++ b/Source/tables/itemdat.cpp @@ -6,11 +6,10 @@ #include "tables/itemdat.h" +#include #include #include -#include - #include "data/file.hpp" #include "data/iterators.hpp" #include "data/record_reader.hpp" @@ -367,7 +366,7 @@ std::expected ParseOrAddUniqueBaseItem(std::strin const size_t newUniqueBaseItemIndex = static_cast(NUM_DEFAULT_UITYPES) + AdditionalUniqueBaseItemStringsToIndices.size(); if (newUniqueBaseItemIndex >= static_cast(NUM_MAX_UITYPES)) { - return std::unexpected(fmt::format("Could not define new unique base item \"{}\", since the maximum number of {} has already been reached.", value, static_cast(NUM_MAX_UITYPES))); + return std::unexpected(std::format("Could not define new unique base item \"{}\", since the maximum number of {} has already been reached.", value, static_cast(NUM_MAX_UITYPES))); } const auto newUniqueBaseItem = static_cast(newUniqueBaseItemIndex); @@ -599,7 +598,7 @@ void LoadItemDatFromFile(DataFile &dataFile, std::string_view filename, int32_t item.iMappingId = currentMappingId; const auto [it, inserted] = ItemMappingIdsToIndices.emplace(item.iMappingId, static_cast(AllItemsList.size()) - 1); if (!inserted) { - DisplayFatalErrorAndExit("Adding Item Failed", fmt::format("An item already exists for mapping ID {}.", item.iMappingId)); + DisplayFatalErrorAndExit("Adding Item Failed", std::format("An item already exists for mapping ID {}.", item.iMappingId)); } ++currentMappingId; @@ -657,7 +656,7 @@ void LoadUniqueItemDatFromFile(DataFile &dataFile, std::string_view filename, in item.mappingId = currentMappingId; const auto [it, inserted] = UniqueItemMappingIdsToIndices.emplace(item.mappingId, static_cast(UniqueItems.size()) - 1); if (!inserted) { - DisplayFatalErrorAndExit("Adding Unique Item Failed", fmt::format("A unique item already exists for mapping ID {}.", item.mappingId)); + DisplayFatalErrorAndExit("Adding Unique Item Failed", std::format("A unique item already exists for mapping ID {}.", item.mappingId)); } ++currentMappingId; diff --git a/Source/tables/monstdat.cpp b/Source/tables/monstdat.cpp index 191f7c816..5c6d1c171 100644 --- a/Source/tables/monstdat.cpp +++ b/Source/tables/monstdat.cpp @@ -15,8 +15,6 @@ #include #include -#include -#include #include #include "appfat.h" @@ -28,6 +26,7 @@ #include "lua/lua_event.hpp" #include "monster.h" #include "tables/textdat.h" +#include "utils/format.hpp" #include "utils/language.h" namespace devilution { @@ -329,7 +328,7 @@ void LoadMonstDatFromFile(DataFile &dataFile, const std::string_view filename, b for (DataFileRecord record : dataFile) { if (MonstersData.size() >= static_cast(NUM_MAX_MTYPES)) { - DisplayFatalErrorAndExit(_("Loading Monster Data Failed"), fmt::format(fmt::runtime(_("Could not add a monster, since the maximum monster type number of {} has already been reached.")), static_cast(NUM_MAX_MTYPES))); + DisplayFatalErrorAndExit(_("Loading Monster Data Failed"), FormatRuntime(_("Could not add a monster, since the maximum monster type number of {} has already been reached."), static_cast(NUM_MAX_MTYPES))); } RecordReader reader { record, filename }; @@ -342,7 +341,7 @@ void LoadMonstDatFromFile(DataFile &dataFile, const std::string_view filename, b const size_t monsterIndex = MonstersData.size(); const auto [it, inserted] = AdditionalMonsterIdStringsToIndices.emplace(monsterId, static_cast(monsterIndex)); if (!inserted) { - DisplayFatalErrorAndExit(_("Loading Monster Data Failed"), fmt::format(fmt::runtime(_("A monster type already exists for ID \"{}\".")), monsterId)); + DisplayFatalErrorAndExit(_("Loading Monster Data Failed"), FormatRuntime(_("A monster type already exists for ID \"{}\"."), monsterId)); } } diff --git a/Source/tables/playerdat.cpp b/Source/tables/playerdat.cpp index d287287a6..8f3133fa8 100644 --- a/Source/tables/playerdat.cpp +++ b/Source/tables/playerdat.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include "data/file.hpp" @@ -23,6 +22,7 @@ #include "items.h" #include "player.h" #include "tables/textdat.h" +#include "utils/format.hpp" #include "utils/language.h" #include "utils/static_vector.hpp" #include "utils/str_cat.hpp" @@ -317,7 +317,7 @@ void LoadClassDatFromFile(DataFile &dataFile, const std::string_view filename) for (DataFileRecord record : dataFile) { if (PlayersData.size() >= static_cast(HeroClass::NUM_MAX_CLASSES)) { - DisplayFatalErrorAndExit(_("Loading Class Data Failed"), fmt::format(fmt::runtime(_("Could not add a class, since the maximum class number of {} has already been reached.")), static_cast(HeroClass::NUM_MAX_CLASSES))); + DisplayFatalErrorAndExit(_("Loading Class Data Failed"), FormatRuntime(_("Could not add a class, since the maximum class number of {} has already been reached."), static_cast(HeroClass::NUM_MAX_CLASSES))); } RecordReader reader { record, filename }; diff --git a/Source/tables/textdat.cpp b/Source/tables/textdat.cpp index ccc40b28b..7999f14b1 100644 --- a/Source/tables/textdat.cpp +++ b/Source/tables/textdat.cpp @@ -6,13 +6,12 @@ #include "tables/textdat.h" #include -#include -#include #include #include "data/file.hpp" #include "data/record_reader.hpp" #include "effects.h" +#include "utils/format.hpp" #include "utils/language.h" namespace devilution { @@ -70,7 +69,7 @@ void LoadTextDatFromFile(DataFile &dataFile, std::string_view filename, bool gro const size_t textEntryIndex = Speeches.size(); const auto [it, inserted] = AdditionalTextIdStringsToIndices.emplace(txtstrid, static_cast(textEntryIndex)); if (!inserted) { - DisplayFatalErrorAndExit(_("Loading Text Data Failed"), fmt::format(fmt::runtime(_("A text data entry already exists for ID \"{}\".")), txtstrid)); + DisplayFatalErrorAndExit(_("Loading Text Data Failed"), FormatRuntime(_("A text data entry already exists for ID \"{}\"."), txtstrid)); } } diff --git a/Source/utils/format.hpp b/Source/utils/format.hpp new file mode 100644 index 000000000..33dd10bf6 --- /dev/null +++ b/Source/utils/format.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +namespace devilution { + +/** + * @brief Formats using a format string that is only known at runtime, such as a translation. + * + * Unlike `std::format`, the format string is not checked at compile time. + * An invalid format string results in `std::format_error` being thrown + * (or program termination if exceptions are disabled). + */ +template +[[nodiscard]] std::string FormatRuntime(std::string_view fmt, Args &&...args) +{ + return std::vformat(fmt, std::make_format_args(args...)); +} + +} // namespace devilution diff --git a/Source/utils/ini.cpp b/Source/utils/ini.cpp index ecda31827..14c856161 100644 --- a/Source/utils/ini.cpp +++ b/Source/utils/ini.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -16,8 +17,6 @@ #include #include -#include -#include #include "utils/algorithm/container.hpp" #include "utils/str_cat.hpp" @@ -160,10 +159,10 @@ std::expected Ini::parse(std::string_view buffer) ++keyEnd; } if (keyEnd == lineEnd) { - return std::unexpected(fmt::format("line {}: unclosed section name {}", lineNum, std::string_view(keyBegin, keyEnd - keyBegin))); + return std::unexpected(std::format("line {}: unclosed section name {}", lineNum, std::string_view(keyBegin, keyEnd - keyBegin))); } if (const char *after = SkipTrailingWhitespace(keyEnd + 1, lineEnd); after != lineEnd) { - return std::unexpected(fmt::format("line {}: content after section [{}]: {}", lineNum, std::string_view(keyBegin, keyEnd - keyBegin), std::string_view(after, lineEnd - after))); + return std::unexpected(std::format("line {}: content after section [{}]: {}", lineNum, std::string_view(keyBegin, keyEnd - keyBegin), std::string_view(after, lineEnd - after))); } const std::string_view sectionName = std::string_view(keyBegin, keyEnd - keyBegin); auto it = fileData.sections.find(sectionName); @@ -180,10 +179,10 @@ std::expected Ini::parse(std::string_view buffer) continue; } - if (sectionEntries == nullptr) return std::unexpected(fmt::format("line {}: key not in any section", lineNum)); + if (sectionEntries == nullptr) return std::unexpected(std::format("line {}: key not in any section", lineNum)); const char *eqPos = static_cast(memchr(keyBegin, '=', lineEnd - keyBegin)); if (eqPos == nullptr) { - return std::unexpected(fmt::format("line {}: key {} has no value", lineNum, std::string_view(keyBegin, lineEnd - keyBegin))); + return std::unexpected(std::format("line {}: key {} has no value", lineNum, std::string_view(keyBegin, lineEnd - keyBegin))); } const char *keyEnd = SkipTrailingWhitespace(keyBegin, eqPos); const std::string_view key = std::string_view(keyBegin, keyEnd - keyBegin); @@ -230,7 +229,7 @@ int Ini::getInt(std::string_view section, std::string_view key, int defaultValue int value; const std::from_chars_result result = std::from_chars(str.data(), str.data() + str.size(), value); if (result.ec != std::errc()) { - app_fatal(fmt::format("ini: Failed to parse {}.{}={} as int", section, key, str)); + app_fatal(std::format("ini: Failed to parse {}.{}={} as int", section, key, str)); return defaultValue; } return value; @@ -243,7 +242,7 @@ bool Ini::getBool(std::string_view section, std::string_view key, bool defaultVa const std::string_view str = xs.back().value; if (str == "0") return false; if (str == "1") return true; - app_fatal(fmt::format("ini: Failed to parse {}.{}={} as bool", section, key, str)); + app_fatal(std::format("ini: Failed to parse {}.{}={} as bool", section, key, str)); } float Ini::getFloat(std::string_view section, std::string_view key, float defaultValue) const @@ -256,7 +255,7 @@ float Ini::getFloat(std::string_view section, std::string_view key, float defaul float value; const std::from_chars_result result = std::from_chars(str.data(), str.data() + str.size(), value); if (result.ec != std::errc()) { - app_fatal(fmt::format("ini: Failed to parse {}.{}={} as float", section, key, str)); + app_fatal(std::format("ini: Failed to parse {}.{}={} as float", section, key, str)); return defaultValue; } return value; @@ -375,7 +374,6 @@ void Ini::set(std::string_view section, std::string_view key, bool value) void Ini::set(std::string_view section, std::string_view key, float value) { -#if __cpp_lib_to_chars >= 201611L constexpr size_t BufSize = 64; char buf[BufSize] {}; const std::to_chars_result result = std::to_chars(buf, buf + BufSize, value); @@ -383,9 +381,6 @@ void Ini::set(std::string_view section, std::string_view key, float value) app_fatal("float->string failed"); // should never happen } set(section, key, std::string_view(buf, result.ptr - buf)); -#else - set(section, key, fmt::format("{}", value)); -#endif } namespace { diff --git a/Source/utils/language.h b/Source/utils/language.h index 62fbb1798..c11a1af9b 100644 --- a/Source/utils/language.h +++ b/Source/utils/language.h @@ -3,6 +3,16 @@ #include #include +// libstdc++'s (which includes in GCC 13) includes +// , which declares `ngettext`. Include it before defining the +// `ngettext` macro below, so that the declaration is not rewritten into a +// conflicting `LanguagePluralTranslate` overload that is never defined. +#if __has_include() +#include +// GNU libintl.h may define `ngettext` as a macro itself. +#undef ngettext +#endif + #define _(x) LanguageTranslate(x) #define ngettext(x, y, z) LanguagePluralTranslate(x, y, z) #define pgettext(context, x) LanguageParticularTranslate(context, x) diff --git a/Source/utils/log.hpp b/Source/utils/log.hpp index 028908e70..d145a83dd 100644 --- a/Source/utils/log.hpp +++ b/Source/utils/log.hpp @@ -8,10 +8,10 @@ #include #endif -#include -#include -#include +#include +#include +#include "utils/attributes.h" #include "utils/str_cat.hpp" #ifdef USE_SDL1 @@ -51,25 +51,18 @@ namespace detail { template std::string format(std::string_view fmt, Args &&...args) { - FMT_TRY - { - return fmt::format(fmt::runtime(fmt), std::forward(args)...); +#if DVL_EXCEPTIONS + try { + return std::vformat(fmt, std::make_format_args(args...)); + } catch (const std::format_error &e) { + const std::string fullError = StrCat("Format error, fmt: ", fmt, " error: ", e.what()); + SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "%s", fullError.c_str()); + app_fatal(fullError); } -#if FMT_EXCEPTIONS - FMT_CATCH(const fmt::format_error &e) - { - // e.what() is undefined if exceptions are disabled, so we wrap it - // with an `FMT_EXCEPTIONS` check. - std::string error = e.what(); #else - FMT_CATCH(const fmt::format_error &) - { - std::string error = "unknown (FMT_EXCEPTIONS disabled)"; + // `std::vformat` terminates the program on error when exceptions are disabled. + return std::vformat(fmt, std::make_format_args(args...)); #endif - std::string fullError = StrCat("Format error, fmt: ", fmt, " error: ", error); - SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "%s", error.c_str()); - app_fatal(error); - } } } // namespace detail diff --git a/Source/utils/str_cat.cpp b/Source/utils/str_cat.cpp index 90097137b..69ddd4354 100644 --- a/Source/utils/str_cat.cpp +++ b/Source/utils/str_cat.cpp @@ -1,8 +1,8 @@ #include "utils/str_cat.hpp" +#include #include - -#include +#include namespace devilution { @@ -15,15 +15,17 @@ namespace { char *BufCopy(char *out, long long value) { - const fmt::format_int formatted { value }; - std::memcpy(out, formatted.data(), formatted.size()); - return out + formatted.size(); + char buf[MaxNumDigits]; + char *end = std::to_chars(buf, buf + sizeof(buf), value).ptr; + std::memcpy(out, buf, end - buf); + return out + (end - buf); } char *BufCopy(char *out, unsigned long long value) { - const fmt::format_int formatted { value }; - std::memcpy(out, formatted.data(), formatted.size()); - return out + formatted.size(); + char buf[MaxNumDigits]; + char *end = std::to_chars(buf, buf + sizeof(buf), value).ptr; + std::memcpy(out, buf, end - buf); + return out + (end - buf); } char *BufCopy(char *out, AsHexU8Pad2 value) { @@ -50,13 +52,15 @@ char *BufCopy(char *out, AsHexU16Pad2 value) void StrAppend(std::string &out, long long value) { - const fmt::format_int formatted { value }; - out.append(formatted.data(), formatted.size()); + char buf[MaxNumDigits]; + char *end = std::to_chars(buf, buf + sizeof(buf), value).ptr; + out.append(buf, end); } void StrAppend(std::string &out, unsigned long long value) { - const fmt::format_int formatted { value }; - out.append(formatted.data(), formatted.size()); + char buf[MaxNumDigits]; + char *end = std::to_chars(buf, buf + sizeof(buf), value).ptr; + out.append(buf, end); } void StrAppend(std::string &out, AsHexU8Pad2 value) { diff --git a/docs/building.md b/docs/building.md index fecf1d7c7..d1957c08b 100644 --- a/docs/building.md +++ b/docs/building.md @@ -12,7 +12,7 @@ Note that ```pkg-config``` is an optional dependency for finding libsodium, alth ### Installing dependencies on Debian and Ubuntu ``` -sudo apt-get install cmake g++ libsdl2-dev libsodium-dev libpng-dev libbz2-dev libgtest-dev libgmock-dev libbenchmark-dev libsdl2-image-dev libfmt-dev +sudo apt-get install cmake g++ libsdl2-dev libsodium-dev libpng-dev libbz2-dev libgtest-dev libgmock-dev libbenchmark-dev libsdl2-image-dev ``` ### If you want to build the translations (optional) @@ -30,7 +30,7 @@ sudo apt-get install smpq ### Installing dependencies on Fedora ``` -sudo dnf install cmake gcc-c++ glibc-devel libstdc++-static SDL2-devel SDL2_image-devel libsodium-devel libpng-devel bzip2-devel gmock-devel gtest-devel google-benchmark-devel libasan libubsan fmt-devel libpfm-devel +sudo dnf install cmake gcc-c++ glibc-devel libstdc++-static SDL2-devel SDL2_image-devel libsodium-devel libpng-devel bzip2-devel gmock-devel gtest-devel google-benchmark-devel libasan libubsan libpfm-devel ``` ### Compiling @@ -54,7 +54,6 @@ Then, build DevilutionX using the cross-compilation CMake toolchain file: cmake -S. -Bbuild-aarch64-rel \ -DCMAKE_TOOLCHAIN_FILE=../CMake/platforms/aarch64-linux-gnu.toolchain.cmake \ -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr -DCPACK=ON \ - -DDEVILUTIONX_SYSTEM_LIBFMT=OFF cmake --build build-aarch64-rel -j $(getconf _NPROCESSORS_ONLN) --target package ``` @@ -334,7 +333,7 @@ You can find the compiled APK in `/android-project/app/build/outputs/apk/` ### Installing dependencies on Debian and Ubuntu ``` -pkg i which getconf cmake gettext libsodium sdl2 sdl2-image zlib bzip2 fmt +pkg i which getconf cmake gettext libsodium sdl2 sdl2-image zlib bzip2 ``` ### If you want to build the devilutionX.mpq File (optional) @@ -622,8 +621,7 @@ sudo port install curl curl-ca-bundle gcc14 cmake \ sudo port select --set gcc mp-gcc14 ~~~ - + Then, build DevilutionX: diff --git a/test/drlg_l2_test.cpp b/test/drlg_l2_test.cpp index 7bb4ef3d5..252473d55 100644 --- a/test/drlg_l2_test.cpp +++ b/test/drlg_l2_test.cpp @@ -1,4 +1,3 @@ -#include #include #include "drlg_test.hpp" diff --git a/test/drlg_l3_test.cpp b/test/drlg_l3_test.cpp index f0f3bf8af..7fbfa85b0 100644 --- a/test/drlg_l3_test.cpp +++ b/test/drlg_l3_test.cpp @@ -1,4 +1,3 @@ -#include #include #include "drlg_test.hpp" diff --git a/test/drlg_l4_test.cpp b/test/drlg_l4_test.cpp index 3a4025ad9..229d176c0 100644 --- a/test/drlg_l4_test.cpp +++ b/test/drlg_l4_test.cpp @@ -1,4 +1,3 @@ -#include #include #include "drlg_test.hpp" diff --git a/tools/Dockerfile.s390x b/tools/Dockerfile.s390x index 5a166769b..324f5dcdd 100644 --- a/tools/Dockerfile.s390x +++ b/tools/Dockerfile.s390x @@ -7,7 +7,7 @@ RUN wget -nv -nc https://github.com/diasurgical/devilutionx-assets/releases/down # We also need to install GCC because it provides crtbeginS.o: # https://pkgs.alpinelinux.org/contents?file=crtbeginS.o&path=&name=gcc&branch=edge&repo=main&arch=s390x RUN apk add --no-cache \ - clang15 gcc binutils musl-dev ninja cmake ccache sdl2-dev sdl2_image-dev fmt-dev \ + clang15 gcc binutils musl-dev ninja cmake ccache sdl2-dev sdl2_image-dev \ libpng-dev bzip2-dev gtest-dev ENV CC=/usr/bin/clang CXX=/usr/bin/clang++ diff --git a/tools/make_src_dist.py b/tools/make_src_dist.py index 16147de03..8d0858d1f 100755 --- a/tools/make_src_dist.py +++ b/tools/make_src_dist.py @@ -50,7 +50,7 @@ _ALWAYS_VENDORED_DEPS = ['asio', 'mpqfs', 'libsmackerdec', 'libzt'] # These dependencies are not vendored by default. # Run with `--fully_vendored` to include them. _DEPS_NOT_VENDORED_BY_DEFAULT = ['googletest', 'benchmark', 'sdl2', 'sdl_image', - 'libpng', 'libfmt', 'bzip2', 'libsodium'] + 'libpng', 'bzip2', 'libsodium'] _ROOT_DIR = pathlib.Path(__file__).resolve().parent.parent _BUILD_DIR = _ROOT_DIR.joinpath('build-src-dist') diff --git a/uwp-project/devilutionx.vcxproj b/uwp-project/devilutionx.vcxproj index d3ff80297..f1a0e14de 100644 --- a/uwp-project/devilutionx.vcxproj +++ b/uwp-project/devilutionx.vcxproj @@ -74,12 +74,12 @@ sdl_image.lib;libpng16_staticd.lib;mpqfs.lib;fmtd.lib;zlibstatic.lib;bzip2.lib;libsmackerdec.lib;libdevilutionx.lib;lua_static.lib;sdl2.lib;sdl_audiolib.lib;asio.lib;sodium.lib;zt.lib;lwip_pic.lib;miniupnpc_pic.lib;natpmp_pic.lib;zt_pic.lib;zto_pic.lib;shlwapi.lib;shell32.lib;%(AdditionalDependencies) - ..\build\SDL\VisualC-WinRT\x64\Debug\SDL-UWP;..\build\3rdParty\SDL_image\Debug;..\build\_deps\zlib-build\Debug;..\build\3rdParty\bzip2\Debug;..\build\3rdParty\libsmackerdec\Debug;..\build\_deps\mpqfs-build\Debug;..\build\_deps\lua-build\lua-5.4.7\Debug;..\build\_deps\sdl_audiolib-build\Debug;..\build\3rdParty\asio\Debug;..\build\_deps\libsodium-build\Debug;..\build\_deps\libzt-build\lib\Debug;..\build\_deps\libfmt-build\Debug;..\build\_deps\libpng-build\Debug;..\build\Source\libdevilutionx.dir\Debug;%(AdditionalLibraryDirectories) + ..\build\SDL\VisualC-WinRT\x64\Debug\SDL-UWP;..\build\3rdParty\SDL_image\Debug;..\build\_deps\zlib-build\Debug;..\build\3rdParty\bzip2\Debug;..\build\3rdParty\libsmackerdec\Debug;..\build\_deps\mpqfs-build\Debug;..\build\_deps\lua-build\lua-5.4.7\Debug;..\build\_deps\sdl_audiolib-build\Debug;..\build\3rdParty\asio\Debug;..\build\_deps\libsodium-build\Debug;..\build\_deps\libzt-build\lib\Debug;..\build\_deps\libpng-build\Debug;..\build\Source\libdevilutionx.dir\Debug;%(AdditionalLibraryDirectories) pch.h $(IntDir)pch.pch - ..\Source;..\build\SDL\include;..\build\_deps\sdl_audiolib-src\include;..\build\_deps\sdl_audiolib-build;..\build\_deps\unordered_dense-src\include;..\build\_deps\libfmt-src\include;..\3rdParty\tl;%(AdditionalIncludeDirectories) + ..\Source;..\build\SDL\include;..\build\_deps\sdl_audiolib-src\include;..\build\_deps\sdl_audiolib-build;..\build\_deps\unordered_dense-src\include;..\3rdParty\tl;%(AdditionalIncludeDirectories) /utf-8 /bigobj %(AdditionalOptions) 4453;28204 _DEBUG;__UWP__=1;%(PreprocessorDefinitions) @@ -92,8 +92,8 @@ - sdl_image.lib;libpng16_static.lib;mpqfs.lib;fmt.lib;zlibstatic.lib;bzip2.lib;libsmackerdec.lib;libdevilutionx.lib;lua_static.lib;sdl2.lib;sdl_audiolib.lib;sheenbidi.lib;asio.lib;sodium.lib;zt.lib;lwip_pic.lib;miniupnpc_pic.lib;natpmp_pic.lib;zt_pic.lib;zto_pic.lib;shlwapi.lib;shell32.lib;%(AdditionalDependencies) - ..\build\SDL\VisualC-WinRT\x64\Release\SDL-UWP;..\build\3rdParty\SDL_image\Release;..\build\_deps\zlib-build\Release;..\build\3rdParty\bzip2\Release;..\build\3rdParty\libsmackerdec\Release;..\build\_deps\mpqfs-build\Release;..\build\_deps\lua-build\lua-5.4.7\Release;..\build\_deps\sdl_audiolib-build\Release;..\build\3rdParty\asio\Release;..\build\_deps\libsodium-build\Release;..\build\_deps\libzt-build\lib\Release;..\build\_deps\libfmt-build\Release;..\build\_deps\libpng-build\Release;..\build\_deps\sheenbidi-build\Release;..\build\Source\libdevilutionx.dir\Release;%(AdditionalLibraryDirectories) + sdl_image.lib;libpng16_static.lib;mpqfs.lib;zlibstatic.lib;bzip2.lib;libsmackerdec.lib;libdevilutionx.lib;lua_static.lib;sdl2.lib;sdl_audiolib.lib;sheenbidi.lib;asio.lib;sodium.lib;zt.lib;lwip_pic.lib;miniupnpc_pic.lib;natpmp_pic.lib;zt_pic.lib;zto_pic.lib;shlwapi.lib;shell32.lib;%(AdditionalDependencies) + ..\build\SDL\VisualC-WinRT\x64\Release\SDL-UWP;..\build\3rdParty\SDL_image\Release;..\build\_deps\zlib-build\Release;..\build\3rdParty\bzip2\Release;..\build\3rdParty\libsmackerdec\Release;..\build\_deps\mpqfs-build\Release;..\build\_deps\lua-build\lua-5.4.7\Release;..\build\_deps\sdl_audiolib-build\Release;..\build\3rdParty\asio\Release;..\build\_deps\libsodium-build\Release;..\build\_deps\libzt-build\lib\Release;..\build\_deps\libpng-build\Release;..\build\_deps\sheenbidi-build\Release;..\build\Source\libdevilutionx.dir\Release;%(AdditionalLibraryDirectories) pch.h @@ -102,7 +102,7 @@ 4453;28204 NDEBUG;__UWP__=1;%(PreprocessorDefinitions) NotUsing - ..\Source;..\build\SDL\include;..\build\_deps\sdl_audiolib-src\include;..\build\_deps\sdl_audiolib-build;..\build\_deps\unordered_dense-src\include;..\build\_deps\libfmt-src\include;..\3rdParty\tl;%(AdditionalIncludeDirectories) + ..\Source;..\build\SDL\include;..\build\_deps\sdl_audiolib-src\include;..\build\_deps\sdl_audiolib-build;..\build\_deps\unordered_dense-src\include;..\3rdParty\tl;%(AdditionalIncludeDirectories) stdcpp17 diff --git a/vcpkg.json b/vcpkg.json index eecfd4895..2c7290842 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,38 +1,50 @@ { - "name": "devilutionx", - "version-string": "1.6.0", - "dependencies": [ - "fmt", - "bzip2", - "lua", - "magic-enum" - ], - "builtin-baseline": "a62ce77d56ee07513b4b67de1ec2daeaebfae51a", - "features": { - "sdl1": { - "description": "Use SDL1.2 instead of SDL2", - "dependencies": [ "sdl1", "libpng" ] - }, - "sdl2": { - "description": "Use SDL2", - "dependencies": [ "sdl2", "sdl2-image" ] - }, - "encryption": { - "description": "Build libsodium for packet encryption", - "dependencies": [ "libsodium" ] - }, - "translations": { - "description": "Build translation files", - "dependencies": [ - { - "name": "gettext", - "features": [ "tools" ] - } - ] - }, - "tests": { - "description": "Build tests", - "dependencies": [ "gtest", "benchmark" ] - } - } + "name": "devilutionx", + "version-string": "1.6.0", + "dependencies": [ + "bzip2", + "lua", + "magic-enum" + ], + "builtin-baseline": "a62ce77d56ee07513b4b67de1ec2daeaebfae51a", + "features": { + "sdl1": { + "description": "Use SDL1.2 instead of SDL2", + "dependencies": [ + "sdl1", + "libpng" + ] + }, + "sdl2": { + "description": "Use SDL2", + "dependencies": [ + "sdl2", + "sdl2-image" + ] + }, + "encryption": { + "description": "Build libsodium for packet encryption", + "dependencies": [ + "libsodium" + ] + }, + "translations": { + "description": "Build translation files", + "dependencies": [ + { + "name": "gettext", + "features": [ + "tools" + ] + } + ] + }, + "tests": { + "description": "Build tests", + "dependencies": [ + "gtest", + "benchmark" + ] + } + } }