diff --git a/.gitattributes b/.gitattributes index 7751149ac..cd995a815 100644 --- a/.gitattributes +++ b/.gitattributes @@ -20,6 +20,9 @@ *.csproj* -crlf -whitespace *.vcxproj* -crlf -whitespace *.manifest -crlf -whitespace +# vcpkg +/vcpkg.json text=auto +/vcpkg-configuration.json text=auto # Patches /tools/SDL-1.2.14-gc/SDL-1.2.14-gc.patch -whitespace #Appveyor diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 29f5ef5ff..798ecf151 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,8 +12,7 @@ variables: stages: - build + - osxcross default: interruptible: true - artifacts: - expire_in: 1 day diff --git a/.gitlab/ci/jobs/alpine-3-gcc-dedicated.yml b/.gitlab/ci/jobs/alpine-3-gcc-dedicated.yml index 3401b6996..47aeb5cea 100644 --- a/.gitlab/ci/jobs/alpine-3-gcc-dedicated.yml +++ b/.gitlab/ci/jobs/alpine-3-gcc-dedicated.yml @@ -3,8 +3,8 @@ Alpine 3 GCC Dedicated: artifacts: paths: - - "build.alpine3ded/bin/" - - "build.alpine3ded/src/config.h" + - "build.cmake/bin/" + - "build.cmake/src/config.h" expose_as: "Apline-3-Dedicated" name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-Apline-3-Dedicated" @@ -12,7 +12,7 @@ Alpine 3 GCC Dedicated: - - | # apk_toolchain echo -e "\e[0Ksection_start:`date +%s`:apk_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages" - - apk add gcc + - apk add g++ - | # apk_toolchain echo -e "\e[0Ksection_end:`date +%s`:apk_toolchain\r\e[0K" @@ -20,7 +20,7 @@ Alpine 3 GCC Dedicated: - - | # apk_development echo -e "\e[0Ksection_start:`date +%s`:apk_development[collapsed=true]\r\e[0KInstalling development packages" - - apk add musl-dev libpng-dev curl-dev + - apk add cmake musl-dev sdl2-dev libpng-dev curl-dev elfutils-dev - | # apk_development echo -e "\e[0Ksection_end:`date +%s`:apk_development\r\e[0K" @@ -28,7 +28,15 @@ Alpine 3 GCC Dedicated: - - | # cmake echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles" - - cmake -B build.alpine3ded -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles" + - | + cmake \ + --preset ninja-debug \ + -B build.cmake \ + -G "Unix Makefiles" \ + -DCMAKE_COLOR_DIAGNOSTICS=OFF \ + -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF \ + -DSRB2_CONFIG_EXECINFO=NO \ + -DSRB2_CONFIG_DEDICATED=ON - | # cmake echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K" @@ -36,7 +44,7 @@ Alpine 3 GCC Dedicated: - - | # make echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" - - make --directory=build.alpine3ded --keep-going || make --directory=build.alpine3ded --keep-going + - cmake --build build.cmake --parallel 1 - | # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" diff --git a/.gitlab/ci/jobs/alpine-3-gcc.yml b/.gitlab/ci/jobs/alpine-3-gcc.yml index 4400cba8f..0bef7dfe0 100644 --- a/.gitlab/ci/jobs/alpine-3-gcc.yml +++ b/.gitlab/ci/jobs/alpine-3-gcc.yml @@ -15,10 +15,11 @@ Alpine 3 GCC: artifacts: paths: - - "build.alpine3/bin/" - - "build.alpine3/src/config.h" + - "build.cmake/bin/" + - "build.cmake/src/config.h" expose_as: "Apline-3" name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-Apline-3" + expire_in: 1 day before_script: - - | @@ -50,7 +51,7 @@ Alpine 3 GCC: - - | # apk_common echo -e "\e[0Ksection_start:`date +%s`:apk_common[collapsed=true]\r\e[0KInstalling common packages" - - apk add make git ccache nasm + - apk add cmake make git ccache nasm - | # apk_common echo -e "\e[0Ksection_end:`date +%s`:apk_common\r\e[0K" @@ -58,26 +59,24 @@ Alpine 3 GCC: - - | # ccache_config echo -e "\e[0Ksection_start:`date +%s`:ccache_config[collapsed=true]\r\e[0KSetting up ccache config" - - mkdir --parents --verbose ~/.ccache/ - - touch ~/.ccache/ccache.conf - | # cache.conf echo Adding ccache configution option - | # base_dir - echo base_dir = $PWD | tee -a ~/.ccache/ccache.conf + ccache --set-config base_dir=$CI_PROJECT_DIR - | # cache_dir - echo cache_dir = $PWD/ccache | tee -a ~/.ccache/ccache.conf + ccache --set-config cache_dir=$CI_PROJECT_DIR/build/ccache - | # compiler_check - echo compiler_check = content | tee -a ~/.ccache/ccache.conf + ccache --set-config compiler_check=content - | # stats_log - echo stats_log = $PWD/ccache_statslog | tee -a ~/.ccache/ccache.conf + ccache --set-config stats_log=$CI_PROJECT_DIR/build/ccache_statslog - | # max_size - echo max_size = 50M | tee -a ~/.ccache/ccache.conf + ccache --set-config max_size=300M - | # ccache_config echo -e "\e[0Ksection_end:`date +%s`:ccache_config\r\e[0K" @@ -95,7 +94,7 @@ Alpine 3 GCC: - - | # apk_toolchain echo -e "\e[0Ksection_start:`date +%s`:apk_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages" - - apk add gcc + - apk add g++ - | # apk_toolchain echo -e "\e[0Ksection_end:`date +%s`:apk_toolchain\r\e[0K" @@ -103,7 +102,7 @@ Alpine 3 GCC: - - | # apk_development echo -e "\e[0Ksection_start:`date +%s`:apk_development[collapsed=true]\r\e[0KInstalling development packages" - - apk add cmake musl-dev sdl2_mixer-dev libpng-dev curl-dev libgme-dev libopenmpt-dev miniupnpc-dev + - apk add cmake musl-dev sdl2-dev libpng-dev curl-dev elfutils-dev - | # apk_development echo -e "\e[0Ksection_end:`date +%s`:apk_development\r\e[0K" @@ -111,7 +110,14 @@ Alpine 3 GCC: - - | # cmake echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles" - - cmake -B build.alpine3 -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles" + - | + cmake \ + --preset ninja-debug \ + -B build.cmake \ + -G "Unix Makefiles" \ + -DCMAKE_COLOR_DIAGNOSTICS=OFF \ + -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF \ + -DSRB2_CONFIG_EXECINFO=NO - | # cmake echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K" @@ -119,7 +125,7 @@ Alpine 3 GCC: - - | # make echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" - - make --directory=build.alpine3 --keep-going || make --directory=build.alpine3 --keep-going + - cmake --build build.cmake --parallel 1 -- --keep-going - | # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" diff --git a/.gitlab/ci/jobs/batocera-arm64.yml b/.gitlab/ci/jobs/batocera-arm64.yml index ee647a300..5d4e30d13 100644 --- a/.gitlab/ci/jobs/batocera-arm64.yml +++ b/.gitlab/ci/jobs/batocera-arm64.yml @@ -1,6 +1,8 @@ batocera:arm64: extends: Debian stable:arm64 + stage: build + when: manual allow_failure: true @@ -16,7 +18,7 @@ batocera:arm64: - - | # apt_toolchain echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages" - - apt-get install gcc-aarch64-linux-gnu || apt-get install gcc + - apt-get install g++-aarch64-linux-gnu || apt-get install g++ - | # apt_toolchain echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K" @@ -24,7 +26,7 @@ batocera:arm64: - - | # apt_development echo -e "\e[0Ksection_start:`date +%s`:apt_development[collapsed=true]\r\e[0KInstalling development packages" - - apt-get install libsdl2-mixer-dev:arm64 libpng-dev:arm64 libcurl4-openssl-dev:arm64 libopenmpt-dev:arm64 libminiupnpc-dev:arm64 + - apt-get install libsdl2-dev:arm64 libpng-dev:arm64 libcurl4-openssl-dev:arm64 libyuv-dev:arm64 - | # apt_development echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" @@ -32,7 +34,14 @@ batocera:arm64: - - | # cmake echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles" - - cmake -B build.cmake -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles" + - | + cmake \ + --preset ninja-debug \ + -B build.cmake \ + -G "Unix Makefiles" \ + -DCMAKE_COLOR_DIAGNOSTICS=OFF \ + -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF \ + -DSRB2_CONFIG_FORCE_NO_MS_BITFIELDS=ON - | # cmake echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K" @@ -40,7 +49,7 @@ batocera:arm64: - - | # make echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" - - make --directory=build.cmake --keep-going || make --directory=build.cmake --keep-going + - cmake --build build.cmake --parallel 1 -- --keep-going - | # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" diff --git a/.gitlab/ci/jobs/debian-oldstable-amd64.yml b/.gitlab/ci/jobs/debian-oldstable-amd64.yml index 9c3c78323..0d6bac14c 100644 --- a/.gitlab/ci/jobs/debian-oldstable-amd64.yml +++ b/.gitlab/ci/jobs/debian-oldstable-amd64.yml @@ -1,6 +1,8 @@ Debian oldstable:amd64: extends: Debian stable:amd64 + stage: build + when: manual image: git.do.srb2.org:5050/stjr/srb2ci/srb2ci:oldstable @@ -18,7 +20,7 @@ Debian oldstable:amd64: - - | # apt_toolchain echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages" - - apt-get install gcc-x86-64-linux-gnu || apt-get install gcc + - apt-get install g++-x86-64-linux-gnu || apt-get install g++ - | # apt_toolchain echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K" @@ -26,7 +28,7 @@ Debian oldstable:amd64: - - | # apt_development echo -e "\e[0Ksection_start:`date +%s`:apt_development[collapsed=true]\r\e[0KInstalling development packages" - - apt-get install libsdl2-mixer-dev:amd64 libpng-dev:amd64 libcurl4-openssl-dev:amd64 libopenmpt-dev:amd64 libminiupnpc-dev:amd64 + - apt-get install libsdl2-dev:amd64 libpng-dev:amd64 libcurl4-openssl-dev:amd64 - | # apt_development echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" @@ -34,7 +36,16 @@ Debian oldstable:amd64: - - | # cmake echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles" - - cmake -B build.cmake -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles" + - | + cmake \ + -B build.cmake \ + -G "Unix Makefiles" \ + -DCMAKE_COLOR_DIAGNOSTICS=OFF \ + -DCMAKE_C_FLAGS_RELWITHDEBINFO="-O3 -g -DNDEBUG" \ + -DCMAKE_CXX_FLAGS_RELWITHDEBINFO="-O3 -g -DNDEBUG" + -DSRB2_CONFIG_DEV_BUILD=ON \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF - | # cmake echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K" @@ -42,7 +53,7 @@ Debian oldstable:amd64: - - | # make echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" - - make --directory=build.cmake --keep-going || make --directory=build.cmake --keep-going + - cmake --build build.cmake --parallel 1 -- --keep-going - | # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" diff --git a/.gitlab/ci/jobs/debian-oldstable-arm64.yml b/.gitlab/ci/jobs/debian-oldstable-arm64.yml index 566f409ae..bdf12dbe1 100644 --- a/.gitlab/ci/jobs/debian-oldstable-arm64.yml +++ b/.gitlab/ci/jobs/debian-oldstable-arm64.yml @@ -1,6 +1,8 @@ Debian oldstable:arm64: extends: Debian stable:arm64 + stage: build + when: manual image: git.do.srb2.org:5050/stjr/srb2ci/srb2ci:oldstable @@ -18,7 +20,7 @@ Debian oldstable:arm64: - - | # apt_toolchain echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages" - - apt-get install gcc-aarch64-linux-gnu || apt-get install gcc + - apt-get install g++-aarch64-linux-gnu || apt-get install g++ - | # apt_toolchain echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K" @@ -26,7 +28,7 @@ Debian oldstable:arm64: - - | # apt_development echo -e "\e[0Ksection_start:`date +%s`:apt_development[collapsed=true]\r\e[0KInstalling development packages" - - apt-get install libsdl2-mixer-dev:arm64 libpng-dev:arm64 libcurl4-openssl-dev:arm64 libopenmpt-dev:arm64 libminiupnpc-dev:arm64 + - apt-get install libsdl2-dev:arm64 libpng-dev:arm64 libcurl4-openssl-dev:arm64 - | # apt_development echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" @@ -34,7 +36,17 @@ Debian oldstable:arm64: - - | # cmake echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles" - - cmake -B build.cmake -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles" + - | + cmake \ + -B build.cmake \ + -G "Unix Makefiles" \ + -DCMAKE_COLOR_DIAGNOSTICS=OFF \ + -DCMAKE_C_FLAGS_RELWITHDEBINFO="-O3 -g -DNDEBUG" \ + -DCMAKE_CXX_FLAGS_RELWITHDEBINFO="-O3 -g -DNDEBUG" + -DSRB2_CONFIG_DEV_BUILD=ON \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF \ + -DSRB2_CONFIG_FORCE_NO_MS_BITFIELDS=ON - | # cmake echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K" @@ -42,7 +54,7 @@ Debian oldstable:arm64: - - | # make echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" - - make --directory=build.cmake --keep-going || make --directory=build.cmake --keep-going + - cmake --build build.cmake --parallel 1 -- --keep-going - | # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" diff --git a/.gitlab/ci/jobs/debian-stable-amd64.yml b/.gitlab/ci/jobs/debian-stable-amd64.yml index 0b4149c9b..419e8b218 100644 --- a/.gitlab/ci/jobs/debian-stable-amd64.yml +++ b/.gitlab/ci/jobs/debian-stable-amd64.yml @@ -12,7 +12,7 @@ Debian stable:amd64: variables: CC: x86_64-linux-gnu-gcc - LDFLAGS: -Wl,-fuse-ld=gold + CXX: x86_64-linux-gnu-g++ OBJCOPY: x86_64-linux-gnu-objcopy OBJDUMP: x86_64-linux-gnu-objdump PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig @@ -23,7 +23,7 @@ Debian stable:amd64: - - | # apt_toolchain echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages" - - apt-get install gcc-x86-64-linux-gnu || apt-get install gcc + - apt-get install g++-x86-64-linux-gnu || apt-get install g++ - | # apt_toolchain echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K" @@ -31,7 +31,7 @@ Debian stable:amd64: - - | # apt_development echo -e "\e[0Ksection_start:`date +%s`:apt_development[collapsed=true]\r\e[0KInstalling development packages" - - apt-get install libsdl2-mixer-dev:amd64 libpng-dev:amd64 libcurl4-openssl-dev:amd64 libgme-dev:amd64 libopenmpt-dev:amd64 libminiupnpc-dev:amd64 + - apt-get install libsdl2-dev:amd64 libpng-dev:amd64 libcurl4-openssl-dev:amd64 libyuv-dev:amd64 - | # apt_development echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" @@ -40,11 +40,11 @@ Debian stable:amd64: # cmake echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles" - | - CCACHE=$(which ccache) - cmake -B build.cmake \ + cmake \ + --preset ninja-debug \ + -B build.cmake \ -G "Unix Makefiles" \ - -DCMAKE_C_COMPILER_LAUNCHER=$CCACHE \ - -DCMAKE_CXX_COMPILER_LAUNCHER=$CCACHE \ + -DCMAKE_COLOR_DIAGNOSTICS=OFF \ -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF - | # cmake @@ -53,7 +53,7 @@ Debian stable:amd64: - - | # make echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" - - make --directory=build.cmake --keep-going || make --directory=build.cmake --keep-going + - cmake --build build.cmake --parallel 1 -- --keep-going - | # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" diff --git a/.gitlab/ci/jobs/debian-stable-arm64.yml b/.gitlab/ci/jobs/debian-stable-arm64.yml index e5f73a55a..0420c3345 100644 --- a/.gitlab/ci/jobs/debian-stable-arm64.yml +++ b/.gitlab/ci/jobs/debian-stable-arm64.yml @@ -14,16 +14,17 @@ Debian stable:arm64: variables: CC: aarch64-linux-gnu-gcc - LDFLAGS: -Wl,-fuse-ld=gold + CXX: aarch64-linux-gnu-g++ OBJCOPY: aarch64-linux-gnu-objcopy OBJDUMP: aarch64-linux-gnu-objdump + LD: aarch64-linux-gnu-ld PKG_CONFIG_PATH: /usr/lib/aarch64-linux-gnu/pkgconfig script: - - | # apt_toolchain echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages" - - apt-get install gcc-aarch64-linux-gnu || apt-get install gcc + - apt-get install g++-aarch64-linux-gnu || apt-get install g++ - | # apt_toolchain echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K" @@ -31,7 +32,7 @@ Debian stable:arm64: - - | # apt_development echo -e "\e[0Ksection_start:`date +%s`:apt_development[collapsed=true]\r\e[0KInstalling development packages" - - apt-get install libsdl2-mixer-dev:arm64 libpng-dev:arm64 libcurl4-openssl-dev:arm64 libgme-dev:arm64 libopenmpt-dev:arm64 libminiupnpc-dev:arm64 + - apt-get install libsdl2-dev:arm64 libpng-dev:arm64 libcurl4-openssl-dev:arm64 libyuv-dev:arm64 - | # apt_development echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" @@ -39,7 +40,14 @@ Debian stable:arm64: - - | # cmake echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles" - - cmake -B build.cmake -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles" + - | + cmake \ + --preset ninja-debug \ + -B build.cmake \ + -G "Unix Makefiles" \ + -DCMAKE_COLOR_DIAGNOSTICS=OFF \ + -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF \ + -DSRB2_CONFIG_FORCE_NO_MS_BITFIELDS=ON - | # cmake echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K" @@ -47,7 +55,7 @@ Debian stable:arm64: - - | # make echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" - - make --directory=build.cmake --keep-going || make --directory=build.cmake --keep-going + - cmake --build build.cmake --parallel 1 -- --keep-going - | # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" diff --git a/.gitlab/ci/jobs/debian-stable-clang-amd64.yml b/.gitlab/ci/jobs/debian-stable-clang-amd64.yml index 2c59be4e3..ff2c63e85 100644 --- a/.gitlab/ci/jobs/debian-stable-clang-amd64.yml +++ b/.gitlab/ci/jobs/debian-stable-clang-amd64.yml @@ -3,23 +3,20 @@ Debian stable Clang: stage: build - when: manual + when: on_success - allow_failure: true + allow_failure: false artifacts: paths: - - "build.clang/bin/" - - "build.clang/src/config.h" + - "build.cmake/bin/" + - "build.cmake/src/config.h" expose_as: "clang" name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-clang" variables: CC: clang - CXX: clang - WFLAGS: -Wno-cast-align -Wno-implicit-const-int-float-conversion -Werror - CFLAGS: -Wno-cast-align -Wno-implicit-const-int-float-conversion -Werror - LDFLAGS: -Wl,-fuse-ld=gold + CXX: clang++ script: - - | @@ -33,7 +30,7 @@ Debian stable Clang: - - | # apt_development echo -e "\e[0Ksection_start:`date +%s`:apt_development[collapsed=true]\r\e[0KInstalling development packages" - - apt-get install libsdl2-mixer-dev libpng-dev libcurl4-openssl-dev libgme-dev libopenmpt-dev libminiupnpc-dev + - apt-get install libsdl2-dev libpng-dev libcurl4-openssl-dev libyuv-dev - | # apt_development echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" @@ -41,7 +38,16 @@ Debian stable Clang: - - | # cmake echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles" - - cmake -B build.clang -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles" + - | + cmake \ + --preset ninja-debug \ + -B build.cmake \ + -G "Unix Makefiles" \ + -DCMAKE_COLOR_DIAGNOSTICS=OFF \ + -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF \ + -DCPM_USE_LOCAL_PACKAGES:BOOL=ON \ + -DSRB2_CONFIG_ENABLE_TESTS:BOOL=OFF \ + -DSRB2_CONFIG_SYSTEM_LIBRARIES:BOOL=ON - | # cmake echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K" @@ -49,7 +55,7 @@ Debian stable Clang: - - | # make echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" - - make --directory=build.clang --keep-going || make --directory=build.clang --keep-going + - cmake --build build.cmake --parallel 1 -- --keep-going - | # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" diff --git a/.gitlab/ci/jobs/debian-stable-i386.yml b/.gitlab/ci/jobs/debian-stable-i386.yml index 8b9785bb2..0c6b0798a 100644 --- a/.gitlab/ci/jobs/debian-stable-i386.yml +++ b/.gitlab/ci/jobs/debian-stable-i386.yml @@ -14,15 +14,17 @@ Debian stable:i386: variables: CC: i686-linux-gnu-gcc + CXX: i686-linux-gnu-g++ OBJCOPY: i686-linux-gnu-objcopy OBJDUMP: i686-linux-gnu-objdump + LD: i686-linux-gnu-ld PKG_CONFIG_PATH: /usr/lib/i386-linux-gnu/pkgconfig script: - - | # apt_toolchain echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages" - - apt-get install gcc-i686-linux-gnu || apt-get install gcc + - apt-get install g++-i686-linux-gnu || apt-get install g++ - | # apt_toolchain echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K" @@ -30,7 +32,7 @@ Debian stable:i386: - - | # apt_development echo -e "\e[0Ksection_start:`date +%s`:apt_development[collapsed=true]\r\e[0KInstalling development packages" - - apt-get install libsdl2-mixer-dev:i386 libpng-dev:i386 libcurl4-openssl-dev:i386 libgme-dev:i386 libopenmpt-dev:i386 libminiupnpc-dev:i386 + - apt-get install libsdl2-dev:i386 libpng-dev:i386 libcurl4-openssl-dev:i386 libyuv-dev:i386 - | # apt_development echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" @@ -38,7 +40,13 @@ Debian stable:i386: - - | # cmake echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles" - - cmake -B build.cmake -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles" + - | + cmake \ + --preset ninja-debug \ + -B build.cmake \ + -G "Unix Makefiles" \ + -DCMAKE_COLOR_DIAGNOSTICS=OFF \ + -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF - | # cmake echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K" @@ -46,7 +54,7 @@ Debian stable:i386: - - | # make echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" - - make --directory=build.cmake --keep-going || make --directory=build.cmake --keep-going + - cmake --build build.cmake --parallel 1 -- --keep-going - | # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" diff --git a/.gitlab/ci/jobs/debian-testing-clang-amd64.yml b/.gitlab/ci/jobs/debian-testing-clang-amd64.yml index dc790b397..637d7e49c 100644 --- a/.gitlab/ci/jobs/debian-testing-clang-amd64.yml +++ b/.gitlab/ci/jobs/debian-testing-clang-amd64.yml @@ -9,14 +9,7 @@ Debian testing Clang: artifacts: paths: - - "build.clang/bin/" - - "build.clang/src/config.h" + - "build.cmake/bin/" + - "build.cmake/src/config.h" expose_as: "testing-clang" name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-testing-clang" - - variables: - CC: clang - CXX: clang - WFLAGS: -Wno-cast-align -Wno-implicit-const-int-float-conversion -Werror -Wno-deprecated-non-prototype -Wno-single-bit-bitfield-constant-conversion - CFLAGS: -Wno-cast-align -Wno-implicit-const-int-float-conversion -Werror -Wno-deprecated-non-prototype -Wno-single-bit-bitfield-constant-conversion - LDFLAGS: -Wl,-fuse-ld=gold diff --git a/.gitlab/ci/jobs/debian-testing-gcc-amd64.yml b/.gitlab/ci/jobs/debian-testing-gcc-amd64.yml index 309b86d83..15544076d 100644 --- a/.gitlab/ci/jobs/debian-testing-gcc-amd64.yml +++ b/.gitlab/ci/jobs/debian-testing-gcc-amd64.yml @@ -18,13 +18,13 @@ Debian testing GCC: variables: CC: gcc - LDFLAGS: -Wl,-fuse-ld=gold + CXX: g++ script: - - | # apt_toolchain echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages" - - apt-get install gcc + - apt-get install g++ - | # apt_toolchain echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K" @@ -32,7 +32,7 @@ Debian testing GCC: - - | # apt_development echo -e "\e[0Ksection_start:`date +%s`:apt_development[collapsed=true]\r\e[0KInstalling development packages" - - apt-get install libsdl2-mixer-dev libpng-dev libcurl4-openssl-dev libgme-dev libopenmpt-dev libminiupnpc-dev + - apt-get install libsdl2-dev libpng-dev libcurl4-openssl-dev libyuv-dev - | # apt_development echo -e "\e[0Ksection_end:`date +%s`:apt_development\r\e[0K" @@ -40,7 +40,13 @@ Debian testing GCC: - - | # cmake echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles" - - cmake -B build.cmake -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles" + - | + cmake \ + --preset ninja-debug \ + -B build.cmake \ + -G "Unix Makefiles" \ + -DCMAKE_COLOR_DIAGNOSTICS=OFF \ + -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF - | # cmake echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K" @@ -48,7 +54,7 @@ Debian testing GCC: - - | # make echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" - - make --directory=build.cmake --keep-going || make --directory=build.cmake --keep-going + - cmake --build build.cmake --parallel 1 -- --keep-going - | # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" diff --git a/.gitlab/ci/jobs/macos-arm64.yml b/.gitlab/ci/jobs/macos-arm64.yml index 2674cac4f..3afbb7896 100644 --- a/.gitlab/ci/jobs/macos-arm64.yml +++ b/.gitlab/ci/jobs/macos-arm64.yml @@ -3,26 +3,57 @@ osxcross arm64: stage: build - when: manual + cache: + - key: ccache-$CI_JOB_NAME_SLUG-$CI_COMMIT_REF_SLUG + fallback_keys: + - ccache-$CI_JOB_NAME_SLUG-$CI_DEFAULT_BRANCH + - ccache-$CI_JOB_NAME_SLUG-master + paths: + - build/ccache + - build/ccache_statslog - allow_failure: true + - key: apt-$CI_JOB_IMAGE + paths: + - build/apt-cache + unprotect: true + + - key: vcpkg-binary-cache-arm64-osx + paths: + - build/vcpkg-binary-cache + unprotect: true artifacts: paths: - - "build.osxcross/bin/" - - "build.osxcross/src/config.h" - expose_as: "Mac arm64" - name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-clang" + - "build.arm64/bin/" + - "build.arm64/dist/arm64.h" + - "build.arm64/src/config.h" + name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-arm64-apple-darwin" variables: - OSXCROSS_HOST: arm64-apple-darwin21.4 - LD: arm64-apple-darwin21.4-ld + CMAKE_TOOLCHAIN_FILE: /osxcross/toolchain.cmake + CCACHE_CPP2: yes script: + - - | + # osxcross Config + echo -e "\e[0Ksection_start:`date +%s`:osxcross_Config[collapsed=true]\r\e[0Kosxcross Config" + - export VCPKG_CHAINLOAD_TOOLCHAIN_FILE=${OSXCROSS_TARGET_DIR}/toolchain.cmake + - export PATH="/opt/osxcross.arm64:${PATH}" + - $(osxcross-conf) + - export OSXCROSS_HOST=arm64-apple-${OSXCROSS_TARGET} + - export VCPKG_DEFAULT_TRIPLET=arm64-osx + - export CC=${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-clang + - export CXX=${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-clang++ + - export SDKROOT=${OSXCROSS_SDK} + - | + # osxcross Config + echo -e "\e[0Ksection_end:`date +%s`:osxcross_Config\r\e[0K" + - - | # apt_development echo -e "\e[0Ksection_start:`date +%s`:macports_development[collapsed=true]\r\e[0KInstalling development packages" - - osxcross-macports install --arm64 curl libopenmpt libsdl2_mixer + - osxcross-macports install --static --arm64 curl || osxcross-macports install --verbose --static --arm64 curl || true + - osxcross-macports install --static --arm64 libpng libopus || osxcross-macports install --verbose --static --arm64 libpng libopus - | # apt_development echo -e "\e[0Ksection_end:`date +%s`:macports_development\r\e[0K" @@ -30,7 +61,22 @@ osxcross arm64: - - | # cmake echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles" - - cmake -B build.osxcross --toolchain /osxcross/toolchain.cmake -DCPM_USE_LOCAL_PACKAGES:BOOL=ON -DOPENMPT_INCLUDE_DIR:PATH="/osxcross/macports/pkgs/opt/local/include" -DSDL2_INCLUDE_DIR:PATH="/osxcross/macports/pkgs/opt/local/lib" -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles" + - | + cmake \ + --preset ninja-debug \ + -B build.arm64 \ + -G "Unix Makefiles" \ + -DCMAKE_COLOR_DIAGNOSTICS=OFF \ + -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF \ + -DCPM_USE_LOCAL_PACKAGES:BOOL=ON \ + -DOPENMPT_INCLUDE_DIR:PATH="/osxcross/macports/pkgs/opt/local/include" \ + -DSDL2_INCLUDE_DIR:PATH="/osxcross/macports/pkgs/opt/local/lib" \ + -DSRB2_CONFIG_ENABLE_TESTS:BOOL=OFF \ + -DSRB2_CONFIG_SYSTEM_LIBRARIES:BOOL=ON \ + -DSRB2_SDL2_EXE_NAME=ringracers_$CI_PIPELINE_ID \ + -DSRB2_CONFIG_FORCE_NO_MS_BITFIELDS:BOOL=ON \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - | # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" @@ -38,7 +84,16 @@ osxcross arm64: - - | # make echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" - - make --directory=build.osxcross --keep-going || make --directory=build.osxcross --keep-going + - cmake --build build.arm64 --parallel 1 -- --keep-going - | # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" + + - - | + # copy config.h + echo -e "\e[0Ksection_start:`date +%s`:copy[collapsed=false]\r\e[0KCopying config.h" + - mkdir --parents --verbose build.arm64/dist + - cp --reflink=auto --sparse=always build.arm64/src/config.h build.arm64/dist/arm64.h + - | + # make + echo -e "\e[0Ksection_end:`date +%s`:copy\r\e[0K" diff --git a/.gitlab/ci/jobs/macos-x86_64.yml b/.gitlab/ci/jobs/macos-x86_64.yml index d469b54f9..c13f0bc5e 100644 --- a/.gitlab/ci/jobs/macos-x86_64.yml +++ b/.gitlab/ci/jobs/macos-x86_64.yml @@ -17,11 +17,6 @@ osxcross x86_64: - build/apt-cache unprotect: true - - key: vcpkg-root - paths: - - build/vcpkg-root - unprotect: true - - key: vcpkg-binary-cache-x64-osx paths: - build/vcpkg-binary-cache @@ -29,41 +24,36 @@ osxcross x86_64: artifacts: paths: - - "build.osxcross/bin/" - - "build.osxcross/src/config.h" - expose_as: "Mac x86_64" - name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-clang" + - "build.x86_64/bin/" + - "build.x86_64/dist/x86_64.h" + - "build.x86_64/src/config.h" + name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-x86_64-apple-darwin" variables: - OSXCROSS_HOST: x86_64-apple-darwin21.4 - LD: x86_64-apple-darwin21.4-ld + CMAKE_TOOLCHAIN_FILE: /osxcross/toolchain.cmake + CCACHE_CPP2: yes script: - - | - # vcpkg - echo -e "\e[0Ksection_start:`date +%s`:vcpkg-root[collapsed=true]\r\e[0KUpdating vcpkg" - - if [ -d "build/vcpkg-root" ]; then - pushd build/vcpkg-root - git fetch https://github.com/Microsoft/vcpkg master - git reset --hard FETCH_HEAD - popd - else - mkdir -p build - git clone https://github.com/Microsoft/vcpkg build/vcpkg-root - fi - - export VCPKG_ROOT=$(pwd)/build/vcpkg-root - export VCPKG_BINARY_SOURCES="clear;files,$(pwd)/build/vcpkg-binary-cache,readwrite" - - mkdir -p "build/vcpkg-binary-cache" - - echo -e "\e[0Ksection_end:`date +%s`:vcpkg-root\r\e[0K" + - - | + # osxcross Config + echo -e "\e[0Ksection_start:`date +%s`:osxcross_Config[collapsed=true]\r\e[0Kosxcross Config" + - export VCPKG_CHAINLOAD_TOOLCHAIN_FILE=${OSXCROSS_TARGET_DIR}/toolchain.cmake + - export PATH="/opt/osxcross.x86_64:${PATH}" + - $(osxcross-conf) + - export OSXCROSS_HOST=x86_64-apple-${OSXCROSS_TARGET} + - export VCPKG_DEFAULT_TRIPLET=x64-osx + - export CC=${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-clang + - export CXX=${OSXCROSS_TARGET_DIR}/bin/${OSXCROSS_HOST}-clang++ + - export SDKROOT=${OSXCROSS_SDK} + - | + # osxcross Config + echo -e "\e[0Ksection_end:`date +%s`:osxcross_Config\r\e[0K" - - | # apt_development echo -e "\e[0Ksection_start:`date +%s`:macports_development[collapsed=true]\r\e[0KInstalling development packages" - - osxcross-macports install curl libopenmpt libsdl2_mixer + - osxcross-macports install --static curl || osxcross-macports install --verbose --static curl || true + - osxcross-macports install --static libpng libopus || osxcross-macports install --verbose --static libpng libopus - | # apt_development echo -e "\e[0Ksection_end:`date +%s`:macports_development\r\e[0K" @@ -71,7 +61,21 @@ osxcross x86_64: - - | # cmake echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles" - - cmake -B build.osxcross --toolchain /osxcross/toolchain.cmake -DCPM_USE_LOCAL_PACKAGES:BOOL=ON -DOPENMPT_INCLUDE_DIR:PATH="/osxcross/macports/pkgs/opt/local/include" -DSDL2_INCLUDE_DIR:PATH="/osxcross/macports/pkgs/opt/local/lib" -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -G "Unix Makefiles" + - | + cmake \ + --preset ninja-debug \ + -B build.x86_64 \ + -G "Unix Makefiles" \ + -DCMAKE_COLOR_DIAGNOSTICS=OFF \ + -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF \ + -DCPM_USE_LOCAL_PACKAGES:BOOL=ON \ + -DOPENMPT_INCLUDE_DIR:PATH="/osxcross/macports/pkgs/opt/local/include" \ + -DSDL2_INCLUDE_DIR:PATH="/osxcross/macports/pkgs/opt/local/lib" \ + -DSRB2_CONFIG_ENABLE_TESTS:BOOL=OFF \ + -DSRB2_CONFIG_SYSTEM_LIBRARIES:BOOL=ON \ + -DSRB2_SDL2_EXE_NAME=ringracers_$CI_PIPELINE_ID \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache - | # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" @@ -79,37 +83,16 @@ osxcross x86_64: - - | # make echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" - - make --directory=build.osxcross --keep-going || make --directory=build.osxcross --keep-going + - cmake --build build.x86_64 --parallel 1 -- --keep-going - | # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" - after_script: - - | - # apt_clean - echo -e "\e[0Ksection_start:`date +%s`:apt_clean[collapsed=true]\r\e[0KCleaning of unneeded APT packages" - - apt-get autoclean + # copy config.h + echo -e "\e[0Ksection_start:`date +%s`:copy[collapsed=false]\r\e[0KCopying config.h" + - mkdir --parents --verbose build.x86_64/dist + - cp --reflink=auto --sparse=always --verbose build.x86_64/src/config.h build.x86_64/dist/x86_64.h - | - # apt_clean - echo -e "\e[0Ksection_end:`date +%s`:apt_clean\r\e[0K" - - - - | - # vcpkg_clean - echo -e "\e[0Ksection_start:`date +%s`:vcpkg_clean[collapsed=true]\r\e[0KCleaning vcpkg-root" - - if [ -d "build/vcpkg-root" ]; then - pushd "build/vcpkg-root" - git clean - popd - fi - - echo -e "\e[0Ksection_end:`date +%s`:vcpkg_clean\r\e[0K" - - - - | - # ccache_stats - echo -e "\e[0Ksection_start:`date +%s`:ccache_stats[collapsed=true]\r\e[0Kccache statistics:" - - ccache --show-stats - - ccache --show-log-stats || true - - | - # ccahe_stats - echo -e "\e[0Ksection_end:`date +%s`:ccache_stats\r\e[0K" + # make + echo -e "\e[0Ksection_end:`date +%s`:copy\r\e[0K" diff --git a/.gitlab/ci/jobs/osxccross-universal.yml b/.gitlab/ci/jobs/osxccross-universal.yml new file mode 100644 index 000000000..a72c4ea0a --- /dev/null +++ b/.gitlab/ci/jobs/osxccross-universal.yml @@ -0,0 +1,67 @@ +osxcross universal: + image: git.do.srb2.org:5050/stjr/srb2ci/srb2ci:stable + + dependencies: + - osxcross arm64 + - osxcross x86_64 + needs: + - job: osxcross arm64 + - job: osxcross x86_64 + + stage: osxcross + + artifacts: + paths: + - "dist/bin" + - "dist/src" + expose_as: "Mac Universal" + name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-lipo-apple-darwin21.4" + + script: + - - | + # mkdir + echo -e "\e[0Ksection_start:`date +%s`:mkdir[collapsed=true]\r\e[0KMaking dist folder" + mkdir --parents --verbose dist/src dist/bin + - | + # mkdir + echo -e "\e[0Ksection_end:`date +%s`:mkdir\r\e[0K" + + - - | + # copy-config + echo -e "\e[0Ksection_start:`date +%s`:x86_64-config[collapsed=true]\r\e[0KCopying x86_64 config" + - cp --reflink=auto --sparse=always --verbose --target-directory=dist/src/ build.*/dist/*.h + - | + # x86_64-config + echo -e "\e[0Ksection_end:`date +%s`:x86_64-config\r\e[0K" + + - - | + # copy-build + echo -e "\e[0Ksection_start:`date +%s`:copy-build[collapsed=true]\r\e[0KCopying ALL build" + - cp --reflink=auto --sparse=always --recursive --verbose --target-directory=dist/ build.*/bin/ + - | + # copy-build + echo -e "\e[0Ksection_end:`date +%s`:copy-build\r\e[0K" + + - - | + # link-build + echo -e "\e[0Ksection_start:`date +%s`:link-build[collapsed=true]\r\e[0KLinking universal build" + - lipo -create -output dist/bin/ringracers_$CI_PIPELINE_ID.app/Contents/MacOS/ringracers_$CI_PIPELINE_ID build.*/bin/ringracers_$CI_PIPELINE_ID.app/Contents/MacOS/ringracers_$CI_PIPELINE_ID + - | + # universal-build + echo -e "\e[0Ksection_end:`date +%s`:link-build\r\e[0K" + + - - | + # arm64-verify + echo -e "\e[0Ksection_start:`date +%s`:arm64-verify[collapsed=true]\r\e[0KVerifying arm64" + - lipo dist/bin/ringracers_$CI_PIPELINE_ID.app/Contents/MacOS/ringracers_$CI_PIPELINE_ID -verify_arch arm64 + - | + # arm64-verify + echo -e "\e[0Ksection_end:`date +%s`:arm64-verify\r\e[0K" + + - - | + # x86_64-verify + echo -e "\e[0Ksection_start:`date +%s`:x86_64-verify[collapsed=true]\r\e[0KVerifying x86_64" + - lipo dist/bin/ringracers_$CI_PIPELINE_ID.app/Contents/MacOS/ringracers_$CI_PIPELINE_ID -verify_arch x86_64 + - | + # x86_64-verify + echo -e "\e[0Ksection_end:`date +%s`:x86_64-verify\r\e[0K" diff --git a/.gitlab/ci/jobs/windows-x64.yml b/.gitlab/ci/jobs/windows-x64.yml index 12ad1a75c..b92eae29d 100644 --- a/.gitlab/ci/jobs/windows-x64.yml +++ b/.gitlab/ci/jobs/windows-x64.yml @@ -5,8 +5,29 @@ Windows x64: when: manual + timeout: 2h + allow_failure: true + cache: + - key: ccache-$CI_JOB_NAME_SLUG-$CI_COMMIT_REF_SLUG + fallback_keys: + - ccache-$CI_JOB_NAME_SLUG-$CI_DEFAULT_BRANCH + - ccache-$CI_JOB_NAME_SLUG-master + paths: + - build/ccache + - build/ccache_statslog + + - key: apt-$CI_JOB_IMAGE + paths: + - build/apt-cache + unprotect: true + + - key: vcpkg-binary-cache-x64-mingw-static + paths: + - build/vcpkg-binary-cache + unprotect: true + artifacts: paths: - "build.cmake/bin/" @@ -15,13 +36,26 @@ Windows x64: name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-Win64" variables: - PREFIX: x86_64-w64-mingw32 + VCPKG_TARGET_TRIPLET: x64-mingw-static + CC: x86_64-w64-mingw32-gcc + CXX: x86_64-w64-mingw32-g++ + LD: x86_64-w64-mingw32-ld script: + - | + # vcpkg + echo -e "\e[0Ksection_start:`date +%s`:vcpkg-root[collapsed=true]\r\e[0KSetting vcpkg cache" + + export VCPKG_DEFAULT_BINARY_CACHE="$(pwd)/build/vcpkg-binary-cache" + + mkdir -p "build/vcpkg-binary-cache" + + echo -e "\e[0Ksection_end:`date +%s`:vcpkg-root\r\e[0K" + - - | # apt_toolchain echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages" - - apt-get install gcc-mingw-w64-x86-64-win32 + - apt-get install g++-mingw-w64-x86-64-win32 - | # apt_toolchain echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K" @@ -37,7 +71,18 @@ Windows x64: - - | # cmake echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles" - - cmake -B build.cmake -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF -DSRB2_CONFIG_ENABLE_DISCORDRPC=OFF -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-mingw-static -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/toolchains/mingw.cmake + - | + cmake \ + --preset ninja-debug \ + -B build.cmake \ + -G "Unix Makefiles" \ + -DCMAKE_COLOR_DIAGNOSTICS=OFF \ + -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/toolchains/mingw.cmake \ + -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF \ + -DSRB2_CONFIG_ENABLE_DISCORDRPC=OFF \ + -DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake \ + -DVCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET} \ + -DSRB2_CONFIG_ALWAYS_MAKE_DEBUGLINK=ON - | # cmake echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K" @@ -45,7 +90,7 @@ Windows x64: - - | # make echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" - - make --directory=build.clang --keep-going || make --directory=build.clang --keep-going + - cmake --build build.cmake --parallel 1 -- --keep-going - | # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" diff --git a/.gitlab/ci/jobs/windows-x86.yml b/.gitlab/ci/jobs/windows-x86.yml index 6c203eb5c..22c2d0563 100644 --- a/.gitlab/ci/jobs/windows-x86.yml +++ b/.gitlab/ci/jobs/windows-x86.yml @@ -5,6 +5,8 @@ Windows x86: when: on_success + timeout: 2h + cache: - key: ccache-$CI_JOB_NAME_SLUG-$CI_COMMIT_REF_SLUG fallback_keys: @@ -19,11 +21,6 @@ Windows x86: - build/apt-cache unprotect: true - - key: vcpkg-root - paths: - - build/vcpkg-root - unprotect: true - - key: vcpkg-binary-cache-x86-mingw-static paths: - build/vcpkg-binary-cache @@ -31,33 +28,23 @@ Windows x86: artifacts: paths: - - "build/ninja-x86_mingw_static_vcpkg-debug/bin/" - - "build/ninja-x86_mingw_static_vcpkg-debug/src/config.h" + - "build.cmake/bin/" + - "build.cmake/src/config.h" expose_as: "Win32" name: "$CI_PROJECT_PATH_SLUG-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA-Win32" variables: - PREFIX: i686-w64-mingw32 - CC: /usr/bin/i686-w64-mingw32-gcc-posix - CXX: /usr/bin/i686-w64-mingw32-g++-posix + VCPKG_TARGET_TRIPLET: x86-mingw-static + CC: i686-w64-mingw32-gcc + CXX: i686-w64-mingw32-g++ + LD: i686-w64-mingw32-ld script: - | # vcpkg - echo -e "\e[0Ksection_start:`date +%s`:vcpkg-root[collapsed=true]\r\e[0KUpdating vcpkg" + echo -e "\e[0Ksection_start:`date +%s`:vcpkg-root[collapsed=true]\r\e[0KSetting vcpkg cache" - if [ -d "build/vcpkg-root" ]; then - pushd build/vcpkg-root - git fetch https://github.com/Microsoft/vcpkg master - git reset --hard FETCH_HEAD - popd - else - mkdir -p build - git clone https://github.com/Microsoft/vcpkg build/vcpkg-root - fi - - export VCPKG_ROOT=$(pwd)/build/vcpkg-root - export VCPKG_BINARY_SOURCES="clear;files,$(pwd)/build/vcpkg-binary-cache,readwrite" + export VCPKG_DEFAULT_BINARY_CACHE="$(pwd)/build/vcpkg-binary-cache" mkdir -p "build/vcpkg-binary-cache" @@ -66,7 +53,7 @@ Windows x86: - - | # apt_toolchain echo -e "\e[0Ksection_start:`date +%s`:apt_toolchain[collapsed=true]\r\e[0KInstalling toolchain packages" - - apt-get install gcc-mingw-w64-i686-win32 + - apt-get install g++-mingw-w64-i686-win32 - | # apt_toolchain echo -e "\e[0Ksection_end:`date +%s`:apt_toolchain\r\e[0K" @@ -83,18 +70,17 @@ Windows x86: # cmake echo -e "\e[0Ksection_start:`date +%s`:cmake[collapsed=false]\r\e[0KBuilding Makefiles" - | - CCACHE=$(which ccache) - cmake \ - --preset ninja-x86_mingw_static_vcpkg-debug \ + --preset ninja-debug \ + -B build.cmake \ -G "Unix Makefiles" \ - -DCMAKE_C_COMPILER_LAUNCHER=$CCACHE \ - -DCMAKE_CXX_COMPILER_LAUNCHER=$CCACHE \ - -DCMAKE_C_COMPILER=/usr/bin/i686-w64-mingw32-gcc-posix \ - -DCMAKE_CXX_COMPILER=/usr/bin/i686-w64-mingw32-g++-posix \ + -DCMAKE_COLOR_DIAGNOSTICS=OFF \ -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/toolchains/mingw.cmake \ + -DSRB2_CONFIG_ENABLE_WEBM_MOVIES=OFF \ + -DSRB2_CONFIG_ENABLE_DISCORDRPC=OFF \ + -DCMAKE_TOOLCHAIN_FILE=${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake \ + -DVCPKG_TARGET_TRIPLET=${VCPKG_TARGET_TRIPLET} \ -DSRB2_CONFIG_ALWAYS_MAKE_DEBUGLINK=ON - - | # cmake echo -e "\e[0Ksection_end:`date +%s`:cmake\r\e[0K" @@ -102,37 +88,7 @@ Windows x86: - - | # make echo -e "\e[0Ksection_start:`date +%s`:make[collapsed=false]\r\e[0KCompiling SRB2" - - cmake --build --preset ninja-x86_mingw_static_vcpkg-debug --parallel 1 -- --keep-going || cmake --build --preset ninja-x86_mingw_static_vcpkg-debug --parallel 1 -- --keep-going + - cmake --build build.cmake --parallel 1 -- --keep-going - | # make echo -e "\e[0Ksection_end:`date +%s`:make\r\e[0K" - - after_script: - - - | - # apt_clean - echo -e "\e[0Ksection_start:`date +%s`:apt_clean[collapsed=true]\r\e[0KCleaning of unneeded APT packages" - - apt-get autoclean - - | - # apt_clean - echo -e "\e[0Ksection_end:`date +%s`:apt_clean\r\e[0K" - - - - | - # vcpkg_clean - echo -e "\e[0Ksection_start:`date +%s`:vcpkg_clean[collapsed=true]\r\e[0KCleaning vcpkg-root" - - if [ -d "build/vcpkg-root" ]; then - pushd "build/vcpkg-root" - git clean -f - popd - fi - - echo -e "\e[0Ksection_end:`date +%s`:vcpkg_clean\r\e[0K" - - - - | - # ccache_stats - echo -e "\e[0Ksection_start:`date +%s`:ccache_stats[collapsed=true]\r\e[0Kccache statistics:" - - ccache --show-stats - - ccache --show-log-stats || true - - | - # ccahe_stats - echo -e "\e[0Ksection_end:`date +%s`:ccache_stats\r\e[0K" diff --git a/.gitlab/ci/templates/srb2ci.yml b/.gitlab/ci/templates/srb2ci.yml index 3716d9df9..3f4c99cdd 100644 --- a/.gitlab/ci/templates/srb2ci.yml +++ b/.gitlab/ci/templates/srb2ci.yml @@ -15,6 +15,9 @@ - build/apt-cache unprotect: true + artifacts: + expire_in: 1 day + before_script: - - | # debconf @@ -61,7 +64,7 @@ - - | # apt_update echo -e "\e[0Ksection_start:`date +%s`:apt_update[collapsed=true]\r\e[0KUpdating APT listing" - - apt-get update + - timeout 2m apt-get update || timeout 2m apt-get update - | # apt_update echo -e "\e[0Ksection_end:`date +%s`:apt_update\r\e[0K" @@ -93,26 +96,24 @@ - - | # ccache_config echo -e "\e[0Ksection_start:`date +%s`:ccache_config[collapsed=true]\r\e[0KSetting up ccache config" - - mkdir --parents --verbose ~/.ccache/ - - touch ~/.ccache/ccache.conf - | # cache.conf echo Adding ccache configution option - | # base_dir - echo base_dir = $CI_PROJECT_DIR | tee --append ~/.ccache/ccache.conf + ccache --set-config base_dir=$CI_PROJECT_DIR - | # cache_dir - echo cache_dir = $CI_PROJECT_DIR/build/ccache | tee --append ~/.ccache/ccache.conf + ccache --set-config cache_dir=$CI_PROJECT_DIR/build/ccache - | # compiler_check - echo compiler_check = content | tee --append ~/.ccache/ccache.conf + ccache --set-config compiler_check=content - | # stats_log - echo stats_log = $CI_PROJECT_DIR/build/ccache_statslog | tee --append ~/.ccache/ccache.conf + ccache --set-config stats_log=$CI_PROJECT_DIR/build/ccache_statslog || true - | # max_size - echo max_size = 300M | tee --append ~/.ccache/ccache.conf + ccache --set-config max_size=300M - | # ccache_config echo -e "\e[0Ksection_end:`date +%s`:ccache_config\r\e[0K" diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f852c981..f9c457546 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,7 +53,19 @@ SET(CPACK_OUTPUT_FILE_PREFIX package) include(CPack) # Options +if("${CMAKE_SYSTEM_NAME}" MATCHES Windows) + if(DEFINED VCPKG_TARGET_TRIPLET) + set(SRB2_CONFIG_SYSTEM_LIBRARIES_DEFAULT ON) + else() + set(SRB2_CONFIG_SYSTEM_LIBRARIES_DEFAULT OFF) + endif() +else() + set(SRB2_CONFIG_SYSTEM_LIBRARIES_DEFAULT ON) +endif() +# Clang tidy options will be ignored if CMAKE__CLANG_TIDY are set. +option(SRB2_CONFIG_ENABLE_CLANG_TIDY_C "Enable default clang-tidy check configuration for C" OFF) +option(SRB2_CONFIG_ENABLE_CLANG_TIDY_CXX "Enable default clang-tidy check configuration for C++" OFF) option( SRB2_CONFIG_STATIC_STDLIB "Link static version of standard library. All dependencies must also be static" @@ -70,8 +82,10 @@ option(SRB2_CONFIG_ALWAYS_MAKE_DEBUGLINK "Always make a debuglink .debug." OFF) option(SRB2_CONFIG_TESTERS "Compile a build for testers." OFF) option(SRB2_CONFIG_MOBJCONSISTANCY "Compile with MOBJCONSISTANCY defined." OFF) option(SRB2_CONFIG_PACKETDROP "Compile with PACKETDROP defined." OFF) +option(SRB2_CONFIG_EXECINFO "Enable stack trace dump support." ON) option(SRB2_CONFIG_ZDEBUG "Compile with ZDEBUG defined." OFF) option(SRB2_CONFIG_SKIP_COMPTIME "Skip regenerating comptime. To speed up iterative debug builds in IDEs." OFF) +option(SRB2_CONFIG_FORCE_NO_MS_BITFIELDS "Compile without -mno-ms-bitfields compiler flag" OFF) # SRB2_CONFIG_PROFILEMODE is probably superceded by some CMake setting. option(SRB2_CONFIG_PROFILEMODE "Compile for profiling (GCC only)." OFF) option(SRB2_CONFIG_TRACY "Compile with Tracy profiling enabled" OFF) @@ -94,12 +108,23 @@ if("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL Windows) endif() endif() +# Dependencies add_subdirectory(thirdparty) +if(SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES) + set(SRB2_INTERNAL_LIBRARY_TYPE SHARED) + set(NOT_SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES OFF) +else() + set(SRB2_INTERNAL_LIBRARY_TYPE STATIC) + set(NOT_SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES ON) +endif() + + find_package(ZLIB REQUIRED) find_package(PNG REQUIRED) find_package(SDL2 CONFIG REQUIRED) find_package(CURL REQUIRED) +find_package(Opus REQUIRED) # Use the one in thirdparty/fmt to guarantee a minimum version #find_package(FMT CONFIG REQUIRED) diff --git a/CMakePresets.json b/CMakePresets.json index a1e5b59c8..141e76486 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -101,88 +101,113 @@ "name": "ninja-debug", "hidden": false, "binaryDir": "build/${presetName}", - "inherits": ["__debug", "__ninja"] + "inherits": [ "__debug", "__ninja" ] }, { "name": "ninja-develop", "hidden": false, "binaryDir": "build/${presetName}", - "inherits": ["__develop", "__ninja"] + "inherits": [ "__develop", "__ninja" ] }, { "name": "ninja-release", "hidden": false, "binaryDir": "build/${presetName}", - "inherits": ["__release", "__ninja"] + "inherits": [ "__release", "__ninja" ] }, { "name": "ninja-testers", "hidden": false, "binaryDir": "build/${presetName}", - "inherits": ["__testers", "__ninja"] + "inherits": [ "__testers", "__ninja" ] + }, + + { + "name": "ninja-vcpkg-debug", + "hidden": false, + "binaryDir": "build/${presetName}", + "inherits": [ "__debug", "__ninja", "__vcpkg-toolchain" ] + }, + { + "name": "ninja-vcpkg-develop", + "hidden": false, + "binaryDir": "build/${presetName}", + "inherits": [ "__develop", "__ninja", "__vcpkg-toolchain" ] + }, + { + "name": "ninja-vcpkg-release", + "hidden": false, + "binaryDir": "build/${presetName}", + "inherits": [ "__release", "__ninja", "__vcpkg-toolchain" ] + }, + { + "name": "ninja-vcpkg-testers", + "hidden": false, + "binaryDir": "build/${presetName}", + "inherits": [ "__testers", "__ninja", "__vcpkg-toolchain" ] }, { "name": "ninja-x86_mingw_static_vcpkg-debug", "hidden": false, "binaryDir": "build/${presetName}", - "inherits": ["__debug", "__compiler-mingw-w64-i686", "__ninja", "__vcpkg-toolchain", "__mingw-static"] + "inherits": [ "__debug", "__compiler-mingw-w64-i686", "__ninja", "__vcpkg-toolchain", "__mingw-static" ] }, { "name": "ninja-x86_mingw_static_vcpkg-develop", "hidden": false, "binaryDir": "build/${presetName}", - "inherits": ["__develop", "__compiler-mingw-w64-i686", "__ninja", "__vcpkg-toolchain", "__mingw-static"] + "inherits": [ "__develop", "__compiler-mingw-w64-i686", "__ninja", "__vcpkg-toolchain", "__mingw-static" ] }, { "name": "ninja-x86_mingw_static_vcpkg-release", "hidden": false, "binaryDir": "build/${presetName}", - "inherits": ["__release", "__compiler-mingw-w64-i686", "__ninja", "__vcpkg-toolchain", "__mingw-static"] + "inherits": [ "__release", "__compiler-mingw-w64-i686", "__ninja", "__vcpkg-toolchain", "__mingw-static" ] }, { "name": "ninja-x86_mingw_static_vcpkg-testers", "hidden": false, "binaryDir": "build/${presetName}", - "inherits": ["__testers", "__compiler-mingw-w64-i686", "__ninja", "__vcpkg-toolchain", "__mingw-static"] + "inherits": [ "__testers", "__compiler-mingw-w64-i686", "__ninja", "__vcpkg-toolchain", "__mingw-static" ] }, { "name": "ninja-x64_osx_vcpkg-debug", "hidden": false, "binaryDir": "build/${presetName}", - "inherits": ["__debug", "__ninja", "__vcpkg-toolchain", "__osx_x64"] + "inherits": [ "__debug", "__ninja", "__vcpkg-toolchain", "__osx_x64" ] }, { "name": "ninja-x64_osx_vcpkg-develop", "hidden": false, "binaryDir": "build/${presetName}", - "inherits": ["__develop", "__ninja", "__vcpkg-toolchain", "__osx_x64"] + "inherits": [ "__develop", "__ninja", "__vcpkg-toolchain", "__osx_x64" ] }, { "name": "ninja-x64_osx_vcpkg-release", "hidden": false, "binaryDir": "build/${presetName}", - "inherits": ["__release", "__ninja", "__vcpkg-toolchain", "__osx_x64"] + "inherits": [ "__release", "__ninja", "__vcpkg-toolchain", "__osx_x64" ] }, { "name": "ninja-arm64_osx_vcpkg-debug", "hidden": false, "binaryDir": "build/${presetName}", - "inherits": ["__debug", "__ninja", "__vcpkg-toolchain", "__osx_arm64"] + "inherits": [ "__debug", "__ninja", "__vcpkg-toolchain", "__osx_arm64" ] }, { "name": "ninja-arm64_osx_vcpkg-develop", "hidden": false, "binaryDir": "build/${presetName}", - "inherits": ["__develop", "__ninja", "__vcpkg-toolchain", "__osx_arm64"] + "inherits": [ "__develop", "__ninja", "__vcpkg-toolchain", "__osx_arm64" ] }, { "name": "ninja-arm64_osx_vcpkg-release", "hidden": false, "binaryDir": "build/${presetName}", - "inherits": ["__release", "__ninja", "__vcpkg-toolchain", "__osx_arm64"] + "inherits": [ "__release", "__ninja", "__vcpkg-toolchain", "__osx_arm64" ] } ], diff --git a/LICENSE-3RD-PARTY.txt b/LICENSE-3RD-PARTY.txt index 84cba2acc..767805efe 100644 --- a/LICENSE-3RD-PARTY.txt +++ b/LICENSE-3RD-PARTY.txt @@ -1616,7 +1616,7 @@ met: notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR @@ -1641,6 +1641,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - stb_vorbis + stb_rect_pack Copyright (c) 2017 Sean Barrett https://github.com/nothings/stb + - Vulkan Headers + Copyright (c) 2015-2023 The Khronos Group Inc. + https://github.com/KhronosGroup/Vulkan-Headers + - volk + Copyright (c) 2018-2025 Arseny Kapoulkine + https://github.com/zeux/volk + - VulkanMemoryAllocator + Copyright (c) 2017-2025 Advanced Micro Devices, Inc. + https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator + -------------------------------------------------------------------------------- Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/assets/CMakeLists.txt b/assets/CMakeLists.txt index 1b397ba08..b837216ce 100644 --- a/assets/CMakeLists.txt +++ b/assets/CMakeLists.txt @@ -39,7 +39,6 @@ set(SRB2_ASSETS_GAME "scripts.pk3" "staffghosts.pk3" "unlocks.pk3" - "shaders.pk3" ) list(TRANSFORM SRB2_ASSETS_GAME PREPEND "/") list(TRANSFORM SRB2_ASSETS_GAME PREPEND "${SRB2_ASSET_DIRECTORY_ABSOLUTE}") diff --git a/cmake/Modules/FindOpus.cmake b/cmake/Modules/FindOpus.cmake new file mode 100644 index 000000000..6c739d5cb --- /dev/null +++ b/cmake/Modules/FindOpus.cmake @@ -0,0 +1,7 @@ +find_package(Opus CONFIG) +if(NOT TARGET Opus::opus) + find_package(PkgConfig REQUIRED) + pkg_check_modules(Opus REQUIRED IMPORTED_TARGET opus) + set_target_properties(PkgConfig::Opus PROPERTIES IMPORTED_GLOBAL TRUE) + add_library(Opus::opus ALIAS PkgConfig::Opus) +endif() diff --git a/docs/udmf.txt b/docs/udmf.txt new file mode 100644 index 000000000..ac2c957d9 --- /dev/null +++ b/docs/udmf.txt @@ -0,0 +1,273 @@ +=============================================================================== +Universal Doom Map Format - Ring Racers extensions v1.0 - 20.09.2024 + + Copyright (C) 2025 Sally Cochenour. + Copyright (C) 2025 Kart Krew Dev. + + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.2 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + +=============================================================================== + +This document serves to only specify changes that "Dr. Robotnik's Ring Racers" +makes to the base UDMF specification. + +======================================= +I. Grammar / Syntax +======================================= + +No changes were made. + +======================================= +II. Implementation Semantics +======================================= + +------------------------------------ +II.A : Storage and Retrieval of Data +------------------------------------ + +No changes were made. + +----------------------------------- +II.B : Storage Within Archive Files +----------------------------------- + +Between the TEXTMAP and ENDMAP lumps, Ring Racers supports the following +additional lumps: + + BEHAVIOR = Compiled ACS code. + ZNODES = Compiled extended / GL friendly nodes. These are required. + PICTURE = A Doom graphic lump, expected to be 320x200. Intended to be a + screenshot of the map itself. This is used by the game for level + select menus. + MINIMAP = A Doom graphic lump, expected to be 100x100. Intended to be a + an overview of the map. This is used by the game for the minimap + on-screen HUD. + ENCORE = A Doom flat lump, expected to be 16x16. Describes a color remap + palette to use in Encore Mode. + TWEAKMAP = A Doom flat lump, expected to be 16x16. Describes a color remap + palette to use outside of Encore Mode. + +Any lumps not listed or specified in the original document will be ignored by +the game. In particular, the "SCRIPTS" lump is considered to be ACS source +code, and is garantueed to be ignored by the engine. + +-------------------------------- +II.C : Implementation Dependence +-------------------------------- + +Ring Racers does not aspire for Doom backwards compatibility, thus it does not +support any of the namespaces in the original document, and only implements +its own: "ringracers". Any maps not using the "ringracers" namespace is +considered unsupported. + +======================================= +III. Standardized Fields +======================================= + +Ring Racers' namespace implements the following additional fields: + + version = ; // Specifies the map format version. + // This is used for resolving backwards compatibility issues. + + // Note that this doesn't map directly to specification version; + // it means behavior of an already existing field or action special + // was changed. + + // 0 / default - RR indev + // 1 - RR v2.0, spec v1.0 + // 2 - RR v2.4, spec v1.0 + + linedef + { + moreids = ; // Additional IDs, specified as a space separated list of numbers (e.g. "2 666 1003 4505") + + arg5 = ; // Argument 5. Default = 0. + arg6 = ; // Argument 6. Default = 0. + arg7 = ; // Argument 7. Default = 0. + arg8 = ; // Argument 8. Default = 0. + arg9 = ; // Argument 9. Default = 0. + + stringarg0 = ; // String argument 0. This replaces usage of 'arg0' when specified. + stringarg1 = ; // String argument 1. This replaces usage of 'arg1' when specified. + + alpha = ; // Transparency value of the mid-textures. Default = 1.0. + renderstyle = ; // The rendering mode to use for the mid-textures. + // Can be "translucent", "add", "subtract", "reversesubtract", "modulate", or "fog". + // Default = "translucent". + + // The following flags default to false. + blockplayers = ; // true = line blocks players. + skewtd = ; // true = upper and lower textures are skewed to match slopes. + noskew = ; // true = mid-textures are not skewed to match slopes. + midpeg = ; // true = invert mid-texture unpegged behavior. + midsolid = ; // true = mid-texture has collision. + wrapmidtex = ; // true = mid-textures are wrapped. + nonet = ; // true = special is disabled in networked multiplayer games. + netonly = ; // true = special is only enabled in networked multiplayer games. + notbouncy = ; // true = disable bouncing collision. + transfer = ; // true = use FOF transfer properties effect. + } + + sidedef + { + repeatcnt = ; // Number of times to mid-texture wrap. Default = 0. + } + + vertex + { + zfloor = ; // The floor height at this vertex, for vertex slopes. + zceiling = ; // The ceiling height at this vertex, for vertex slopes + } + + sector + { + lightfloor = ; // The floor's light level. Default is 0. + lightceiling = ; // The ceiling's light level. Default is 0. + + lightfloorabsolute = ; // true = 'lightfloor' is an absolute value. Default is + // relative to the owning sector's light level. + lightceilingabsolute = ; // true = 'lightceiling' is an absolute value. Default is + // relative to the owning sector's light level. + + moreids = ; // Additional IDs, specified as a space separated list of numbers (e.g. "2 666 1003 4505") + + xpanningfloor = ; // X texture offset of floor texture, Default = 0.0. + ypanningfloor = ; // Y texture offset of floor texture, Default = 0.0. + + xpanningceiling = ; // X texture offset of ceiling texture, Default = 0.0. + ypanningceiling = ; // Y texture offset of ceiling texture, Default = 0.0. + + rotationfloor = ; // Rotation of floor texture in degrees, Default = 0.0. + rotationceiling = ; // Rotation of ceiling texture in degrees, Default = 0.0. + + floorplane_a = ; // Define the plane equation for the sector's floor. Default is a horizontal plane at 'heightfloor'. + floorplane_b = ; // 'heightfloor' will still be used to calculate texture alignment. + floorplane_c = ; // The plane equation will only be used if all 4 values are given. + floorplane_d = ; + + ceilingplane_a = ; // Define the plane equation for the sector's ceiling. Default is a horizontal plane at 'heightceiling'. + ceilingplane_b = ; // 'heightceiling' will still be used to calculate texture alignment. + ceilingplane_c = ; // The plane equation will only be used if all 4 values are given. + ceilingplane_d = ; + + friction = ; // Sector's friction. Default = 0.90625. + gravity = ; // Sector's gravity multiplier. Default = 1.0. + damagetype = ; // Damage inflicted by the sector. + // Can be "None", "Generic", "Lava", "DeathPit", "Instakill", or "Stumble". + // Default = "None". + + action = ; // Sector action, same as line special. Default = 0. + arg0 = ; // Argument 0. Default = 0. + arg1 = ; // Argument 1. Default = 0. + arg2 = ; // Argument 2. Default = 0. + arg3 = ; // Argument 3. Default = 0. + arg4 = ; // Argument 4. Default = 0. + arg5 = ; // Argument 5. Default = 0. + arg6 = ; // Argument 6. Default = 0. + arg7 = ; // Argument 7. Default = 0. + arg8 = ; // Argument 8. Default = 0. + arg9 = ; // Argument 9. Default = 0. + stringarg0 = ; // String argument 0. This replaces usage of 'arg0' when specified. + stringarg1 = ; // String argument 1. This replaces usage of 'arg1' when specified. + + lightcolor = ; // Sector's light color as RRGGBB value. Default = 0x000000. + lightalpha = ; // Sector's light color alpha value. Default = 25. + fadecolor = ; // Sector's fog color as RRGGBB value. Default = 0x000000. + fadealpha = ; // Sector's fog color alpha value. Default = 25. + fadestart = ; // Sector's fog start distance. Default = 0. + fadeend = ; // Sector's fog end distance. Default = 31. + + // The following flags default to false. + colormapfog = ; // true = render transparent planes at light level instead of fullbright + colormapfadesprites = ; // true = fog color affects fullbright sprites + colormapprotected = ; // true = colormap cannot be changed at run-time + + flipspecial_nofloor = ; // true = plane touch specials aren't ran when on the floor + flipspecial_ceiling = ; // true = plane touch specials are ran when on the ceiling + triggerspecial_touch = ; // true = specials are ran when touching edges of sector + triggerspecial_headbump = ; // true = plane touch specials are ran when touching the opposite plane than gravity + invertprecip = ; // true = precipitation spawning rules are inverted + gravityflip = ; // true = flip gravity of objects in this sector + heatwave = ; // true = add heat wave screen effect + noclipcamera = ; // true = camera is not blocked by this sector + ripple_floor = ; // true = add ripple effect to floor + ripple_ceiling = ; // true = add ripple effect to ceiling + invertencore = ; // true = encore remap rules are inverted + flatlighting = ; // true = directional lighting is forced off + forcedirectionallighting = ; // true = directional lighting is forced on + nostepup = ; // true = objects can't step up + doublestepup = ; // true = objects have increased step up + nostepdown = ; // true = objects can't step down + cheatcheckactivator = ; // true = players activate cheat checks when in this sector + exit = ; // true = players finish match when entering sector + deleteitems = ; // true = items instantly explode when entering sector + fan = ; // true = players are propelled upwards in this sector + zoomtubestart = ; // true = sector is start of a zoom tube + zoomtubeend = ; // true = sector is end of a zoom tube + + repeatspecial = ; // true = repeatable action + continuousspecial = ; // true = action is executed every game tick + playerenter = ; // true = player activates when entering + playerfloor = ; // true = player activates when touching floor + playerceiling = ; // true = player activates when touching ceiling + monsterenter = ; // true = enemy activates when entering + monsterfloor = ; // true = enemy activates when touching floor + monsterceiling = ; // true = enemy activates when touching ceiling + missileenter = ; // true = items / projectiles activate when entering + missilefloor = ; // true = items / projectiles activate when touching floor + missileceiling = ; // true = items / projectiles activate when touching ceiling + } + + thing + { + pitch = ; // Pitch of thing in degrees. Default = 0 (horizontal). + roll = ; // Pitch of thing in degrees. Default = 0 (horizontal). + + scalex = ; // Vertical visual scale on thing. Default = 1.0. + scaley = ; // Horizontal visual scale on thing. Default = 1.0. + scale = ; // Vertical and horizontal visual scale on thing. Default = 1.0. + + mobjscale = ; // Physical scale on thing. Default = 1.0. + + foflayer = ; // Which FOF is treated as the base floor/ceiling. + // This changes what 'height' is relative to. + // Default = 0, for no FOF. + + // Action special arguments + arg5 = ; // Argument 5. Default = 0. + arg6 = ; // Argument 6. Default = 0. + arg7 = ; // Argument 7. Default = 0. + arg8 = ; // Argument 8. Default = 0. + arg9 = ; // Argument 9. Default = 0. + + stringarg0 = ; // String argument 0. This replaces usage of 'arg0' when specified. + stringarg1 = ; // String argument 1. This replaces usage of 'arg1' when specified. + + // These arguments modify object behavior on a per-type basis. + // Not to be confused with action special arguments. + thingarg0 = ; // Argument 0. Default = 0. + thingarg1 = ; // Argument 1. Default = 0. + thingarg2 = ; // Argument 2. Default = 0. + thingarg3 = ; // Argument 3. Default = 0. + thingarg4 = ; // Argument 4. Default = 0. + thingarg5 = ; // Argument 5. Default = 0. + thingarg6 = ; // Argument 6. Default = 0. + thingarg7 = ; // Argument 7. Default = 0. + thingarg8 = ; // Argument 8. Default = 0. + thingarg9 = ; // Argument 9. Default = 0. + thingstringarg0 = ; // String argument 0. This replaces usage of 'thingarg0' when specified. + thingstringarg1 = ; // String argument 1. This replaces usage of 'thingarg1' when specified. + + // Following flags default to false. + flip = ; // true = object has reversed gravity + } + +======================================= +Changelog +======================================= + +1.0: 20.09.2024 +- Initial document created. diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f61513b31..ec00e3114 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,14 +6,14 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 string.c d_main.cpp d_clisrv.c - d_net.c + d_net.cpp d_netfil.c d_netcmd.c dehacked.c deh_soc.c deh_lua.c deh_tables.c - z_zone.c + z_zone.cpp f_finale.c f_wipe.cpp g_build_ticcmd.cpp @@ -60,7 +60,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 p_maputl.c p_mobj.c p_polyobj.c - p_saveg.c + p_saveg.cpp p_setup.cpp p_sight.c p_spec.c @@ -77,7 +77,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 r_debug_parser.cpp r_debug_printer.cpp r_draw.cpp - r_fps.c + r_fps.cpp r_main.cpp r_plane.cpp r_segs.cpp @@ -87,7 +87,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 r_spritefx.cpp r_things.cpp r_bbox.c - r_textures.c + r_textures.cpp r_textures_dups.cpp r_patch.cpp r_patchrotation.c @@ -137,7 +137,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 k_bot.cpp k_botitem.cpp k_botsearch.cpp - k_grandprix.c + k_grandprix.cpp k_boss.c k_hud.cpp k_hud_track.cpp @@ -167,6 +167,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 music.cpp music_manager.cpp sanitize.cpp + p_deepcopy.cpp ) if(SRB2_CONFIG_ENABLE_WEBM_MOVIES) @@ -201,11 +202,14 @@ add_custom_target(_SRB2_reconf ALL ) add_dependencies(SRB2SDL2 _SRB2_reconf) -if("${CMAKE_COMPILER_IS_GNUCC}" AND "${CMAKE_SYSTEM_NAME}" MATCHES "Windows") +if(("${CMAKE_COMPILER_IS_GNUCC}" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") AND "${CMAKE_SYSTEM_NAME}" MATCHES "Windows") target_link_options(SRB2SDL2 PRIVATE "-Wl,--disable-dynamicbase") if("${SRB2_CONFIG_STATIC_STDLIB}") # On MinGW with internal libraries, link the standard library statically - target_link_options(SRB2SDL2 PRIVATE "-static") + target_link_options(SRB2SDL2 PRIVATE -Wl,--add-stdcall-alias -static-libgcc -static-libstdc++ -static -lpthread) + set(THREADS_PREFER_PTHREAD_FLAG TRUE) + find_package(Threads REQUIRED) + target_link_libraries(SRB2SDL2 PRIVATE Threads::Threads) endif() if(CMAKE_SIZEOF_VOID_P EQUAL 4) target_link_options(SRB2SDL2 PRIVATE "-Wl,--large-address-aware") @@ -239,6 +243,10 @@ if (UNIX) target_compile_definitions(SRB2SDL2 PRIVATE -DUNIXCOMMON) endif() +if (BSD MATCHES "FreeBSD") + target_compile_definitions(SRB2SDL2 PRIVATE -DFREEBSD) +endif() + if(CMAKE_COMPILER_IS_GNUCC) find_program(OBJCOPY objcopy) endif() @@ -250,6 +258,11 @@ if("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") endif() endif() +if("${CMAKE_SYSTEM_NAME}" MATCHES "Haiku") + target_compile_definitions(SRB2SDL2 PRIVATE -DNOEXECINFO) + target_link_libraries(SRB2SDL2 PRIVATE network) +endif() + if("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin") target_compile_definitions(SRB2SDL2 PRIVATE -DMACOSX) endif() @@ -257,6 +270,7 @@ endif() target_link_libraries(SRB2SDL2 PRIVATE ZLIB::ZLIB) target_link_libraries(SRB2SDL2 PRIVATE PNG::PNG) target_link_libraries(SRB2SDL2 PRIVATE CURL::libcurl) +target_link_libraries(SRB2SDL2 PRIVATE Opus::opus) if("${CMAKE_SYSTEM_NAME}" MATCHES "FreeBSD") target_link_libraries(SRB2SDL2 PRIVATE -lexecinfo) target_link_libraries(SRB2SDL2 PRIVATE -lpthread) @@ -408,10 +422,12 @@ endif() # Compatibility flag with later versions of GCC # We should really fix our code to not need this -if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") - check_cxx_compiler_flag("-mno-ms-bitfields" HAS_NO_MS_BITFIELDS) - if(HAS_NO_MS_BITFIELDS) - target_compile_options(SRB2SDL2 PRIVATE -mno-ms-bitfields) +if (NOT SRB2_CONFIG_FORCE_NO_MS_BITFIELDS) + if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") + check_cxx_compiler_flag("-mno-ms-bitfields" HAS_NO_MS_BITFIELDS) + if(HAS_NO_MS_BITFIELDS) + target_compile_options(SRB2SDL2 PRIVATE -mno-ms-bitfields) + endif() endif() endif() @@ -581,6 +597,11 @@ endif() if(SRB2_CONFIG_PACKETDROP) target_compile_definitions(SRB2SDL2 PRIVATE -DPACKETDROP) endif() +if(SRB2_CONFIG_EXECINFO) +else() + target_compile_definitions(SRB2SDL2 PRIVATE -DNOEXECINFO) + message(STATUS "You have disabled stack trace dump support") +endif() if(SRB2_CONFIG_ZDEBUG) target_compile_definitions(SRB2SDL2 PRIVATE -DZDEBUG) endif() @@ -609,32 +630,182 @@ endif() add_subdirectory(hud) add_subdirectory(modp_b64) -# strip debug symbols into separate file when using gcc. +# strip debug symbols into separate file when using gcc or clang. # to be consistent with Makefile, don't generate for OS X. -if((CMAKE_COMPILER_IS_GNUCC) AND NOT ("${CMAKE_SYSTEM_NAME}" MATCHES Darwin)) +if((CMAKE_COMPILER_IS_GNUCC OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") AND NOT ("${CMAKE_SYSTEM_NAME}" MATCHES Darwin)) if(${CMAKE_BUILD_TYPE} MATCHES RelWithDebInfo OR SRB2_CONFIG_ALWAYS_MAKE_DEBUGLINK) message(STATUS "Will make separate debug symbols in *.debug") add_custom_command(TARGET SRB2SDL2 POST_BUILD - COMMAND ${OBJCOPY} ${OBJCOPY_ONLY_KEEP_DEBUG} $ $.debug + COMMAND ${CMAKE_OBJCOPY} ${OBJCOPY_ONLY_KEEP_DEBUG} $ $.debug # mold linker: .gnu_debuglink is present by default, so --add-gnu-debuglink would fail - COMMAND ${OBJCOPY} --strip-debug --remove-section=.gnu_debuglink $ - COMMAND ${OBJCOPY} --add-gnu-debuglink=$.debug $ + COMMAND ${CMAKE_OBJCOPY} --strip-debug --remove-section=.gnu_debuglink $ + COMMAND ${CMAKE_OBJCOPY} --add-gnu-debuglink=$.debug $ ) endif() endif() # copy DLLs to bin/ directory if building internal shared on windows -if("${CMAKE_SYSTEM_NAME}" STREQUAL Windows AND NOT "${SRB2_CONFIG_INTERNAL_LIBRARIES}" AND "${SRB2_CONFIG_SHARED_INTERNAL_LIBRARIES}") +if("${CMAKE_SYSTEM_NAME}" STREQUAL Windows AND NOT "${SRB2_CONFIG_INTERNAL_LIBRARIES}") + # also copy implicitly linked system libraries set(ADDITIONAL_DLLS "") - if("${CMAKE_C_COMPILER_ID}" STREQUAL GNU) - # also copy implicitly linked system libraries - get_filename_component(MINGW_BIN_PATH ${CMAKE_CXX_COMPILER} PATH) - list(APPEND ADDITIONAL_DLLS - "libgcc_s_dw2-1.dll" - "libstdc++-6.dll" - "libwinpthread-1.dll" + if("${CMAKE_CXX_COMPILER_ID}" STREQUAL GNU) + string(CONCAT gcc_search_dirs_path "${CMAKE_BINARY_DIR}" /gcc_search_dirs.txt) + #message(STATUS gcc_search_dirs_path=${gcc_search_dirs_path}) + execute_process( + COMMAND ${CMAKE_CXX_COMPILER} -print-search-dirs + OUTPUT_FILE "${gcc_search_dirs_path}" + #OUTPUT_VARIABLE gcc_search_dirs ) - list(TRANSFORM ADDITIONAL_DLLS PREPEND "${MINGW_BIN_PATH}/") + + file(READ "${gcc_search_dirs_path}" gcc_search_dirs) + #message(STATUS gcc_search_dirs=${gcc_search_dirs}) + + #set(gcc_install_dir "${gcc_search_dirs}") + #string(REGEX MATCH "install: =[ \t]*([^\r\n]*)" gcc_install_dir "${gcc_install_dir}" ) + #set(gcc_install_dir "${CMAKE_MATCH_1}") + #message(STATUS gcc_install_dir="${gcc_install_dir}") + + + string(CONCAT gcc_search_dirs_install_path_1 "${CMAKE_BINARY_DIR}" /gcc_search_dirs_install_1.txt) + #message(STATUS gcc_search_dirs_install_path_1=${gcc_search_dirs_install_path_1}) + execute_process( + COMMAND grep "^install:" + INPUT_FILE "${gcc_search_dirs_path}" + OUTPUT_FILE "${gcc_search_dirs_install_path_1}" + ) + + string(CONCAT gcc_search_dirs_install_path_2 "${CMAKE_BINARY_DIR}" /gcc_search_dirs_install_2.txt) + #message(STATUS gcc_search_dirs_install_path_2=${gcc_search_dirs_install_path_2}) + execute_process( + COMMAND sed -e "s/^install: //" -e "s,=/,/,g" + INPUT_FILE "${gcc_search_dirs_install_path_1}" + OUTPUT_FILE "${gcc_search_dirs_install_path_2}" + ) + + if(NOT ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")) + string(REPLACE ":" ";" gcc_install_dir "${gcc_install_dir}") + endif() + + file(READ ${gcc_search_dirs_install_path_2} gcc_install_dir) + + #set(gcc_programs_dir "${gcc_search_dirs}") + #string(REGEX MATCH "programs: =[ \t]*([^\r\n]*)" gcc_programs_dir "${gcc_programs_dir}" ) + #set(gcc_programs_dir "${CMAKE_MATCH_1}") + #message(STATUS gcc_programs_dir="${gcc_programs_dir}") + + string(CONCAT gcc_search_dirs_programs_path_1 "${CMAKE_BINARY_DIR}" /gcc_search_dirs_programs_1.txt) + #message(STATUS gcc_search_dirs_programs_path_1=${gcc_search_dirs_programs_path_1}) + execute_process( + COMMAND grep "^programs:" + INPUT_FILE "${gcc_search_dirs_path}" + OUTPUT_FILE "${gcc_search_dirs_programs_path_1}" + ) + + string(CONCAT gcc_search_dirs_programs_path_2 "${CMAKE_BINARY_DIR}" /gcc_search_dirs_programs_2.txt) + #message(STATUS gcc_search_dirs_programs_path_2=${gcc_search_dirs_programs_path_2}) + execute_process( + COMMAND sed -e "s/^programs: =//" -e "s,=/,/,g" + INPUT_FILE "${gcc_search_dirs_programs_path_1}" + OUTPUT_FILE "${gcc_search_dirs_programs_path_2}" + ) + + file(READ ${gcc_search_dirs_programs_path_2} gcc_programs_dir) + + if(NOT ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")) + string(REPLACE ":" ";" gcc_programs_dir "${gcc_programs_dir}") + endif() + + #set(gcc_libraries_dir "${gcc_search_dirs}") + #string(REGEX MATCH "libraries: =[ \t]*([^\r\n]*)" gcc_libraries_dir "${gcc_libraries_dir}" ) + #set(gcc_libraries_dir "${CMAKE_MATCH_1}") + #message(STATUS gcc_libraries_dir="${gcc_libraries_dir}") + + string(CONCAT gcc_search_dirs_libraries_path_1 "${CMAKE_BINARY_DIR}" /gcc_search_dirs_libraries_1.txt) + #message(STATUS gcc_search_dirs_libraries_path_1=${gcc_search_dirs_libraries_path_1}) + execute_process( + COMMAND grep "^libraries:" + INPUT_FILE "${gcc_search_dirs_path}" + OUTPUT_FILE "${gcc_search_dirs_libraries_path_1}" + ) + + string(CONCAT gcc_search_dirs_libraries_path_2 "${CMAKE_BINARY_DIR}" /gcc_search_dirs_libraries_2.txt) + #message(STATUS gcc_search_dirs_libraries_path_2=${gcc_search_dirs_libraries_path_2}) + execute_process( + COMMAND sed -e "s/^libraries: =//" -e "s,=/,/,g" + INPUT_FILE "${gcc_search_dirs_libraries_path_1}" + OUTPUT_FILE "${gcc_search_dirs_libraries_path_2}" + ) + + file(READ ${gcc_search_dirs_libraries_path_2} gcc_libraries_dir) + + if(NOT ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows")) + string(REPLACE ":" ";" gcc_libraries_dir "${gcc_libraries_dir}") + endif() + + #list(LENGTH gcc_install_dir gcc_install_dir_len) + #list(LENGTH gcc_programs_dir gcc_programs_dir_len) + #list(LENGTH gcc_libraries_dir gcc_libraries_dir_len) + #message(STATUS gcc_install_dir_len="${gcc_install_dir_len}") + #message(STATUS gcc_install_dir="${gcc_install_dir}") + #message(STATUS gcc_programs_dir_len="${gcc_programs_dir_len}") + #message(STATUS gcc_programs_dir="${gcc_programs_dir}") + #message(STATUS gcc_libraries_dir_len="${gcc_libraries_dir_len}") + #message(STATUS gcc_libraries_dir="${gcc_libraries_dir}") + + get_filename_component(CMAKE_CXX_COMPILER_DIR ${CMAKE_CXX_COMPILER} DIRECTORY) + #message(STATUS CMAKE_CXX_COMPILER_DIR="${CMAKE_CXX_COMPILER_DIR}") + + set(OLD_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) + set(CMAKE_FIND_LIBRARY_SUFFIXES "" ${CMAKE_FIND_LIBRARY_SUFFIXES}) + set(OLD_CMAKE_FIND_LIBRARY_PREFIXES ${CMAKE_FIND_LIBRARY_PREFIXES}) + set(CMAKE_FIND_LIBRARY_PREFIXES "" ${CMAKE_FIND_LIBRARY_PREFIXES}) + + find_library(LIBUNWIND + NAMES "libunwind.dll" + PATHS ${gcc_programs_dir} ${CMAKE_CXX_COMPILER_DIR} + ) + if (LIBUNWIND) + #message(STATUS LIBUNWIND="${LIBUNWIND}") + list(APPEND ADDITIONAL_DLLS ${LIBUNWIND}) + endif() + unset(LIBUNWIND) + find_library(LIBGCC + NAMES "libgcc_s_dw2-1.dll" "libgcc_s_sjlj-1.dll" "libgcc_s_seh-1.dll" + PATHS ${gcc_programs_dir} ${CMAKE_CXX_COMPILER_DIR} + ) + if (LIBGCC) + #message(STATUS LIBGCC="${LIBGCC}") + list(APPEND ADDITIONAL_DLLS ${LIBGCC}) + endif() + unset(LIBGCC) + find_library(LIBSTDCPP + NAMES "libstdc++-6.dll" "libc++.dll" + PATHS ${gcc_programs_dir} ${CMAKE_CXX_COMPILER_DIR} + ) + if (LIBSTDCPP) + #message(STATUS LIBSTDCPP="${LIBSTDCPP}") + list(APPEND ADDITIONAL_DLLS ${LIBSTDCPP}) + endif() + unset(LIBSTDCPP) + find_library(LIBPTHREAD + NAMES "winpthread-1.dll" "libwinpthread-1.dll" "pthreadGC2.dll" + PATHS ${gcc_libraries_dir} ${CMAKE_CXX_COMPILER_DIR} + ) + if(LIBPTHREAD) + #message(STATUS LIBPTHREAD="${LIBPTHREAD}") + list(APPEND ADDITIONAL_DLLS ${LIBPTHREAD}) + endif() + + set(CMAKE_FIND_LIBRARY_SUFFIXES ${OLD_CMAKE_FIND_LIBRARY_SUFFIXES}) + set(CMAKE_FIND_LIBRARY_PREFIXES ${OLD_CMAKE_FIND_LIBRARY_PREFIXES}) + unset(LIBPTHREAD) + unset(gcc_install_dir) + unset(gcc_programs_dir) + unset(gcc_libraries_dir) + unset(OLD_CMAKE_FIND_LIBRARY_SUFFIXES) + unset(OLD_CMAKE_FIND_LIBRARY_PREFIXES) + + #message(STATUS ADDITIONAL_DLLS="${ADDITIONAL_DLLS}") endif() add_custom_command(TARGET SRB2SDL2 POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different @@ -646,3 +817,11 @@ if("${CMAKE_SYSTEM_NAME}" STREQUAL Windows AND NOT "${SRB2_CONFIG_INTERNAL_LIBRA COMMENT "Copying runtime DLLs" ) endif() + +# Setup clang-tidy +if(SRB2_CONFIG_ENABLE_CLANG_TIDY_C) + target_set_default_clang_tidy(SRB2SDL2 C "-*,clang-analyzer-*,-clang-analyzer-cplusplus-*") +endif() +if(SRB2_CONFIG_ENABLE_CLANG_TIDY_CXX) + target_set_default_clang_tidy(SRB2SDL2 CXX "-*,clang-analyzer-*,modernize-*") +endif() diff --git a/src/acs/acsvm.hpp b/src/acs/acsvm.hpp index 84f7ea70d..16f5db72f 100644 --- a/src/acs/acsvm.hpp +++ b/src/acs/acsvm.hpp @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/acs/call-funcs.cpp b/src/acs/call-funcs.cpp index 3283bb865..47502cb0f 100644 --- a/src/acs/call-funcs.cpp +++ b/src/acs/call-funcs.cpp @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -1147,6 +1147,37 @@ bool CallFunc_SetLineTexture(ACSVM::Thread *thread, const ACSVM::Word *argV, ACS return false; } +/*-------------------------------------------------- + bool CallFunc_SetLineBlocking(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) + + Changes a linedef's blocking flag. +--------------------------------------------------*/ +bool CallFunc_SetLineBlocking(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) +{ + mtag_t tag = 0; + UINT32 blocking = 0; + INT32 lineId = -1; + + tag = argV[0]; + + if (argV[1] != 0) + { + blocking = ML_IMPASSABLE; + } + + TAG_ITER_LINES(tag, lineId) + { + line_t *line = &lines[lineId]; + + if (line->flags & ML_TWOSIDED) // disallow changing this for 1-sided lines + { + line->flags = (line->flags & ~ML_IMPASSABLE) | blocking; + } + } + + return false; +} + /*-------------------------------------------------- bool CallFunc_SetLineSpecial(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) @@ -1271,7 +1302,7 @@ bool CallFunc_EndPrintBold(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM bool CallFunc_PlayerTeam(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC) { auto info = &static_cast(thread)->info; - UINT8 teamID = 0; + UINT8 teamID = TEAM_UNASSIGNED; (void)argV; (void)argC; @@ -1280,7 +1311,7 @@ bool CallFunc_PlayerTeam(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM:: && (info->mo != NULL && P_MobjWasRemoved(info->mo) == false) && (info->mo->player != NULL)) { - teamID = info->mo->player->ctfteam; + teamID = info->mo->player->team; } thread->dataStk.push(teamID); @@ -1882,8 +1913,9 @@ bool CallFunc_GetGrabbedSprayCan(ACSVM::Thread *thread, const ACSVM::Word *argV, && gamemap-1 < basenummapheaders) { // See also P_SprayCanInit - UINT16 can_id = mapheaderinfo[gamemap-1]->cache_spraycan; + UINT16 can_id = mapheaderinfo[gamemap-1]->records.spraycan; + // Intentionally not affected by MCAN_BONUS if (can_id < gamedata->numspraycans) { UINT16 col = gamedata->spraycans[can_id].col; diff --git a/src/acs/call-funcs.hpp b/src/acs/call-funcs.hpp index ba2dee6e8..8d48bf12f 100644 --- a/src/acs/call-funcs.hpp +++ b/src/acs/call-funcs.hpp @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -57,6 +57,7 @@ bool CallFunc_IsNetworkGame(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSV bool CallFunc_SectorSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_AmbientSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_SetLineTexture(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); +bool CallFunc_SetLineBlocking(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_SetLineSpecial(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_ThingSound(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); bool CallFunc_EndPrintBold(ACSVM::Thread *thread, const ACSVM::Word *argV, ACSVM::Word argC); diff --git a/src/acs/environment.cpp b/src/acs/environment.cpp index 0a4939811..223ed74a9 100644 --- a/src/acs/environment.cpp +++ b/src/acs/environment.cpp @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -88,7 +88,7 @@ Environment::Environment() addCodeDataACS0( 95, {"", 2, addCallFunc(CallFunc_AmbientSound)}); addCodeDataACS0( 97, {"", 4, addCallFunc(CallFunc_SetLineTexture)}); - + addCodeDataACS0( 98, {"", 2, addCallFunc(CallFunc_SetLineBlocking)}); addCodeDataACS0( 99, {"", 7, addCallFunc(CallFunc_SetLineSpecial)}); addCodeDataACS0(100, {"", 3, addCallFunc(CallFunc_ThingSound)}); addCodeDataACS0(101, {"", 0, addCallFunc(CallFunc_EndPrintBold)}); @@ -228,8 +228,7 @@ void Environment::loadModule(ACSVM::Module *module) if (name->i == (size_t)LUMPERROR) { // No lump given for module. - CONS_Alert(CONS_WARNING, "Could not find ACS module \"%s\"; scripts will not function properly!\n", name->s->str); - return; //throw ACSVM::ReadError("file open failure"); + throw ACSVM::ReadError("invalid lump"); } lumpLen = W_LumpLength(name->i); @@ -280,9 +279,7 @@ void Environment::loadModule(ACSVM::Module *module) } else { - // Unlike Hexen, a BEHAVIOR lump is not required. - // Simply ignore in this instance. - CONS_Debug(DBG_SETUP, "ACS module has no data, ignoring...\n"); + throw ACSVM::ReadError("file empty"); } } diff --git a/src/acs/environment.hpp b/src/acs/environment.hpp index 79aa4a4ae..028467bda 100644 --- a/src/acs/environment.hpp +++ b/src/acs/environment.hpp @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/acs/interface.cpp b/src/acs/interface.cpp index 7f6a703cc..2e7908f24 100644 --- a/src/acs/interface.cpp +++ b/src/acs/interface.cpp @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -142,6 +142,13 @@ void ACS_LoadLevelScripts(size_t mapID) map->active = true; // Insert BEHAVIOR lump into the list. + virtres_t *vRes = vres_GetMap(mapheaderinfo[mapID]->lumpnum); + auto _ = srb2::finally([vRes]() { vres_Free(vRes); }); + + // Unlike Hexen, a BEHAVIOR lump is not required. + // Simply ignore in this instance. + virtlump_t *vLump = vres_Find(vRes, "BEHAVIOR"); + if (vLump != nullptr && vLump->size > 0) { ACSVM::ModuleName name = ACSVM::ModuleName( env->getString( mapheaderinfo[mapID]->lumpname ), @@ -150,6 +157,7 @@ void ACS_LoadLevelScripts(size_t mapID) ); modules.push_back(env->getModule(name)); + CONS_Debug(DBG_SETUP, "Found BEHAVIOR lump.\n"); } if (modules.empty() == false) @@ -562,8 +570,10 @@ void ACS_Archive(savebuffer_t *save) std::ostream stream{&buffer}; ACSVM::Serial serial{stream}; +#if 0 // Enable debug signatures. serial.signs = true; +#endif try { diff --git a/src/acs/interface.h b/src/acs/interface.h index cba20b253..e22c7f08d 100644 --- a/src/acs/interface.h +++ b/src/acs/interface.h @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/acs/stream.cpp b/src/acs/stream.cpp index a4d3e74cf..b5321d682 100644 --- a/src/acs/stream.cpp +++ b/src/acs/stream.cpp @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/acs/stream.hpp b/src/acs/stream.hpp index bbd83c7f3..901f736d1 100644 --- a/src/acs/stream.hpp +++ b/src/acs/stream.hpp @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/acs/thread.cpp b/src/acs/thread.cpp index 81bfbeb76..ae15b08c3 100644 --- a/src/acs/thread.cpp +++ b/src/acs/thread.cpp @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/acs/thread.hpp b/src/acs/thread.hpp index 9c4da6626..88d8d4307 100644 --- a/src/acs/thread.hpp +++ b/src/acs/thread.hpp @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // Copyright (C) 2016 by James Haley, David Hill, et al. (Team Eternity) -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/acs/vm/ACSVM/CMakeLists.txt b/src/acs/vm/ACSVM/CMakeLists.txt index 29760733f..5ae770b1d 100644 --- a/src/acs/vm/ACSVM/CMakeLists.txt +++ b/src/acs/vm/ACSVM/CMakeLists.txt @@ -75,6 +75,7 @@ add_library(acsvm ${ACSVM_SHARED_DECL} Types.hpp Vector.hpp ) +target_compile_features(acsvm PRIVATE cxx_std_17) ACSVM_INSTALL_LIB(acsvm) diff --git a/src/acs/vm/CMakeLists.txt b/src/acs/vm/CMakeLists.txt index 3116012c6..a370424b3 100644 --- a/src/acs/vm/CMakeLists.txt +++ b/src/acs/vm/CMakeLists.txt @@ -10,7 +10,7 @@ ## ##----------------------------------------------------------------------------- -cmake_minimum_required(VERSION 2.6) +cmake_minimum_required(VERSION 3.14) cmake_policy(SET CMP0017 NEW) diff --git a/src/am_map.c b/src/am_map.c index 55590edea..a8bf36d24 100644 --- a/src/am_map.c +++ b/src/am_map.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/am_map.h b/src/am_map.h index de9fb0ce8..31bce8f2a 100644 --- a/src/am_map.h +++ b/src/am_map.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/android/i_cdmus.c b/src/android/i_cdmus.c index 52406d72d..6c546d4a2 100644 --- a/src/android/i_cdmus.c +++ b/src/android/i_cdmus.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/android/i_main.c b/src/android/i_main.c index b40223fd2..ca6435e88 100644 --- a/src/android/i_main.c +++ b/src/android/i_main.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/android/i_net.c b/src/android/i_net.c index 9fba60388..525941e52 100644 --- a/src/android/i_net.c +++ b/src/android/i_net.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/android/i_sound.c b/src/android/i_sound.c index 25081feb1..91ec4b02c 100644 --- a/src/android/i_sound.c +++ b/src/android/i_sound.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/android/i_system.c b/src/android/i_system.c index 5f045add4..e212ae021 100644 --- a/src/android/i_system.c +++ b/src/android/i_system.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/android/i_video.c b/src/android/i_video.c index 055fdd074..bf8e3120e 100644 --- a/src/android/i_video.c +++ b/src/android/i_video.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/android/i_video.h b/src/android/i_video.h index 92158212f..bc364acdd 100644 --- a/src/android/i_video.h +++ b/src/android/i_video.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/apng.c b/src/apng.c index a5f2485d7..6216fb347 100644 --- a/src/apng.c +++ b/src/apng.c @@ -71,7 +71,7 @@ apng_create_info_struct (png_structp pngp) { apng_infop ainfop; (void)pngp; - if (( ainfop = calloc(sizeof (apng_info),1) )) + if (( ainfop = calloc(1, sizeof (apng_info)) )) { apng_set_write_fn(pngp, ainfop, 0, 0, 0, 0, 0); apng_set_set_acTL_fn(pngp, ainfop, 0); diff --git a/src/archive_wrapper.hpp b/src/archive_wrapper.hpp index 972570ab2..4ba4c4f88 100644 --- a/src/archive_wrapper.hpp +++ b/src/archive_wrapper.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/asm_defs.inc b/src/asm_defs.inc index a2ba4f919..1b418cabd 100644 --- a/src/asm_defs.inc +++ b/src/asm_defs.inc @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/audio/chunk_load.cpp b/src/audio/chunk_load.cpp index 52e84d1d5..312d310bd 100644 --- a/src/audio/chunk_load.cpp +++ b/src/audio/chunk_load.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/chunk_load.hpp b/src/audio/chunk_load.hpp index 391374539..3c97cad70 100644 --- a/src/audio/chunk_load.hpp +++ b/src/audio/chunk_load.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/expand_mono.cpp b/src/audio/expand_mono.cpp index 2ff42a804..da9716044 100644 --- a/src/audio/expand_mono.cpp +++ b/src/audio/expand_mono.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/expand_mono.hpp b/src/audio/expand_mono.hpp index 77e3224d8..c05538f09 100644 --- a/src/audio/expand_mono.hpp +++ b/src/audio/expand_mono.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/filter.cpp b/src/audio/filter.cpp index 2fd3fa4d0..c9693f891 100644 --- a/src/audio/filter.cpp +++ b/src/audio/filter.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/filter.hpp b/src/audio/filter.hpp index 24bbb8ac0..7f79436aa 100644 --- a/src/audio/filter.hpp +++ b/src/audio/filter.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/gain.cpp b/src/audio/gain.cpp index 4e2dcc8ee..6d8d472a8 100644 --- a/src/audio/gain.cpp +++ b/src/audio/gain.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/gain.hpp b/src/audio/gain.hpp index cb7eec380..8050596e5 100644 --- a/src/audio/gain.hpp +++ b/src/audio/gain.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/mixer.cpp b/src/audio/mixer.cpp index 33aa17e02..a944ec61d 100644 --- a/src/audio/mixer.cpp +++ b/src/audio/mixer.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/mixer.hpp b/src/audio/mixer.hpp index bde834573..88305699f 100644 --- a/src/audio/mixer.hpp +++ b/src/audio/mixer.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/music_player.cpp b/src/audio/music_player.cpp index bd85cbf86..6f4bf5fec 100644 --- a/src/audio/music_player.cpp +++ b/src/audio/music_player.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/music_player.hpp b/src/audio/music_player.hpp index f9c91429e..5ab502bcd 100644 --- a/src/audio/music_player.hpp +++ b/src/audio/music_player.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/ogg.cpp b/src/audio/ogg.cpp index 3e153febf..6afaf849a 100644 --- a/src/audio/ogg.cpp +++ b/src/audio/ogg.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -76,7 +76,7 @@ Ogg::Ogg() noexcept : memory_data_(), instance_(nullptr) { } -Ogg::Ogg(std::vector data) : memory_data_(std::move(data)), instance_(nullptr) +Ogg::Ogg(Vector data) : memory_data_(std::move(data)), instance_(nullptr) { _init_with_data(); } @@ -153,8 +153,8 @@ OggComment Ogg::comment() const stb_vorbis_comment c_comment = stb_vorbis_get_comment(instance_); return OggComment { - std::string(c_comment.vendor), - std::vector(c_comment.comment_list, c_comment.comment_list + c_comment.comment_list_length)}; + String(c_comment.vendor), + Vector(c_comment.comment_list, c_comment.comment_list + c_comment.comment_list_length)}; } std::size_t Ogg::sample_rate() const diff --git a/src/audio/ogg.hpp b/src/audio/ogg.hpp index 3d9bc4307..ef19a0ce0 100644 --- a/src/audio/ogg.hpp +++ b/src/audio/ogg.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -35,19 +35,19 @@ public: struct OggComment { - std::string vendor; - std::vector comments; + String vendor; + Vector comments; }; class Ogg final { - std::vector memory_data_; + Vector memory_data_; stb_vorbis* instance_; public: Ogg() noexcept; - explicit Ogg(std::vector data); + explicit Ogg(Vector data); explicit Ogg(tcb::span data); Ogg(const Ogg&) = delete; @@ -77,7 +77,7 @@ private: template , int> = 0> inline Ogg load_ogg(I& stream) { - std::vector data = srb2::io::read_to_vec(stream); + srb2::Vector data = srb2::io::read_to_vec(stream); return Ogg {std::move(data)}; } diff --git a/src/audio/ogg_player.cpp b/src/audio/ogg_player.cpp index 71c0f0258..c32e80cf1 100644 --- a/src/audio/ogg_player.cpp +++ b/src/audio/ogg_player.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/ogg_player.hpp b/src/audio/ogg_player.hpp index 5e468fec9..1797c16b7 100644 --- a/src/audio/ogg_player.hpp +++ b/src/audio/ogg_player.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/resample.cpp b/src/audio/resample.cpp index 9b73cb38b..3d76f6439 100644 --- a/src/audio/resample.cpp +++ b/src/audio/resample.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/resample.hpp b/src/audio/resample.hpp index 1f8f3782a..7d968ff2e 100644 --- a/src/audio/resample.hpp +++ b/src/audio/resample.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/sample.hpp b/src/audio/sample.hpp index 46e295d68..e9a54a33d 100644 --- a/src/audio/sample.hpp +++ b/src/audio/sample.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/sound_chunk.hpp b/src/audio/sound_chunk.hpp index 33da99677..2d19904da 100644 --- a/src/audio/sound_chunk.hpp +++ b/src/audio/sound_chunk.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/sound_effect_player.cpp b/src/audio/sound_effect_player.cpp index 899e61c63..73d2b6240 100644 --- a/src/audio/sound_effect_player.cpp +++ b/src/audio/sound_effect_player.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/sound_effect_player.hpp b/src/audio/sound_effect_player.hpp index c5b0c146d..3a6f878e6 100644 --- a/src/audio/sound_effect_player.hpp +++ b/src/audio/sound_effect_player.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/source.hpp b/src/audio/source.hpp index 2dab24674..99ec5fb0f 100644 --- a/src/audio/source.hpp +++ b/src/audio/source.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/wav.cpp b/src/audio/wav.cpp index 89c7d1ff3..a57eed22b 100644 --- a/src/audio/wav.cpp +++ b/src/audio/wav.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -126,9 +126,9 @@ void visit_tag(Visitor& visitor, io::SpanStream& stream, const TagHeader& header stream.seek(io::SeekFrom::kStart, dest); } -std::vector read_uint8_samples_from_stream(io::SpanStream& stream, std::size_t count) +Vector read_uint8_samples_from_stream(io::SpanStream& stream, std::size_t count) { - std::vector samples; + Vector samples; samples.reserve(count); for (std::size_t i = 0; i < count; i++) { @@ -137,9 +137,9 @@ std::vector read_uint8_samples_from_stream(io::SpanStream& stream, std: return samples; } -std::vector read_int16_samples_from_stream(io::SpanStream& stream, std::size_t count) +Vector read_int16_samples_from_stream(io::SpanStream& stream, std::size_t count) { - std::vector samples; + Vector samples; samples.reserve(count); for (std::size_t i = 0; i < count; i++) { @@ -177,7 +177,7 @@ Wav::Wav(tcb::span data) } std::optional read_fmt; - std::variant, std::vector> interleaved_samples; + std::variant, Vector> interleaved_samples; while (stream.seek(io::SeekFrom::kCurrent, 0) < riff_end) { @@ -247,7 +247,7 @@ template std::size_t read_samples( std::size_t channels, std::size_t offset, - const std::vector& samples, + const Vector& samples, tcb::span> buffer ) noexcept { @@ -281,8 +281,8 @@ std::size_t read_samples( std::size_t Wav::get_samples(std::size_t offset, tcb::span> buffer) const noexcept { auto samples_visitor = srb2::Overload { - [&](const std::vector& samples) { return read_samples(channels(), offset, samples, buffer); }, - [&](const std::vector& samples) + [&](const Vector& samples) { return read_samples(channels(), offset, samples, buffer); }, + [&](const Vector& samples) { return read_samples(channels(), offset, samples, buffer); }}; return std::visit(samples_visitor, interleaved_samples_); @@ -291,7 +291,7 @@ std::size_t Wav::get_samples(std::size_t offset, tcb::span> buf std::size_t Wav::interleaved_length() const noexcept { auto samples_visitor = srb2::Overload { - [](const std::vector& samples) { return samples.size(); }, - [](const std::vector& samples) { return samples.size(); }}; + [](const Vector& samples) { return samples.size(); }, + [](const Vector& samples) { return samples.size(); }}; return std::visit(samples_visitor, interleaved_samples_); } diff --git a/src/audio/wav.hpp b/src/audio/wav.hpp index 2e44343b9..fa1aec374 100644 --- a/src/audio/wav.hpp +++ b/src/audio/wav.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -15,10 +15,10 @@ #include #include #include -#include #include +#include "../core/vector.hpp" #include "../io/streams.hpp" #include "sample.hpp" @@ -27,7 +27,7 @@ namespace srb2::audio class Wav final { - std::variant, std::vector> interleaved_samples_; + std::variant, Vector> interleaved_samples_; std::size_t channels_ = 1; std::size_t sample_rate_ = 44100; @@ -46,7 +46,7 @@ public: template , int> = 0> inline Wav load_wav(I& stream) { - std::vector data = srb2::io::read_to_vec(stream); + Vector data = srb2::io::read_to_vec(stream); return Wav {data}; } diff --git a/src/audio/wav_player.cpp b/src/audio/wav_player.cpp index 457754cc9..c23ae4338 100644 --- a/src/audio/wav_player.cpp +++ b/src/audio/wav_player.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/wav_player.hpp b/src/audio/wav_player.hpp index 4ddaefc0b..3beba7c5a 100644 --- a/src/audio/wav_player.hpp +++ b/src/audio/wav_player.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/audio/xmp.cpp b/src/audio/xmp.cpp index 383b06418..9ed496e5f 100644 --- a/src/audio/xmp.cpp +++ b/src/audio/xmp.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -50,7 +50,7 @@ Xmp::Xmp() : data_(), instance_(nullptr), module_loaded_(false), looping_(fal } template -Xmp::Xmp(std::vector data) +Xmp::Xmp(Vector data) : data_(std::move(data)), instance_(nullptr), module_loaded_(false), looping_(false) { _init(); diff --git a/src/audio/xmp.hpp b/src/audio/xmp.hpp index c924a2965..50aba1711 100644 --- a/src/audio/xmp.hpp +++ b/src/audio/xmp.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -15,11 +15,11 @@ #include #include #include -#include #include #include +#include "../core/vector.hpp" #include "../io/streams.hpp" namespace srb2::audio @@ -37,7 +37,7 @@ public: template class Xmp final { - std::vector data_; + Vector data_; xmp_context instance_; bool module_loaded_; bool looping_; @@ -45,7 +45,7 @@ class Xmp final public: Xmp(); - explicit Xmp(std::vector data); + explicit Xmp(Vector data); explicit Xmp(tcb::span data); Xmp(const Xmp&) = delete; @@ -74,7 +74,7 @@ extern template class Xmp<2>; template , int> = 0> inline Xmp load_xmp(I& stream) { - std::vector data = srb2::io::read_to_vec(stream); + Vector data = srb2::io::read_to_vec(stream); return Xmp {std::move(data)}; } diff --git a/src/audio/xmp_player.cpp b/src/audio/xmp_player.cpp index afd6af97b..35285bacc 100644 --- a/src/audio/xmp_player.cpp +++ b/src/audio/xmp_player.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -12,6 +12,8 @@ #include +#include "../core/vector.hpp" + using namespace srb2; using namespace srb2::audio; diff --git a/src/audio/xmp_player.hpp b/src/audio/xmp_player.hpp index 04d2b905a..bc9e1898c 100644 --- a/src/audio/xmp_player.hpp +++ b/src/audio/xmp_player.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,6 +14,8 @@ #include "source.hpp" #include "xmp.hpp" +#include "../core/vector.hpp" + namespace srb2::audio { @@ -21,7 +23,7 @@ template class XmpPlayer final : public Source { Xmp xmp_; - std::vector> buf_; + srb2::Vector> buf_; public: XmpPlayer(Xmp&& xmp); diff --git a/src/byteptr.h b/src/byteptr.h index e8deb88a2..56d18e93f 100644 --- a/src/byteptr.h +++ b/src/byteptr.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/command.c b/src/command.c index b42b73a5c..3551f436d 100644 --- a/src/command.c +++ b/src/command.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // @@ -102,6 +102,18 @@ CV_PossibleValue_t gpdifficulty_cons_t[] = { {KARTGP_MASTER, "Master"}, {0, NULL} }; +CV_PossibleValue_t descriptiveinput_cons_t[] = { + {0, "\"Emulator\""}, + {1, "Modern"}, + {2, "Modern Flip"}, + {3, "6Bt. (Auto)"}, + {4, "6Bt. (A)"}, + {5, "6Bt. (B)"}, + {6, "6Bt. (C)"}, + {7, "6Bt. (D)"}, + {8, "6Bt. (E)"}, + {0, NULL} +}; // Filter consvars by EXECVERSION // First implementation is 2 (1.0.2), so earlier configs default at 1 (1.0.0) @@ -855,8 +867,19 @@ static void COM_Exec_f(void) if (!COM_CheckParm("-silent")) CONS_Printf(M_GetText("executing %s\n"), COM_Argv(1)); - // insert text file into the command buffer - COM_ImmedExecute((char *)buf); + if (COM_CheckParm("-immediate")) + { + // immediately parses and executes all lines + // sidesteps wait from all sources, even self + COM_ImmedExecute((char *)buf); + } + else + { + // insert text file into the command buffer + // delays execution if interpreting wait cmd + COM_BufAddTextEx((char *)buf, com_flags); + COM_BufAddTextEx("\n", com_flags); + } // free buffer Z_Free(buf); diff --git a/src/command.h b/src/command.h index eabbc1b1b..0a0a44875 100644 --- a/src/command.h +++ b/src/command.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // @@ -233,7 +233,7 @@ extern CV_PossibleValue_t CV_TrueFalse[]; // SRB2kart // the KARTSPEED and KARTGP were previously defined here, but moved to doomstat to avoid circular dependencies -extern CV_PossibleValue_t kartspeed_cons_t[], dummykartspeed_cons_t[], gpdifficulty_cons_t[]; +extern CV_PossibleValue_t kartspeed_cons_t[], dummykartspeed_cons_t[], gpdifficulty_cons_t[], descriptiveinput_cons_t[]; extern consvar_t cv_cheats; extern consvar_t cv_execversion; diff --git a/src/comptime.c b/src/comptime.c index a8cfc79de..11269a2fa 100644 --- a/src/comptime.c +++ b/src/comptime.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/console.c b/src/console.c index cde8e3110..1e7585fbc 100644 --- a/src/console.c +++ b/src/console.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/console.h b/src/console.h index e0c1e58e3..4200d4d2a 100644 --- a/src/console.h +++ b/src/console.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index a5d0b521a..5c91e1d66 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,8 +1,17 @@ target_sources(SRB2SDL2 PRIVATE + hash_map.hpp + hash_set.cpp + hash_set.hpp + json.cpp + json.hpp memory.cpp memory.h spmc_queue.hpp static_vec.hpp + string.cpp + string.h thread_pool.cpp thread_pool.h + vector.cpp + vector.hpp ) diff --git a/src/core/hash_map.hpp b/src/core/hash_map.hpp new file mode 100644 index 000000000..70cc18c37 --- /dev/null +++ b/src/core/hash_map.hpp @@ -0,0 +1,687 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef SRB2_CORE_HASH_MAP_HPP +#define SRB2_CORE_HASH_MAP_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include "../cxxutil.hpp" + +namespace srb2 +{ + +template +class HashSet; + +template +class HashMap +{ + struct Elem; + + using Hasher = std::hash; + using KeyEqual = std::equal_to; + +public: + using Entry = std::pair; + + class ConstIter; + + class Iter + { + const HashMap* self_; + uint32_t bucket_; + Elem* cur_; + + Iter(const HashMap* self, uint32_t bucket, Elem* cur) + : self_(self) + , bucket_(bucket) + , cur_(cur) + {} + + Iter(const ConstIter& r) + : self_(r.iter_.self_) + , bucket_(r.iter_.bucket_) + , cur_(r.iter_.cur_) + {} + + friend class HashMap; + friend class HashSet; + friend class ConstIter; + + public: + Iter() : Iter(nullptr, 0, nullptr) {} + Iter(const Iter&) = default; + Iter(Iter&&) noexcept = default; + ~Iter() = default; + Iter& operator=(const Iter&) = default; + Iter& operator=(Iter&&) noexcept = default; + + Entry& operator*() const noexcept { return cur_->entry; } + Entry* operator->() const noexcept { return &cur_->entry; } + bool operator==(const Iter& r) const noexcept + { + return self_ == r.self_ && bucket_ == r.bucket_ && cur_ == r.cur_; + } + bool operator!=(const Iter& r) const noexcept { return !(*this == r); } + + Iter& operator++() + { + if (cur_ == nullptr) + { + return *this; + } + + if (cur_->next == nullptr) + { + do + { + if (bucket_ < self_->buckets_ - 1) + { + bucket_++; + cur_ = self_->heads_[bucket_]; + } + else + { + self_ = nullptr; + bucket_ = 0; + cur_ = nullptr; + return *this; + } + } while (cur_ == nullptr); + } + else + { + cur_ = cur_->next; + } + + return *this; + } + + Iter operator++(int) + { + Iter itr = *this; + ++(*this); + return itr; + } + }; + + class ConstIter + { + mutable Iter iter_; + + friend class HashMap; + public: + ConstIter() : iter_() {} + ConstIter(const ConstIter&) = default; + ConstIter(ConstIter&&) noexcept = default; + ~ConstIter() = default; + ConstIter& operator=(const ConstIter&) = default; + ConstIter& operator=(ConstIter&&) noexcept = default; + + ConstIter(const Iter& iter) : iter_(iter) {} + + const Entry& operator*() const noexcept { return iter_.cur_->entry; } + const Entry* operator->() const noexcept { return &iter_.cur_->entry; } + bool operator==(const ConstIter& r) const noexcept { return iter_ == r.iter_; } + bool operator!=(const ConstIter& r) const noexcept { return !(*this == r); } + ConstIter& operator++() noexcept + { + ++iter_; + return *this; + } + ConstIter operator++(int) noexcept + { + ConstIter itr = *this; + ++*this; + return itr; + } + }; + + // iter traits + using key_type = K; + using mapped_type = V; + using value_type = Entry; + using size_type = uint32_t; + using difference_type = int64_t; + using hasher = Hasher; + using key_equal = KeyEqual; + using reference = Entry&; + using const_reference = const Entry&; + using pointer = Entry*; + using const_pointer = const Entry*; + using iterator = Iter; + using const_iterator = ConstIter; + +private: + struct Elem + { + Elem* prev; + Elem* next; + Entry entry; + }; + + Elem** heads_ = nullptr; + size_t size_ = 0; + uint32_t buckets_; + Hasher hasher_; + KeyEqual key_equal_; + + constexpr static uint32_t kDefaultBuckets = 16; + + void init_buckets() + { + if (buckets_ == 0) + { + buckets_ = kDefaultBuckets; + } + + std::allocator allocator; + heads_ = allocator.allocate(buckets_); + for (uint32_t i = 0; i < buckets_; i++) + { + heads_[i] = nullptr; + } + } + + void init_if_needed() + { + if (heads_ == nullptr) + { + init_buckets(); + } + } + + void rehash_if_needed(size_t target_size) + { + if (target_size >= buckets_) + { + rehash(std::max(target_size, buckets_ * 2)); + } + } + + + friend class Iter; + friend class ConstIter; + +public: + HashMap() : heads_(nullptr), size_(0), buckets_(0), hasher_(), key_equal_() + { + + } + + HashMap(uint32_t buckets) + : heads_(nullptr) + , size_(0) + , buckets_(buckets) + , hasher_() + , key_equal_() + { + init_buckets(); + } + + HashMap(const HashMap& r) + { + *this = r; + } + + HashMap(HashMap&& r) noexcept + { + buckets_ = r.buckets_; + r.buckets_ = 0; + size_ = r.size_; + r.size_ = 0; + heads_ = r.heads_; + r.heads_ = nullptr; + hasher_ = r.hasher_; + r.hasher_ = {}; + key_equal_ = r.key_equal_; + r.key_equal_ = {}; + }; + + ~HashMap() + { + clear(); + } + + HashMap(std::initializer_list list) : HashMap(list.size()) + { + for (auto v : list) + { + insert(v); + } + } + + HashMap& operator=(const HashMap& r) + { + clear(); + buckets_ = r.buckets_; + if (buckets_ == 0) + { + return *this; + } + + init_buckets(); + for (auto itr = r.begin(); itr != r.end(); itr++) + { + insert({itr->first, itr->second}); + } + + return *this; + } + + HashMap& operator=(HashMap&& r) noexcept + { + std::swap(buckets_, r.buckets_); + std::swap(size_, r.size_); + std::swap(heads_, r.heads_); + std::swap(hasher_, r.hasher_); + std::swap(key_equal_, r.key_equal_); + return *this; + }; + + constexpr bool empty() const noexcept { return size_ == 0; } + constexpr size_t size() const noexcept { return size_; } + constexpr uint32_t buckets() const noexcept { return buckets_; } + + Iter begin() noexcept + { + if (size_ == 0) + { + return end(); + } + + Iter ret = end(); + for (uint32_t i = 0; i < buckets_; i++) + { + Elem* ptr = heads_[i]; + if (ptr != nullptr) + { + ret = Iter { this, i, ptr }; + break; + } + } + return ret; + } + + ConstIter cbegin() const noexcept + { + ConstIter ret = end(); + for (uint32_t i = 0; i < buckets_; i++) + { + Elem* ptr = heads_[i]; + if (ptr != nullptr) + { + ret = ConstIter { Iter { this, i, ptr } }; + break; + } + } + return ret; + } + + ConstIter begin() const noexcept { return cbegin(); } + + constexpr Iter end() noexcept { return Iter(); } + + constexpr ConstIter cend() const noexcept { return ConstIter(); } + + constexpr ConstIter end() const noexcept { return cend(); } + + void clear() + { + if (heads_) + { + std::allocator elem_allocator; + for (uint32_t i = 0; i < buckets_; i++) + { + auto itr = heads_[i]; + while (itr != nullptr) + { + auto nextitr = itr->next; + itr->~Elem(); + elem_allocator.deallocate(itr, 1); + itr = nextitr; + } + } + + std::allocator buckets_allocator; + buckets_allocator.deallocate(heads_, buckets_); + } + heads_ = nullptr; + size_ = 0; + } + + Iter find(const K& key) + { + if (buckets_ == 0 || heads_ == nullptr) + { + return end(); + } + + uint32_t bucket = (hasher_)(key) % buckets_; + for (Elem* p = heads_[bucket]; p != nullptr; p = p->next) + { + if ((key_equal_)(p->entry.first, key)) + { + return Iter { this, bucket, p }; + } + }; + return end(); + } + + ConstIter find(const K& key) const + { + Iter iter = const_cast(this)->find(key); + return ConstIter { iter }; + } + + std::pair insert(const Entry& value) + { + Entry copy = value; + return insert(std::move(copy)); + } + + void rehash(uint32_t count) + { + count = size_ > count ? size_ : count; + HashMap rehashed { count }; + for (Iter itr = begin(); itr != end();) + { + Iter itrcopy = itr; + ++itr; + + uint32_t oldbucket = itrcopy.bucket_; + if (heads_[oldbucket] == itrcopy.cur_) + { + heads_[oldbucket] = nullptr; + } + itrcopy.self_ = &rehashed; + itrcopy.bucket_ = (rehashed.hasher_)(itrcopy.cur_->entry.first) % count; + Elem* p = rehashed.heads_[itrcopy.bucket_]; + if (p == nullptr) + { + p = rehashed.heads_[itrcopy.bucket_] = itrcopy.cur_; + size_ -= 1; + rehashed.size_ += 1; + + if (p->next) + { + p->next->prev = nullptr; + } + if (p->prev) + { + p->prev->next = nullptr; + } + p->next = nullptr; + p->prev = nullptr; + } + else + { + for (; p != nullptr; p = p->next) + { + if (p->next != nullptr) + { + continue; + } + p->next = itrcopy.cur_; + size_ -= 1; + rehashed.size_ += 1; + p->next->prev = p; + p->next->next = nullptr; + break; + } + } + } + + if (heads_) + { + std::allocator buckets_allocator; + buckets_allocator.deallocate(heads_, buckets_); + heads_ = nullptr; + } + + *this = std::move(rehashed); + } + + std::pair insert(Entry&& value) + { + std::pair ret { end(), false }; + std::allocator allocator; + + init_if_needed(); + + rehash_if_needed(size_ + 1); + + uint32_t bucket = (hasher_)(value.first) % buckets_; + Elem* p = heads_[bucket]; + if (p == nullptr) + { + // Make a new slot + ret.second = true; + Elem* newslot = allocator.allocate(1); + newslot->prev = nullptr; + newslot->next = nullptr; + heads_[bucket] = newslot; + new (&newslot->entry) Entry { std::move(value) }; + size_++; + ret.first = Iter { this, bucket, newslot }; + return ret; + } + + for(; p != nullptr;) + { + if ((key_equal_)(p->entry.first, value.first)) + { + ret.second = false; + ret.first = Iter { this, bucket, p }; + return ret; + } + + if (p->next == nullptr) + { + // Make a new slot + ret.second = true; + Elem* newslot = allocator.allocate(1); + newslot->prev = p; + newslot->next = nullptr; + p->next = newslot; + new (&newslot->entry) Entry { std::move(value) }; + size_++; + ret.first = Iter { this, bucket, newslot }; + return ret; + } + p = p->next; + } + + return ret; + } + + template + std::pair insert_or_assign(const K& key, M&& value) + { + K kcopy = key; + return insert_or_assign(std::move(kcopy), std::forward(value)); + } + + template + std::pair insert_or_assign(K&& key, M&& value) + { + std::pair ret { find(key), false }; + if (ret.first != end()) + { + ret.first->second = std::forward(value); + } + else + { + ret = insert({ std::move(key), std::forward(value) }); + } + return ret; + } + + template + std::pair emplace(Args&&... args) + { + Entry entry { std::forward(args)... }; + return insert(std::move(entry)); + } + + template + std::pair try_emplace(const K& key, Args&&... args) + { + Iter itr = find(key); + if (itr != end()) + { + return { itr, false }; + } + + return insert({ key, V(std::forward(args)...) }); + } + + template + std::pair try_emplace(K&& key, Args... args) + { + std::pair ret { end(), false }; + return insert({ std::move(key), V(std::forward(args)...)}); + } + + V& operator[](const K& key) + { + K copy { key }; + return this->operator[](std::move(copy)); + } + + V& operator[](K&& key) + { + std::allocator allocator; + init_if_needed(); + + rehash_if_needed(size_ + 1); + + uint32_t bucket = (hasher_)(key) % buckets_; + Elem* p = heads_[bucket]; + if (p == nullptr) + { + // Make a new slot + Elem* newslot = allocator.allocate(1); + newslot->prev = nullptr; + newslot->next = nullptr; + heads_[bucket] = newslot; + new (&newslot->entry) Entry { std::move(key), {} }; + size_++; + return newslot->entry.second; + } + for (; p != nullptr;) + { + if ((key_equal_)(p->entry.first, key)) + { + return p->entry.second; + } + + if (p->next == nullptr) + { + // Make a new slot + Elem* newslot = allocator.allocate(1); + newslot->prev = p; + newslot->next = nullptr; + p->next = newslot; + new (&newslot->entry) Entry { std::move(key), {} }; + size_++; + return newslot->entry.second; + } + p = p->next; + } + return end()->second; + } + + V& at(const K& key) + { + auto itr = find(key); + if (itr == end()) + { + throw std::out_of_range("key not found"); + } + return itr->second; + } + + const V& at(const K& key) const + { + auto itr = find(key); + if (itr == cend()) + { + throw std::out_of_range("key not found"); + } + return itr->second; + } + + Iter erase(Iter pos) + { + Iter ret = pos; + if (pos == end()) + { + return end(); + } + + std::allocator allocator; + uint32_t bucket = (hasher_)(pos.cur_->entry.first) % buckets_; + Elem* p = heads_[bucket]; + for (; p != nullptr;) + { + if ((key_equal_)(p->entry.first, pos.cur_->entry.first)) + { + // found it; remove, return next iter + ++ret; + if (p->next != nullptr) + { + p->next->prev = p->prev; + } + if (p->prev != nullptr) + { + p->prev->next = p->next; + } + if (p == heads_[bucket]) + { + heads_[bucket] = p->next; + } + p->~Elem(); + allocator.deallocate(p, 1); + size_ -= 1; + return ret; + } + p = p->next; + } + return ret; + } + + Iter erase(ConstIter pos) + { + return erase(pos.iter_); + } + + uint32_t erase(const K& key) + { + Iter pos = find(key); + if (pos != end()) + { + erase(pos); + return 1; + } + return 0; + } +}; + +} // namespace srb2 + +#endif // SRB2_CORE_HASH_MAP_HPP diff --git a/src/core/hash_set.cpp b/src/core/hash_set.cpp new file mode 100644 index 000000000..72c0dd5da --- /dev/null +++ b/src/core/hash_set.cpp @@ -0,0 +1,32 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "hash_set.hpp" + +#include + +#include "string.h" + +namespace srb2 +{ + +template class HashSet; +template class HashSet; +template class HashSet; +template class HashSet; +template class HashSet; +template class HashSet; +template class HashSet; +template class HashSet; +template class HashSet; +template class HashSet; +template class HashSet; + +} // namespace srb2 diff --git a/src/core/hash_set.hpp b/src/core/hash_set.hpp new file mode 100644 index 000000000..ed2a714a0 --- /dev/null +++ b/src/core/hash_set.hpp @@ -0,0 +1,214 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef SRB2_CORE_HASH_SET_HPP +#define SRB2_CORE_HASH_SET_HPP + +#include +#include + +#include "hash_map.hpp" +#include "string.h" + +namespace srb2 +{ + +template +class HashSet +{ + using Inner = HashMap; + +public: + class Iter + { + typename Inner::Iter iter_; + + Iter(typename Inner::Iter iter) : iter_(iter) {} + + friend class HashSet; + friend class ConstIter; + + public: + Iter() = default; + Iter(const Iter&) = default; + Iter(Iter&&) noexcept = default; + ~Iter() = default; + + Iter& operator=(const Iter&) = default; + Iter& operator=(Iter&&) noexcept = default; + + bool operator==(const Iter& r) const noexcept { return iter_ == r.iter_; } + bool operator!=(const Iter& r) const noexcept { return !(*this == r); } + + V& operator*() const noexcept { return iter_->first; } + V* operator->() const noexcept { return &iter_->first; } + Iter& operator++() noexcept + { + ++iter_; + return *this; + } + Iter operator++(int) noexcept + { + Iter self = *this; + ++*this; + return self; + } + }; + + class ConstIter + { + typename Inner::ConstIter iter_; + + friend class HashSet; + friend class Iter; + + public: + ConstIter() = default; + ConstIter(const ConstIter&) = default; + ConstIter(ConstIter&&) noexcept = default; + ~ConstIter() = default; + + ConstIter(const Iter& iter) : iter_(iter.iter_) {} + ConstIter(const typename Inner::ConstIter& iter) : iter_(iter) {} + + ConstIter& operator=(const ConstIter&) = default; + ConstIter& operator=(ConstIter&&) noexcept = default; + + bool operator==(const ConstIter& r) const noexcept { return iter_ == r.iter_; } + bool operator!=(const ConstIter& r) const noexcept { return !(*this == r); } + + const V& operator*() const noexcept { return iter_->first; } + const V* operator->() const noexcept { return &iter_->first; } + ConstIter& operator++() noexcept + { + ++iter_; + return *this; + } + ConstIter operator++(int) noexcept + { + ConstIter self = *this; + ++*this; + return self; + } + }; + +private: + Inner map_; + +public: + using key_type = V; + using value_type = V; + using size_type = typename Inner::size_type; + using difference_type = typename Inner::difference_type; + using hasher = typename Inner::hasher; + using key_equal = typename Inner::key_equal; + using reference = V&; + using const_reference = const V&; + using pointer = V*; + using const_pointer = const V*; + using iterator = Iter; + using const_iterator = ConstIter; + + HashSet() = default; + HashSet(const HashSet&) = default; + HashSet(HashSet&&) noexcept = default; + ~HashSet() = default; + + HashSet& operator=(const HashSet&) = default; + HashSet& operator=(HashSet&&) noexcept = default; + + Iter begin() { return map_.begin(); } + Iter end() { return map_.end(); } + ConstIter cbegin() const { return map_.cbegin(); } + ConstIter cend() const { return map_.cend(); } + ConstIter begin() const { return cbegin(); } + ConstIter end() const { return cend(); } + + bool empty() const noexcept { return map_.empty(); } + size_t size() const noexcept { return map_.size(); } + + void clear() { map_.clear(); } + + std::pair insert(const V& value) + { + V copy { value }; + return insert(std::move(copy)); + } + + std::pair insert(V&& value) + { + return emplace(std::move(value)); + } + + template + std::pair emplace(Args&&... args) + { + std::pair res; + auto [map_iter, map_inserted] = map_.emplace(std::forward(args)..., 0); + res.first = map_iter; + res.second = map_inserted; + return res; + } + + Iter erase(Iter pos) + { + auto map_ret = map_.erase(pos.iter_); + return map_ret; + } + + Iter erase(ConstIter pos) + { + auto map_ret = map_.erase(pos.iter_); + return map_ret; + } + + Iter erase(ConstIter first, ConstIter last) + { + ConstIter iter; + for (iter = first; iter != last;) + { + iter = erase(iter); + } + return typename Inner::Iter(iter.iter_); + } + + Iter find(const V& value) + { + auto map_ret = map_.find(value); + return map_ret; + } + + ConstIter find(const V& value) const + { + auto map_ret = map_.find(value); + return map_ret; + } + + void rehash(size_t count) + { + map_.rehash(count); + } +}; + +extern template class HashSet; +extern template class HashSet; +extern template class HashSet; +extern template class HashSet; +extern template class HashSet; +extern template class HashSet; +extern template class HashSet; +extern template class HashSet; +extern template class HashSet; +extern template class HashSet; +extern template class HashSet; + +} // namespace srb2 + +#endif // SRB2_CORE_HASH_SET_HPP diff --git a/src/core/json.cpp b/src/core/json.cpp new file mode 100644 index 000000000..d123637e3 --- /dev/null +++ b/src/core/json.cpp @@ -0,0 +1,1809 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "json.hpp" + +#include +#include +#include +#include + +#include + +using namespace srb2; + +JsonError::JsonError(const std::string& what) : std::runtime_error(what) {} +JsonError::JsonError(const char* what) : std::runtime_error(what) {} +JsonError::JsonError(const JsonError&) noexcept = default; +JsonParseError::JsonParseError(const std::string& what) : JsonError(what) {} +JsonParseError::JsonParseError(const char* what) : JsonError(what) {} +JsonParseError::JsonParseError(const JsonParseError&) noexcept = default; + +JsonValue::JsonValue() + : type_(Type::kNull) +{} + +JsonValue::JsonValue(const JsonValue& r) + : type_(Type::kNull) +{ + *this = r; +} + +JsonValue::JsonValue(JsonValue&& r) noexcept + : type_(Type::kNull) +{ + *this = std::move(r); +} + +JsonValue& JsonValue::operator=(const JsonValue& r) +{ + switch (type_) + { + case Type::kString: + string_.~String(); + break; + case Type::kArray: + array_.~JsonArray(); + break; + case Type::kObject: + object_.~JsonObject(); + break; + default: + break; + } + type_ = r.type_; + switch (type_) + { + case Type::kNull: + break; + case Type::kBoolean: + boolean_ = r.boolean_; + break; + case Type::kNumber: + number_ = r.number_; + break; + case Type::kString: + new (&string_) String(r.string_); + break; + case Type::kArray: + new (&array_) JsonArray(r.array_); + break; + case Type::kObject: + new (&object_) JsonObject(r.object_); + break; + } + return *this; +} + +JsonValue& JsonValue::operator=(JsonValue&& r) noexcept +{ + switch (type_) + { + case Type::kString: + string_.~String(); + break; + case Type::kArray: + array_.~JsonArray(); + break; + case Type::kObject: + object_.~JsonObject(); + break; + default: + break; + } + type_ = r.type_; + switch (type_) + { + case Type::kBoolean: + boolean_ = r.boolean_; + break; + case Type::kNumber: + number_ = r.number_; + break; + case Type::kString: + new (&string_) String(std::move(r.string_)); + break; + case Type::kArray: + new (&array_) JsonArray(std::move(r.array_)); + break; + case Type::kObject: + new (&object_) JsonObject(std::move(r.object_)); + break; + default: + break; + } + switch (r.type_) + { + case Type::kString: + r.string_.~String(); + break; + case Type::kArray: + r.array_.~JsonArray(); + break; + case Type::kObject: + r.object_.~JsonObject(); + default: + break; + } + r.type_ = Type::kNull; + return *this; +} + +JsonValue::~JsonValue() +{ + switch (type_) + { + case Type::kString: + string_.~String(); + break; + case Type::kArray: + array_.~JsonArray(); + break; + case Type::kObject: + object_.~JsonObject(); + break; + default: + break; + } + type_ = Type::kNull; +} + +template <> bool JsonValue::get() const { return type_ == Type::kBoolean ? boolean_ : false; } +template <> int8_t JsonValue::get() const { return type_ == Type::kNumber ? number_ : 0; } +template <> int16_t JsonValue::get() const { return type_ == Type::kNumber ? number_ : 0; } +template <> int32_t JsonValue::get() const { return type_ == Type::kNumber ? number_ : 0; } +template <> int64_t JsonValue::get() const { return type_ == Type::kNumber ? number_ : 0; } +template <> uint8_t JsonValue::get() const { return type_ == Type::kNumber ? number_ : 0; } +template <> uint16_t JsonValue::get() const { return type_ == Type::kNumber ? (uint16_t)number_ : 0; } +template <> uint32_t JsonValue::get() const { return type_ == Type::kNumber ? (uint32_t)number_ : 0; } +template <> uint64_t JsonValue::get() const { return type_ == Type::kNumber ? (uint64_t)number_ : 0; } +template <> float JsonValue::get() const { return type_ == Type::kNumber ? number_ : 0; } +template <> double JsonValue::get() const { return type_ == Type::kNumber ? number_ : 0; } +template <> String JsonValue::get() const { return type_ == Type::kString ? string_ : String(); } +template <> std::string JsonValue::get() const { return type_ == Type::kString ? static_cast(string_) : std::string(); } +template <> std::string_view JsonValue::get() const { return type_ == Type::kString ? static_cast(string_) : std::string_view(); } +template <> JsonArray JsonValue::get() const { return type_ == Type::kArray ? array_ : JsonArray(); } +template <> JsonObject JsonValue::get() const { return type_ == Type::kObject ? object_ : JsonObject(); } +template <> JsonValue JsonValue::get() const { return *this; } + +JsonArray& JsonValue::as_array() +{ + if (!is_array()) throw JsonError("accessing non-array as array"); + return array_; +} + +const JsonArray& JsonValue::as_array() const +{ + if (!is_array()) throw JsonError("accessing non-array as array"); + return array_; +} + +JsonObject& JsonValue::as_object() +{ + if (!is_object()) throw JsonError("accessing non-object as object"); + return object_; +} + +const JsonObject& JsonValue::as_object() const +{ + if (!is_object()) throw JsonError("accessing non-array as object"); + return object_; +} + +JsonValue& JsonValue::at(uint32_t i) +{ + JsonArray& arr = as_array(); + return arr.at(i); +} + +const JsonValue& JsonValue::at(uint32_t i) const +{ + const JsonArray& arr = as_array(); + return arr.at(i); +} + +JsonValue& JsonValue::at(const char* key) +{ + JsonObject& obj = as_object(); + return obj.at(key); +} + +JsonValue& JsonValue::at(const String& key) +{ + JsonObject& obj = as_object(); + return obj.at(key); +} + +const JsonValue& JsonValue::at(const char* key) const +{ + const JsonObject& obj = as_object(); + return obj.at(key); +} + +const JsonValue& JsonValue::at(const String& key) const +{ + const JsonObject& obj = as_object(); + return obj.at(key); +} + +JsonValue& JsonValue::operator[](uint32_t i) +{ + if (is_null() && !is_array()) + { + *this = JsonArray(); + } + else if (!is_array()) + { + throw JsonError("indexing non-array as array"); + } + JsonArray& arr = as_array(); + return arr[i]; +} + +JsonValue& JsonValue::operator[](const char* key) +{ + if (is_null() && !is_object()) + { + *this = JsonObject(); + } + else if (!is_object()) + { + throw JsonError("indexing non-object as object"); + } + JsonObject& obj = as_object(); + return obj[key]; +} + +JsonValue& JsonValue::operator[](const String& key) +{ + if (is_null() && !is_object()) + { + *this = JsonObject(); + } + else if (!is_object()) + { + throw JsonError("indexing non-object as object"); + } + JsonObject& obj = as_object(); + return obj[key]; +} + +bool JsonValue::contains(const String& key) const +{ + return contains(key.c_str()); +} + +bool JsonValue::contains(const char* key) const +{ + if (!is_object()) + { + return false; + } + const JsonObject& obj = as_object(); + return obj.find(key) != obj.end(); +} + +void JsonValue::to_json(JsonValue& to) const +{ + to = *this; +} + +void JsonValue::from_json(const JsonValue& from) +{ + *this = from; +} + +String JsonValue::to_json_string() const +{ + String ret; + + switch (type_) + { + case Type::kNull: + ret = "null"; + break; + case Type::kBoolean: + ret = boolean_ ? "true" : "false"; + break; + case Type::kNumber: + if (std::isnan(number_) || std::isinf(number_)) + { + ret = "null"; + break; + } + ret = fmt::format("{}", number_); + break; + case Type::kString: + ret = "\""; + for (auto c : string_) + { + switch (c) + { + case '"': ret.append("\\\""); break; + case '\\': ret.append("\\\\"); break; + case '\b': ret.append("\\b"); break; + case '\f': ret.append("\\f"); break; + case '\n': ret.append("\\n"); break; + case '\r': ret.append("\\r"); break; + case '\t': ret.append("\\t"); break; + default: + if (c >= 0x00 && c <= 0x1f) + { + ret.append(fmt::format("\\u{:04x}", c)); + } + else + { + ret.push_back(c); + } + } + } + ret.append("\""); + break; + case Type::kArray: + { + ret = "["; + for (auto itr = array_.begin(); itr != array_.end(); itr++) + { + ret.append(JsonValue(*itr).to_json_string()); + if (std::next(itr) != array_.end()) + { + ret.append(","); + } + } + ret.append("]"); + break; + } + case Type::kObject: + { + ret = "{"; + for (auto itr = object_.begin(); itr != object_.end(); itr++) + { + ret.append(JsonValue(itr->first).to_json_string()); + ret.append(":"); + ret.append(JsonValue(itr->second).to_json_string()); + auto next = itr; + ++next; + if (next != object_.end()) + { + ret.append(","); + } + } + ret.append("}"); + return ret; + } + } + return ret; +} + +srb2::Vector JsonValue::to_ubjson() const +{ + srb2::Vector out; + + do_to_ubjson(out); + + return out; +} + +void JsonValue::value_to_ubjson(srb2::Vector& out) +{ + out.push_back(std::byte { 'Z' }); +} + +void JsonValue::value_to_ubjson(srb2::Vector& out, bool value) +{ + out.push_back(value ? std::byte { 'T' } : std::byte { 'F' }); +} + +void JsonValue::value_to_ubjson(srb2::Vector& out, double value) +{ + if (std::isnan(value) || std::isinf(value)) + { + out.push_back(std::byte { 'Z' }); + return; + } + + uint64_t num_as_int; + std::byte buf[8]; + out.push_back(std::byte { 'D' }); + std::memcpy(&num_as_int, &value, sizeof(double)); + buf[0] = (std::byte)((num_as_int & 0xFF00000000000000) >> 56); + buf[1] = (std::byte)((num_as_int & 0x00FF000000000000) >> 48); + buf[2] = (std::byte)((num_as_int & 0x0000FF0000000000) >> 40); + buf[3] = (std::byte)((num_as_int & 0x000000FF00000000) >> 32); + buf[4] = (std::byte)((num_as_int & 0x00000000FF000000) >> 24); + buf[5] = (std::byte)((num_as_int & 0x0000000000FF0000) >> 16); + buf[6] = (std::byte)((num_as_int & 0x000000000000FF00) >> 8); + buf[7] = (std::byte)((num_as_int & 0x00000000000000FF)); + for (int i = 0; i < 8; i++) + { + out.push_back(buf[i]); + } +} + +void JsonValue::value_to_ubjson(srb2::Vector& out, uint64_t value) +{ + + std::byte buf[8]; + int64_t string_len_i64; + int32_t string_len_32; + int16_t string_len_16; + uint8_t string_len_8; + if (value < std::numeric_limits().max()) + { + string_len_8 = value; + out.push_back(std::byte { 'U' }); + out.push_back((std::byte)(string_len_8)); + } + else if (value < std::numeric_limits().max()) + { + string_len_16 = value; + buf[0] = (std::byte)((string_len_16 & 0xFF00) >> 8); + buf[1] = (std::byte)((string_len_16 & 0x00FF) >> 0); + out.push_back(std::byte { 'I' }); + out.push_back(buf[0]); + out.push_back(buf[1]); + } + else if (value < std::numeric_limits().max()) + { + string_len_32 = value; + buf[0] = (std::byte)((string_len_32 & 0xFF000000) >> 24); + buf[1] = (std::byte)((string_len_32 & 0x00FF0000) >> 16); + buf[2] = (std::byte)((string_len_32 & 0x0000FF00) >> 8); + buf[3] = (std::byte)((string_len_32 & 0x000000FF)); + out.push_back(std::byte { 'l' }); + out.push_back(buf[0]); + out.push_back(buf[1]); + out.push_back(buf[2]); + out.push_back(buf[3]); + } + else if (value < std::numeric_limits().max()) + { + string_len_i64 = value; + buf[0] = (std::byte)((string_len_i64 & 0xFF00000000000000) >> 56); + buf[1] = (std::byte)((string_len_i64 & 0x00FF000000000000) >> 48); + buf[2] = (std::byte)((string_len_i64 & 0x0000FF0000000000) >> 40); + buf[3] = (std::byte)((string_len_i64 & 0x000000FF00000000) >> 32); + buf[4] = (std::byte)((string_len_i64 & 0x00000000FF000000) >> 24); + buf[5] = (std::byte)((string_len_i64 & 0x0000000000FF0000) >> 16); + buf[6] = (std::byte)((string_len_i64 & 0x000000000000FF00) >> 8); + buf[7] = (std::byte)((string_len_i64 & 0x00000000000000FF)); + out.push_back(std::byte { 'L' }); + out.push_back(buf[0]); + out.push_back(buf[1]); + out.push_back(buf[2]); + out.push_back(buf[3]); + out.push_back(buf[4]); + out.push_back(buf[5]); + out.push_back(buf[6]); + out.push_back(buf[7]); + } + else + { + throw JsonParseError("inexpressible integer"); + } +} + +void JsonValue::value_to_ubjson(srb2::Vector& out, const String& value, bool include_type) +{ + if (include_type) + { + out.push_back(std::byte { 'S' }); + } + + value_to_ubjson(out, static_cast(value.size())); + for (auto c : value) + { + out.push_back((std::byte)(c)); + } +} + +void JsonValue::value_to_ubjson(srb2::Vector& out, const JsonArray& value) +{ + out.push_back(std::byte { '[' }); + if (value.empty()) + { + out.push_back(std::byte { ']' }); + return; + } + + out.push_back(std::byte { '#' }); + value_to_ubjson(out, (uint64_t)value.size()); + for (auto& v : value) + { + v.do_to_ubjson(out); + } + + // Don't emit end because we included size prefix +} + +void JsonValue::value_to_ubjson(srb2::Vector& out, const JsonObject& value) +{ + out.push_back(std::byte { '{' }); + if (value.empty()) + { + out.push_back(std::byte { '}' }); + return; + } + + out.push_back(std::byte { '#' }); + value_to_ubjson(out, static_cast(value.size())); + for (auto& v : value) + { + value_to_ubjson(out, v.first, false); + v.second.do_to_ubjson(out); + } + + // Don't emit end because we included size prefix +} + +void JsonValue::do_to_ubjson(srb2::Vector& out) const +{ + switch (type_) + { + case Type::kNull: + value_to_ubjson(out); + break; + case Type::kBoolean: + value_to_ubjson(out, boolean_); + break; + case Type::kNumber: + value_to_ubjson(out, number_); + break; + case Type::kString: + value_to_ubjson(out, string_, true); + break; + case Type::kArray: + value_to_ubjson(out, array_); + break; + case Type::kObject: + value_to_ubjson(out, object_); + break; + default: + break; + } +} + +static uint8_t u8_from_ubjson(tcb::span& ubjson) +{ + if (ubjson.size() < 1) throw JsonParseError("insufficient data"); + uint8_t ret = std::to_integer(ubjson[0]); + ubjson = ubjson.subspan(1); + return ret; +} + +static int8_t i8_from_ubjson(tcb::span& ubjson) +{ + if (ubjson.size() < 1) throw JsonParseError("insufficient data"); + int8_t ret = std::to_integer(ubjson[0]); + ubjson = ubjson.subspan(1); + return ret; +} + +static int16_t i16_from_ubjson(tcb::span& ubjson) +{ + uint8_t b[2]; + uint16_t native; + int16_t ret; + if (ubjson.size() < 2) throw JsonParseError("insufficient data"); + b[0] = std::to_integer(ubjson[0]); + b[1] = std::to_integer(ubjson[1]); + native = b[0] << 8 | b[1]; + std::memcpy(&ret, &native, 2); + ubjson = ubjson.subspan(2); + return ret; +} + +static int32_t i32_from_ubjson(tcb::span& ubjson) +{ + uint8_t b[4]; + uint32_t native; + int32_t ret; + if (ubjson.size() < 4) throw JsonParseError("insufficient data"); + b[0] = std::to_integer(ubjson[0]); + b[1] = std::to_integer(ubjson[1]); + b[2] = std::to_integer(ubjson[2]); + b[3] = std::to_integer(ubjson[3]); + native = b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3]; + std::memcpy(&ret, &native, 4); + ubjson = ubjson.subspan(4); + return ret; +} + +static int64_t i64_from_ubjson(tcb::span& ubjson) +{ + uint64_t b[8]; + uint64_t native; + int64_t ret; + if (ubjson.size() < 8) throw JsonParseError("insufficient data"); + b[0] = std::to_integer(ubjson[0]); + b[1] = std::to_integer(ubjson[1]); + b[2] = std::to_integer(ubjson[2]); + b[3] = std::to_integer(ubjson[3]); + b[4] = std::to_integer(ubjson[4]); + b[5] = std::to_integer(ubjson[5]); + b[6] = std::to_integer(ubjson[6]); + b[7] = std::to_integer(ubjson[7]); + native = b[0] << 56 | b[1] << 48 | b[2] << 40 | b[3] << 32 | b[4] << 24 | b[5] << 16 | b[6] << 8 | b[7]; + std::memcpy(&ret, &native, 8); + ubjson = ubjson.subspan(8); + return ret; +} + +static float f32_from_ubjson(tcb::span& ubjson) +{ + uint8_t b[8]; + uint32_t native; + float ret; + if (ubjson.size() < 4) throw JsonParseError("insufficient data"); + b[0] = std::to_integer(ubjson[0]); + b[1] = std::to_integer(ubjson[1]); + b[2] = std::to_integer(ubjson[2]); + b[3] = std::to_integer(ubjson[3]); + native = b[0] << 24 | b[1] << 16 | b[2] << 8 | b[3]; + std::memcpy(&ret, &native, 4); + ubjson = ubjson.subspan(4); + return ret; +} + +static double f64_from_ubjson(tcb::span& ubjson) +{ + uint64_t b[8]; + uint64_t native; + double ret; + if (ubjson.size() < 8) throw JsonParseError("insufficient data"); + b[0] = std::to_integer(ubjson[0]); + b[1] = std::to_integer(ubjson[1]); + b[2] = std::to_integer(ubjson[2]); + b[3] = std::to_integer(ubjson[3]); + b[4] = std::to_integer(ubjson[4]); + b[5] = std::to_integer(ubjson[5]); + b[6] = std::to_integer(ubjson[6]); + b[7] = std::to_integer(ubjson[7]); + native = b[0] << 56 | b[1] << 48 | b[2] << 40 | b[3] << 32 | b[4] << 24 | b[5] << 16 | b[6] << 8 | b[7]; + std::memcpy(&ret, &native, 8); + ubjson = ubjson.subspan(8); + return ret; +} + +static uint64_t length_from_ubjson(tcb::span& ubjson) +{ + if (ubjson.size() < 1) throw JsonParseError("insufficient data"); + uint64_t size = 0; + switch ((char)(ubjson[0])) + { + case 'i': + ubjson = ubjson.subspan(1); + size = i8_from_ubjson(ubjson); + break; + case 'U': + ubjson = ubjson.subspan(1); + size = u8_from_ubjson(ubjson); + break; + case 'I': + ubjson = ubjson.subspan(1); + size = i16_from_ubjson(ubjson); + break; + case 'l': + ubjson = ubjson.subspan(1); + size = i32_from_ubjson(ubjson); + break; + case 'L': + ubjson = ubjson.subspan(1); + size = i64_from_ubjson(ubjson); + break; + default: + throw JsonParseError("illegal data length type"); + } + return size; +} + +static String string_from_ubjson(tcb::span& ubjson) +{ + uint64_t len = length_from_ubjson(ubjson); + if (len > std::numeric_limits().max()) + { + throw JsonParseError("unloadable string length"); + } + String ret; + for (size_t i = 0; i < len; i++) + { + + } + auto strdata = ubjson.subspan(0, (size_t)len); + ubjson = ubjson.subspan((size_t)len); + for (auto itr = strdata.begin(); itr != strdata.end(); itr++) + { + ret.push_back((char)(*itr)); + } + return ret; +} + +template +static void read_ubjson_array_elements(F f, JsonArray& arr, tcb::span& ubjson, uint64_t len) +{ + ubjson = ubjson.subspan(1); + for (uint64_t i = 0; i < len; i++) + { + arr.push_back((f)(ubjson)); + } +} + +template +static void read_ubjson_array_elements(F f, JsonArray& arr, tcb::span& ubjson, uint64_t len, int depth) +{ + ubjson = ubjson.subspan(1); + for (uint64_t i = 0; i < len; i++) + { + arr.push_back((f)(ubjson, depth)); + } +} + +static JsonValue do_from_ubjson(tcb::span& ubjson, int depth); +static JsonObject object_from_ubjson(tcb::span& ubjson, int depth); + +static JsonArray array_from_ubjson(tcb::span& ubjson, int depth) +{ + char typecode = 0; + if ((char)(ubjson[0]) == '$') + { + if (ubjson.size() < 2) throw JsonParseError("insufficient data"); + typecode = (char)(ubjson[1]); + ubjson = ubjson.subspan(2); + } + bool has_len = false; + uint64_t len = 0; + if (ubjson.size() < 1) throw JsonParseError("insufficient data"); + if ((char)(ubjson[0]) == '#') + { + ubjson = ubjson.subspan(1); + has_len = true; + len = length_from_ubjson(ubjson); + } + JsonArray ret; + if (has_len) + { + ret.reserve(len); + } + if (typecode != 0) + { + switch (typecode) + { + case 'i': + read_ubjson_array_elements(i8_from_ubjson, ret, ubjson, len); + break; + case 'U': + read_ubjson_array_elements(u8_from_ubjson, ret, ubjson, len); + break; + case 'I': + read_ubjson_array_elements(i16_from_ubjson, ret, ubjson, len); + break; + case 'l': + read_ubjson_array_elements(i32_from_ubjson, ret, ubjson, len); + break; + case 'L': + read_ubjson_array_elements(i64_from_ubjson, ret, ubjson, len); + break; + case 'd': + read_ubjson_array_elements(f32_from_ubjson, ret, ubjson, len); + break; + case 'D': + read_ubjson_array_elements(f64_from_ubjson, ret, ubjson, len); + break; + case 'S': + read_ubjson_array_elements(string_from_ubjson, ret, ubjson, len); + break; + case '[': + read_ubjson_array_elements(array_from_ubjson, ret, ubjson, len, depth + 1); + break; + case '{': + read_ubjson_array_elements(object_from_ubjson, ret, ubjson, len, depth + 1); + break; + default: + throw JsonParseError("invalid typecode for array"); + } + } + else + { + if (has_len) + { + for (uint64_t i = 0; i < len; i++) + { + JsonValue v = do_from_ubjson(ubjson, depth); + ret.push_back(std::move(v)); + } + } + else + { + if (ubjson.size() < 1) throw JsonParseError("insufficient data"); + while ((char)(ubjson[0]) != ']') + { + JsonValue v = do_from_ubjson(ubjson, depth); + ret.push_back(std::move(v)); + } + ubjson = ubjson.subspan(1); + } + } + return ret; +} + +template +static void read_ubjson_object_elements(F f, JsonObject& obj, tcb::span& ubjson, uint64_t len) +{ + ubjson = ubjson.subspan(1); + for (uint64_t i = 0; i < len; i++) + { + String key = string_from_ubjson(ubjson); + JsonValue value = (f)(ubjson); + obj[std::move(key)] = std::move(value); + } +} + +template +static void read_ubjson_object_elements(F f, JsonObject& obj, tcb::span& ubjson, uint64_t len, int depth) +{ + ubjson = ubjson.subspan(1); + for (uint64_t i = 0; i < len; i++) + { + String key = string_from_ubjson(ubjson); + JsonValue value = (f)(ubjson, depth); + obj[std::move(key)] = std::move(value); + } +} + +static JsonObject object_from_ubjson(tcb::span& ubjson, int depth) +{ + char typecode = 0; + if ((char)(ubjson[0]) == '$') + { + if (ubjson.size() < 2) throw JsonParseError("insufficient data"); + typecode = (char)(ubjson[1]); + ubjson = ubjson.subspan(2); + } + bool has_len = false; + uint64_t len = 0; + if (ubjson.size() < 1) throw JsonParseError("insufficient data"); + if ((char)(ubjson[0]) == '#') + { + ubjson = ubjson.subspan(1); + has_len = true; + len = length_from_ubjson(ubjson); + } + JsonObject ret; + if (has_len) + { + ret.rehash(len); + } + if (typecode != 0) + { + switch (typecode) + { + case 'i': + read_ubjson_object_elements(i8_from_ubjson, ret, ubjson, len); + break; + case 'U': + read_ubjson_object_elements(u8_from_ubjson, ret, ubjson, len); + break; + case 'I': + read_ubjson_object_elements(i16_from_ubjson, ret, ubjson, len); + break; + case 'l': + read_ubjson_object_elements(i32_from_ubjson, ret, ubjson, len); + break; + case 'L': + read_ubjson_object_elements(i64_from_ubjson, ret, ubjson, len); + break; + case 'd': + read_ubjson_object_elements(f32_from_ubjson, ret, ubjson, len); + break; + case 'D': + read_ubjson_object_elements(f64_from_ubjson, ret, ubjson, len); + break; + case 'S': + read_ubjson_object_elements(string_from_ubjson, ret, ubjson, len); + break; + case '[': + read_ubjson_object_elements(array_from_ubjson, ret, ubjson, len, depth + 1); + break; + case '{': + read_ubjson_object_elements(object_from_ubjson, ret, ubjson, len, depth + 1); + break; + default: + throw JsonParseError("invalid typecode for array"); + } + } + else + { + if (has_len) + { + for (uint64_t i = 0; i < len; i++) + { + String key = string_from_ubjson(ubjson); + JsonValue value = do_from_ubjson(ubjson, depth); + ret[std::move(key)] = std::move(value); + } + } + else + { + if (ubjson.size() < 1) throw JsonParseError("insufficient data"); + while ((char)(ubjson[0]) != '}') + { + String key = string_from_ubjson(ubjson); + JsonValue value = do_from_ubjson(ubjson, depth); + ret[std::move(key)] = std::move(value); + } + ubjson = ubjson.subspan(1); + } + } + return ret; +} + +static JsonValue do_from_ubjson(tcb::span& ubjson, int depth) +{ + if (depth > 1000) + { + throw JsonParseError("ubjson depth limit exceeded"); + } + + if (ubjson.empty()) + { + throw JsonParseError("empty ubjson payload"); + } + + char typecode = (char)(ubjson[0]); + switch (typecode) + { + case 'Z': + ubjson = ubjson.subspan(1); + return JsonValue(); + case 'F': + ubjson = ubjson.subspan(1); + return JsonValue(false); + case 'T': + ubjson = ubjson.subspan(1); + return JsonValue(true); + case 'i': + ubjson = ubjson.subspan(1); + return i8_from_ubjson(ubjson); + case 'U': + ubjson = ubjson.subspan(1); + return u8_from_ubjson(ubjson); + case 'I': + ubjson = ubjson.subspan(1); + return i16_from_ubjson(ubjson); + case 'l': + ubjson = ubjson.subspan(1); + return i32_from_ubjson(ubjson); + case 'L': + ubjson = ubjson.subspan(1); + return i64_from_ubjson(ubjson); + case 'd': + ubjson = ubjson.subspan(1); + return f32_from_ubjson(ubjson); + case 'D': + ubjson = ubjson.subspan(1); + return f64_from_ubjson(ubjson); + case 'S': + ubjson = ubjson.subspan(1); + return string_from_ubjson(ubjson); + case '[': + ubjson = ubjson.subspan(1); + return array_from_ubjson(ubjson, depth); + case '{': + ubjson = ubjson.subspan(1); + return object_from_ubjson(ubjson, depth); + default: + throw JsonParseError(fmt::format("unrecognized ubjson typecode 0x{:02x}", typecode)); + } +} + +JsonValue JsonValue::from_ubjson(tcb::span ubjson) +{ + return do_from_ubjson(ubjson, 0); +} + +namespace +{ +struct Token +{ + enum class Type + { + kEof, + kOpenCurly, + kCloseCurly, + kColon, + kOpenSquare, + kCloseSquare, + kComma, + kBoolean, + kString, + kNumber, + kNull + }; + Type type = Type::kEof; + std::string_view slice; +}; + +class Tokenizer +{ + std::string_view in_; + + void consume(size_t len); +public: + Tokenizer(const std::string_view& in) : in_(in) {} + Token peek(); + Token next(); +}; + +Token Tokenizer::peek() +{ + Token ret; + while (!in_.empty() && ret.type == Token::Type::kEof) + { + unsigned char next = in_[0]; + switch (next) + { + case ' ': + case '\n': + case '\r': + case '\t': + in_.remove_prefix(1); + break; + case 'n': + if (in_.size() < 4) throw JsonParseError("reached end of buffer parsing null"); + ret = Token { Token::Type::kNull, in_.substr(0, 4) }; + if (ret.slice != "null") throw JsonParseError("invalid null token"); + break; + case 'f': + if (in_.size() < 5) throw JsonParseError("reached end of buffer parsing false"); + ret = Token { Token::Type::kBoolean, in_.substr(0, 5) }; + if (ret.slice != "false") throw JsonParseError("invalid boolean token"); + break; + case 't': + if (in_.size() < 4) throw JsonParseError("reached end of buffer parsing true"); + ret = Token { Token::Type::kBoolean, in_.substr(0, 4) }; + if (ret.slice != "true") throw JsonParseError("invalid boolean token"); + break; + case '{': + ret = Token { Token::Type::kOpenCurly, in_.substr(0, 1) }; + break; + case '}': + ret = Token { Token::Type::kCloseCurly, in_.substr(0, 1) }; + break; + case '[': + ret = Token { Token::Type::kOpenSquare, in_.substr(0, 1) }; + break; + case ']': + ret = Token { Token::Type::kCloseSquare, in_.substr(0, 1) }; + break; + case ':': + ret = Token { Token::Type::kColon, in_.substr(0, 1) }; + break; + case ',': + ret = Token { Token::Type::kComma, in_.substr(0, 1) }; + break; + case '"': + { + bool skipnextquote = false; + size_t len; + for (len = 1; len < in_.size(); len++) + { + char c = in_[len]; + bool shouldbreak = false; + switch (c) + { + case '\r': + throw JsonParseError("illegal carriage return in string literal"); + case '\n': + throw JsonParseError("illegal line feed in string literal"); + case '\\': + skipnextquote = true; + break; + case '"': + if (skipnextquote) skipnextquote = false; + else shouldbreak = true; + break; + default: + if (skipnextquote) skipnextquote = false; + } + if (shouldbreak) break; + } + if (in_[len] != '"') throw JsonParseError("found unterminated string"); + ret = Token { Token::Type::kString, in_.substr(0, len + 1) }; + break; + } + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + size_t len; + bool firstiszero = next == '0'; + bool zeroseen = next == '0'; + bool integerseen = next >= '0' && next <= '9'; + bool periodseen = false; + bool periodlast = false; + bool exponentseen = false; + for (len = 1; len < in_.size(); len++) + { + char c = in_[len]; + bool shouldbreak = false; + switch (c) + { + default: + shouldbreak = true; + break; + case '.': + if (periodseen || exponentseen || (!periodseen && !zeroseen && !integerseen)) + throw JsonParseError("unexpected period in number token"); + periodseen = true; + periodlast = true; + break; + case '0': + if (firstiszero && len == 1) throw JsonParseError("more than 1 preceding 0"); + if ((next == '-' || next == '+') && len == 1) firstiszero = true; + zeroseen = true; + integerseen = true; + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (firstiszero && !periodseen && !exponentseen) throw JsonParseError("nonzero integral part of number preceded by 0"); + periodlast = false; + integerseen = true; + break; + case 'e': + case 'E': + if (exponentseen) throw JsonParseError("multiple exponent"); + exponentseen = true; + break; + case '+': + case '-': + if (!exponentseen && len != 0) throw JsonParseError("sign after number started"); + break; + } + if (shouldbreak) break; + } + if (periodlast) throw JsonParseError("real number without fractional part"); + ret = Token { Token::Type::kNumber, in_.substr(0, len) }; + break; + } + default: + throw JsonParseError(fmt::format("unexpected character {:c}", next)); + } + } + std::string copy { ret.slice }; + return ret; +} + +void Tokenizer::consume(size_t len) +{ + in_.remove_prefix(len); +} + +Token Tokenizer::next() +{ + Token peeked = peek(); + consume(peeked.slice.size()); + std::string p { peeked.slice }; + return peeked; +} + +} // namespace + +static JsonValue parse_value(Tokenizer& tokenizer, int depth); + +static JsonValue parse_number(const Token& token) +{ + std::string_view s { token.slice }; + if (s.empty()) throw JsonParseError("empty number token"); + while (s[0] == ' ' || s[0] == '\t' || s[0] == '\n' || s[0] == '\r') + { + s.remove_prefix(1); + } + + bool negative = false; + if (s[0] == '-') + { + negative = true; + s.remove_prefix(1); + } + else if (s[0] == '+') + { + negative = false; + s.remove_prefix(1); + } + + if (s.empty()) + { + throw JsonParseError("only sign present on number"); + } + + const char* integral_start = s.begin(); + const char* integral_end; + const char* decimal = nullptr; + while (!s.empty()) + { + if (s[0] == '.') + { + decimal = s.begin(); + integral_end = s.begin(); + s.remove_prefix(1); + break; + } + else if (s[0] < '0' || s[0] > '9') + { + integral_end = s.begin() - 1; + break; + } + integral_end = s.begin() + 1; + s.remove_prefix(1); + } + + const char* decimal_start = s.end(); + const char* decimal_end = s.end(); + const char* exponent_start = s.end(); + const char* exponent_end = s.end(); + bool should_have_exponent = false; + if (decimal != nullptr && (decimal + 1) < s.end()) + { + decimal_start = decimal + 1; + } + while (!s.empty()) + { + // ingest decimal + if (s[0] == 'E' || s[0] == 'e') + { + if (decimal_start != s.end()) decimal_end = s.begin(); + exponent_start = s.begin() + 1; + should_have_exponent = true; + s.remove_prefix(1); + break; + } + else if ((s[0] < '0' || s[0] > '9') && s[0] != '+' && s[0] != '-') + { + throw JsonParseError("invalid character after decimal"); + } + decimal_end = s.begin() + 1; + s.remove_prefix(1); + } + + bool exponent_negative = false; + + if (should_have_exponent) + { + if (s.empty()) + { + throw JsonParseError("exponent started but not specified"); + } + bool exponent_was_signed = false; + while (!s.empty()) + { + if (s[0] == '-') + { + if (exponent_was_signed) throw JsonParseError("multiple signs on exponent"); + exponent_negative = true; + exponent_start++; + exponent_was_signed = true; + s.remove_prefix(1); + continue; + } + else if (s[0] == '+') + { + if (exponent_was_signed) throw JsonParseError("multiple signs on exponent"); + exponent_start++; + exponent_was_signed = true; + s.remove_prefix(1); + continue; + } + + if (s[0] < '0' || s[0] > '9') + { + throw JsonParseError("invalid character after exponent"); + } + exponent_end = s.begin() + 1; + s.remove_prefix(1); + } + if ((exponent_end - exponent_start) == 0) + { + throw JsonParseError("exponent started but not specified"); + } + } + + std::string_view integral_view { integral_start, (size_t)(integral_end - integral_start) }; + std::string_view decimal_view { decimal_start, (size_t)(decimal_end - decimal_start) }; + std::string_view exponent_view { exponent_start, (size_t)(exponent_end - exponent_start) }; + + if (should_have_exponent && decimal_start != s.end() && decimal_view.empty()) + { + throw JsonParseError("exponent after decimal but no decimal value"); + } + if (should_have_exponent && exponent_view.empty()) + { + throw JsonParseError("no exponent despite e/E +/-"); + } + // if (!exponent_negative && exponent_view == "1") + // { + // throw JsonParseError("exponent of 1 not allowed"); + // } + + double number = 0.0; + uint64_t integral_int = 0; + for (auto i : integral_view) + { + integral_int = 10 * integral_int + (i - '0'); + } + double decimal_value = 0.0; + for (auto i : decimal_view) + { + decimal_value = (decimal_value / 10) + ((double)(i - '0') / 10); + } + uint64_t exponent_int = 0; + for (auto i : exponent_view) + { + exponent_int = 10 * exponent_int + (i - '0'); + } + if (negative) + { + integral_int *= -1; + } + if (exponent_negative) + { + exponent_int *= -1; + } + number = std::pow(10, exponent_int) * (double)integral_int + decimal_value; + + return JsonValue(number); +} + +static char hexconv(char c) +{ + switch (c) + { + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + c += 32; + break; + default: + break; + } + switch (c) + { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + case 'a': return 10; + case 'b': return 11; + case 'c': return 12; + case 'd': return 13; + case 'e': return 14; + case 'f': return 15; + default: throw JsonParseError("illegal unicode escape sequence character"); + } +} + +static constexpr bool is_surrogate(uint16_t code) +{ + return (0xf800 & code) == 0xd800; +} + +static constexpr bool is_high_surrogate(uint16_t code) +{ + return (code & 0xfc00) == 0xd800; +} + +static constexpr bool is_low_surrogate(uint16_t code) +{ + return (code & 0xfc00) == 0xdc00; +} + +static constexpr uint32_t merge_surrogate_pair(uint16_t low, uint16_t high) +{ + return ((high - 0xd800) << 10) + ((low - 0xdc00) + 0x10000); +} + +static JsonValue parse_string(const Token& token) +{ + std::string_view read { token.slice.substr(1, token.slice.size() - 2) }; + String conv; + for (auto itr = read.begin(); itr != read.end();) + { + int c = *itr & 0xFF; + switch (c) + { + case '\\': + { + // Reverse solidus indicates escape sequence + if (std::next(itr) == read.end()) throw JsonParseError("unterminated string escape sequence"); + char control = *++itr; + switch (control) + { + case '"': + case '\\': + case '/': + conv.push_back(control); + ++itr; + break; + case 'b': + conv.push_back('\b'); + ++itr; + break; + case 'f': + conv.push_back('\f'); + ++itr; + break; + case 'n': + conv.push_back('\n'); + ++itr; + break; + case 'r': + conv.push_back('\r'); + ++itr; + break; + case 't': + conv.push_back('\t'); + ++itr; + break; + case 'x': + { + if (std::distance(itr, read.end()) < 3) throw JsonParseError("unterminated hex char sequence"); + char hex[2]; + hex[0] = *++itr; + hex[1] = *++itr; + char byte = (hexconv(hex[0]) << 4) | hexconv(hex[1]); + if (byte < 0x20) throw JsonParseError("bad escaped control code"); + conv.push_back(byte); + ++itr; + break; + } + case 'u': + { + if (std::distance(itr, read.end()) < 5) throw JsonParseError("unterminated utf16 code sequence"); + // Next 4 characters are a hex sequence representing a UTF-16 surrogate + // We have to do silly things to make this work correctly + char hex[4]; + hex[0] = *++itr; + hex[1] = *++itr; + hex[2] = *++itr; + hex[3] = *++itr; + if (hex[0] == -1 || hex[1] == -1 || hex[2] == -1 || hex[3] == -1) + throw JsonParseError("invalid unicode escape"); + char byte[2]; + byte[0] = (hexconv(hex[0]) << 4) | hexconv(hex[1]); + byte[1] = (hexconv(hex[2]) << 4) | hexconv(hex[3]); + uint16_t utf16 = hexconv(hex[0]) << 12 | hexconv(hex[1]) << 8 | hexconv(hex[2]) << 4 | hexconv(hex[3]); + bool valid_codepoint = false; + uint32_t codepoint = 0; + if (is_low_surrogate(utf16)) + { + // invalid surrogate pair -- high must precede low + conv.push_back('\\'); + conv.push_back('u'); + conv.push_back(hex[0]); + conv.push_back(hex[1]); + conv.push_back(hex[2]); + conv.push_back(hex[3]); + } + else if (is_high_surrogate(utf16)) + { + if (std::distance(itr, read.end()) < 6) + { + // invalid surrogate pair -- high must precede low + conv += "\\u"; + } + else + { + // potentially valid... + if ( + *itr == '\\' && + *(itr + 1) == 'u' && + (hex[0] = *(itr + 2)) != -1 && + (hex[1] = *(itr + 3)) != -1 && + (hex[2] = *(itr + 4)) != -1 && + (hex[3] = *(itr + 5)) != -1 + ) + { + uint16_t utf16_2 = hexconv(hex[0]) << 12 | hexconv(hex[1]) << 8 | hexconv(hex[2]) << 4 | hexconv(hex[3]); + if (is_low_surrogate(utf16_2)) + { + itr += 6; + codepoint = merge_surrogate_pair(utf16_2, utf16); + valid_codepoint = true; + } + else + { + itr += 2; + conv += "\\u"; + } + } + else + { + conv += "\\u"; + } + } + } + else + { + // non-surrogate represents unicode codepoint + codepoint = utf16; + valid_codepoint = true; + } + + if (valid_codepoint) + { + char encoded[4]; + int len; + // encode codepoint as UTF-8 + if (codepoint <= 0x7f) + { + encoded[0] = codepoint; + len = 1; + } + else if (codepoint <= 0x7ff) + { + encoded[0] = 0300 | (codepoint >> 6); + encoded[1] = 0200 | (codepoint & 077); + len = 2; + } + else if (codepoint <= 0xffff) + { + if (is_surrogate(codepoint)) + { + codepoint = 0xfffd; + } + encoded[0] = 0340 | (codepoint >> 2); + encoded[1] = 0200 | ((codepoint >> 6) & 077); + encoded[2] = 0200 | (codepoint & 077); + len = 3; + } + else if (~(codepoint >> 18) & 007) + { + encoded[0] = 0360 | (codepoint >> 18); + encoded[1] = 0200 | ((codepoint >> 12) & 077); + encoded[2] = 0200 | ((c >> 6) & 077); + encoded[3] = 0200 | (c & 077); + len = 4; + } + else + { + encoded[0] = 0xef; + encoded[1] = 0xbf; + encoded[2] = 0xbd; + len = 3; + } + conv.append(encoded, len); + } + + ++itr; + break; + } + default: + throw JsonParseError("invalid string escape control code"); + } + break; + } + default: + if (c < 0x20) throw JsonParseError("unescaped control code"); + conv.push_back(c); + ++itr; + break; + } + } + return JsonValue(conv); +} + +static JsonValue parse_object(Tokenizer& tokenizer, int depth) +{ + JsonObject obj; + bool done = false; + if (tokenizer.peek().type == Token::Type::kCloseCurly) + { + tokenizer.next(); + return obj; + } + while (!done) + { + Token key_token = tokenizer.next(); + if (key_token.type != Token::Type::kString) throw JsonParseError("unexpected token; expected string (for key)"); + String key_string = parse_string(key_token).get(); + Token colon = tokenizer.next(); + if (colon.type != Token::Type::kColon) throw JsonParseError("unexpected token; expected colon (after key)"); + JsonValue value = parse_value(tokenizer, depth + 1); + Token last = tokenizer.next(); + if (last.type == Token::Type::kCloseCurly) done = true; + else if (last.type != Token::Type::kComma) throw JsonParseError("unexpected token; expected comma (after value)"); + + obj.insert_or_assign(std::move(key_string), std::move(value)); + } + return obj; +} + +static JsonArray parse_array(Tokenizer& tokenizer, int depth) +{ + JsonArray arr; + bool done = false; + if (tokenizer.peek().type == Token::Type::kCloseSquare) + { + tokenizer.next(); + return arr; + } + while (!done) + { + JsonValue value = parse_value(tokenizer, depth + 1); + Token last = tokenizer.next(); + if (last.type == Token::Type::kCloseSquare) done = true; + else if (last.type != Token::Type::kComma) throw JsonParseError("unexpected token; expected comma (after value)"); + arr.push_back(value); + } + return arr; +} + +constexpr const int kMaxDepth = 1000; + +static JsonValue parse_value(Tokenizer& tokenizer, int depth) +{ + using Type = Token::Type; + JsonValue ret; + Token token = tokenizer.next(); + + if (depth >= kMaxDepth) + { + throw JsonParseError("parse depth limit exceeded"); + } + + switch (token.type) + { + case Type::kNull: + ret = JsonValue(); + break; + case Type::kBoolean: + if (token.slice == "true") ret = JsonValue(true); + else if (token.slice == "false") ret = JsonValue(false); + else throw JsonParseError("illegal boolean token"); + break; + case Type::kNumber: + ret = parse_number(token); + break; + case Type::kString: + ret = parse_string(token); + break; + case Type::kOpenCurly: + ret = parse_object(tokenizer, depth); + break; + case Type::kOpenSquare: + ret = parse_array(tokenizer, depth); + break; + case Type::kEof: + throw JsonParseError("reached EOF before parsing value"); + default: + throw JsonParseError("unexpected token"); + } + + return ret; +} + +JsonValue JsonValue::from_json_string(const String& str) +{ + JsonValue ret; + Tokenizer tokenizer { str }; + ret = parse_value(tokenizer, 0); + + Token peek = tokenizer.peek(); + if (peek.type != Token::Type::kEof) throw JsonParseError("unexpected token after expression"); + return ret; +} + +JsonValue::JsonValue(bool value) : type_(Type::kBoolean), boolean_(value) {} +JsonValue::JsonValue(float value) : type_(Type::kNumber), number_((float)value) {} +JsonValue::JsonValue(double value) : type_(Type::kNumber), number_((double)value) {} +JsonValue::JsonValue(int8_t value) : type_(Type::kNumber), number_((int8_t)value) {} +JsonValue::JsonValue(int16_t value) : type_(Type::kNumber), number_((int16_t)value) {} +JsonValue::JsonValue(int32_t value) : type_(Type::kNumber), number_((int32_t)value) {} +JsonValue::JsonValue(int64_t value) : type_(Type::kNumber), number_((int64_t)value) {} +JsonValue::JsonValue(uint8_t value) : type_(Type::kNumber), number_((uint8_t)value) {} +JsonValue::JsonValue(uint16_t value) : type_(Type::kNumber), number_((double)value) {} +JsonValue::JsonValue(uint32_t value) : type_(Type::kNumber), number_((double)value) {} +JsonValue::JsonValue(uint64_t value) : type_(Type::kNumber), number_((double)value) {} +JsonValue::JsonValue(const String& value) : type_(Type::kString), string_(value) {} +JsonValue::JsonValue(String&& value) : type_(Type::kString), string_(std::move(value)) {} +JsonValue::JsonValue(const JsonArray& value) : type_(Type::kArray), array_(value) {} +JsonValue::JsonValue(JsonArray&& value) : type_(Type::kArray), array_(std::move(value)) {} +JsonValue::JsonValue(const JsonObject& value) : type_(Type::kObject), object_(value) {} +JsonValue::JsonValue(JsonObject&& value) : type_(Type::kObject), object_(std::move(value)) {} + +bool JsonValue::operator==(const JsonValue& rhs) const +{ + if (type_ != rhs.type_) + { + return false; + } + switch (type_) + { + case Type::kNull: + return true; + case Type::kBoolean: + return boolean_ == rhs.boolean_; + case Type::kNumber: + return number_ == rhs.number_; + case Type::kString: + return string_ == rhs.string_; + case Type::kArray: + return array_ == rhs.array_; + case Type::kObject: + return object_ == rhs.object_; + } + return false; +} + +void srb2::to_json(JsonValue& to, bool value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, int8_t value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, int16_t value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, int32_t value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, int64_t value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, uint8_t value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, uint16_t value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, uint32_t value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, uint64_t value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, float value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, double value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, const String& value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, const JsonArray& value) { to = JsonValue(value); } +void srb2::to_json(JsonValue& to, const JsonObject& value) { to = JsonValue(value); } + +void srb2::to_json(JsonValue& to, const std::string& v) +{ + to = JsonValue(static_cast(v)); +} + +void srb2::from_json(const JsonValue& from, bool& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, int8_t& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, int16_t& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, int32_t& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, int64_t& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, uint8_t& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, uint16_t& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, uint32_t& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, uint64_t& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, float& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, double& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, String& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, JsonArray& value) { value = from.get(); } +void srb2::from_json(const JsonValue& from, JsonObject& value) { value = from.get(); } + +void srb2::from_json(const JsonValue& from, std::string& to) +{ + to = static_cast(from.get()); +} + +template class srb2::Vector; +template class srb2::HashMap; diff --git a/src/core/json.hpp b/src/core/json.hpp new file mode 100644 index 000000000..6492c50bb --- /dev/null +++ b/src/core/json.hpp @@ -0,0 +1,533 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef SRB2_CORE_JSON_HPP +#define SRB2_CORE_JSON_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "hash_map.hpp" +#include "string.h" +#include "vector.hpp" + +#include "json_macro.inl" + +namespace srb2 +{ + +class JsonValue; + +using JsonArray = Vector; +using JsonObject = HashMap; + +class JsonError : public std::runtime_error +{ +public: + JsonError(const std::string& what); + JsonError(const char* what); + JsonError(const JsonError&) noexcept; +}; + +class JsonParseError : public JsonError +{ +public: + JsonParseError(const std::string& what); + JsonParseError(const char* what); + JsonParseError(const JsonParseError&) noexcept; +}; + +class JsonValue +{ +public: + enum class Type : int + { + kNull, + kBoolean, + kNumber, + kString, + kArray, + kObject + }; + +private: + Type type_; + union { + struct {} dummy; + bool boolean_; + double number_; + String string_; + JsonArray array_; + JsonObject object_; + }; + + static void value_to_ubjson(srb2::Vector& out); + static void value_to_ubjson(srb2::Vector& out, bool value); + static void value_to_ubjson(srb2::Vector& out, double value); + static void value_to_ubjson(srb2::Vector& out, uint64_t value); + static void value_to_ubjson(srb2::Vector& out, const String& value, bool include_type); + static void value_to_ubjson(srb2::Vector& out, const JsonArray& value); + static void value_to_ubjson(srb2::Vector& out, const JsonObject& value); + + void do_to_ubjson(srb2::Vector& out) const; + +public: + JsonValue(); + JsonValue(const JsonValue&); + JsonValue(JsonValue&&) noexcept; + ~JsonValue(); + JsonValue& operator=(const JsonValue&); + JsonValue& operator=(JsonValue&&) noexcept; + + JsonValue(const String& value); + JsonValue(String&& value); + JsonValue(const JsonArray& value); + JsonValue(JsonArray&& value); + JsonValue(const JsonObject& value); + JsonValue(JsonObject&& value); + JsonValue(float value); + JsonValue(double value); + JsonValue(int8_t value); + JsonValue(int16_t value); + JsonValue(int32_t value); + JsonValue(int64_t value); + JsonValue(uint8_t value); + JsonValue(uint16_t value); + JsonValue(uint32_t value); + JsonValue(uint64_t value); + JsonValue(bool value); + + static JsonValue array() { return JsonValue(JsonArray()); } + static JsonValue object() { return JsonValue(JsonObject()); } + + bool operator==(const JsonValue& rhs) const; + bool operator!=(const JsonValue& rhs) const { return !(*this == rhs); } + + void to_json(JsonValue& to) const; + void from_json(const JsonValue& from); + String to_json_string() const; + static JsonValue from_json_string(const String& str); + srb2::Vector to_ubjson() const; + static JsonValue from_ubjson(tcb::span ubjson); + constexpr Type type() const noexcept { return type_; } + + template V get() const; + + constexpr bool is_null() const noexcept { return type_ == Type::kNull; } + constexpr bool is_boolean() const noexcept { return type_ == Type::kBoolean; } + constexpr bool is_number() const noexcept { return type_ == Type::kNumber; } + constexpr bool is_string() const noexcept { return type_ == Type::kString; } + constexpr bool is_array() const noexcept { return type_ == Type::kArray; } + constexpr bool is_object() const noexcept { return type_ == Type::kObject; } + JsonArray& as_array(); + JsonObject& as_object(); + const JsonArray& as_array() const; + const JsonObject& as_object() const; + + JsonValue& at(uint32_t i); + const JsonValue& at(uint32_t i) const; + JsonValue& at(const char* key); + JsonValue& at(const String& key); + const JsonValue& at(const char* key) const; + const JsonValue& at(const String& key) const; + + template + V value(const char* key, const V& def) const; + template + V value(const String& key, const V& def) const; + template + V value(const char* key, V&& def) const; + template + V value(const String& key, V&& def) const; + + bool contains(const char* key) const; + bool contains(const String& key) const; + + JsonValue& operator[](uint32_t i); + JsonValue& operator[](const char* key); + JsonValue& operator[](const String& key); +}; + +void to_json(JsonValue& to, bool value); +void to_json(JsonValue& to, int8_t value); +void to_json(JsonValue& to, int16_t value); +void to_json(JsonValue& to, int32_t value); +void to_json(JsonValue& to, int64_t value); +void to_json(JsonValue& to, uint8_t value); +void to_json(JsonValue& to, uint16_t value); +void to_json(JsonValue& to, uint32_t value); +void to_json(JsonValue& to, uint64_t value); +void to_json(JsonValue& to, float value); +void to_json(JsonValue& to, double value); +void to_json(JsonValue& to, const String& value); +void to_json(JsonValue& to, const JsonArray& value); +void to_json(JsonValue& to, const JsonObject& value); + +void from_json(const JsonValue& to, bool& value); +void from_json(const JsonValue& to, int8_t& value); +void from_json(const JsonValue& to, int16_t& value); +void from_json(const JsonValue& to, int32_t& value); +void from_json(const JsonValue& to, int64_t& value); +void from_json(const JsonValue& to, uint8_t& value); +void from_json(const JsonValue& to, uint16_t& value); +void from_json(const JsonValue& to, uint32_t& value); +void from_json(const JsonValue& to, uint64_t& value); +void from_json(const JsonValue& to, float& value); +void from_json(const JsonValue& to, double& value); +void from_json(const JsonValue& to, String& value); +void from_json(const JsonValue& to, JsonArray& value); +void from_json(const JsonValue& to, JsonObject& value); + +template +inline void to_json(JsonValue& to, const std::array& v) +{ + JsonArray arr; + for (auto itr = v.begin(); itr != v.end(); itr++) + { + JsonValue conv; + to_json(conv, *itr); + arr.push_back(conv); + } + to = JsonValue(std::move(arr)); +} + +template +inline void from_json(const JsonValue& to, std::array& v) +{ + if (!to.is_array()) + { + throw JsonError("json value must be an array"); + } + const JsonArray& arr = to.as_array(); + size_t si = 0; + for (auto& i : arr) + { + if (si >= v.size()) + { + break; + } + + T conv; + from_json(i, conv); + v[si] = std::move(conv); + si++; + } +} + +template +inline void to_json(JsonValue& to, const std::vector& v) +{ + JsonArray arr; + for (auto itr = v.begin(); itr != v.end(); itr++) + { + JsonValue conv; + to_json(conv, *itr); + arr.push_back(conv); + } + to = JsonValue(std::move(arr)); +} + +template +inline void from_json(const JsonValue& to, std::vector& v) +{ + if (!to.is_array()) + { + throw JsonError("json value must be an array"); + } + v.clear(); + const JsonArray& arr = to.as_array(); + for (auto& i : arr) + { + T conv; + from_json(i, conv); + v.push_back(std::move(conv)); + } +} + +template +inline void to_json(JsonValue& to, const srb2::Vector& v) +{ + JsonArray arr; + for (auto itr = v.begin(); itr != v.end(); itr++) + { + JsonValue conv; + to_json(conv, *itr); + arr.push_back(conv); + } + to = JsonValue(std::move(arr)); +} + +template +inline void from_json(const JsonValue& to, srb2::Vector& v) +{ + if (!to.is_array()) + { + throw JsonError("json value must be an array"); + } + v.clear(); + const JsonArray& arr = to.as_array(); + for (auto& i : arr) + { + T conv; + from_json(i, conv); + v.push_back(std::move(conv)); + } +} + +template +inline void to_json(JsonValue& to, const std::unordered_map& v) +{ + JsonObject obj; + for (auto itr = v.begin(); itr != v.end(); itr++) + { + to_json(obj[itr->first], itr->second); + } + to = JsonValue(std::move(obj)); +} + +template +inline void from_json(const JsonValue& to, std::unordered_map& v) +{ + if (!to.is_object()) + { + throw JsonError("json value must be an object"); + } + v.clear(); + const JsonObject& obj = to.as_object(); + for (auto itr = obj.begin(); itr != obj.end(); itr++) + { + V conv; + from_json(itr->second, conv); + v[itr->first] = std::move(conv); + } +} + +template +inline void to_json(JsonValue& to, const srb2::HashMap& v) +{ + JsonObject obj; + for (auto itr = v.begin(); itr != v.end(); itr++) + { + to_json(obj[itr->first], itr->second); + } + to = JsonValue(std::move(obj)); +} + +template +inline void from_json(const JsonValue& to, srb2::HashMap& v) +{ + if (!to.is_object()) + { + throw JsonError("json value must be an object"); + } + v.clear(); + const JsonObject& obj = to.as_object(); + for (auto itr = obj.begin(); itr != obj.end(); itr++) + { + V conv; + from_json(itr->second, conv); + v[itr->first] = std::move(conv); + } +} + +void from_json(const JsonValue& to, std::string& v); +void to_json(JsonValue& to, const std::string& v); + +// template +// inline void to_json(JsonValue& to, const srb2::OAHashMap& v) +// { +// JsonObject obj; +// for (auto itr = v.begin(); itr != v.end(); itr++) +// { +// to_json(obj[itr->first], itr->second); +// } +// to = JsonValue(std::move(obj)); +// } + +// template +// inline void from_json(const JsonValue& to, srb2::OAHashMap& v) +// { +// if (!to.is_object()) +// { +// throw JsonError("json value must be an object"); +// } +// v.clear(); +// const JsonObject& obj = to.as_object(); +// for (auto itr = obj.begin(); itr != obj.end(); itr++) +// { +// V conv; +// from_json(itr->second, conv); +// v[itr->first] = std::move(conv); +// } +// } +// + +template +inline V JsonValue::get() const +{ + V v; + from_json(*this, v); + return v; +} + +template +inline V JsonValue::value(const char* key, const V& def) const +{ + V copy { def }; + return value(key, std::move(copy)); +} + +template +inline V JsonValue::value(const String& key, const V& def) const +{ + return value(key.c_str(), def); +} + +template +inline V JsonValue::value(const char* key, V&& def) const +{ + const JsonObject& obj = as_object(); + auto itr = obj.find(key); + if (itr != obj.end()) + { + return itr->second.get(); + } + return def; +} + +template <> bool JsonValue::get() const; +template <> int8_t JsonValue::get() const; +template <> int16_t JsonValue::get() const; +template <> int32_t JsonValue::get() const; +template <> int64_t JsonValue::get() const; +template <> uint8_t JsonValue::get() const; +template <> uint16_t JsonValue::get() const; +template <> uint32_t JsonValue::get() const; +template <> uint64_t JsonValue::get() const; +template <> float JsonValue::get() const; +template <> double JsonValue::get() const; +template <> String JsonValue::get() const; +template <> std::string JsonValue::get() const; +template <> std::string_view JsonValue::get() const; +template <> JsonArray JsonValue::get() const; +template <> JsonObject JsonValue::get() const; +template <> JsonValue JsonValue::get() const; + +inline bool operator==(const JsonValue& lhs, bool rhs) +{ + if (!lhs.is_boolean()) + { + return false; + } + return lhs.get() == rhs; +} + +inline bool operator==(const JsonValue& lhs, int64_t rhs) +{ + if (!lhs.is_number()) + { + return false; + } + return lhs.get() == rhs; +} + +inline bool operator==(const JsonValue& lhs, uint64_t rhs) +{ + if (!lhs.is_number()) + { + return false; + } + return lhs.get() == rhs; +} + +inline bool operator==(const JsonValue& lhs, double rhs) +{ + if (!lhs.is_number()) + { + return false; + } + return lhs.get() == rhs; +} + +inline bool operator==(const JsonValue& lhs, std::string_view rhs) +{ + if (!lhs.is_string()) + { + return false; + } + return lhs.get() == rhs; +} + +inline bool operator==(const JsonValue& lhs, const JsonArray& rhs) +{ + if (!lhs.is_array()) + { + return false; + } + return lhs.as_array() == rhs; +} + +inline bool operator==(const JsonValue& lhs, const JsonObject& rhs) +{ + if (!lhs.is_object()) + { + return false; + } + return lhs.as_object() == rhs; +} + +inline bool operator!=(const JsonValue& lhs, bool rhs) +{ + return !(lhs == rhs); +} + +inline bool operator!=(const JsonValue& lhs, int64_t rhs) +{ + return !(lhs == rhs); +} + +inline bool operator!=(const JsonValue& lhs, uint64_t rhs) +{ + return !(lhs == rhs); +} + +inline bool operator!=(const JsonValue& lhs, double rhs) +{ + return !(lhs == rhs); +} + +inline bool operator!=(const JsonValue& lhs, std::string_view rhs) +{ + return !(lhs == rhs); +} + +inline bool operator!=(const JsonValue& lhs, const JsonArray& rhs) +{ + return !(lhs == rhs); +} + +inline bool operator!=(const JsonValue& lhs, const JsonObject& rhs) +{ + return !(lhs == rhs); +} + +extern template class Vector; +extern template class HashMap; + +} // namespace srb2 + +#endif // SRB2_CORE_JSON_HPP diff --git a/src/core/json_macro.inl b/src/core/json_macro.inl new file mode 100644 index 000000000..ce8b01db5 --- /dev/null +++ b/src/core/json_macro.inl @@ -0,0 +1,185 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ +// | | |__ | | | | | | version 3.11.3 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann +// SPDX-FileCopyrightText: 2016-2021 Evan Nemerson +// SPDX-License-Identifier: MIT + +// file: macro_scope.hpp + +// This file is derived from nlohmman json's codegen macros and thus is provided under its license. + +#define SRB2_JSON_EXPAND( x ) x +#define SRB2_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME +#define SRB2_JSON_PASTE(...) SRB2_JSON_EXPAND(SRB2_JSON_GET_MACRO(__VA_ARGS__, \ + SRB2_JSON_PASTE64, \ + SRB2_JSON_PASTE63, \ + SRB2_JSON_PASTE62, \ + SRB2_JSON_PASTE61, \ + SRB2_JSON_PASTE60, \ + SRB2_JSON_PASTE59, \ + SRB2_JSON_PASTE58, \ + SRB2_JSON_PASTE57, \ + SRB2_JSON_PASTE56, \ + SRB2_JSON_PASTE55, \ + SRB2_JSON_PASTE54, \ + SRB2_JSON_PASTE53, \ + SRB2_JSON_PASTE52, \ + SRB2_JSON_PASTE51, \ + SRB2_JSON_PASTE50, \ + SRB2_JSON_PASTE49, \ + SRB2_JSON_PASTE48, \ + SRB2_JSON_PASTE47, \ + SRB2_JSON_PASTE46, \ + SRB2_JSON_PASTE45, \ + SRB2_JSON_PASTE44, \ + SRB2_JSON_PASTE43, \ + SRB2_JSON_PASTE42, \ + SRB2_JSON_PASTE41, \ + SRB2_JSON_PASTE40, \ + SRB2_JSON_PASTE39, \ + SRB2_JSON_PASTE38, \ + SRB2_JSON_PASTE37, \ + SRB2_JSON_PASTE36, \ + SRB2_JSON_PASTE35, \ + SRB2_JSON_PASTE34, \ + SRB2_JSON_PASTE33, \ + SRB2_JSON_PASTE32, \ + SRB2_JSON_PASTE31, \ + SRB2_JSON_PASTE30, \ + SRB2_JSON_PASTE29, \ + SRB2_JSON_PASTE28, \ + SRB2_JSON_PASTE27, \ + SRB2_JSON_PASTE26, \ + SRB2_JSON_PASTE25, \ + SRB2_JSON_PASTE24, \ + SRB2_JSON_PASTE23, \ + SRB2_JSON_PASTE22, \ + SRB2_JSON_PASTE21, \ + SRB2_JSON_PASTE20, \ + SRB2_JSON_PASTE19, \ + SRB2_JSON_PASTE18, \ + SRB2_JSON_PASTE17, \ + SRB2_JSON_PASTE16, \ + SRB2_JSON_PASTE15, \ + SRB2_JSON_PASTE14, \ + SRB2_JSON_PASTE13, \ + SRB2_JSON_PASTE12, \ + SRB2_JSON_PASTE11, \ + SRB2_JSON_PASTE10, \ + SRB2_JSON_PASTE9, \ + SRB2_JSON_PASTE8, \ + SRB2_JSON_PASTE7, \ + SRB2_JSON_PASTE6, \ + SRB2_JSON_PASTE5, \ + SRB2_JSON_PASTE4, \ + SRB2_JSON_PASTE3, \ + SRB2_JSON_PASTE2, \ + SRB2_JSON_PASTE1)(__VA_ARGS__)) +#define SRB2_JSON_PASTE2(func, v1) func(v1) +#define SRB2_JSON_PASTE3(func, v1, v2) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE2(func, v2) +#define SRB2_JSON_PASTE4(func, v1, v2, v3) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE3(func, v2, v3) +#define SRB2_JSON_PASTE5(func, v1, v2, v3, v4) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE4(func, v2, v3, v4) +#define SRB2_JSON_PASTE6(func, v1, v2, v3, v4, v5) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE5(func, v2, v3, v4, v5) +#define SRB2_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE6(func, v2, v3, v4, v5, v6) +#define SRB2_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) +#define SRB2_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) +#define SRB2_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) +#define SRB2_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) +#define SRB2_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) +#define SRB2_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) +#define SRB2_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) +#define SRB2_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) +#define SRB2_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) +#define SRB2_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) +#define SRB2_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) +#define SRB2_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) +#define SRB2_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) +#define SRB2_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) +#define SRB2_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) +#define SRB2_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) +#define SRB2_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) +#define SRB2_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) +#define SRB2_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) +#define SRB2_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) +#define SRB2_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) +#define SRB2_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) +#define SRB2_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) +#define SRB2_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) +#define SRB2_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) +#define SRB2_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) +#define SRB2_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) +#define SRB2_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) +#define SRB2_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) +#define SRB2_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) +#define SRB2_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) +#define SRB2_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) +#define SRB2_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) +#define SRB2_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) +#define SRB2_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) +#define SRB2_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) +#define SRB2_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) +#define SRB2_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) +#define SRB2_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) +#define SRB2_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) +#define SRB2_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) +#define SRB2_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) +#define SRB2_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) +#define SRB2_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) +#define SRB2_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) +#define SRB2_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) +#define SRB2_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) +#define SRB2_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) +#define SRB2_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) +#define SRB2_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) +#define SRB2_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) +#define SRB2_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) +#define SRB2_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) +#define SRB2_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) +#define SRB2_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) +#define SRB2_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) +#define SRB2_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) SRB2_JSON_PASTE2(func, v1) SRB2_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) + +#define SRB2_JSON_TO(v1) to_json(srb2_json_j.as_object()[#v1], srb2_json_t.v1); +#define SRB2_JSON_FROM(v1) srb2_json_t.v1 = srb2_json_j.as_object().at(#v1); +#define SRB2_JSON_FROM_WITH_DEFAULT(v1) if (srb2_json_j.as_object().find(#v1) != srb2_json_j.as_object().end()) \ + { \ + from_json(srb2_json_j.as_object().find(#v1)->second, srb2_json_t.v1); \ + } \ + else \ + { \ + srb2_json_t.v1 = srb2_json_default_obj.v1; \ + } + +/*! +@brief macro +@def SRB2_JSON_DEFINE_TYPE_INTRUSIVE +@since version 3.9.0 +*/ +#define SRB2_JSON_DEFINE_TYPE_INTRUSIVE(Type, ...) \ + friend void to_json(srb2::JsonValue& srb2_json_j, const Type& srb2_json_t) { srb2_json_j = srb2::JsonObject(); SRB2_JSON_EXPAND(SRB2_JSON_PASTE(SRB2_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const srb2::JsonValue& srb2_json_j, Type& srb2_json_t) { SRB2_JSON_EXPAND(SRB2_JSON_PASTE(SRB2_JSON_FROM, __VA_ARGS__)) } + +#define SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + friend void to_json(srb2::JsonValue& srb2_json_j, const Type& srb2_json_t) { srb2_json_j = srb2::JsonObject(); SRB2_JSON_EXPAND(SRB2_JSON_PASTE(SRB2_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const srb2::JsonValue& srb2_json_j, Type& srb2_json_t) { const Type srb2_json_default_obj{}; SRB2_JSON_EXPAND(SRB2_JSON_PASTE(SRB2_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } + +/*! +@brief macro +@def SRB2_JSON_DEFINE_TYPE_NON_INTRUSIVE +@since version 3.9.0 +*/ +#define SRB2_JSON_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ + inline void to_json(srb2::JsonValue& srb2_json_j, const Type& srb2_json_t) { SRB2_JSON_EXPAND(SRB2_JSON_PASTE(SRB2_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const srb2::JsonValue& srb2_json_j, Type& srb2_json_t) { SRB2_JSON_EXPAND(SRB2_JSON_PASTE(SRB2_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def SRB2_JSON_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT +@since version 3.11.0 +*/ +#define SRB2_JSON_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(Type, ...) \ + inline void to_json(srb2::JsonValue& srb2_json_j, const Type& srb2_json_t) { SRB2_JSON_EXPAND(SRB2_JSON_PASTE(SRB2_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const srb2::JsonValue& srb2_json_j, Type& srb2_json_t) { const Type srb2_json_default_obj{}; SRB2_JSON_EXPAND(SRB2_JSON_PASTE(SRB2_JSON_FROM_WITH_DEFAULT, __VA_ARGS__)) } diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 73898fd65..b6e0387e9 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -10,11 +10,13 @@ #include "memory.h" -#include -#include +#include +#include "../cxxutil.hpp" #include "../z_zone.h" +using namespace srb2; + namespace { @@ -58,6 +60,81 @@ void LinearMemory::reset() noexcept } // namespace +PoolAllocator::~PoolAllocator() +{ + release(); +} + +constexpr static size_t nearest_multiple(size_t v, size_t divisor) +{ + return (v + (divisor - 1)) & ~(divisor - 1); +} + +PoolAllocator::ChunkFooter* PoolAllocator::allocate_chunk() +{ + uint8_t* chunk = (uint8_t*)Z_Malloc(nearest_multiple(blocks_ * block_size_, alignof(ChunkFooter)) + sizeof(ChunkFooter), tag_, nullptr); + ChunkFooter* footer = (ChunkFooter*)(chunk + (blocks_ * block_size_)); + footer->next = nullptr; + footer->start = (void*)chunk; + for (size_t i = 0; i < blocks_; i++) + { + FreeBlock* cur = (FreeBlock*)(chunk + (i * block_size_)); + FreeBlock* next = (FreeBlock*)(chunk + ((i + 1) * block_size_)); + cur->next = next; + } + ((FreeBlock*)(chunk + ((blocks_ - 1) * block_size_)))->next = nullptr; + return footer; +} + +void* PoolAllocator::allocate() +{ + if (first_chunk_ == nullptr) + { + SRB2_ASSERT(head_ == nullptr); + + // No chunks allocated yet + first_chunk_ = allocate_chunk(); + head_ = (FreeBlock*)first_chunk_->start; + } + + if (head_->next == nullptr) + { + // Current chunk will be full; allocate another at the end of the list + ChunkFooter* last_chunk = first_chunk_; + while (last_chunk->next != nullptr) + { + last_chunk = last_chunk->next; + } + ChunkFooter* new_chunk = allocate_chunk(); + last_chunk->next = new_chunk; + head_->next = (FreeBlock*)new_chunk->start; + } + + FreeBlock* ret = head_; + head_ = head_->next; + return ret; +} + +void PoolAllocator::deallocate(void* p) +{ + FreeBlock* block = reinterpret_cast(p); + block->next = head_; + head_ = block; +} + +void PoolAllocator::release() +{ + ChunkFooter* next = nullptr; + for (ChunkFooter* i = first_chunk_; i != nullptr; i = next) + { + next = i->next; + Z_Free(i->start); + } + + first_chunk_ = nullptr; + head_ = nullptr; +} + static LinearMemory g_frame_memory {4 * 1024 * 1024}; void* Z_Frame_Alloc(size_t size) diff --git a/src/core/memory.h b/src/core/memory.h index 3d75143f2..83cd84d79 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,6 +14,58 @@ #include #ifdef __cplusplus + +#include + +namespace srb2 +{ + +/// Pool allocator; manages bulk allocations of same-size block. If the pool is full, +/// allocations will fail by returning nullptr. +class PoolAllocator final +{ + struct FreeBlock + { + FreeBlock* next; + }; + struct ChunkFooter + { + ChunkFooter* next; + void* start; + }; + ChunkFooter* first_chunk_; + FreeBlock* head_; + size_t block_size_; + size_t blocks_; + int32_t tag_; + + ChunkFooter* allocate_chunk(); + +public: + constexpr PoolAllocator(size_t block_size, size_t blocks, int32_t tag) + : first_chunk_(nullptr) + , head_(nullptr) + , block_size_(block_size) + , blocks_(blocks) + , tag_(tag) + {} + PoolAllocator(const PoolAllocator&) = delete; + PoolAllocator(PoolAllocator&&) noexcept = default; + ~PoolAllocator(); + + PoolAllocator& operator=(const PoolAllocator&) = delete; + PoolAllocator& operator=(PoolAllocator&&) noexcept = default; + + void* allocate(); + void deallocate(void* p); + constexpr size_t block_size() const noexcept { return block_size_; }; + + void release(); +}; + +} // namespace srb2 + + extern "C" { #endif // __cpluspplus diff --git a/src/core/static_vec.hpp b/src/core/static_vec.hpp index dc81e8692..f31f17939 100644 --- a/src/core/static_vec.hpp +++ b/src/core/static_vec.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -238,11 +238,12 @@ bool operator!=(const srb2::StaticVec& lhs, const srb2::StaticVec& template struct std::hash> { - uint64_t operator()(const srb2::StaticVec& input) const + size_t operator()(const srb2::StaticVec& input) const { - constexpr const uint64_t prime {0x00000100000001B3}; - std::size_t ret {0xcbf29ce484222325}; + constexpr size_t prime = sizeof(size_t) == 8 ? 0x00000100000001B3 : 0x01000193; + constexpr size_t basis = sizeof(size_t) == 8 ? 0xcbf29ce484222325 : 0x811c9dc5; + size_t ret = basis; for (auto itr = input.begin(); itr != input.end(); itr++) { ret = (ret * prime) ^ std::hash(*itr); diff --git a/src/core/string.cpp b/src/core/string.cpp new file mode 100644 index 000000000..2bd0bd24f --- /dev/null +++ b/src/core/string.cpp @@ -0,0 +1,1125 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "string.h" +#include "fmt/format.h" + +#include +#include +#include +#include +#include + +namespace srb2 +{ + +String::String(const String&) = default; +String::String(String&&) noexcept = default; +String::~String() = default; +String& String::operator=(const String&) = default; +String& String::operator=(String&&) noexcept = default; + +String::String(const char* rhs) : String(std::string_view { rhs }) +{} + +String::String(const char* rhs, size_t len) : String(std::string_view { rhs, len }) +{} + +String::String(const std::string& rhs) : String(std::string_view { rhs }) +{} + +String::String(std::string_view view) : String() +{ + append(view); +} + +String::operator std::string() const +{ + std::string_view view = *this; + return std::string(view); +} + +String::operator std::string_view() const +{ + return std::string_view((const char*)data(), size()); +} + +uint32_t String::size() const noexcept +{ + if (data_.empty()) + { + return 0; + } + return data_.size() - 1; +} + +static const char* kEmptyString = ""; + +const char* String::c_str() const +{ + if (data_.empty()) + { + return kEmptyString; + } + return reinterpret_cast(data_.data()); +} + +void String::reserve(size_type capacity) +{ + if (capacity == 0) + { + data_.reserve(0); + return; + } + data_.reserve(capacity + 1); +} + +uint8_t* String::begin() noexcept +{ + if (data_.empty()) + { + return nullptr; + } + return data(); +} + +uint8_t* String::end() noexcept +{ + if (data_.empty()) + { + return nullptr; + } + return data() + size(); +} + +const uint8_t* String::cbegin() const noexcept +{ + if (data_.empty()) + { + return nullptr; + } + return data(); +} + +const uint8_t* String::cend() const noexcept +{ + if (data_.empty()) + { + return nullptr; + } + return data() + size(); +} + +uint8_t& String::at(size_type i) +{ + if (i >= size()) + { + throw std::out_of_range("string byte index out of bounds"); + } + return data_.at(i); +} + +const uint8_t& String::at(size_type i) const +{ + if (i >= size()) + { + throw std::out_of_range("string byte index out of bounds"); + } + return data_.at(i); +} + +String& String::insert(size_type index, size_type count, uint8_t ch) +{ + if (index > size()) + { + throw std::out_of_range("string byte index out of bounds"); + } + data_.insert(data_.begin() + index, count, ch); + return *this; +} + +String& String::insert(size_type index, const char* s) +{ + return insert(index, s, (size_type)std::strlen(s)); +} + +String& String::insert(size_type index, const char* s, size_type count) +{ + if (index > size()) + { + throw std::out_of_range("string byte index out of bounds"); + } + if (!empty()) + { + // remove null byte + data_.pop_back(); + } + data_.insert(data_.begin() + index, s, s + count); + if (data_.size() > 0) + { + data_.push_back(0); + } + return *this; +} + +String& String::insert(size_type index, std::string_view str) +{ + return insert(index, str.begin(), (size_type)str.size()); +} + +String& String::insert(size_type index, std::string_view str, size_t s_index, size_t count) +{ + if (s_index > str.size()) + { + throw std::out_of_range("s_index > str.size()"); + } + return insert(index, str.substr(s_index, std::max(str.size() - s_index, count))); +} + +String::iterator String::insert(const_iterator pos, uint8_t ch) +{ + if (pos < cbegin() || pos > cend()) + { + throw std::out_of_range("insert iterator out of bounds"); + } + return data_.insert(pos, ch); +} + +String::iterator String::insert(const_iterator pos, size_type count, uint8_t ch) +{ + if (pos < cbegin() || pos > cend()) + { + throw std::out_of_range("insert iterator out of bounds"); + } + if (!empty()) + { + data_.pop_back(); + } + for (size_type i = 0; i < count; i++) + { + data_.insert(pos, ch); + } + if (data_.size() > 0) + { + data_.push_back(0); + } + return const_cast(pos); +} + +String& String::erase(size_type index, size_type count) +{ + if (index + count >= size()) + { + throw std::out_of_range("string byte index out of bounds"); + } + const_iterator first = begin() + index; + const_iterator last = first + count; + data_.erase(first, last); + if (data_.size() == 1) + { + data_.pop_back(); + } + return *this; +} + +String::iterator String::erase(const_iterator position) +{ + return data_.erase(position); +} + +String::iterator String::erase(const_iterator first, const_iterator last) +{ + return data_.erase(first, last); +} + +void String::push_back(uint8_t v) +{ + if (data_.empty()) + { + data_.push_back(v); + data_.push_back(0); + return; + } + data_[data_.size() - 1] = v; + data_.push_back(0); +} + +void String::pop_back() +{ + data_.pop_back(); + if (data_.size() == 1) + { + data_.pop_back(); + } + else + { + data_[data_.size() - 1] = 0; + } +} + +String& String::append(size_type count, uint8_t ch) +{ + if (count == 0) + { + return *this; + } + + if (!data_.empty()) + { + data_.pop_back(); + } + + for (size_type i = 0; i < count; i++) + { + data_.push_back(ch); + } + data_.push_back(0); + return *this; +} + +String& String::append(const char* s, size_type count) +{ + insert(size(), s, count); + return *this; +} + +String& String::append(const char* s) +{ + insert(size(), s); + return *this; +} + +String& String::append(std::string_view str) +{ + insert(size(), str); + return *this; +} + +String& String::append(std::string_view str, size_type pos, size_type count) +{ + insert(size(), str, pos, count); + return *this; +} + +String& String::operator+=(std::string_view r) +{ + insert(size(), r); + return *this; +} + +String& String::operator+=(const char* r) +{ + insert(size(), r); + return *this; +} + +String& String::operator+=(uint8_t r) +{ + push_back(r); + return *this; +} + +String& String::operator+=(std::initializer_list r) +{ + append(r.begin(), r.end()); + return *this; +} + +String& String::replace(size_type pos, size_type count, std::string_view str) +{ + return replace(pos, count, str, 0, str.size()); +} + +String& String::replace(const_iterator first, const_iterator last, std::string_view str) +{ + if (first < begin() || last > end() || first + str.size() > end()) + { + throw std::out_of_range("string replacement range out of bounds"); + } + size_type index = first - data_.data(); + size_type count = last - first; + + return replace(index, count, str); +} + +String& String::replace(size_type pos, size_type count, std::string_view str, size_t pos2, size_t count2) +{ + if (pos >= size()) + { + throw std::out_of_range("string replacement range out of bounds"); + } + if (pos2 >= str.size()) + { + throw std::out_of_range("string replacement string_view range out of bounds"); + } + erase(pos, count); + insert(pos, str, pos2, count2); + + return *this; +} + +String& String::replace(size_type pos, size_type count, const char* cstr, size_type count2) +{ + size_t len = std::strlen(cstr); + return replace(pos, count, std::string_view(cstr, len), count2); +} + +String& String::replace(const_iterator first, const_iterator last, const char* cstr, size_type count2) +{ + size_type index = first - data_.data(); + size_type count = last - first; + + return replace(index, count, cstr, count2); +} + +String& String::replace(size_type pos, size_type count, const char* cstr) +{ + size_t len = std::strlen(cstr); + return replace(pos, count, std::string_view(cstr, len)); +} + +String& String::replace(const_iterator first, const_iterator last, const char* cstr) +{ + size_type index = first - data_.data(); + size_type count = last - first; + + return replace(index, count, cstr); +} + +String& String::replace(const_iterator first, const_iterator last, uint8_t ch) +{ + if (first < begin() || last > end()) + { + throw std::out_of_range("string iterators out of range"); + } + for (; first != last; first++) + { + *const_cast(first) = ch; + } + return *this; +} + +String& String::replace(const_iterator first, const_iterator last, std::initializer_list ilist) +{ + return replace(first, last, ilist.begin(), ilist.end()); +} + +String::size_type String::copy(uint8_t* dest, size_type count, size_type pos) const +{ + if (pos > size()) + { + throw std::out_of_range("string byte index out of bounds"); + } + size_type copied = 0; + for (size_type i = 0; i < count && (i + pos) < size(); i++) + { + dest[i] = data_[i + pos]; + copied += 1; + } + return copied; +} + +String::size_type String::copy(char* dest, size_type count, size_type pos) const +{ + if (pos > size()) + { + throw std::out_of_range("string byte index out of bounds"); + } + size_type copied = 0; + for (size_type i = 0; i < count && (i + pos) < size(); i++) + { + dest[i] = data_[i + pos]; + copied += 1; + } + return copied; +} + +void String::resize(size_type count) +{ + if (count == 0) + { + data_.clear(); + return; + } + data_.resize(count + 1); + data_[count] = 0; +} + +void String::resize(size_type count, uint8_t ch) +{ + if (count == 0) + { + data_.clear(); + return; + } + data_.resize(count + 1, ch); + data_[count] = 0; +} + +void String::swap(String& other) noexcept +{ + std::swap(this->data_, other.data_); +} + +String::size_type String::find(const String& str, size_type pos) const +{ + return find(static_cast(str), pos); +} + +String::size_type String::find(std::string_view str, size_type pos) const +{ + if (size() == 0) + { + return npos; + } + + for (size_type i = pos; i < size(); i++) + { + bool found = true; + for (size_t j = 0; j < str.size() && found; j++) + { + if (i + j >= size() || data_[i + j] != str[j]) + { + found = false; + } + } + if (found) + { + return i; + } + } + return npos; +} + +String::size_type String::find(const char* s, size_type pos, size_t count) const +{ + return find(std::string_view(s, count), pos); +} + +String::size_type String::find(const char* s, size_type pos) const +{ + size_t len = std::strlen(s); + return find(std::string_view(s, len), pos); +} + +String::size_type String::find(uint8_t ch, size_type pos) const +{ + for (size_type i = pos; i < size(); i++) + { + if (data_[i] == ch) + { + return i; + } + } + return npos; +} + +String::size_type String::rfind(const String& str, size_type pos) const +{ + return rfind(static_cast(str), pos); +} + +String::size_type String::rfind(std::string_view str, size_type pos) const +{ + if (str.empty()) + { + return std::min(pos, size()); + } + if (size() == 0) + { + return npos; + } + + for (size_type i = std::min(pos, size()); i >= 0; i--) + { + bool found = true; + for (size_t j = 0; j < str.size() && found; j++) + { + if (i + j >= size() || data_[i + j] != str[j]) + { + found = false; + } + } + if (found) + { + return i; + } + } + return npos; +} + +String::size_type String::rfind(const char* s, size_type pos, size_type count) const +{ + return rfind(std::string_view(s, count), pos); +} + +String::size_type String::rfind(const char* s, size_type pos) const +{ + size_t len = std::strlen(s); + return rfind(std::string_view(s, len), pos); +} + +String::size_type String::rfind(uint8_t ch, size_type pos) const +{ + if (empty()) + { + return npos; + } + + for (size_type i = std::min(pos, size()); i >= 0; i--) + { + if (data_[i] == ch) + { + return i; + } + } + + return npos; +} + +int String::compare(std::string_view str) const noexcept +{ + std::string_view self = *this; + return self.compare(str); +} + +int String::compare(const char* s) const +{ + std::string_view self = *this; + std::string_view that { s, std::strlen(s) }; + return self.compare(that); +} + +String String::substr(size_type pos, size_type count) const +{ + String ret; + if (pos >= size()) + { + throw std::out_of_range("string byte index invalid"); + } + size_type start = pos; + size_type end = std::min(pos + count, size() - pos); + ret.reserve(end - start); + for (size_type i = start; i < end; i++) + { + ret.push_back(data_[i]); + } + return ret; +} + +bool String::valid_utf8() const noexcept +{ + for (auto itr = decode_begin(); itr != decode_end(); itr++) + { + if (!itr.valid()) + { + return false; + } + } + return true; +} + +Vector String::to_utf16() const +{ + return ::srb2::to_utf16(static_cast(*this)); +} + +Vector to_utf16(std::string_view utf8) +{ + Vector ret; + for (auto itr = Utf8Iter::begin(utf8); itr != Utf8Iter::end(utf8); itr++) + { + uint32_t codepoint = *itr; + if (codepoint < 0x10000) + { + ret.push_back(static_cast(codepoint)); + continue; + } + // high surrogate + ret.push_back(static_cast( + (((codepoint - 0x10000) & 0b11111111110000000000) >> 10) + 0xD800 + )); + // low surrogate + ret.push_back(static_cast( + (((codepoint - 0x10000) & 0b1111111111)) + 0xDC00 + )); + } + return ret; +} + +Vector to_utf32(std::string_view utf8) +{ + Vector ret; + for (auto itr = Utf8Iter::begin(utf8); itr != Utf8Iter::end(utf8); itr++) + { + ret.push_back(itr.codepoint()); + } + return ret; +} + +StaticVec to_utf8(uint32_t codepoint) +{ + StaticVec enc; + if (codepoint < 0x80) + { + enc.push_back(static_cast(codepoint)); + } + else if (codepoint >= 0x80 && codepoint < 0x800) + { + enc.push_back(((codepoint >> 6) & 0b11111) + 0xC0); + enc.push_back((codepoint & 0b111111) + 0x80); + } + else if (codepoint >= 0x800 && codepoint < 0x10000) + { + enc.push_back(((codepoint >> 12) & 0b1111) + 0xE0); + enc.push_back(((codepoint >> 6) & 0b111111) + 0x80); + enc.push_back((codepoint & 0b111111) + 0x80); + } + else if (codepoint >= 0x10000 && codepoint < 0x110000) + { + enc.push_back(((codepoint >> 18) & 0b111) + 0xF0); + enc.push_back(((codepoint >> 12) & 0b111111) + 0x80); + enc.push_back(((codepoint >> 6) & 0b111111) + 0x80); + enc.push_back((codepoint & 0b111111) + 0x80); + } + else + { + // replacement char due to invalid codepoint + enc = to_utf8(0xFFFD); + } + return enc; +} + +String to_utf8(std::u32string_view utf32view) +{ + return to_utf8(utf32view.begin(), utf32view.end()); +} + +String operator+(const String& lhs, const String& rhs) +{ + String ret; + ret.append(lhs); + ret.append(rhs); + return ret; +} + +String operator+(const String& lhs, const char* rhs) +{ + String ret; + size_t len = std::strlen(rhs); + ret.append(lhs); + ret.append(std::string_view(rhs, len)); + return ret; +} + +String operator+(const String& lhs, uint8_t rhs) +{ + String ret; + ret.append(lhs); + ret.push_back(rhs); + return ret; +} + +String operator+(const String& lhs, std::string_view view) +{ + String ret; + ret.append(lhs); + ret.append(view); + return ret; +} + +bool operator==(const String& lhs, const String& rhs) +{ + return lhs.compare(rhs) == 0; +} + +bool operator==(const String& lhs, const char* rhs) +{ + return lhs.compare(rhs) == 0; +} + +// bool operator==(const String& lhs, std::string_view rhs) +// { +// return lhs.compare(rhs) == 0; +// } + +bool operator!=(const String& lhs, const String& rhs) +{ + return !(lhs == rhs); +} + +bool operator!=(const String& lhs, const char* rhs) +{ + return !(lhs == rhs); +} + +// bool operator!=(const String& lhs, std::string_view rhs) +// { +// return !(lhs == rhs); +// } + +bool operator<(const String& lhs, const String& rhs) +{ + return lhs.compare(rhs) < 0; +} + +bool operator<(const String& lhs, const char* rhs) +{ + return lhs.compare(rhs) < 0; +} + +// bool operator<(const String& lhs, std::string_view rhs) +// { +// return lhs.compare(rhs) < 0; +// } + +bool operator<=(const String& lhs, const String& rhs) +{ + return lhs.compare(rhs) <= 0; +} + +bool operator<=(const String& lhs, const char* rhs) +{ + return lhs.compare(rhs) <= 0; +} + +// bool operator<=(const String& lhs, std::string_view rhs) +// { +// return lhs.compare(rhs) <= 0; +// } + +bool operator>(const String& lhs, const String& rhs) +{ + return lhs.compare(rhs) > 0; +} + +bool operator>(const String& lhs, const char* rhs) +{ + return lhs.compare(rhs) > 0; +} + +// bool operator>(const String& lhs, std::string_view rhs) +// { +// return lhs.compare(rhs) > 0; +// } + +bool operator>=(const String& lhs, const String& rhs) +{ + return lhs.compare(rhs) >= 0; +} + +bool operator>=(const String& lhs, const char* rhs) +{ + return lhs.compare(rhs) >= 0; +} + +// bool operator>=(const String& lhs, std::string_view rhs) +// { +// return lhs.compare(rhs) >= 0; +// } + +static constexpr bool is_utf8_byte(uint8_t b) +{ + return b != 0xC0 && b != 0xC1 && b < 0xF5; +} + +static constexpr bool is_utf8_continuation(uint8_t b) +{ + return b >= 0x80 && b < 0xC0; +} + +uint32_t Utf8Iter::do_codepoint() const +{ + uint8_t b[4]; + uint8_t s; + bool v = true; + b[0] = s_[i_]; + if (b[0] < 0x80) s = 1; + else if (b[0] >= 0x80 && b[0] < 0xC0) + { + // invalid, first byte continuation + s = 1; + v = false; + } + else if (b[0] >= 0xC0 && b[0] < 0xE0) + { + // 2 byte + if (s_.size() - i_ < 2) + { + // invalid, truncated + s = 1; + v = false; + goto decode; + } + + b[1] = s_[i_ + 1]; + + if (!is_utf8_continuation(b[1])) + { + // invalid, not a continuation + s = 1; + v = false; + goto decode; + } + + s = 2; + } + else if (b[0] >= 0xE0 && b[0] < 0xF0) + { + // 3 byte + if (s_.size() - i_ < 2) + { + // invalid, truncated + s = 1; + v = false; + goto decode; + } + if (s_.size() - i_ < 3) + { + // invalid, truncated + s = 2; + v = false; + goto decode; + } + + b[1] = s_[i_ + 1]; + b[2] = s_[i_ + 2]; + + if (!is_utf8_continuation(b[1])) + { + // invalid, not a continuation + s = 1; + v = false; + goto decode; + } + + if (!is_utf8_continuation(b[2])) + { + // invalid, not a continuation + s = 2; + v = false; + goto decode; + } + + s = 3; + } + else if (b[0] >= 0xF0 && b[0] < 0xF5) + { + // 4 byte + if (s_.size() - i_ < 2) + { + // invalid, truncated + s = 1; + v = false; + goto decode; + } + if (s_.size() - i_ < 3) + { + // invalid, truncated + s = 2; + v = false; + goto decode; + } + if (s_.size() - i_ < 4) + { + // invalid, truncated + s = 3; + v = false; + goto decode; + } + + b[1] = s_[i_ + 1]; + b[2] = s_[i_ + 2]; + b[3] = s_[i_ + 3]; + + if (!is_utf8_continuation(b[1])) + { + // invalid, not a continuation + s = 1; + v = false; + goto decode; + } + + if (!is_utf8_continuation(b[2])) + { + // invalid, not a continuation + s = 2; + v = false; + goto decode; + } + + if (!is_utf8_continuation(b[3])) + { + // invalid, not a continuation + s = 3; + v = false; + goto decode; + } + + s = 4; + } + else + { + // invalid + s = 1; + v = false; + } + +decode: + // bit 29 indicates unparseable (immediately invalid, replacement char U+FFFD) + // bit 30-31 indicates byte size (0-3) + if (v == false) return 0xFFFD + ((s - 1) << 30) + (1 << 29); + + switch (s) + { + default: + case 1: return b[0] & 0x7f; + case 2: return (b[1] & 0x3f) + ((b[0] & 0x1f) << 6) + (1 << 30); + case 3: return (b[2] & 0x3f) + ((b[1] & 0x3f) << 6) + ((b[0] & 0x0f) << 12) + (2 << 30); + case 4: return (b[3] & 0x3f) + ((b[2] & 0x3f) << 6) + ((b[1] & 0x3f) << 12) + ((b[2] & 0x7) << 18) + (3 << 30); + } +} + +uint32_t Utf8Iter::codepoint() const +{ + uint32_t c = do_codepoint(); + uint32_t ret = c & 0x001fffff; + uint8_t s = c >> 30; + + // overlong encodings are still invalid and should be replaced, + // even if bit 29 is unset + switch (s) + { + default: + case 0: return ret >= (2 << 8) ? 0xFFFD : ret; + case 1: return ret >= (2 << 12) ? 0xFFFD : ret; + case 2: return ret >= (2 << 17) ? 0xFFFD : ret; + case 3: return ret; + } +} + +bool Utf8Iter::valid() const +{ + uint32_t c = do_codepoint(); + uint32_t ret = c & 0x001fffff; + if ((c >> 29) & 1) return false; + uint8_t s = c >> 30; + + switch (s) + { + default: + case 0: return ret >= (2 << 8) ? false : true; + case 1: return ret >= (2 << 12) ? false : true; + case 2: return ret >= (2 << 17) ? false : true; + case 3: return true; + } +} + +uint8_t Utf8Iter::size() const +{ + uint32_t c = do_codepoint(); + uint8_t s = (c >> 30); + return s + 1; +} + +static constexpr bool utf16_is_low_surrogate(uint16_t word) +{ + return word >= 0xDC00 && word < 0xDFFF; +} + +static constexpr bool utf16_is_high_surrogate(uint16_t word) +{ + return word >= 0xD800 && word < 0xDBFF; +} + +static constexpr bool utf16_is_surrogate(uint16_t word) +{ + return utf16_is_high_surrogate(word) || utf16_is_low_surrogate(word); +} + +uint32_t Utf16Iter::do_codepoint() const +{ + uint16_t words[2]; + words[0] = s_[i_]; + if (!utf16_is_high_surrogate(words[0])) + { + // unpaired low surrogates allowed as-is for windows compatibility + return words[0]; + } + if (s_.size() - i_ < 2) + { + // unpaired high surrogates allowed as-is for windows compatibility + return words[0]; + } + words[1] = s_[i_ + 1]; + return ((words[1] - 0xDC00) & 0x3FF) + + ((words[0] - 0xD800) & 0x3FF) + + 0x10000; +} + +uint32_t Utf16Iter::codepoint() const +{ + uint32_t c = do_codepoint(); + uint32_t ret = c & 0x001fffff; + + return ret; +} + +uint8_t Utf16Iter::size() const +{ + uint32_t c = do_codepoint() & 0x001fffff; + return c >= 0x10000 ? 2 : 1; +} + +// fmtlib + +String vformat(fmt::string_view fmt, fmt::format_args args) +{ + auto buf = fmt::memory_buffer(); + vformat_to(buf, fmt, args); + return { buf.data(), buf.size() }; +} + +} // namespace srb2 + +size_t std::hash::operator()(const srb2::String& v) +{ + std::string_view str = v; + return std::hash()(str); +} + +// C functions + +int Str_IsValidUTF8(const char* str) +{ + size_t len = std::strlen(str); + if (len == 0) + { + return 1; + } + + for (auto itr = srb2::Utf8Iter::begin(str); itr != srb2::Utf8Iter::end(str + len - 1); ++itr) + { + if (!itr.valid()) + { + return false; + } + } + return true; +} + +uint32_t Str_NextCodepointFromUTF8(const char** itr) +{ + auto i = srb2::Utf8Iter::begin(*itr); + uint32_t ret = i.codepoint(); + uint8_t s = i.size(); + *itr += s; + return ret; +} diff --git a/src/core/string.h b/src/core/string.h new file mode 100644 index 000000000..f91294b97 --- /dev/null +++ b/src/core/string.h @@ -0,0 +1,453 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef SRB2_CORE_STRING_HPP +#define SRB2_CORE_STRING_HPP + +#ifdef __cplusplus + +#include +#include +#include +#include +#include + +#include "fmt/core.h" + +#include "static_vec.hpp" +#include "vector.hpp" + +namespace srb2 +{ + +class Utf8Iter +{ +public: + using difference_type = size_t; + using value_type = uint32_t; + using pointer = void; + using reference = void; + using iterator_category = std::input_iterator_tag; + +private: + size_t i_; + std::string_view s_; + + Utf8Iter(std::string_view s, size_t i) : i_(i), s_(s) {} + + friend class String; + uint32_t do_codepoint() const; + +public: + Utf8Iter() = default; + Utf8Iter(const Utf8Iter&) = default; + Utf8Iter(Utf8Iter&&) noexcept = default; + ~Utf8Iter() = default; + Utf8Iter& operator=(const Utf8Iter&) = default; + Utf8Iter& operator=(Utf8Iter&&) noexcept = default; + + static Utf8Iter begin(std::string_view s) { return Utf8Iter(s, 0); } + static Utf8Iter end(std::string_view s) { return Utf8Iter(s, s.size()); } + + bool operator==(const Utf8Iter& r) const noexcept + { + return s_ == r.s_ && i_ == r.i_; + } + + bool operator!=(const Utf8Iter& r) const noexcept { return !(*this == r); } + + uint32_t operator*() const { return codepoint(); } + Utf8Iter& operator++() + { + i_ += size(); + return *this; + } + Utf8Iter operator++(int) + { + Utf8Iter copy = *this; + ++*this; + return copy; + } + uint32_t codepoint() const; + bool valid() const; + uint8_t size() const; +}; + +class Utf16Iter +{ +public: + using difference_type = size_t; + using value_type = uint32_t; + using pointer = void; + using reference = void; + using iterator_category = std::input_iterator_tag; + +private: + size_t i_; + std::u16string_view s_; + + Utf16Iter(std::u16string_view s, size_t i) : i_(i), s_(s) {} + + uint32_t do_codepoint() const; + +public: + Utf16Iter() = default; + Utf16Iter(const Utf16Iter&) = default; + Utf16Iter(Utf16Iter&&) noexcept = default; + ~Utf16Iter() = default; + Utf16Iter& operator=(const Utf16Iter&) = default; + Utf16Iter& operator=(Utf16Iter&&) = default; + + static Utf16Iter begin(std::u16string_view s) { return Utf16Iter(s, 0); } + static Utf16Iter end(std::u16string_view s) { return Utf16Iter(s, s.size()); } + + bool operator==(const Utf16Iter& r) const noexcept + { + return s_ == r.s_ && i_ == r.i_; + } + + bool operator!=(const Utf16Iter& r) const noexcept { return !(*this == r); } + + uint32_t operator*() const { return codepoint(); } + Utf16Iter& operator++() + { + i_ += size(); + return *this; + } + Utf16Iter operator++(int) + { + Utf16Iter copy = *this; + ++*this; + return copy; + } + + uint32_t codepoint() const; + bool valid() const { return true; } // we allow unpaired surrogates in general + uint8_t size() const; +}; + +class String +{ +public: + using size_type = uint32_t; + using difference_type = int64_t; + using value_type = uint8_t; + using reference = uint8_t&; + using const_reference = const uint8_t&; + using pointer = uint8_t*; + using const_pointer = const uint8_t*; + using iterator = uint8_t*; + using const_iterator = const uint8_t*; + +private: + srb2::Vector data_; + +public: + + friend struct std::hash; + + static constexpr const size_type npos = -1; + + String() = default; + String(const String&); + String(String&&) noexcept; + ~String(); + String& operator=(const String&); + String& operator=(String&&) noexcept; + + String(const char* s); + String(const char* s, size_t len); + String(const std::string&); + + explicit String(std::string_view view); + + operator std::string() const; + operator std::string_view() const; + + size_type size() const noexcept; + bool empty() const noexcept { return data_.empty(); } + const char* c_str() const; + uint8_t* data() noexcept { return data_.data(); } + const uint8_t* data() const noexcept { return data_.data(); } + void reserve(size_type capacity); + + uint8_t* begin() noexcept; + uint8_t* end() noexcept; + const uint8_t* cbegin() const noexcept; + const uint8_t* cend() const noexcept; + const uint8_t* begin() const noexcept { return cbegin(); } + const uint8_t* end() const noexcept { return cend(); } + + uint8_t& at(size_type i); + const uint8_t& at(size_type i) const; + uint8_t& operator[](size_type i) { return data_[i]; } + const uint8_t& operator[](size_type i) const { return data_[i]; } + uint8_t& front() { return data_[0]; } + const uint8_t& front() const { return data_[0]; } + uint8_t& back() { return data_[size() - 1]; } + const uint8_t& back() const { return data_[size() - 1]; } + + void clear() { data_.clear(); } + String& insert(size_type index, size_type count, uint8_t ch); + String& insert(size_type index, const char* s); + String& insert(size_type index, const char* s, size_type count); + String& insert(size_type index, std::string_view str); + String& insert(size_type index, std::string_view str, size_t s_index, size_t count = npos); + iterator insert(const_iterator pos, uint8_t ch); + iterator insert(const_iterator pos, size_type count, uint8_t ch); + + template < + typename InputIt, + typename std::enable_if_t< + std::is_constructible< + uint8_t, + typename std::iterator_traits::reference + >::value, + int + > = 0 + > + iterator insert(const_iterator pos, InputIt first, InputIt last) + { + size_type offset = pos - data(); + if (!empty()) + { + // remove null byte + data_.pop_back(); + } + auto ret = data_.insert(pos, first, last); + if (data_.size() > 0) + { + data_.push_back(0); + } + return data() + offset; + } + + iterator insert(const_iterator pos, std::initializer_list list); + String& erase(size_type index = 0, size_type count = npos); + iterator erase(const_iterator position); + iterator erase(const_iterator first, const_iterator last); + void push_back(uint8_t v); + void pop_back(); + String& append(size_type count, uint8_t ch); + String& append(const char* s, size_type count); + String& append(const char* s); + String& append(std::string_view str); + String& append(std::string_view str, size_type pos, size_type count = npos); + + template < + typename InputIt, + typename std::enable_if_t< + std::is_constructible< + uint8_t, + typename std::iterator_traits::reference + >::value, + int + > = 0 + > + String& append(InputIt first, InputIt last) + { + if (!empty()) + { + // remove null byte + data_.pop_back(); + } + for (; first != last; first++) + { + data_.push_back(*first); + } + if (data_.size() > 0) + { + data_.push_back(0); + } + return *this; + } + + String& append(std::initializer_list ilist); + String& operator+=(std::string_view r); + String& operator+=(const char* r); + String& operator+=(uint8_t r); + String& operator+=(std::initializer_list r); + String& replace(size_type pos, size_type count, std::string_view str); + String& replace(const_iterator first, const_iterator last, std::string_view str); + String& replace(size_type pos, size_type count, std::string_view str, size_t pos2, size_t count2 = -1); + String& replace(size_type pos, size_type count, const char* cstr, size_type count2); + String& replace(const_iterator first, const_iterator last, const char* cstr, size_type count2); + String& replace(size_type pos, size_type count, const char* cstr); + String& replace(const_iterator first, const_iterator last, const char* cstr); + // String& replace(size_type pos, size_type count, size_type count2, uint8_t ch); + String& replace(const_iterator first, const_iterator last, uint8_t ch); + + template < + typename InputIt, + typename std::enable_if_t< + std::is_constructible< + uint8_t, + typename std::iterator_traits::reference + >::value, + int + > = 0 + > + String& replace(const_iterator first, const_iterator last, InputIt first2, InputIt last2) + { + for (; first != last && first2 != last2; first++, first2++) + { + *const_cast(first) = *first2; + } + return *this; + } + + String& replace(const_iterator first, const_iterator last, std::initializer_list ilist); + size_type copy(uint8_t* dest, size_type count, size_type pos = 0) const; + size_type copy(char* dest, size_type count, size_type pos = 0) const; + void resize(size_type count); + void resize(size_type count, uint8_t ch); + void swap(String& other) noexcept; + + size_type find(const String& str, size_type pos = 0) const; + size_type find(std::string_view str, size_type pos = 0) const; + size_type find(const char* s, size_type pos, size_t count) const; + size_type find(const char* s, size_type pos = 0) const; + size_type find(uint8_t ch, size_type pos = 0) const; + size_type rfind(const String& str, size_type pos = npos) const; + size_type rfind(std::string_view str, size_type pos = npos) const; + size_type rfind(const char* s, size_type pos, size_type count) const; + size_type rfind(const char* s, size_type pos = npos) const; + size_type rfind(uint8_t ch, size_type pos = npos) const; + // size_type find_first_of(std::string_view str, size_type pos = 0) const; + // size_type find_first_of(const char* s, size_type pos, size_type count) const; + // size_type find_first_of(const char* s, size_type pos = 0) const; + // size_type find_first_of(uint8_t ch, size_type pos = 0) const; + // size_type find_first_not_of(std::string_view str, size_type pos = 0) const; + // size_type find_first_not_of(const char* s, size_type pos, size_type count) const; + // size_type find_first_not_of(const char* s, size_type pos = 0) const; + // size_type find_first_not_of(uint8_t ch, size_type pos = 0) const; + // size_type find_last_of(std::string_view str, size_type pos = npos) const; + // size_type find_last_of(const char* s, size_type pos, size_type count) const; + // size_type find_last_of(const char* s, size_type pos = npos) const; + // size_type find_last_of(uint8_t ch, size_type pos = npos) const; + // size_type find_last_not_of(std::string_view str, size_type pos = npos) const; + // size_type find_last_not_of(const char* s, size_type pos, size_type count) const; + // size_type find_last_not_of(const char* s, size_type pos = npos) const; + // size_type find_last_not_of(uint8_t ch, size_type pos = npos) const; + + int compare(std::string_view str) const noexcept; + // int compare(size_type pos1, size_type count1, std::string_view str) const; + // int compare(size_type pos1, size_type count1, std::string_view str, size_type pos2, size_type count2 = npos) const; + int compare(const char* s) const; + // int compare(size_type pos1, size_type count1, const char* s) const; + // int compare(size_type pos1, size_type count1, const char* s, size_type count2) const; + String substr(size_type pos = 0, size_type count = npos) const; + + // Non-STL String functions + bool valid_utf8() const noexcept; + srb2::Vector to_utf16() const; + srb2::Vector to_utf32() const; + size_t length_decoded() const; + + Utf8Iter decode_begin() const { return Utf8Iter(*this, 0); } + Utf8Iter decode_end() const { return Utf8Iter(*this, size()); }; +}; + +String operator+(const String& lhs, const String& rhs); +String operator+(const String& lhs, const char* rhs); +String operator+(const String& lhs, uint8_t rhs); +String operator+(const String& lhs, std::string_view view); + +bool operator==(const String& lhs, const String& rhs); +bool operator==(const String& lhs, const char* rhs); +// bool operator==(const String& lhs, std::string_view rhs); +bool operator!=(const String& lhs, const String& rhs); +bool operator!=(const String& lhs, const char* rhs); +// bool operator!=(const String& lhs, std::string_view rhs); +bool operator<(const String& lhs, const String& rhs); +bool operator<(const String& lhs, const char* rhs); +// bool operator<(const String& lhs, std::string_view rhs); +bool operator<=(const String& lhs, const String& rhs); +bool operator<=(const String& lhs, const char* rhs); +// bool operator<=(const String& lhs, std::string_view rhs); +bool operator>(const String& lhs, const String& rhs); +bool operator>(const String& lhs, const char* rhs); +// bool operator>(const String& lhs, std::string_view rhs); +bool operator>=(const String& lhs, const String& rhs); +bool operator>=(const String& lhs, const char* rhs); +// bool operator>=(const String& lhs, std::string_view rhs); + +Vector to_utf16(std::string_view utf8); +Vector to_utf32(std::string_view utf8); + +srb2::StaticVec to_utf8(uint32_t codepoint); + +template < + typename ItUTF32, + typename std::enable_if_t< + std::is_constructible< + uint32_t, + typename std::iterator_traits::reference + >::value, + int + > = 0 +> +srb2::String to_utf8(ItUTF32 begin, ItUTF32 end) +{ + srb2::String ret; + for (auto itr = begin; itr != end; ++itr) + { + srb2::StaticVec utf8 = to_utf8(*itr); + ret.append(utf8.begin(), utf8.end()); + } + return ret; +} + +srb2::String to_utf8(std::u32string_view utf32view); + +// fmtlib +inline auto format_as(const String& str) { return fmt::string_view(static_cast(str)); } + +srb2::String vformat(fmt::string_view fmt, fmt::format_args args); + +template +inline auto format(fmt::format_string fmt, T&&... args) +{ + return ::srb2::vformat(fmt, fmt::vargs{{args...}}); +} + +} // namespace srb2 + +namespace std +{ + +template <> struct hash +{ + size_t operator()(const srb2::String& v); +}; + +} // namespace std + +#endif // __cplusplus + +#ifndef __cplusplus +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + +int Str_IsValidUTF8(const char* str); +// int Str_ToUTF16(uint16_t* dst, size_t dstlen, const char* src); +// int Str_ToUTF32(uint32_t* dst, size_t dstlen, const char* src); +uint32_t Str_NextCodepointFromUTF8(const char** itr); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // SRB2_CORE_STRING_HPP diff --git a/src/core/thread_pool.cpp b/src/core/thread_pool.cpp index 753628391..8a434ba63 100644 --- a/src/core/thread_pool.cpp +++ b/src/core/thread_pool.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,12 +14,13 @@ #include #include #include -#include #include #include #include +#include "../core/string.h" +#include "../core/vector.hpp" #include "../cxxutil.hpp" #include "../m_argv.h" @@ -50,11 +51,11 @@ static void pool_executor( std::shared_ptr worker_ready_mutex, std::shared_ptr worker_ready_condvar, std::shared_ptr my_wq, - std::vector> other_wqs + srb2::Vector> other_wqs ) { { - std::string thread_name = fmt::format("Thread Pool Thread {}", thread_index); + srb2::String thread_name = srb2::format("Thread Pool Thread {}", thread_index); tracy::SetThreadName(thread_name.c_str()); } @@ -133,7 +134,7 @@ ThreadPool::ThreadPool(size_t threads) for (size_t i = 0; i < threads; i++) { std::shared_ptr my_queue = work_queues_[i]; - std::vector> other_queues; + srb2::Vector> other_queues; for (size_t j = 0; j < threads; j++) { // Order the other queues starting from the next adjacent worker diff --git a/src/core/thread_pool.h b/src/core/thread_pool.h index 28f8a4fb3..0c1fc88ad 100644 --- a/src/core/thread_pool.h +++ b/src/core/thread_pool.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/core/vector.cpp b/src/core/vector.cpp new file mode 100644 index 000000000..2321ebcc2 --- /dev/null +++ b/src/core/vector.cpp @@ -0,0 +1,61 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "vector.hpp" + +#include +#include +#include + +#include "string.h" + +namespace srb2 +{ + +AbstractVector::GrowResult AbstractVector::_realloc_mem(void* data, size_t size, size_t old_cap, size_t elem_size, Move move, size_t cap) noexcept +{ + GrowResult ret; + std::allocator allocator; + + if (old_cap == 0) + { + cap = std::max(cap, 1); + ret.data = allocator.allocate(cap * elem_size + sizeof(std::max_align_t)); + ret.cap = cap; + return ret; + } + + cap = std::max(cap, old_cap * 2); + ret.data = allocator.allocate(cap * elem_size + sizeof(std::max_align_t)); + (move)(ret.data, data, size); + allocator.deallocate(reinterpret_cast(data), old_cap * elem_size + sizeof(std::max_align_t)); + ret.cap = cap; + return ret; +} + +void AbstractVector::_free_mem(void* data, size_t cap, size_t elem_size) noexcept +{ + std::allocator allocator; + allocator.deallocate(reinterpret_cast(data), cap * elem_size + sizeof(std::max_align_t)); +} + +template class Vector; +template class Vector; +template class Vector; +template class Vector; +template class Vector; +template class Vector; +template class Vector; +template class Vector; +template class Vector; +template class Vector; +template class Vector; + +} // namespace srb2 diff --git a/src/core/vector.hpp b/src/core/vector.hpp new file mode 100644 index 000000000..e6d66f91f --- /dev/null +++ b/src/core/vector.hpp @@ -0,0 +1,366 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef SRB2_CORE_VEC_HPP +#define SRB2_CORE_VEC_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace srb2 +{ + +class AbstractVector +{ +protected: + size_t size_; + size_t capacity_; + size_t elem_size_; + void* data_; + + using Move = void(*)(void* dst, void* src, size_t size); + + template + static void _move(void* dst, void* src, size_t size) noexcept + { + T* d = (T*)dst; + T* s = (T*)src; + for (size_t i = 0; i < size; i++) + { + new (&d[i]) T(std::move(s[i])); + } + } + + struct GrowResult + { + void* data; + size_t cap; + }; + static GrowResult _realloc_mem(void* data, size_t size, size_t old_cap, size_t elem_size, Move move, size_t cap) noexcept; + static void _free_mem(void* data, size_t cap, size_t elem_size) noexcept; + + template + void _reserve_mem(size_t c) noexcept + { + if (c > capacity_) + { + GrowResult r = _realloc_mem(data_, size_, capacity_, elem_size_, _move, c); + data_ = r.data; + capacity_ = r.cap; + } + } + + constexpr AbstractVector() : size_(0), capacity_(0), elem_size_(0), data_(nullptr) {} +}; + +template +class Vector : AbstractVector +{ +public: + // iter traits + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + using iterator = T*; + using const_iterator = const T*; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + Vector() : AbstractVector() + { + elem_size_ = sizeof(T); + } + Vector(const Vector& rhs) : Vector() + { + *this = rhs; + } + Vector(Vector&& rhs) noexcept : AbstractVector() + { + elem_size_ = sizeof(T); + *this = std::move(rhs); + } + ~Vector() + { + if (data_) + { + for (size_type i = 0; i < size_; i++) + { + ((T*)(data_))[(size_ - i - 1)].~T(); + } + _free_mem(data_, capacity_, elem_size_); + } + data_ = nullptr; + } + + explicit Vector(size_type capacity) : AbstractVector() + { + elem_size_ = sizeof(T); + _reserve_mem(capacity); + } + Vector(std::initializer_list l) : AbstractVector() + { + elem_size_ = sizeof(T); + if (l.size() == 0) + { + return; + } + + _reserve_mem(l.size()); + for (auto itr = l.begin(); itr != l.end(); itr++) + { + emplace_back(*itr); + } + } + + template < + typename It, + typename std::enable_if_t< + std::is_constructible_v< + T, + typename std::iterator_traits::reference + >, + int + > = 0 + > + Vector(It begin, It end) : AbstractVector() + { + elem_size_ = sizeof(T); + for (auto itr = begin; itr != end; ++itr) + { + push_back(*itr); + } + } + + Vector& operator=(const Vector& rhs) + { + for (auto itr = rhs.begin(); itr != rhs.end(); itr++) + { + push_back(*itr); + } + return *this; + } + + Vector& operator=(Vector&& rhs) noexcept + { + std::swap(size_, rhs.size_); + std::swap(capacity_, rhs.capacity_); + std::swap(data_, rhs.data_); + return *this; + } + + constexpr size_type size() const noexcept { return size_; } + constexpr size_type capacity() const noexcept { return capacity_; } + constexpr bool empty() const noexcept { return size_ == 0; } + + constexpr T* data() noexcept { return (T*) data_; } + constexpr const T* data() const noexcept { return (const T*) data_; } + constexpr T* begin() noexcept { return data(); } + constexpr const T* begin() const noexcept { return data(); } + constexpr T* end() noexcept { return data() + size(); } + constexpr const T* end() const noexcept { return data() + size(); } + constexpr const T* cbegin() const noexcept { return data(); } + constexpr const T* cend() const noexcept { return end(); } + constexpr auto rbegin() noexcept { return std::reverse_iterator(end()); } + constexpr auto rbegin() const noexcept { return std::reverse_iterator(end()); } + constexpr auto rend() noexcept { return std::reverse_iterator(begin()); } + constexpr auto rend() const noexcept { return std::reverse_iterator(begin()); } + constexpr auto crbegin() const noexcept { return rbegin(); } + constexpr auto crend() const noexcept { return rend(); } + T& front() noexcept { return data()[0]; } + const T& front() const noexcept { return data()[0]; } + T& back() noexcept { return data()[size() - 1]; } + const T& back() const noexcept { return data()[size() - 1]; } + + void push_back(const T& t) + { + reserve(size() + 1); + new (&data()[size()]) T(t); + size_++; + } + + void push_back(T&& t) + { + reserve(size() + 1); + new (&data()[size()]) T(std::move(t)); + size_++; + } + + void pop_back() + { + T* end = &data()[size() - 1]; + end->~T(); + size_--; + } + + void clear() { for (auto& x : *this) x.~T(); size_ = 0; } + + void reserve(size_type c) + { + _reserve_mem(c); + } + + void resize(size_type s) + { + resize(s, T()); + } + + void resize(size_type s, const T& value) + { + if (s <= size()) + { + auto itr_begin = rbegin(); + auto itr_end = std::prev(rend(), s); + size_t count_destroyed = 0; + for (auto itr = itr_begin; itr != itr_end; itr++) + { + itr->~T(); + count_destroyed++; + } + size_ = s; + } + else + { + reserve(s); + size_type oldsize = size(); + size_ = s; + for (auto itr = std::next(begin(), oldsize); itr != end(); itr++) + { + try + { + new (itr) T(value); + } + catch (...) + { + size_ = oldsize; + std::rethrow_exception(std::current_exception()); + } + } + } + } + + const T& at(size_type i) const { if (i >= size()) throw std::out_of_range("index out of range"); return data()[i]; } + T& at(size_type i) { if (i >= size()) throw std::out_of_range("index out of range"); return data()[i]; } + + T& operator[](size_type i) { return data()[i]; } + const T& operator[](size_type i) const { return data()[i]; } + + iterator erase(const_iterator first) { return erase(first, first + 1); } + iterator erase(const_iterator first, const_iterator last) + { + iterator firstm = const_cast(first); + iterator lastm = const_cast(last); + if (first == last) return firstm; + + auto diff = last - first; + if (last != end()) std::move(lastm, end(), firstm); + resize(size_ - diff); + + return firstm; + } + + iterator insert(const_iterator pos, const T& value) + { + return insert(pos, (size_type)1, std::forward(value)); + } + + iterator insert(const_iterator pos, T&& value) + { + size_type oldsize = size(); + difference_type offs = pos - data(); + push_back(std::move(value)); + std::rotate(data() + offs, data() + oldsize, data() + size()); + return data() + offs; + } + + iterator insert(const_iterator pos, size_type count, const T& value) + { + size_type oldsize = size(); + difference_type offs = pos - data(); + reserve(oldsize + count); + T* d = data(); + for (uint32_t i = 0; i < count; i++) + { + // must be copy-initialized; + // value is currently uninitialized + new ((T*)(&d[oldsize + i])) T(value); + } + size_ = oldsize + count; + std::rotate(d + offs, d + oldsize, d + (oldsize + count)); + return data() + offs; + } + + template < + class InputIt, + typename std::enable_if_t< + std::is_constructible< + T, + typename std::iterator_traits::reference + >::value, + int + > = 0 + > + iterator insert(const_iterator pos, InputIt first, InputIt last) + { + size_type oldsize = size(); + difference_type offs = pos - data(); + + size_type count = 0; + while (first != last) + { + push_back(*first); + ++first; + ++count; + } + std::rotate(data() + offs, data() + oldsize, data() + (oldsize + count)); + return data() + offs; + } + + template + iterator emplace(const_iterator position, A&&... args) + { + return insert(position, T(std::forward(args)...)); + } + + template + T& emplace_back(A&&... args) + { + reserve(size() + 1); + new (&data()[size()]) T(std::forward(args)...); + size_++; + return (*this)[size_ - 1]; + } +}; + +class String; + +extern template class Vector; +extern template class Vector; +extern template class Vector; +extern template class Vector; +extern template class Vector; +extern template class Vector; +extern template class Vector; +extern template class Vector; +extern template class Vector; +extern template class Vector; +extern template class Vector; + +} // namespace srb2 + +#endif // SRB2_CORE_VEC_HPP diff --git a/src/cvars.cpp b/src/cvars.cpp index 6056e0c1f..2ea673fdc 100644 --- a/src/cvars.cpp +++ b/src/cvars.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -244,10 +244,6 @@ X (cvlist_timer); X (cvlist_execversion); -#ifdef DUMPCONSISTENCY - X (cvlist_dumpconsistency); -#endif - #undef X namespace @@ -326,6 +322,7 @@ consvar_t cv_controlperkey = Player("controlperkey", "One").values({{1, "One"}, consvar_t cv_mastervolume = Player("volume", "80").min_max(0, 100); consvar_t cv_digmusicvolume = Player("musicvolume", "80").min_max(0, 100); consvar_t cv_soundvolume = Player("soundvolume", "80").min_max(0, 100); +consvar_t cv_voicevolume = Player("voicevolume", "100").min_max(0, 100); #ifdef HAVE_DISCORDRPC void DRPC_UpdatePresence(void); @@ -597,17 +594,15 @@ consvar_t cv_sleep = Server("cpusleep", "1").min_max(0, 1000/TICRATE); // There's a separate section for netvars that don't save... // -void AutoBalance_OnChange(void); -consvar_t cv_autobalance = NetVar("autobalance", "Off").on_off().onchange(AutoBalance_OnChange); - consvar_t cv_blamecfail = NetVar("blamecfail", "Off").on_off(); // Speed of file downloading (in packets per tic) consvar_t cv_downloadspeed = NetVar("downloadspeed", "32").min_max(1, 300); -#ifdef DUMPCONSISTENCY - consvar_t cv_dumpconsistency = NetVar(cvlist_dumpconsistency)("dumpconsistency", "Off").on_off(); -#endif +// Dump gamestates to an external file when a resync occurs. +// This is a cheat because enabling this can take up file storage +// for connected players very fast. +consvar_t cv_dumpconsistency = OnlineCheat("dumpconsistency", "Off").on_off(); // Intermission time Tails 04-19-2002 consvar_t cv_inttime = NetVar("inttime", "10").min_max(0, 3600); @@ -626,11 +621,6 @@ consvar_t cv_maxsend = NetVar("maxsend", "51200").min_max(0, 51200); consvar_t cv_noticedownload = NetVar("noticedownload", "Off").on_off(); consvar_t cv_pingtimeout = NetVar("maxdelaytimeout", "10").min_max(8, 120); -consvar_t cv_resynchattempts = NetVar("resynchattempts", "2").min_max(1, 20, {{0, "No"}}); - -void TeamScramble_OnChange(void); -consvar_t cv_scrambleonchange = NetVar("scrambleonchange", "Off").values({{0, "Off"}, {1, "Random"}, {2, "Points"}}); -consvar_t cv_teamscramble = NetVar("teamscramble", "Off").values({{0, "Off"}, {1, "Random"}, {2, "Points"}}).onchange_noinit(TeamScramble_OnChange); consvar_t cv_showjoinaddress = NetVar("showjoinaddress", "Off").on_off(); consvar_t cv_zvote_delay = NetVar("zvote_delay", "20").values(CV_Unsigned); @@ -736,6 +726,8 @@ consvar_t cv_kartfrantic = UnsavedNetVar("franticitems", "Off").on_off().onchang void KartSpeed_OnChange(void); consvar_t cv_kartspeed = UnsavedNetVar("gamespeed", "Auto Gear").values(kartspeed_cons_t).onchange_noinit(KartSpeed_OnChange); +consvar_t cv_teamplay = UnsavedNetVar("teamplay", "Off").on_off(); + consvar_t cv_kartusepwrlv = UnsavedNetVar("usepwrlv", "Yes").yes_no(); void LiveStudioAudience_OnChange(void); @@ -747,6 +739,8 @@ void LiveStudioAudience_OnChange(void); consvar_t cv_maxplayers = NetVar("maxplayers", "8").min_max(1, MAXPLAYERS); +consvar_t cv_shuffleloser = NetVar("shuffleloser", "On").on_off(); + // Scoring type options consvar_t cv_overtime = UnsavedNetVar("overtime", "Yes").yes_no(); @@ -913,6 +907,8 @@ consvar_t cv_debugrender_visplanes = PlayerCheat("debugrender_visplanes", "Off") consvar_t cv_debugvirtualkeyboard = PlayerCheat("debugvirtualkeyboard", "Off").on_off().description("Always show virtual keyboard instead of using real keyboard input."); consvar_t cv_devmode_screen = PlayerCheat("devmode_screen", "1").min_max(1, 4).description("Choose which splitscreen player devmode applies to"); consvar_t cv_drawpickups = PlayerCheat("drawpickups", "Yes").yes_no().description("Hide rings, spheres, item capsules, prison capsules (visual only)"); +consvar_t cv_drawtimer = PlayerCheat("drawtimer", "No").yes_no().description("Always draw the timer (race checkpoint timing, etc)"); +consvar_t cv_debugfonts = PlayerCheat("debugfonts", "No").yes_no().description("Draw font bounding boxes (integer precision, beware centered text!)"); void lua_profile_OnChange(void); consvar_t cv_lua_profile = PlayerCheat("lua_profile", "0").values(CV_Unsigned).onchange(lua_profile_OnChange).description("Show hook timings over an average of N tics"); @@ -978,6 +974,8 @@ consvar_t cv_dummymenuplayer = MenuDummy("dummymenuplayer", "P1").onchange(Dummy consvar_t cv_dummyprofileautoroulette = MenuDummy("dummyprofileautoroulette", "Off").on_off(); consvar_t cv_dummyprofilefov = MenuDummy("dummyprofilefov", "100").min_max(70, 110); consvar_t cv_dummyprofilelitesteer = MenuDummy("dummyprofilelitesteer", "Off").on_off(); +consvar_t cv_dummyprofilestrictfastfall = MenuDummy("dummprofilestrictfastfall", "Off").on_off(); +consvar_t cv_dummyprofiledescriptiveinput = Player("dummyprofiledescriptiveinput", "Modern").values(descriptiveinput_cons_t); consvar_t cv_dummyprofileautoring = MenuDummy("dummyprofileautoring", "Off").on_off(); consvar_t cv_dummyprofilekickstart = MenuDummy("dummyprofilekickstart", "Off").on_off(); consvar_t cv_dummyprofilename = MenuDummy("dummyprofilename", ""); @@ -994,9 +992,6 @@ consvar_t cv_dummyspectate = MenuDummy("dummyspectate", "Spectator").values({{0, extern CV_PossibleValue_t dummystaff_cons_t[]; consvar_t cv_dummystaff = MenuDummy("dummystaff", "0").values(dummystaff_cons_t); -consvar_t cv_dummyteam = MenuDummy("dummyteam", "Spectator").values({{0, "Spectator"}, {1, "Red"}, {2, "Blue"}}); - - // // lastprofile // @@ -1091,6 +1086,13 @@ consvar_t cv_litesteer[MAXSPLITSCREENPLAYERS] = { Player("litesteer4", "Off").on_off().onchange(weaponPrefChange4), }; +consvar_t cv_strictfastfall[MAXSPLITSCREENPLAYERS] = { + Player("strictfastfall", "Off").on_off().onchange(weaponPrefChange), + Player("strictfastfall2", "Off").on_off().onchange(weaponPrefChange2), + Player("strictfastfall3", "Off").on_off().onchange(weaponPrefChange3), + Player("strictfastfall4", "Off").on_off().onchange(weaponPrefChange4), +}; + consvar_t cv_autoring[MAXSPLITSCREENPLAYERS] = { Player("autoring", "Off").on_off().onchange(weaponPrefChange), Player("autoring2", "Off").on_off().onchange(weaponPrefChange2), @@ -1112,6 +1114,14 @@ consvar_t cv_cam_height[MAXSPLITSCREENPLAYERS] = { Player("cam4_height", "95").floating_point(), }; +consvar_t cv_descriptiveinput[MAXSPLITSCREENPLAYERS] = { + Player("descriptiveinput", "Modern").values(descriptiveinput_cons_t), + Player("descriptiveinput2", "Modern").values(descriptiveinput_cons_t), + Player("descriptiveinput3", "Modern").values(descriptiveinput_cons_t), + Player("descriptiveinput4", "Modern").values(descriptiveinput_cons_t), +}; + + void CV_CamRotate_OnChange(void); void CV_CamRotate2_OnChange(void); void CV_CamRotate3_OnChange(void); @@ -1352,8 +1362,71 @@ consvar_t cv_chatwidth = Player("chatwidth", "150").min_max(64, 150); // old shit console chat. (mostly exists for stuff like terminal, not because I cared if anyone liked the old chat.) consvar_t cv_consolechat = Player("chatmode", "Yes").values({{0, "Yes"}, {2, "No"}}); +// When off, inbound voice packets are ignored +void VoiceChat_OnChange(void); +consvar_t cv_voice_chat = Player("voice_chat", "Off") + .on_off() + .onchange(VoiceChat_OnChange) + .description("Whether voice chat is played or not. Shown as self-deafen to others."); + +// When on, local player won't transmit voice +consvar_t cv_voice_mode = Player("voice_mode", "Activity") + .values({{0, "Activity"}, {1, "PTT"}}) + .description("How to activate voice transmission"); + +consvar_t cv_voice_selfmute = Player("voice_selfmute", "Off") + .on_off() + .onchange(weaponPrefChange) + .description("Whether the local microphone is muted. Shown as self-mute to others."); + +consvar_t cv_voice_inputamp = Player("voice_inputamp", "14") + .min_max(-30, 30) + .description("How much louder or quieter to make voice input, in decibels."); + +consvar_t cv_voice_activationthreshold = Player("voice_activationthreshold", "-20") + .min_max(-30, 0) + .description("The voice activation threshold, in decibels from maximum amplitude."); + +// When on, local voice is played back out +consvar_t cv_voice_loopback = Player("voice_loopback", "Off") + .on_off() + .dont_save() + .description("When on, plays the local player's voice"); + +consvar_t cv_voice_proximity = NetVar("voice_proximity", "On") + .on_off() + .description("Whether proximity effects for voice chat are enabled on the server."); + +// The relative distance for maximum voice attenuation +consvar_t cv_voice_distanceattenuation_distance = NetVar("voice_distanceattenuation_distance", "4096") + .floating_point() + .description("Voice speaker's distance from listener at which positional voice is fully attenuated"); + +// The volume factor (scaled logarithmically, i.e. 0.5 = "half as loud") for voice distance attenuation +consvar_t cv_voice_distanceattenuation_factor = NetVar("voice_distanceattenuation_factor", "0.2") + .floating_point() + .description("Maximum attenuation, in perceived loudness, when a voice speaker is at voice_distanceattenuation_distance units or further from the listener"); + +// The scale factor applied to stereo separation for voice panning +consvar_t cv_voice_stereopanning_factor = NetVar("voice_stereopanning_factor", "1.0") + .floating_point() + .description("Scale of stereo panning applied to a voice speaker relative to their in-game position, from 0.0-1.0"); + +consvar_t cv_voice_concurrentattenuation_factor = NetVar("voice_concurrentattenuation_factor", "0.6") + .floating_point() + .description("The maximum attenuation factor, in perceived loudness, when at or exceeding voice_concurrentattenuation_max speakers"); +consvar_t cv_voice_concurrentattenuation_min = NetVar("voice_concurrentattenuation_min", "3") + .description("Minimum concurrent speakers before global attenuation starts"); +consvar_t cv_voice_concurrentattenuation_max = NetVar("voice_concurrentattenuation_max", "8") + .description("Maximum concurrent speakers at which full global attenuation is applied"); + void Mute_OnChange(void); +void VoiceMute_OnChange(void); consvar_t cv_mute = UnsavedNetVar("mute", "Off").on_off().onchange(Mute_OnChange); +consvar_t cv_voice_servermute = NetVar("voice_servermute", "On") + .on_off() + .onchange(VoiceMute_OnChange) + .description("If On, the server will not broadcast voice chat to clients"); // diff --git a/src/cxxutil.hpp b/src/cxxutil.hpp index 5ebf1fd21..f91986a93 100644 --- a/src/cxxutil.hpp +++ b/src/cxxutil.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/d_clisrv.c b/src/d_clisrv.c index b03224472..33e9dceaa 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // @@ -16,6 +16,8 @@ #include //for unlink #endif +#include + #include "i_time.h" #include "i_net.h" #include "i_system.h" @@ -75,6 +77,7 @@ // cl loading screen #include "v_video.h" #include "f_finale.h" +#include "k_hud.h" #ifdef HAVE_DISCORDRPC #include "discord.h" @@ -120,6 +123,11 @@ static tic_t freezetimeout[MAXNETNODES]; // Until when can this node freeze the // If higher than cv_joindelay * 2 (3 joins in a short timespan), joins are temporarily disabled. static tic_t joindelay = 0; +// Set when the server requests a signature with an IP mismatch. +// This is a common issue on Hamachi, Radmin, and other VPN software that does noncompliant IP remap horeseshit. +// When ths is set, provide only blank signatures. If this is set while joining, the server will degrade us to a GUEST. +static boolean forceGuest = false; + UINT16 pingmeasurecount = 1; UINT32 realpingtable[MAXPLAYERS]; //the base table of ping where an average will be sent to everyone. UINT32 playerpingtable[MAXPLAYERS]; //table of player latency values. @@ -189,6 +197,17 @@ uint8_t priorKeys[MAXPLAYERS][PUBKEYLENGTH]; // Make a note of keys before consu boolean serverisfull = false; //lets us be aware if the server was full after we check files, but before downloading, so we can ask if the user still wants to download or not tic_t firstconnectattempttime = 0; +static OpusDecoder *g_player_opus_decoders[MAXPLAYERS]; +static UINT64 g_player_opus_lastframe[MAXPLAYERS]; +static OpusEncoder *g_local_opus_encoder; +static UINT64 g_local_opus_frame = 0; +#define SRB2_VOICE_OPUS_FRAME_SIZE 480 +static float g_local_voice_buffer[SRB2_VOICE_OPUS_FRAME_SIZE]; +static INT32 g_local_voice_buffer_len = 0; +static INT32 g_local_voice_threshold_time = 0; +float g_local_voice_last_peak = 0; +boolean g_local_voice_detected = false; + // engine // Must be a power of two @@ -262,9 +281,26 @@ shouldsign_t ShouldSignChallenge(uint8_t *message) if ((max(now, then) - min(now, then)) > 60*15) return SIGN_BADTIME; + // ____ _____ ___ ____ _ + // / ___|_ _/ _ \| _ \| | + // \___ \ | || | | | |_) | | + // ___) || || |_| | __/|_| + // |____/ |_| \___/|_| (_) + // ========================= + // SIGN_BADIP MUST BE CHECKED LAST, AND RETURN ONLY IF ALL OTHER CHECKS HAVE ALREADY SUCCEEDED. + // We allow IP failures through for compatibility with shitty VPNs and fucked-beyond-belief home networks. + // If this is checked before other sign-safety conditons, bad actors can INTENTIONALLY SKIP CHECKS. if (realIP != claimedIP && I_IsExternalAddress(&realIP)) return SIGN_BADIP; - +#ifdef DEVELOP + if (cv_badip.value) + { + CV_AddValue(&cv_badip, -1); + CONS_Alert(CONS_WARNING, "cv_badip enabled, intentionally failing checks\n"); + return SIGN_BADIP; + } +#endif + // Do NOT return SIGN_BADIP before doing all other available checks. return SIGN_OK; } @@ -685,7 +721,7 @@ static inline void CL_DrawConnectionStatus(void) // Draw bottom box M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-24-8, 32, 1); - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-24, V_YELLOWMAP, "Press \xAB or \xAD to abort"); + K_DrawGameControl(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-24, 0, "Press or to abort", 1, 2, V_YELLOWMAP); for (i = 0; i < 16; ++i) V_DrawFill((BASEVIDWIDTH/2-128) + (i * 16), BASEVIDHEIGHT-24, 16, 8, palstart + ((animtime - i) & 15)); @@ -758,7 +794,7 @@ static inline void CL_DrawConnectionStatus(void) INT32 checkednum = 0; INT32 i; - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-24, V_YELLOWMAP, "Press \xAB or \xAD to abort"); + K_DrawGameControl(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-24, 0, "Press or to abort", 1, 2, V_YELLOWMAP); //ima just count files here for (i = 0; i < fileneedednum; i++) @@ -780,7 +816,7 @@ static inline void CL_DrawConnectionStatus(void) INT32 loadcompletednum = 0; INT32 i; - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-24, V_YELLOWMAP, "Press \xAB or \xAD to abort"); + K_DrawGameControl(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-24, 0, "Press or to abort", 1, 2, V_YELLOWMAP); //ima just count files here for (i = 0; i < fileneedednum; i++) @@ -807,7 +843,7 @@ static inline void CL_DrawConnectionStatus(void) // Draw the bottom box. M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-58-8, 32, 1); - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-58-14, V_YELLOWMAP, "Press \xAB or \xAD to abort"); + K_DrawGameControl(BASEVIDWIDTH/2, BASEVIDHEIGHT-58-14, 0, "Press or to abort", 1, 2, V_YELLOWMAP); Net_GetNetStat(); dldlength = (INT32)((file->currentsize/(double)file->totalsize) * 256); @@ -873,7 +909,7 @@ static inline void CL_DrawConnectionStatus(void) //Draw bottom box M_DrawTextBox(BASEVIDWIDTH/2-128-8, BASEVIDHEIGHT-24-8, 32, 1); - V_DrawCenteredString(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-24, V_YELLOWMAP, "Press \xAB or \xAD to abort"); + K_DrawGameControl(BASEVIDWIDTH/2, BASEVIDHEIGHT-24-24, 0, "Press or to abort", 1, 2, V_YELLOWMAP); for (i = 0; i < 16; ++i) V_DrawFill((BASEVIDWIDTH/2-128) + (i * 16), BASEVIDHEIGHT-24, 16, 8, palstart + ((animtime - i) & 15)); @@ -937,17 +973,19 @@ static boolean CL_SendJoin(void) // Don't leak old signatures from prior sessions. memset(&netbuffer->u.clientcfg.challengeResponse, 0, sizeof(((clientconfig_pak *)0)->challengeResponse)); + forceGuest = false; + if (client && netgame) { shouldsign_t safe = ShouldSignChallenge(awaitingChallenge); - if (safe != SIGN_OK) + if (safe == SIGN_BADIP) { - if (safe == SIGN_BADIP) - { - I_Error("External server IP didn't match the message it sent."); - } - else if (safe == SIGN_BADTIME) + forceGuest = true; + } + else if (safe != SIGN_OK) + { + if (safe == SIGN_BADTIME) { I_Error("External server sent a message with an unusual timestamp.\nMake sure your system time is set correctly."); } @@ -964,7 +1002,7 @@ static boolean CL_SendJoin(void) uint8_t signature[SIGNATURELENGTH]; profile_t *localProfile = PR_GetLocalPlayerProfile(i); - if (PR_IsLocalPlayerGuest(i)) // GUESTS don't have keys + if (PR_IsLocalPlayerGuest(i) || forceGuest) // GUESTS don't have keys { memset(signature, 0, sizeof(signature)); } @@ -1049,7 +1087,8 @@ static void SV_SendServerInfo(INT32 node, tic_t servertime) netbuffer->u.serverinfo.kartvars = (UINT8) ( (gamespeed & SV_SPEEDMASK) | - (dedicated ? SV_DEDICATED : 0) + (dedicated ? SV_DEDICATED : 0) | + (!cv_voice_servermute.value ? SV_VOICEENABLED : 0) ); D_ParseCarets(netbuffer->u.serverinfo.servername, cv_servername.string, MAXSERVERNAME); @@ -1144,28 +1183,32 @@ static void SV_SendPlayerInfo(INT32 node) //No, don't do that, you fuckface. memset(netbuffer->u.playerinfo[i].address, 0, 4); - if (G_GametypeHasTeams()) + if (players[i].spectator) { - if (!players[i].ctfteam) - netbuffer->u.playerinfo[i].team = 255; - else - netbuffer->u.playerinfo[i].team = (UINT8)players[i].ctfteam; + netbuffer->u.playerinfo[i].team = 255; } else { - if (players[i].spectator) - netbuffer->u.playerinfo[i].team = 255; + if (G_GametypeHasTeams()) + { + if (players[i].team == TEAM_UNASSIGNED) + { + netbuffer->u.playerinfo[i].team = 255; + } + else + { + netbuffer->u.playerinfo[i].team = players[i].team; + } + } else + { netbuffer->u.playerinfo[i].team = 0; + } } netbuffer->u.playerinfo[i].score = LONG(players[i].score); netbuffer->u.playerinfo[i].timeinserver = SHORT((UINT16)(players[i].jointime / TICRATE)); - netbuffer->u.playerinfo[i].skin = (UINT8)(players[i].skin -#ifdef DEVELOP // it's safe to do this only because PLAYERINFO isn't read by the game itself - % 3 -#endif - ); + netbuffer->u.playerinfo[i].skin = (UINT8)(players[i].skin); // Extra data netbuffer->u.playerinfo[i].data = 0; //players[i].skincolor; @@ -1312,26 +1355,18 @@ static void SV_SendSaveGame(INT32 node, boolean resending) freezetimeout[node] = I_GetTime() + jointimeout + length / 1024; // 1 extra tic for each kilobyte } -#ifdef DUMPCONSISTENCY -#define TMPSAVENAME "badmath.sav" - -static void SV_SavedGame(void) +static void CL_DumpConsistency(const char *file_name) { - extern consvar_t *cv_dumpconsistency; - size_t length; savebuffer_t save = {0}; - char tmpsave[256]; + char tmpsave[1024]; - if (!cv_dumpconsistency.value) - return; - - sprintf(tmpsave, "%s" PATHSEP TMPSAVENAME, srb2home); + snprintf(tmpsave, sizeof(tmpsave), "%s" PATHSEP "%s", srb2home, file_name); // first save it in a malloced buffer if (P_SaveBufferAlloc(&save, NETSAVEGAMESIZE) == false) { - CONS_Alert(CONS_ERROR, M_GetText("No more free memory for savegame\n")); + CONS_Alert(CONS_ERROR, M_GetText("No more free memory for consistency dump\n")); return; } @@ -1341,21 +1376,18 @@ static void SV_SavedGame(void) if (length > NETSAVEGAMESIZE) { P_SaveBufferFree(&save); - I_Error("Savegame buffer overrun"); + I_Error("Consistency dump buffer overrun"); } // then save it! if (!FIL_WriteFile(tmpsave, save.buffer, length)) - CONS_Printf(M_GetText("Didn't save %s for netgame"), tmpsave); + CONS_Printf(M_GetText("Didn't save %s for consistency dump"), tmpsave); P_SaveBufferFree(&save); } -#undef TMPSAVENAME -#endif #define TMPSAVENAME "$$$.sav" - static void CL_LoadReceivedSavegame(boolean reloading) { savebuffer_t save = {0}; @@ -1442,12 +1474,20 @@ static void CL_LoadReceivedSavegame(boolean reloading) } } } + + if (forceGuest) + HU_AddChatText("\x85* This server uses a strange network configuration (VPN?). You have joined as a GUEST.", true); } static void CL_ReloadReceivedSavegame(void) { - INT32 i; + extern consvar_t cv_dumpconsistency; + if (cv_dumpconsistency.value) + { + CL_DumpConsistency("TEMP.consdump"); + } + INT32 i; for (i = 0; i < MAXPLAYERS; i++) { LUA_InvalidatePlayer(&players[i]); @@ -1473,6 +1513,26 @@ static void CL_ReloadReceivedSavegame(void) cl_redownloadinggamestate = false; CONS_Printf(M_GetText("Game state reloaded\n")); + + if (cv_dumpconsistency.value) + { + // This is dumb, but we want the file names + // to be pairable together with the server's + // version, and gametic being randomly off + // is a deal breaker. + char dump_name[1024]; + snprintf( + dump_name, sizeof(dump_name), + "%s_%u_%s-client.consdump", + server_context, + gametic, + player_names[consoleplayer] + ); + if (FIL_RenameFile("TEMP.consdump", dump_name) == false) + { + CONS_Alert(CONS_WARNING, "Failed to rename temporary consdump file.\n"); + } + } } static void SendAskInfo(INT32 node) @@ -2334,6 +2394,11 @@ static void CL_ConnectToServer(void) } SL_ClearServerList(servernode); + for (i = 0; i < MAXPLAYERS; i++) + { + CL_ClearPlayer(i); + } + do { // If the connection was aborted for some reason, leave @@ -2545,6 +2610,27 @@ void CL_ClearPlayer(INT32 playernum) // Handle post-cleanup. RemoveAdminPlayer(playernum); // don't stay admin after you're gone + + // Clear voice chat data + S_ResetVoiceQueue(playernum); + + { + // Destroy and recreate the opus decoder for this playernum + OpusDecoder *opusdecoder = g_player_opus_decoders[playernum]; + if (opusdecoder) + { + opus_decoder_destroy(opusdecoder); + opusdecoder = NULL; + } + int error; + opusdecoder = opus_decoder_create(48000, 1, &error); + if (error != OPUS_OK) + { + CONS_Alert(CONS_WARNING, "Failed to create Opus decoder for player %d: opus error %d\n", playernum, error); + opusdecoder = NULL; + } + g_player_opus_decoders[playernum] = opusdecoder; + } } // @@ -3069,9 +3155,6 @@ static void Got_KickCmd(const UINT8 **p, INT32 playernum) if (playernode[pnum] == playernode[consoleplayer]) { -#ifdef DUMPCONSISTENCY - if (msg == KICK_MSG_CON_FAIL) SV_SavedGame(); -#endif LUA_HookBool(false, HOOK(GameQuit)); //Lua hooks handled differently now Command_ExitGame_f(); @@ -3226,9 +3309,123 @@ static void Command_ResendGamestate(void) } } +static void Command_ServerMute(void) +{ + SINT8 playernum; + UINT8 buf[2]; + + if (!netgame) + { + CONS_Printf(M_GetText("This only works in a netgame.\n")); + return; + } + + if (COM_Argc() == 1) + { + CONS_Printf(M_GetText("servermute : server mute a player's voice\n")); + return; + } + else if (!(server || IsPlayerAdmin(consoleplayer))) + { + CONS_Printf(M_GetText("Only the server or an admin can use this.\n")); + return; + } + + playernum = nametonum(COM_Argv(1)); + if (playernum == -1) + return; + + buf[0] = playernum; + buf[1] = 1; + SendNetXCmd(XD_SERVERMUTEPLAYER, buf, 2); +} + +static void Command_ServerUnmute(void) +{ + SINT8 playernum; + UINT8 buf[2]; + + if (!netgame) + { + CONS_Printf(M_GetText("This only works in a netgame.\n")); + return; + } + + if (COM_Argc() == 1) + { + CONS_Printf(M_GetText("serverunmute : server unmute a player's voice\n")); + return; + } + else if (!(server || IsPlayerAdmin(consoleplayer))) + { + CONS_Printf(M_GetText("Only the server or an admin can use this.\n")); + return; + } + + playernum = nametonum(COM_Argv(1)); + if (playernum == -1) + return; + + buf[0] = playernum; + buf[1] = 0; + SendNetXCmd(XD_SERVERMUTEPLAYER, &buf, 2); +} + +static void Command_ServerDeafen(void) +{ + SINT8 playernum; + UINT8 buf[2]; + + if (COM_Argc() == 1) + { + CONS_Printf(M_GetText("serverdeafen : server deafen a player\n")); + return; + } + else if (client) + { + CONS_Printf(M_GetText("Only the server can use this.\n")); + return; + } + + playernum = nametonum(COM_Argv(1)); + if (playernum == -1) + return; + + buf[0] = playernum; + buf[1] = 1; + SendNetXCmd(XD_SERVERDEAFENPLAYER, &buf, 2); +} + +static void Command_ServerUndeafen(void) +{ + SINT8 playernum; + UINT8 buf[2]; + + if (COM_Argc() == 1) + { + CONS_Printf(M_GetText("serverundeafen : server undeafen a player\n")); + return; + } + else if (client) + { + CONS_Printf(M_GetText("Only the server can use this.\n")); + return; + } + + playernum = nametonum(COM_Argv(1)); + if (playernum == -1) + return; + + buf[0] = playernum; + buf[1] = 0; + SendNetXCmd(XD_SERVERDEAFENPLAYER, buf, 2); +} + static void Got_AddPlayer(const UINT8 **p, INT32 playernum); static void Got_RemovePlayer(const UINT8 **p, INT32 playernum); static void Got_AddBot(const UINT8 **p, INT32 playernum); +static void Got_ServerMutePlayer(const UINT8 **p, INT32 playernum); +static void Got_ServerDeafenPlayer(const UINT8 **p, INT32 playernum); void Joinable_OnChange(void); void Joinable_OnChange(void) @@ -3278,12 +3475,13 @@ void D_ClientServerInit(void) RegisterNetXCmd(XD_ADDPLAYER, Got_AddPlayer); RegisterNetXCmd(XD_REMOVEPLAYER, Got_RemovePlayer); RegisterNetXCmd(XD_ADDBOT, Got_AddBot); -#ifdef DUMPCONSISTENCY - { - extern struct CVarList *cvlist_dumpconsistency; - CV_RegisterList(cvlist_dumpconsistency); - } -#endif + + COM_AddCommand("servermute", Command_ServerMute); + COM_AddCommand("serverunmute", Command_ServerUnmute); + COM_AddCommand("serverdeafen", Command_ServerDeafen); + COM_AddCommand("serverundeafen", Command_ServerUndeafen); + RegisterNetXCmd(XD_SERVERMUTEPLAYER, Got_ServerMutePlayer); + RegisterNetXCmd(XD_SERVERDEAFENPLAYER, Got_ServerDeafenPlayer); gametic = 0; localgametic = 0; @@ -3474,6 +3672,27 @@ void D_QuitNetGame(void) #endif } +static void InitializeLocalVoiceEncoder(void) +{ + // Reset voice opus encoder for local "player 1" + OpusEncoder *encoder = g_local_opus_encoder; + if (encoder != NULL) + { + opus_encoder_destroy(encoder); + encoder = NULL; + } + int error; + encoder = opus_encoder_create(48000, 1, OPUS_APPLICATION_VOIP, &error); + opus_encoder_ctl(encoder, OPUS_SET_VBR(0)); + if (error != OPUS_OK) + { + CONS_Alert(CONS_WARNING, "Failed to create Opus voice encoder: opus error %d\n", error); + encoder = NULL; + } + g_local_opus_encoder = encoder; + g_local_opus_frame = 0; +} + // Adds a node to the game (player will follow at map change or at savegame....) static inline void SV_AddNode(INT32 node) { @@ -3553,6 +3772,8 @@ static void Got_AddPlayer(const UINT8 **p, INT32 playernum) g_localplayers[i] = newplayernum; } DEBFILE("spawning me\n"); + + InitializeLocalVoiceEncoder(); } P_ForceLocalAngle(newplayer, newplayer->angleturn); @@ -3652,6 +3873,56 @@ static void Got_AddBot(const UINT8 **p, INT32 playernum) K_SetBot(newplayernum, skinnum, difficulty, style); } +// Xcmd XD_SERVERMUTEPLAYER +static void Got_ServerMutePlayer(const UINT8 **p, INT32 playernum) +{ + UINT8 forplayer = READUINT8(*p); + UINT8 muted = READUINT8(*p); + if (playernum != serverplayer) + { + CONS_Alert(CONS_WARNING, M_GetText("Illegal server mute player cmd from %s\n"), player_names[playernum]); + if (server) + { + SendKick(playernum, KICK_MSG_CON_FAIL); + } + } + if (muted && !(players[forplayer].pflags2 & PF2_SERVERMUTE)) + { + players[forplayer].pflags2 |= PF2_SERVERMUTE; + HU_AddChatText(va("\x82* %s was server muted.", player_names[forplayer]), false); + } + else if (!muted && players[forplayer].pflags2 & PF2_SERVERMUTE) + { + players[forplayer].pflags2 &= ~PF2_SERVERMUTE; + HU_AddChatText(va("\x82* %s was server unmuted.", player_names[forplayer]), false); + } +} + +// Xcmd XD_SERVERDEAFENPLAYER +static void Got_ServerDeafenPlayer(const UINT8 **p, INT32 playernum) +{ + UINT8 forplayer = READUINT8(*p); + UINT8 deafened = READUINT8(*p); + if (playernum != serverplayer) + { + CONS_Alert(CONS_WARNING, M_GetText("Illegal server deafen player cmd from %s\n"), player_names[playernum]); + if (server) + { + SendKick(playernum, KICK_MSG_CON_FAIL); + } + } + if (deafened && !(players[forplayer].pflags2 & PF2_SERVERDEAFEN)) + { + players[forplayer].pflags2 |= PF2_SERVERDEAFEN; + HU_AddChatText(va("\x82* %s was server deafened.", player_names[forplayer]), false); + } + else if (!deafened && players[forplayer].pflags2 & PF2_SERVERDEAFEN) + { + players[forplayer].pflags2 &= ~PF2_SERVERDEAFEN; + HU_AddChatText(va("\x82* %s was server undeafened.", player_names[forplayer]), false); + } +} + static boolean SV_AddWaitingPlayers(SINT8 node, UINT8 *availabilities, const char *name, uint8_t *key, UINT16 *pwr, const char *name2, uint8_t *key2, UINT16 *pwr2, @@ -4192,8 +4463,19 @@ static void HandleConnect(SINT8 node) if (netgame && sigcheck != 0) { - SV_SendRefuse(node, M_GetText("Signature verification failed.")); - return; + uint8_t allZero[SIGNATURELENGTH]; + memset(allZero, 0, SIGNATURELENGTH); + if (memcmp(netbuffer->u.clientcfg.challengeResponse[i], allZero, SIGNATURELENGTH) == 0) + { + // The connecting client didn't even try to sign this, probably due to an IP mismatch. + // Let them in as a guest. + memset(lastReceivedKey[node][i], 0, PUBKEYLENGTH); + } + else + { + SV_SendRefuse(node, M_GetText("Signature verification failed.")); + return; + } } } @@ -4356,7 +4638,7 @@ static void HandleServerInfo(SINT8 node) static void PT_WillResendGamestate(void) { - char tmpsave[256]; + char tmpsave[1024]; if (server || cl_redownloadinggamestate) return; @@ -4395,6 +4677,20 @@ static void PT_CanReceiveGamestate(SINT8 node) CONS_Printf(M_GetText("Resending game state to %s...\n"), player_names[nodetoplayer[node]]); + extern consvar_t cv_dumpconsistency; + if (cv_dumpconsistency.value) + { + char dump_name[1024]; + snprintf( + dump_name, sizeof(dump_name), + "%s_%u_%s-server.consdump", + server_context, + gametic, + player_names[nodetoplayer[node]] + ); + CL_DumpConsistency(dump_name); + } + SV_SendSaveGame(node, true); // Resend a complete game state resendingsavegame[node] = true; } @@ -4767,7 +5063,293 @@ static void PT_Say(int node) DoSayCommand(say.message, say.target, say.flags, say.source); } -static char NodeToSplitPlayer(int node, int split) +static void PT_ReqMapQueue(int node) +{ + if (client) + return; // Only sent to servers, why are we receiving this? + + reqmapqueue_pak reqmapqueue = netbuffer->u.reqmapqueue; + + // Check for a spoofed source. + if (reqmapqueue.source == serverplayer) + { + // Servers aren't guaranteed to have a playernode, dedis exist. + if (node != servernode) + return; + } + else + { + if (playernode[reqmapqueue.source] != node) + return; + + if (!IsPlayerAdmin(reqmapqueue.source)) + { + CONS_Debug(DBG_NETPLAY,"Received illegal request map queue cmd from Player %d (%s).\n", reqmapqueue.source+1, player_names[reqmapqueue.source]); + SendKick(reqmapqueue.source, KICK_MSG_CON_FAIL); + return; + } + } + + const boolean doclear = (reqmapqueue.newgametype == ROUNDQUEUE_CMD_CLEAR); + + // The following prints will only appear when multiple clients + // attempt to affect the round queue at similar time increments + if (doclear == true) + { + if (roundqueue.size == 0) + { + // therefore this one doesn't really need a error print + // because what both players wanted was done anyways + //CONS_Alert(CONS_ERROR, "queuemap: Queue is already empty!\n"); + return; + } + } + else if (reqmapqueue.newgametype == ROUNDQUEUE_CMD_SHOW) + { + char maprevealmsg[256]; + if (roundqueue.size == 0) + { + strlcpy(maprevealmsg, "There are no Rounds queued.", 256); + } + else if (roundqueue.position >= roundqueue.size) + { + strlcpy(maprevealmsg, "There are no more Rounds queued!", 256); + } + else + { + char *title = G_BuildMapTitle(roundqueue.entries[roundqueue.position].mapnum + 1); + + strlcpy( + maprevealmsg, + va("The next Round will be on \"%s\".", title), + 256 + ); + + Z_Free(title); + } + DoSayCommand(maprevealmsg, 0, HU_SHOUT, servernode); + + return; + } + else if (roundqueue.size >= ROUNDQUEUE_MAX) + { + CONS_Alert(CONS_ERROR, "Recieved REQMAPQUEUE, but unable to add map beyond %u\n", roundqueue.size); + + // But this one does, because otherwise it's silent failure! + char rejectmsg[256]; + strlcpy(rejectmsg, "The server couldn't queue your chosen map.", 256); + SendServerNotice(reqmapqueue.source, rejectmsg); + + return; + } + + if (reqmapqueue.newmapnum == NEXTMAP_VOTING) + { + UINT8 numPlayers = 0, i; + for (i = 0; i < MAXPLAYERS; ++i) + { + if (!playeringame[i] || players[i].spectator) + { + continue; + } + + extern consvar_t cv_forcebots; // debug + + if (!(gametypes[reqmapqueue.newgametype]->rules & GTR_BOTS) && players[i].bot && !cv_forcebots.value) + { + // Gametype doesn't support bots + continue; + } + + numPlayers++; + } + + reqmapqueue.newmapnum = G_RandMapPerPlayerCount(G_TOLFlag(reqmapqueue.newgametype), UINT16_MAX, false, false, NULL, numPlayers); + } + + if (reqmapqueue.newmapnum >= nummapheaders) + { + CONS_Alert(CONS_ERROR, "Recieved REQMAPQUEUE, but unable to add map of invalid ID (%u)\n", reqmapqueue.newmapnum); + + char rejectmsg[256]; + strlcpy(rejectmsg, "The server couldn't queue your chosen map.", 256); + SendServerNotice(reqmapqueue.source, rejectmsg); + + return; + } + + G_AddMapToBuffer(reqmapqueue.newmapnum); + + UINT8 buf[1+2+1]; + UINT8 *buf_p = buf; + + WRITEUINT8(buf_p, reqmapqueue.flags); + WRITEUINT16(buf_p, reqmapqueue.newgametype); + + WRITEUINT8(buf_p, roundqueue.size); + + // Match Got_MapQueuecmd, but with the addition of reqmapqueue.newmapnum available to us + if (doclear == true) + { + memset(&roundqueue, 0, sizeof(struct roundqueue)); + } + else + { + G_MapIntoRoundQueue( + reqmapqueue.newmapnum, + reqmapqueue.newgametype, + ((reqmapqueue.flags & 1) != 0), + false + ); + } + + SendNetXCmd(XD_MAPQUEUE, buf, buf_p - buf); +} + +static void PT_HandleVoiceClient(SINT8 node, boolean isserver) +{ + if (!isserver && node != servernode) + { + // We should never receive voice packets from anything other than the server + return; + } + + if (dedicated) + { + // don't bother decoding on dedicated + return; + } + + doomdata_t *pak = (doomdata_t*)(doomcom->data); + voice_pak *pl = &pak->u.voice; + + UINT64 framenum = (UINT64)LONGLONG(pl->frame); + INT32 playernum = pl->flags & VOICE_PAK_FLAGS_PLAYERNUM_BITS; + if (playernum >= MAXPLAYERS || playernum < 0) + { + // ignore + return; + } + + boolean terminal = (pl->flags & VOICE_PAK_FLAGS_TERMINAL_BIT) > 0; + UINT32 framesize = doomcom->datalength - BASEPACKETSIZE - sizeof(voice_pak); + UINT8 *frame = (UINT8*)(pl) + sizeof(voice_pak); + + OpusDecoder *decoder = g_player_opus_decoders[playernum]; + if (decoder == NULL) + { + return; + } + float *decoded_out = Z_Malloc(sizeof(float) * SRB2_VOICE_OPUS_FRAME_SIZE, PU_STATIC, NULL); + + INT32 decoded_samples = 0; + UINT64 missedframes = 0; + if (framenum > g_player_opus_lastframe[playernum]) + { + missedframes = min((framenum - g_player_opus_lastframe[playernum]) - 1, 16); + } + + for (UINT64 i = 0; i < missedframes; i++) + { + decoded_samples = opus_decode_float(decoder, NULL, 0, decoded_out, SRB2_VOICE_OPUS_FRAME_SIZE, 0); + if (decoded_samples < 0) + { + continue; + } + if (cv_voice_chat.value != 0 && playernum != g_localplayers[0]) + { + S_QueueVoiceFrameFromPlayer(playernum, (void*)decoded_out, decoded_samples * sizeof(float), false); + } + } + g_player_opus_lastframe[playernum] = framenum; + + decoded_samples = opus_decode_float(decoder, frame, framesize, decoded_out, SRB2_VOICE_OPUS_FRAME_SIZE, 0); + if (decoded_samples < 0) + { + Z_Free(decoded_out); + return; + } + + if (cv_voice_chat.value != 0 && playernum != g_localplayers[0]) + { + S_QueueVoiceFrameFromPlayer(playernum, (void*)decoded_out, decoded_samples * sizeof(float), terminal); + } + S_SetPlayerVoiceActive(playernum); + + Z_Free(decoded_out); +} + +static void PT_HandleVoiceServer(SINT8 node) +{ + // Relay to client nodes except the sender + doomdata_t *pak = (doomdata_t*)(doomcom->data); + voice_pak *pl = &pak->u.voice; + int playernum = -1; + player_t *player; + + if (cv_voice_servermute.value != 0) + { + // Don't even relay voice packets if voice_servermute is on + return; + } + + if ((pl->flags & VOICE_PAK_FLAGS_PLAYERNUM_BITS) > 0 || (pl->flags & VOICE_PAK_FLAGS_RESERVED_BITS) > 0) + { + // All bits except the terminal bit must be unset when sending to client + // Anything else is an illegal message + return; + } + + playernum = nodetoplayer[node]; + if (!(playernum >= 0 && playernum < MAXPLAYERS)) + { + return; + } + player = &players[playernum]; + + if (player->pflags2 & (PF2_SELFMUTE | PF2_SELFDEAFEN | PF2_SERVERMUTE | PF2_SERVERDEAFEN)) + { + // ignore, they should not be able to broadcast voice + return; + } + + // Preserve terminal bit, blank all other bits + pl->flags &= VOICE_PAK_FLAGS_TERMINAL_BIT; + // Add playernum to lower bits + pl->flags |= (playernum & VOICE_PAK_FLAGS_PLAYERNUM_BITS); + + for (int i = 0; i < MAXPLAYERS; i++) + { + UINT8 pnode = playernode[i]; + if (pnode == UINT8_MAX) + { + continue; + } + + // Is this node P1 on that node? + boolean isp1onnode = nodetoplayer[pnode] >= 0 && nodetoplayer[pnode] < MAXPLAYERS; + + if (pnode != node && pnode != servernode && isp1onnode && !(players[i].pflags2 & (PF2_SELFDEAFEN | PF2_SERVERDEAFEN))) + { + HSendPacket(pnode, false, 0, doomcom->datalength - BASEPACKETSIZE); + } + } + PT_HandleVoiceClient(node, true); +} + +static void PT_HandleVoice(SINT8 node) +{ + if (server) + { + PT_HandleVoiceServer(node); + } + else + { + PT_HandleVoiceClient(node, false); + } +} + +static int8_t NodeToSplitPlayer(int node, int split) { if (split == 0) return nodetoplayer[node]; @@ -4984,32 +5566,21 @@ static void HandlePacketFromPlayer(SINT8 node) && !resendingsavegame[node] && savegameresendcooldown[node] <= I_GetTime() && !SV_ResendingSavegameToAnyone()) { - if (cv_resynchattempts.value) - { - // Tell the client we are about to resend them the gamestate - netbuffer->packettype = PT_WILLRESENDGAMESTATE; - HSendPacket(node, true, 0, 0); + // Tell the client we are about to resend them the gamestate + netbuffer->packettype = PT_WILLRESENDGAMESTATE; + HSendPacket(node, true, 0, 0); - resendingsavegame[node] = true; + resendingsavegame[node] = true; - if (cv_blamecfail.value) - CONS_Printf(M_GetText("Synch failure for player %d (%s); expected %hd, got %hd\n"), - netconsole+1, player_names[netconsole], - consistancy[realstart%BACKUPTICS], - SHORT(netbuffer->u.clientpak.consistancy)); - DEBFILE(va("Restoring player %d (synch failure) [%update] %d!=%d\n", - netconsole, realstart, consistancy[realstart%BACKUPTICS], - SHORT(netbuffer->u.clientpak.consistancy))); - break; - } - else - { - SendKick(netconsole, KICK_MSG_CON_FAIL); - DEBFILE(va("player %d kicked (synch failure) [%u] %d!=%d\n", - netconsole, realstart, consistancy[realstart%BACKUPTICS], - SHORT(netbuffer->u.clientpak.consistancy))); - break; - } + if (cv_blamecfail.value) + CONS_Printf(M_GetText("Synch failure for player %d (%s); expected %hd, got %hd\n"), + netconsole+1, player_names[netconsole], + consistancy[realstart%BACKUPTICS], + SHORT(netbuffer->u.clientpak.consistancy)); + DEBFILE(va("Restoring player %d (synch failure) [%update] %d!=%d\n", + netconsole, realstart, consistancy[realstart%BACKUPTICS], + SHORT(netbuffer->u.clientpak.consistancy))); + break; } break; case PT_BASICKEEPALIVE: @@ -5112,6 +5683,9 @@ static void HandlePacketFromPlayer(SINT8 node) case PT_SAY: PT_Say(node); break; + case PT_REQMAPQUEUE: + PT_ReqMapQueue(node); + break; case PT_LOGIN: if (client) break; @@ -5328,11 +5902,11 @@ static void HandlePacketFromPlayer(SINT8 node) memcpy(lastChallengeAll, netbuffer->u.challengeall.secret, sizeof(lastChallengeAll)); shouldsign_t safe = ShouldSignChallenge(lastChallengeAll); - if (safe != SIGN_OK) + if (safe == SIGN_BADIP) + forceGuest = true; + else if (safe != SIGN_OK) { - if (safe == SIGN_BADIP) - HandleSigfail("External server sent the wrong IP"); - else if (safe == SIGN_BADTIME) + if (safe == SIGN_BADTIME) HandleSigfail("Bad timestamp - is your time set correctly?"); else HandleSigfail("Unknown auth error - contact a developer"); @@ -5357,7 +5931,7 @@ static void HandlePacketFromPlayer(SINT8 node) { uint8_t signature[SIGNATURELENGTH]; profile_t *localProfile = PR_GetLocalPlayerProfile(challengeplayers); - if (!PR_IsLocalPlayerGuest(challengeplayers)) // GUESTS don't have keys + if (!PR_IsLocalPlayerGuest(challengeplayers) && !forceGuest) // GUESTS don't have keys { crypto_eddsa_sign(signature, localProfile->secret_key, lastChallengeAll, sizeof(lastChallengeAll)); @@ -5453,6 +6027,9 @@ static void HandlePacketFromPlayer(SINT8 node) csprng(lastChallengeAll, sizeof(lastChallengeAll)); expectChallenge = false; break; + case PT_VOICE: + PT_HandleVoice(node); + break; default: DEBFILE(va("UNKNOWN PACKET TYPE RECEIVED %d from host %d\n", netbuffer->packettype, node)); @@ -6600,6 +7177,9 @@ void NetKeepAlive(void) Net_AckTicker(); HandleNodeTimeouts(); FileSendTicker(); + + // Update voice whenever possible. + NetVoiceUpdate(); } // If a tree falls in the forest but nobody is around to hear it, does it make a tic? @@ -6787,6 +7367,138 @@ void NetUpdate(void) FileSendTicker(); } +void NetVoiceUpdate(void) +{ + UINT8 *encoded = NULL; + + if (dedicated) + { + return; + } + + // This necessarily runs every frame, not every tic + S_SoundInputSetEnabled(true); + + UINT32 bytes_dequed = 0; + do + { + // We need to drain the input queue completely, so do this in a full loop + + INT32 to_read = (SRB2_VOICE_OPUS_FRAME_SIZE - g_local_voice_buffer_len) * sizeof(float); + if (to_read > 0) + { + // Attempt to fill the voice frame buffer + + bytes_dequed = S_SoundInputDequeueSamples((void*)(g_local_voice_buffer + g_local_voice_buffer_len), to_read); + g_local_voice_buffer_len += bytes_dequed / 4; + } + else + { + bytes_dequed = 0; + } + + if (g_local_voice_buffer_len < SRB2_VOICE_OPUS_FRAME_SIZE) + { + continue; + } + + // Amp of +10 dB is appromiately "twice as loud" + float ampfactor = powf(10, (float) cv_voice_inputamp.value / 20.f); + for (int i = 0; i < g_local_voice_buffer_len; i++) + { + g_local_voice_buffer[i] *= ampfactor; + } + + float softmem = 0.f; + opus_pcm_soft_clip(g_local_voice_buffer, SRB2_VOICE_OPUS_FRAME_SIZE, 1, &softmem); + + // Voice detection gate open/close + float maxamplitude = 0.f; + for (int i = 0; i < g_local_voice_buffer_len; i++) + { + maxamplitude = max(fabsf(g_local_voice_buffer[i]), maxamplitude); + } + // 20. * log_10(amplitude) -> decibels (up to 0) + // lower than -30 dB is usually inaudible + g_local_voice_last_peak = maxamplitude; + maxamplitude = 20.f * logf(maxamplitude); + if (maxamplitude > (float) cv_voice_activationthreshold.value) + { + g_local_voice_threshold_time = I_GetTime(); + g_local_voice_detected = true; + } + + switch (cv_voice_mode.value) + { + case 0: + if (I_GetTime() - g_local_voice_threshold_time > 15) + { + g_local_voice_buffer_len = 0; + g_local_voice_detected = false; + continue; + } + break; + case 1: + if (!g_voicepushtotalk_on) + { + g_local_voice_buffer_len = 0; + g_local_voice_detected = false; + continue; + } + g_local_voice_detected = true; + break; + default: + g_local_voice_buffer_len = 0; + continue; + } + + if (cv_voice_chat.value == 0) + { + g_local_voice_buffer_len = 0; + continue; + } + + if (!encoded) + { + encoded = Z_Malloc(sizeof(UINT8) * 1400, PU_STATIC, NULL); + } + + if (g_local_opus_encoder == NULL) + { + InitializeLocalVoiceEncoder(); + } + OpusEncoder *encoder = g_local_opus_encoder; + + INT32 result = opus_encode_float(encoder, g_local_voice_buffer, SRB2_VOICE_OPUS_FRAME_SIZE, encoded, 1400); + if (result < 0) + { + continue; + } + + // Only send a voice packet and set local player voice active if: + // 1. In a netgame, + // 2. Not self-muted by cvar + // 3. The consoleplayer is not server or self muted or deafened + if (netgame && !cv_voice_selfmute.value && !(players[consoleplayer].pflags2 & (PF2_SERVERMUTE | PF2_SELFMUTE | PF2_SELFDEAFEN | PF2_SERVERDEAFEN))) + { + DoVoicePacket(servernode, g_local_opus_frame, encoded, result); + S_SetPlayerVoiceActive(consoleplayer); + } + + if (cv_voice_loopback.value) + { + result = opus_decode_float(g_player_opus_decoders[consoleplayer], encoded, result, g_local_voice_buffer, SRB2_VOICE_OPUS_FRAME_SIZE, 0); + S_QueueVoiceFrameFromPlayer(consoleplayer, g_local_voice_buffer, result * sizeof(float), false); + } + + g_local_voice_buffer_len = 0; + g_local_opus_frame += 1; + } while (bytes_dequed > 0); + + if (encoded) Z_Free(encoded); + return; +} + /** Returns the number of players playing. * \return Number of players. Can be zero if we're running a ::dedicated * server. @@ -6967,6 +7679,17 @@ void DoSayPacketFromCommand(SINT8 target, size_t usedargs, UINT8 flags) DoSayPacket(target, flags, consoleplayer, msg); } +void DoVoicePacket(SINT8 target, UINT64 frame, const UINT8* opusdata, size_t len) +{ + voice_pak *pl = &netbuffer->u.voice; + netbuffer->packettype = PT_VOICE; + pl->frame = (UINT64)LONGLONG(frame); + pl->flags = 0; + I_Assert(MAXPACKETLENGTH - sizeof(voice_pak) - BASEPACKETSIZE >= len); + memcpy((UINT8*)netbuffer + BASEPACKETSIZE + sizeof(voice_pak), opusdata, len); + HSendPacket(target, false, 0, sizeof(voice_pak) + len); +} + // This is meant to be targeted at player indices, not whatever the hell XD_SAY is doing with 1-indexed players. void SendServerNotice(SINT8 target, char *message) { diff --git a/src/d_clisrv.h b/src/d_clisrv.h index 3b6889fcb..de606acb6 100644 --- a/src/d_clisrv.h +++ b/src/d_clisrv.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // @@ -135,6 +135,10 @@ typedef enum PT_SAY, // "Hey server, please send this chat message to everyone via XD_SAY" + PT_REQMAPQUEUE, // Client requesting a roundqueue operation + + PT_VOICE, // Voice packet for either side + NUMPACKETTYPE } packettype_t; @@ -281,6 +285,7 @@ struct clientconfig_pak #define SV_SPEEDMASK 0x03 // used to send kartspeed #define SV_DEDICATED 0x40 // server is dedicated +#define SV_VOICEENABLED 0x80 // voice_mute is off/voice chat is enabled #define SV_LOTSOFADDONS 0x20 // flag used to ask for full file list in d_netfil #define MAXFILENEEDED 915 @@ -357,7 +362,7 @@ struct plrconfig UINT16 color; UINT32 pflags; UINT32 score; - UINT8 ctfteam; + UINT8 team; } ATTRPACK; struct filesneededconfig_pak @@ -401,6 +406,14 @@ struct say_pak UINT8 source; } ATTRPACK; +struct reqmapqueue_pak +{ + UINT16 newmapnum; + UINT16 newgametype; + UINT8 flags; + UINT8 source; +} ATTRPACK; + struct netinfo_pak { UINT32 pingtable[MAXPLAYERS+1]; @@ -408,6 +421,22 @@ struct netinfo_pak UINT32 delay[MAXPLAYERS+1]; } ATTRPACK; +// Sent by both sides. Contains Opus-encoded voice packet +// flags bitset map (left to right, low to high) +// | PPPPPTRR | -- P = Player num, T = Terminal, R = Reserved (0) +// Data following voice header is a single Opus frame +struct voice_pak +{ + UINT64 frame; + UINT8 flags; +} ATTRPACK; + +#define VOICE_PAK_FLAGS_PLAYERNUM_BITS 0x1F +#define VOICE_PAK_FLAGS_TERMINAL_BIT 0x20 +#define VOICE_PAK_FLAGS_RESERVED0_BIT 0x40 +#define VOICE_PAK_FLAGS_RESERVED1_BIT 0x80 +#define VOICE_PAK_FLAGS_RESERVED_BITS (VOICE_PAK_FLAGS_RESERVED0_BIT | VOICE_PAK_FLAGS_RESERVED1_BIT) + // // Network packet data // @@ -451,6 +480,8 @@ struct doomdata_t responseall_pak responseall; // 256 bytes resultsall_pak resultsall; // 1024 bytes. Also, you really shouldn't trust anything here. say_pak say; // I don't care anymore. + reqmapqueue_pak reqmapqueue; // Formerly XD_REQMAPQUEUE + voice_pak voice; // Unreliable voice data, variable length } u; // This is needed to pack diff packet types data together } ATTRPACK; @@ -554,7 +585,7 @@ extern boolean server_lagless; extern consvar_t cv_mindelay; extern consvar_t cv_netticbuffer, cv_allownewplayer, cv_maxconnections, cv_joindelay; -extern consvar_t cv_pingtimeout, cv_resynchattempts, cv_blamecfail; +extern consvar_t cv_pingtimeout, cv_blamecfail; extern consvar_t cv_maxsend, cv_noticedownload, cv_downloadspeed; #ifdef VANILLAJOINNEXTROUND @@ -595,6 +626,7 @@ void SendKick(UINT8 playernum, UINT8 msg); // Create any new ticcmds and broadcast to other players. void NetKeepAlive(void); void NetUpdate(void); +void NetVoiceUpdate(void); void SV_StartSinglePlayerServer(INT32 dogametype, boolean donetgame); boolean SV_SpawnServer(void); @@ -699,6 +731,7 @@ void HandleSigfail(const char *string); void DoSayPacket(SINT8 target, UINT8 flags, UINT8 source, char *message); void DoSayPacketFromCommand(SINT8 target, size_t usedargs, UINT8 flags); +void DoVoicePacket(SINT8 target, UINT64 frame, const UINT8* opusdata, size_t len); void SendServerNotice(SINT8 target, char *message); #ifdef __cplusplus diff --git a/src/d_event.h b/src/d_event.h index cd6029683..89dfaeb0b 100644 --- a/src/d_event.h +++ b/src/d_event.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/d_main.cpp b/src/d_main.cpp index 0f4191c9d..7afb32df7 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -141,8 +141,10 @@ INT32 window_y; // // DEMO LOOP // -static char *startupiwads[MAX_WADFILES]; -static char *startuppwads[MAX_WADFILES]; +static size_t num_startupiwads = 0; +static initmultiplefilesentry_t startupiwads[MAX_WADFILES]; +static size_t num_startuppwads = 0; +static initmultiplefilesentry_t startuppwads[MAX_WADFILES]; boolean devparm = false; // started game with -devparm @@ -161,6 +163,7 @@ INT32 postimgparam[MAXSPLITSCREENPLAYERS]; boolean sound_disabled = false; boolean digital_disabled = false; +boolean g_voice_disabled = false; #ifdef DEBUGFILE INT32 debugload = 0; @@ -586,7 +589,7 @@ static bool D_Display(bool world) HWR_RenderPlayerView(); else #endif - if (rendermode != render_none) + if (rendermode == render_soft) { if (i > 0) // Splitscreen-specific { @@ -894,6 +897,12 @@ void D_SRB2Loop(void) because I_FinishUpdate was called afterward */ + // Make sure audio volume is initialized since S_UpdateSounds won't be called during the + // initial wipe. + S_SetMasterVolume(); + S_SetMusicVolume(); + S_SetSfxVolume(); + for (;;) { // capbudget is the minimum precise_t duration of a single loop iteration @@ -1073,6 +1082,7 @@ void D_SRB2Loop(void) // consoleplayer -> displayplayers (hear sounds from viewpoint) S_UpdateSounds(); // move positional sounds + NetVoiceUpdate(); // update voice recording whenever possible if (realtics > 0 || singletics) { S_UpdateClosedCaptions(); @@ -1089,6 +1099,7 @@ void D_SRB2Loop(void) #endif Music_Tick(); + S_UpdateVoicePositionalProperties(); // Fully completed frame made. finishprecise = I_GetPreciseTime(); @@ -1273,31 +1284,37 @@ boolean D_IsDeferredStartTitle(void) // // D_AddFile // -static void D_AddFile(char **list, const char *file) +static void D_AddFile(initmultiplefilesentry_t *list, size_t index, const char *file, const char *md5sum) { - size_t pnumwadfiles; - char *newfile; - - for (pnumwadfiles = 0; list[pnumwadfiles]; pnumwadfiles++) - ; - - newfile = static_cast(malloc(strlen(file) + 1)); - if (!newfile) + char *filecopy = NULL; + if (file) { - I_Error("No more free memory to AddFile %s",file); + size_t len = strlen(file) + 1; + filecopy = (char*)malloc(len); + memcpy(filecopy, file, len); } - strcpy(newfile, file); - - list[pnumwadfiles] = newfile; + char *md5copy = NULL; + if (md5sum) + { + size_t len = strlen(md5sum) + 1; + md5copy = (char*)malloc(len); + memcpy(md5copy, md5sum, len); + } + list[index].filename = filecopy; + list[index].md5sum = md5copy; } -static inline void D_CleanFile(char **list) +static inline void D_CleanFile(initmultiplefilesentry_t *list, size_t count) { - size_t pnumwadfiles; - for (pnumwadfiles = 0; list[pnumwadfiles]; pnumwadfiles++) + size_t i; + for (i = 0; i < count; ++i) { - free(list[pnumwadfiles]); - list[pnumwadfiles] = NULL; + if (list[i].filename != NULL) + free((void*)list[i].filename); + list[i].filename = NULL; + if (list[i].md5sum != NULL) + free((void*)list[i].md5sum); + list[i].md5sum = NULL; } } @@ -1345,7 +1362,7 @@ static boolean AddIWAD(void) if (FIL_ReadFileOK(path)) { - D_AddFile(startupiwads, path); + D_AddFile(startupiwads, num_startupiwads++, path, ASSET_HASH_BIOS_PK3); return true; } else @@ -1386,22 +1403,18 @@ static void IdentifyVersion(void) snprintf(configfile, sizeof configfile, "%s" PATHSEP CONFIGFILENAME, srb2waddir); configfile[sizeof configfile - 1] = '\0'; - // if you change the ordering of this or add/remove a file, be sure to update the md5 - // checking in D_SRB2Main - - D_AddFile(startupiwads, va(spandf,srb2waddir,"data","scripts.pk3")); - D_AddFile(startupiwads, va(spandf,srb2waddir,"data","gfx.pk3")); - D_AddFile(startupiwads, va(spandf,srb2waddir,"data","textures_general.pk3")); - D_AddFile(startupiwads, va(spandf,srb2waddir,"data","textures_segazones.pk3")); - D_AddFile(startupiwads, va(spandf,srb2waddir,"data","textures_originalzones.pk3")); - D_AddFile(startupiwads, va(spandf,srb2waddir,"data","chars.pk3")); - D_AddFile(startupiwads, va(spandf,srb2waddir,"data","followers.pk3")); - D_AddFile(startupiwads, va(spandf,srb2waddir,"data","maps.pk3")); - D_AddFile(startupiwads, va(spandf,srb2waddir,"data","unlocks.pk3")); - D_AddFile(startupiwads, va(spandf,srb2waddir,"data","staffghosts.pk3")); - D_AddFile(startupiwads, va(spandf,srb2waddir,"data","shaders.pk3")); + D_AddFile(startupiwads, num_startupiwads++, va(spandf,srb2waddir,"data","scripts.pk3"), ASSET_HASH_SCRIPTS_PK3); + D_AddFile(startupiwads, num_startupiwads++, va(spandf,srb2waddir,"data","gfx.pk3"), ASSET_HASH_GFX_PK3); + D_AddFile(startupiwads, num_startupiwads++, va(spandf,srb2waddir,"data","textures_general.pk3"), ASSET_HASH_TEXTURES_GENERAL_PK3); + D_AddFile(startupiwads, num_startupiwads++, va(spandf,srb2waddir,"data","textures_segazones.pk3"), ASSET_HASH_TEXTURES_SEGAZONES_PK3); + D_AddFile(startupiwads, num_startupiwads++, va(spandf,srb2waddir,"data","textures_originalzones.pk3"), ASSET_HASH_TEXTURES_ORIGINALZONES_PK3); + D_AddFile(startupiwads, num_startupiwads++, va(spandf,srb2waddir,"data","chars.pk3"), ASSET_HASH_CHARS_PK3); + D_AddFile(startupiwads, num_startupiwads++, va(spandf,srb2waddir,"data","followers.pk3"), ASSET_HASH_FOLLOWERS_PK3); + D_AddFile(startupiwads, num_startupiwads++, va(spandf,srb2waddir,"data","maps.pk3"), ASSET_HASH_MAPS_PK3); + D_AddFile(startupiwads, num_startupiwads++, va(spandf,srb2waddir,"data","unlocks.pk3"), ASSET_HASH_UNLOCKS_PK3); + D_AddFile(startupiwads, num_startupiwads++, va(spandf,srb2waddir,"data","staffghosts.pk3"), ASSET_HASH_STAFFGHOSTS_PK3); #ifdef USE_PATCH_FILE - D_AddFile(startupiwads, va(pandf,srb2waddir,"patch.pk3")); + D_AddFile(startupiwads, num_startupiwads++, va(pandf,srb2waddir,"patch.pk3"), ASSET_HASH_PATCH_PK3); #endif #define MUSICTEST(str) \ @@ -1410,7 +1423,7 @@ static void IdentifyVersion(void) int ms = W_VerifyNMUSlumps(musicpath, false); \ if (ms == 1) \ { \ - D_AddFile(startupiwads, musicpath); \ + D_AddFile(startupiwads, num_startupiwads++, musicpath, NULL); \ musicwads++; \ } \ else if (ms == 0) \ @@ -1493,7 +1506,7 @@ void D_SRB2Main(void) // Print GPL notice for our console users (Linux) CONS_Printf( "\n\nDr. Robotnik's Ring Racers\n" - "Copyright (C) 2024 by Kart Krew\n\n" + "Copyright (C) 2025 by Kart Krew\n\n" "This program comes with ABSOLUTELY NO WARRANTY.\n\n" "This is free software, and you are welcome to redistribute it\n" "and/or modify it under the terms of the GNU General Public License\n" @@ -1679,7 +1692,7 @@ void D_SRB2Main(void) const char *s = M_GetNextParm(); if (s) // Check for NULL? - D_AddFile(startuppwads, s); + D_AddFile(startuppwads, num_startuppwads++, s, NULL); } } } @@ -1705,46 +1718,10 @@ void D_SRB2Main(void) // load wad, including the main wad file CONS_Printf("W_InitMultipleFiles(): Adding IWAD and main PWADs.\n"); - W_InitMultipleFiles(startupiwads, false); - D_CleanFile(startupiwads); - - mainwads = 0; - -#ifndef DEVELOP - // Check MD5s of autoloaded files - // Note: Do not add any files that ignore MD5! - W_VerifyFileMD5(mainwads, ASSET_HASH_BIOS_PK3); // bios.pk3 - mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_SCRIPTS_PK3); // scripts.pk3 - mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_GFX_PK3); // gfx.pk3 - mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_TEXTURES_GENERAL_PK3); // textures_general.pk3 - mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_TEXTURES_SEGAZONES_PK3); // textures_segazones.pk3 - mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_TEXTURES_ORIGINALZONES_PK3); // textures_originalzones.pk3 - mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_CHARS_PK3); // chars.pk3 - mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_FOLLOWERS_PK3); // followers.pk3 - mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_MAPS_PK3); // maps.pk3 - mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_UNLOCKS_PK3); // unlocks.pk3 - mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_STAFFGHOSTS_PK3); // staffghosts.pk3 - mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_SHADERS_PK3); // shaders.pk3 -#ifdef USE_PATCH_FILE - mainwads++; W_VerifyFileMD5(mainwads, ASSET_HASH_PATCH_PK3); // patch.pk3 -#endif -#else - mainwads++; // scripts.pk3 - mainwads++; // gfx.pk3 - mainwads++; // textures_general.pk3 - mainwads++; // textures_segazones.pk3 - mainwads++; // textures_originalzones.pk3 - mainwads++; // chars.pk3 - mainwads++; // followers.pk3 - mainwads++; // maps.pk3 - mainwads++; // unlocks.pk3 - mainwads++; // staffghosts.pk3 - mainwads++; // shaders.pk3 -#ifdef USE_PATCH_FILE - mainwads++; // patch.pk3 -#endif - -#endif //ifndef DEVELOP + W_InitMultipleFiles(startupiwads, num_startupiwads, false); + mainwads = num_startupiwads - musicwads; + D_CleanFile(startupiwads, num_startupiwads); + num_startupiwads = 0; // Load credits_def lump F_LoadCreditsDefinitions(); @@ -1764,6 +1741,8 @@ void D_SRB2Main(void) M_PasswordInit(); + W_InitShaderLookup(va(spandf, srb2path, "data", "shaders.pk3")); + //---------------------------------------------------- READY SCREEN // we need to check for dedicated before initialization of some subsystems @@ -1800,17 +1779,17 @@ void D_SRB2Main(void) S_RegisterSoundStuff(); I_RegisterSysCommands(); - + CON_SetLoadingProgress(LOADED_HUINIT); - + CONS_Printf("W_InitMultipleFiles(): Adding external PWADs.\n"); - + // HACK: Refer to https://git.do.srb2.org/KartKrew/RingRacers/-/merge_requests/29#note_61574 partadd_earliestfile = numwadfiles; - W_InitMultipleFiles(startuppwads, true); - + W_InitMultipleFiles(startuppwads, num_startuppwads, true); + // Only search for pwad maps and reload graphics if we actually have a pwad added - if (startuppwads[0] != NULL) + if (num_startuppwads > 0) { // // search for pwad maps @@ -1818,8 +1797,9 @@ void D_SRB2Main(void) P_InitMapData(); HU_LoadGraphics(); } - - D_CleanFile(startuppwads); + + D_CleanFile(startuppwads, num_startuppwads); + num_startuppwads = 0; partadd_earliestfile = UINT16_MAX; CON_SetLoadingProgress(LOADED_PWAD); @@ -1879,12 +1859,14 @@ void D_SRB2Main(void) { sound_disabled = true; digital_disabled = true; + g_voice_disabled = true; } if (M_CheckParm("-noaudio")) // combines -nosound and -nomusic { sound_disabled = true; digital_disabled = true; + g_voice_disabled = true; } else { @@ -1899,9 +1881,13 @@ void D_SRB2Main(void) if (M_CheckParm("-nodigmusic")) digital_disabled = true; // WARNING: DOS version initmusic in I_StartupSound } + if (M_CheckParm("-novoice")) + { + g_voice_disabled = true; + } } - if (!( sound_disabled && digital_disabled )) + if (!( sound_disabled && digital_disabled && g_voice_disabled )) { CONS_Printf("S_InitSfxChannels(): Setting up sound channels.\n"); I_StartupSound(); diff --git a/src/d_main.h b/src/d_main.h index 8bd4cfd06..2332215d4 100644 --- a/src/d_main.h +++ b/src/d_main.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/d_net.c b/src/d_net.cpp similarity index 99% rename from src/d_net.c rename to src/d_net.cpp index f87e2581f..8ede2419c 100644 --- a/src/d_net.c +++ b/src/d_net.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -9,7 +9,7 @@ // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -/// \file d_net.c +/// \file d_net.cpp /// \brief SRB2 network game communication and protocol, all OS independent parts. // /// Implement a Sliding window protocol without receiver window @@ -17,6 +17,8 @@ /// This protocol uses a mix of "goback n" and "selective repeat" implementation /// The NOTHING packet is sent when connection is idle to acknowledge packets +#include + #include "doomdef.h" #include "g_game.h" #include "i_time.h" @@ -200,7 +202,7 @@ static netnode_t nodes[MAXNETNODES]; // mnemonic: to use it compare to 0: cmpack(a,b)<0 is "a < b" ... FUNCMATH static INT32 cmpack(UINT8 a, UINT8 b) { - register INT32 d = a - b; + INT32 d = a - b; if (d >= 127 || d < -128) return -d; @@ -757,7 +759,7 @@ static void fprintfstring(char *s, size_t len) for (i = 0; i < len; i += 16) { fprintf(debugfile, "%10s: ", sizeu1(i)); - fprintfline(&s[i], min(len - i, 16)); + fprintfline(&s[i], std::min(len - i, 16)); } } @@ -919,7 +921,7 @@ static void DebugPrintpacket(const char *header) fprintf(debugfile, " reason %s\n", netbuffer->u.serverrefuse.reason); break; case PT_FILEFRAGMENT: { - filetx_pak *pak = (void*)&netbuffer->u.filetxpak; + filetx_pak *pak = (filetx_pak*)&netbuffer->u.filetxpak; fprintf(debugfile, " fileid %d datasize %d position %u\n", pak->fileid, (UINT16)SHORT(pak->size), (UINT32)LONG(pak->position)); @@ -1052,14 +1054,14 @@ boolean HSendPacket(INT32 node, boolean reliable, UINT8 acknum, size_t packetlen #ifdef SIGNGAMETRAFFIC if (IsPacketSigned(netbuffer->packettype)) - { + { int i; for (i = 0; i < MAXSPLITSCREENPLAYERS; i++) { const void* message = &netbuffer->u; //CONS_Printf("Signing packet type %d of length %d\n", netbuffer->packettype, packetlength); - if (PR_IsLocalPlayerGuest(i)) + if (PR_IsLocalPlayerGuest(i)) memset(netbuffer->signature[i], 0, sizeof(netbuffer->signature[i])); else crypto_eddsa_sign(netbuffer->signature[i], PR_GetLocalPlayerProfile(i)->secret_key, message, packetlength); @@ -1308,7 +1310,7 @@ SINT8 I_NetMakeNode(const char *hostname) void D_SetDoomcom(void) { if (doomcom) return; - doomcom = Z_Calloc(sizeof (doomcom_t), PU_STATIC, NULL); + doomcom = (doomcom_t*)Z_Calloc(sizeof (doomcom_t), PU_STATIC, NULL); doomcom->id = DOOMCOM_ID; doomcom->numslots = doomcom->numnodes = 1; doomcom->gametype = 0; @@ -1445,8 +1447,8 @@ struct pingcell static int pingcellcmp(const void *va, const void *vb) { const struct pingcell *a, *b; - a = va; - b = vb; + a = (struct pingcell*)va; + b = (struct pingcell*)vb; return ( a->ms - b->ms ); } diff --git a/src/d_net.h b/src/d_net.h index 7797235bc..b73be1320 100644 --- a/src/d_net.h +++ b/src/d_net.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/d_netcmd.c b/src/d_netcmd.c index 939fa84c1..56c1eeef9 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // @@ -102,13 +102,13 @@ static void Got_Addfilecmd(const UINT8 **cp, INT32 playernum); static void Got_Pause(const UINT8 **cp, INT32 playernum); static void Got_RandomSeed(const UINT8 **cp, INT32 playernum); static void Got_RunSOCcmd(const UINT8 **cp, INT32 playernum); -static void Got_Teamchange(const UINT8 **cp, INT32 playernum); -static void Got_Clearscores(const UINT8 **cp, INT32 playernum); +static void Got_Spectate(const UINT8 **cp, INT32 playernum); +static void Got_TeamChange(const UINT8 **cp, INT32 playernum); +static void Got_Setscore(const UINT8 **cp, INT32 playernum); static void Got_DiscordInfo(const UINT8 **cp, INT32 playernum); static void Got_ScheduleTaskcmd(const UINT8 **cp, INT32 playernum); static void Got_ScheduleClearcmd(const UINT8 **cp, INT32 playernum); static void Got_Automatecmd(const UINT8 **cp, INT32 playernum); -static void Got_RequestMapQueuecmd(const UINT8 **cp, INT32 playernum); static void Got_MapQueuecmd(const UINT8 **cp, INT32 playernum); static void Got_Cheat(const UINT8 **cp, INT32 playernum); @@ -155,14 +155,9 @@ static void Command_ExitLevel_f(void); static void Command_Showmap_f(void); static void Command_Mapmd5_f(void); -static void Command_Teamchange_f(void); -static void Command_Teamchange2_f(void); -static void Command_Teamchange3_f(void); -static void Command_Teamchange4_f(void); - static void Command_ServerTeamChange_f(void); -static void Command_Clearscores_f(void); +static void Command_Setscore_f(void); // Remote Administration static void Command_Changepassword_f(void); @@ -293,8 +288,8 @@ const char *netxcmdnames[MAXNETXCMD - 1] = "ADDFILE", // XD_ADDFILE "PAUSE", // XD_PAUSE "ADDPLAYER", // XD_ADDPLAYER - "TEAMCHANGE", // XD_TEAMCHANGE - "CLEARSCORES", // XD_CLEARSCORES + "SPECTATE", // XD_SPECTATE + "SETSCORE", // XD_SETSCORE "VERIFIED", // XD_VERIFIED "RANDOMSEED", // XD_RANDOMSEED "RUNSOC", // XD_RUNSOC @@ -321,10 +316,11 @@ const char *netxcmdnames[MAXNETXCMD - 1] = "SCHEDULETASK", // XD_SCHEDULETASK "SCHEDULECLEAR", // XD_SCHEDULECLEAR "AUTOMATE", // XD_AUTOMATE - "REQMAPQUEUE", // XD_REQMAPQUEUE + "", "MAPQUEUE", // XD_MAPQUEUE "CALLZVOTE", // XD_CALLZVOTE "SETZVOTE", // XD_SETZVOTE + "TEAMCHANGE", // XD_TEAMCHANGE }; // ========================================================================= @@ -372,7 +368,6 @@ void D_RegisterServerCommands(void) RegisterNetXCmd(XD_SCHEDULETASK, Got_ScheduleTaskcmd); RegisterNetXCmd(XD_SCHEDULECLEAR, Got_ScheduleClearcmd); RegisterNetXCmd(XD_AUTOMATE, Got_Automatecmd); - RegisterNetXCmd(XD_REQMAPQUEUE, Got_RequestMapQueuecmd); RegisterNetXCmd(XD_MAPQUEUE, Got_MapQueuecmd); RegisterNetXCmd(XD_CHEAT, Got_Cheat); @@ -388,11 +383,12 @@ void D_RegisterServerCommands(void) COM_AddCommand("motd", Command_MotD_f); RegisterNetXCmd(XD_SETMOTD, Got_MotD_f); // For remote admin - RegisterNetXCmd(XD_TEAMCHANGE, Got_Teamchange); + RegisterNetXCmd(XD_SPECTATE, Got_Spectate); + RegisterNetXCmd(XD_TEAMCHANGE, Got_TeamChange); COM_AddCommand("serverchangeteam", Command_ServerTeamChange_f); - RegisterNetXCmd(XD_CLEARSCORES, Got_Clearscores); - COM_AddDebugCommand("clearscores", Command_Clearscores_f); + RegisterNetXCmd(XD_SETSCORE, Got_Setscore); + COM_AddDebugCommand("setscore", Command_Setscore_f); COM_AddCommand("map", Command_Map_f); COM_AddDebugCommand("randommap", Command_RandomMap); COM_AddCommand("restartlevel", Command_RestartLevel); @@ -504,11 +500,6 @@ void D_RegisterClientCommands(void) if (dedicated) return; - COM_AddCommand("changeteam", Command_Teamchange_f); - COM_AddCommand("changeteam2", Command_Teamchange2_f); - COM_AddCommand("changeteam3", Command_Teamchange3_f); - COM_AddCommand("changeteam4", Command_Teamchange4_f); - COM_AddCommand("invite", Command_Invite_f); COM_AddCommand("cancelinvite", Command_CancelInvite_f); COM_AddCommand("acceptinvite", Command_AcceptInvite_f); @@ -562,7 +553,7 @@ void D_RegisterClientCommands(void) COM_AddDebugCommand("setrings", Command_Setrings_f); COM_AddDebugCommand("setspheres", Command_Setspheres_f); COM_AddDebugCommand("setlives", Command_Setlives_f); - COM_AddDebugCommand("setscore", Command_Setscore_f); + COM_AddDebugCommand("setroundscore", Command_Setroundscore_f); COM_AddDebugCommand("devmode", Command_Devmode_f); COM_AddDebugCommand("savecheckpoint", Command_Savecheckpoint_f); COM_AddDebugCommand("scale", Command_Scale_f); @@ -996,11 +987,6 @@ static void SendNameAndColor(const UINT8 n) cv_follower[n].value = -1; } - if (sendColor == SKINCOLOR_NONE) - { - sendColor = skins[cv_skin[n].value].prefcolor; - } - if (sendFollowerColor == SKINCOLOR_NONE) { if (cv_follower[n].value >= 0) @@ -1015,11 +1001,11 @@ static void SendNameAndColor(const UINT8 n) // Don't send if everything was identical. if (!strcmp(cv_playername[n].string, player_names[playernum]) - && sendColor == player->skincolor - && !stricmp(cv_skin[n].string, skins[player->skin].name) + && sendColor == player->prefcolor + && !stricmp(cv_skin[n].string, skins[player->prefskin].name) && !stricmp(cv_follower[n].string, - (player->followerskin < 0 ? "None" : followers[player->followerskin].name)) - && sendFollowerColor == player->followercolor) + (player->preffollower < 0 ? "None" : followers[player->preffollower].name)) + && sendFollowerColor == player->preffollowercolor) { return; } @@ -1123,7 +1109,6 @@ static void Got_NameAndColor(const UINT8 **cp, INT32 playernum) UINT16 color, followercolor; UINT8 skin; INT16 follower; - SINT8 localplayer = -1; UINT8 i; #ifdef PARANOIA @@ -1145,8 +1130,6 @@ static void Got_NameAndColor(const UINT8 **cp, INT32 playernum) I_Error("snacpending[%d] negative!", i); } #endif - - localplayer = i; break; } } @@ -1164,95 +1147,26 @@ static void Got_NameAndColor(const UINT8 **cp, INT32 playernum) SetPlayerName(playernum, name); } - // set color - p->skincolor = color % numskincolors; - if (p->mo) - p->mo->color = (UINT16)p->skincolor; - demo_extradata[playernum] |= DXD_COLOR; - - // normal player colors - if (server && !P_IsMachineLocalPlayer(p)) + // queue the rest for next round + p->prefcolor = color % numskincolors; + if (K_ColorUsable(p->prefcolor, false, false) == false) { - boolean kick = false; - - // don't allow inaccessible colors - if (K_ColorUsable(p->skincolor, false, false) == false) - { - kick = true; - } - - if (kick) - { - CONS_Alert(CONS_WARNING, M_GetText("Illegal color change received from %s, color: %d)\n"), player_names[playernum], p->skincolor); - SendKick(playernum, KICK_MSG_CON_FAIL); - return; - } + p->prefcolor = SKINCOLOR_NONE; } - // set skin - if (cv_forceskin.value >= 0 && K_CanChangeRules(true)) // Server wants everyone to use the same player + p->prefskin = skin; + p->preffollowercolor = followercolor; + p->preffollower = follower; + + if ( + (p->jointime <= 1) // Just entered + || (cv_restrictskinchange.value == 0 // Not restricted + && !Y_IntermissionPlayerLock()) // Not start of intermission + ) { - const INT32 forcedskin = cv_forceskin.value; - SetPlayerSkinByNum(playernum, forcedskin); - - if (localplayer != -1) - CV_StealthSet(&cv_skin[localplayer], skins[forcedskin].name); + // update preferences immediately + G_UpdatePlayerPreferences(p); } - else - { - UINT8 oldskin = players[playernum].skin; - - SetPlayerSkinByNum(playernum, skin); - - // The following is a miniature subset of Got_Teamchange. - if ((gamestate == GS_LEVEL) // In a level? - && (players[playernum].jointime > 1) // permit on join - && (leveltime > introtime) // permit during intro turnaround - && (players[playernum].skin != oldskin)) // a skin change actually happened? - { - players[playernum].roundconditions.switched_skin = true; - - if ( - cv_restrictskinchange.value // Skin changes are restricted? - && G_GametypeHasSpectators() // not a spectator... - && players[playernum].spectator == false // ...but could be? - ) - { - for (i = 0; i < MAXPLAYERS; ++i) - { - if (i == playernum) - continue; - if (!playeringame[i]) - continue; - if (players[i].spectator) - continue; - break; - } - - if (i != MAXPLAYERS // Someone on your server who isn't you? - && LUA_HookTeamSwitch(&players[playernum], 0, false, false, false)) // fiiiine, lua can except it - { - P_DamageMobj(players[playernum].mo, NULL, NULL, 1, DMG_SPECTATOR); - - if (players[i].spectator) - { - HU_AddChatText(va("\x82*%s became a spectator.", player_names[playernum]), false); - - FinalisePlaystateChange(playernum); - } - } - } - } - } - - // set follower colour: - // Don't bother doing garbage and kicking if we receive None, - // this is both silly and a waste of time, - // this will be handled properly in K_HandleFollower. - p->followercolor = followercolor; - - // set follower - K_SetFollowerByNum(playernum, follower); } enum { @@ -1261,6 +1175,15 @@ enum { WP_AUTOROULETTE = 1<<2, WP_ANALOGSTICK = 1<<3, WP_AUTORING = 1<<4, + WP_SELFMUTE = 1<<5, + WP_SELFDEAFEN = 1<<6, + WP_STRICTFASTFALL = 1<<7, + // WARNING: STUPID LEGACY TIMEWASTER AHEAD + // IF YOU ARE ADDING OR MODIFYING WEAPONPREFS, YOU MUST + // PRESERVE THEM IN G_PlayerReborn -- OTHERWISE THEY + // WILL MYSTERIOUSLY VANISH AFTER ONE RACE + // + // HOURS LOST TO G_PlayerReborn: UNCOUNTABLE }; void WeaponPref_Send(UINT8 ssplayer) @@ -1282,6 +1205,18 @@ void WeaponPref_Send(UINT8 ssplayer) if (cv_autoring[ssplayer].value) prefs |= WP_AUTORING; + if (ssplayer == 0) + { + if (cv_voice_selfmute.value) + prefs |= WP_SELFMUTE; + + if (!cv_voice_chat.value) + prefs |= WP_SELFDEAFEN; + } + + if (cv_strictfastfall[ssplayer].value) + prefs |= WP_STRICTFASTFALL; + UINT8 buf[2]; buf[0] = prefs; buf[1] = cv_mindelay.value; @@ -1310,6 +1245,9 @@ void WeaponPref_Save(UINT8 **cp, INT32 playernum) if (player->pflags & PF_AUTORING) prefs |= WP_AUTORING; + if (player->pflags & PF2_STRICTFASTFALL) + prefs |= WP_STRICTFASTFALL; + WRITEUINT8(*cp, prefs); } @@ -1321,6 +1259,7 @@ size_t WeaponPref_Parse(const UINT8 *bufstart, INT32 playernum) UINT8 prefs = READUINT8(p); player->pflags &= ~(PF_KICKSTARTACCEL|PF_SHRINKME|PF_AUTOROULETTE|PF_AUTORING); + player->pflags2 &= ~(PF2_SELFMUTE | PF2_SELFDEAFEN | PF2_STRICTFASTFALL); if (prefs & WP_KICKSTARTACCEL) player->pflags |= PF_KICKSTARTACCEL; @@ -1339,6 +1278,15 @@ size_t WeaponPref_Parse(const UINT8 *bufstart, INT32 playernum) if (prefs & WP_AUTORING) player->pflags |= PF_AUTORING; + if (prefs & WP_SELFMUTE) + player->pflags2 |= PF2_SELFMUTE; + + if (prefs & WP_SELFDEAFEN) + player->pflags2 |= PF2_SELFDEAFEN; + + if (prefs & WP_STRICTFASTFALL) + player->pflags2 |= PF2_STRICTFASTFALL; + if (leveltime < 2) { // BAD HACK: No other place I tried to slot this in @@ -2289,13 +2237,17 @@ void D_SetupVote(INT16 newgametype) { const UINT32 rules = gametypes[newgametype]->rules; - UINT8 buf[(VOTE_NUM_LEVELS * 2) + 4]; + UINT8 buf[(VOTE_NUM_LEVELS * 2) + 2 + 1 + 1]; UINT8 *p = buf; INT32 i; UINT16 votebuffer[VOTE_NUM_LEVELS + 1]; - memset(votebuffer, UINT16_MAX, sizeof(votebuffer)); + //memset(votebuffer, UINT16_MAX, sizeof(votebuffer)); + for (i = 0; i < VOTE_NUM_LEVELS + 1; i++) + { + votebuffer[i] = UINT16_MAX; + } WRITEINT16(p, newgametype); WRITEUINT8(p, ((cv_kartencore.value == 1) && (rules & GTR_ENCORE))); @@ -2534,6 +2486,7 @@ static void Command_Map_f(void) size_t option_force; size_t option_gametype; size_t option_encore; + size_t option_random; size_t option_skill; size_t option_server; size_t option_match; @@ -2545,7 +2498,7 @@ static void Command_Map_f(void) INT32 newmapnum; - char * mapname; + char * mapname = NULL; char *realmapname = NULL; INT32 newgametype = gametype; @@ -2568,6 +2521,7 @@ static void Command_Map_f(void) option_force = COM_CheckPartialParm("-f"); option_gametype = COM_CheckPartialParm("-g"); option_encore = COM_CheckPartialParm("-e"); + option_random = COM_CheckPartialParm("-r"); option_skill = COM_CheckParm("-skill"); option_server = COM_CheckParm("-server"); option_match = COM_CheckParm("-match"); @@ -2575,35 +2529,110 @@ static void Command_Map_f(void) newforcespecialstage = COM_CheckParm("-forcespecialstage"); usingcheats = CV_CheatsEnabled(); - ischeating = (!(netgame || multiplayer)) || (!newresetplayers); + ischeating = (!(netgame || multiplayer)) || (!newresetplayers) || (!K_CanChangeRules(false)); if (!( first_option = COM_FirstOption() )) first_option = COM_Argc(); - if (first_option < 2) + if (!option_random && first_option < 2) { /* I'm going over the fucking lines and I DON'T CAREEEEE */ - CONS_Printf("map [-gametype ] [-force]:\n"); + CONS_Printf("map [-gametype ] [-force] / [-random]:\n"); CONS_Printf(M_GetText( "Warp to a map, by its name, two character code, with optional \"MAP\" prefix, or by its number (though why would you).\n" "All parameters are case-insensitive and may be abbreviated.\n")); return; } - mapname = ConcatCommandArgv(1, first_option); + boolean getgametypefrommap = false; - newmapnum = G_FindMapByNameOrCode(mapname, &realmapname); - - if (newmapnum == 0) + // new gametype value + // use current one by default + if (option_gametype) { - CONS_Alert(CONS_ERROR, M_GetText("Could not find any map described as '%s'.\n"), mapname); - Z_Free(mapname); - return; + newgametype = GetGametypeParm(option_gametype); + if (newgametype == -1) + { + return; + } + } + else if (option_random) + { + if (!Playing()) + { + CONS_Printf("Can't use -random from the menu without -gametype.\n"); + return; + } + } + else if (!Playing() || (netgame == false && grandprixinfo.gp == true)) + { + getgametypefrommap = true; } - if (/*newmapnum != 1 &&*/ M_MapLocked(newmapnum)) + // new encoremode value + if (option_encore) { - ischeating = true; + newencoremode = !newencoremode; + + if (!M_SecretUnlocked(SECRET_ENCORE, false) && newencoremode == true && !usingcheats) + { + CONS_Alert(CONS_NOTICE, M_GetText("You haven't unlocked Encore Mode yet!\n")); + Z_Free(realmapname); + Z_Free(mapname); + return; + } + } + + if (option_random) + { + UINT8 numPlayers = 0; + UINT16 oldmapnum = UINT16_MAX; + + if (Playing()) + { + UINT8 i; + for (i = 0; i < MAXPLAYERS; ++i) + { + if (!playeringame[i] || players[i].spectator) + { + continue; + } + + extern consvar_t cv_forcebots; // debug + + if (!(gametypes[newgametype]->rules & GTR_BOTS) && players[i].bot && !cv_forcebots.value) + { + // Gametype doesn't support bots + continue; + } + + numPlayers++; + } + + oldmapnum = (gamestate == GS_LEVEL) + ? (gamemap-1) + : prevmap; + } + + newmapnum = G_RandMapPerPlayerCount(G_TOLFlag(newgametype), oldmapnum, false, false, NULL, numPlayers) + 1; + } + else + { + mapname = ConcatCommandArgv(1, first_option); + + newmapnum = G_FindMapByNameOrCode(mapname, &realmapname); + + if (newmapnum == 0) + { + CONS_Alert(CONS_ERROR, M_GetText("Could not find any map described as '%s'.\n"), mapname); + Z_Free(mapname); + return; + } + + if (M_MapLocked(newmapnum)) + { + ischeating = true; + } } if (ischeating && !usingcheats) @@ -2614,21 +2643,8 @@ static void Command_Map_f(void) return; } - // new gametype value - // use current one by default - if (option_gametype) + if (getgametypefrommap) { - newgametype = GetGametypeParm(option_gametype); - if (newgametype == -1) - { - Z_Free(realmapname); - Z_Free(mapname); - return; - } - } - else if (!Playing() || (netgame == false && grandprixinfo.gp == true)) - { - newresetplayers = true; if (mapheaderinfo[newmapnum-1]) { // Let's just guess so we don't have to specify the gametype EVERY time... @@ -2647,25 +2663,13 @@ static void Command_Map_f(void) } } - // new encoremode value - if (option_encore) - { - newencoremode = !newencoremode; - - if (!M_SecretUnlocked(SECRET_ENCORE, false) && newencoremode == true && !usingcheats) - { - CONS_Alert(CONS_NOTICE, M_GetText("You haven't unlocked Encore Mode yet!\n")); - Z_Free(realmapname); - Z_Free(mapname); - return; - } - } - - if (!option_force && newgametype == gametype && Playing()) // SRB2Kart + if (!Playing()) + newresetplayers = true; + else if (!option_force && newgametype == gametype) // SRB2Kart newresetplayers = false; // if not forcing and gametypes is the same // don't use a gametype the map doesn't support - if (cht_debug || option_force || cv_skipmapcheck.value) + if (option_random || cht_debug || option_force || cv_skipmapcheck.value) { // The player wants us to trek on anyway. Do so. } @@ -2947,54 +2951,7 @@ static void Got_Mapcmd(const UINT8 **cp, INT32 playernum) static void Command_RandomMap(void) { - INT32 oldmapnum; - INT32 newmapnum; - INT32 newgametype = (Playing() ? gametype : menugametype); - boolean newencore = false; - boolean newresetplayers; - size_t option_gametype; - - if (client && !IsPlayerAdmin(consoleplayer)) - { - CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n")); - return; - } - - if ((option_gametype = COM_CheckPartialParm("-g"))) - { - newgametype = GetGametypeParm(option_gametype); - if (newgametype == -1) - return; - } - - // TODO: Handle singleplayer conditions. - // The existing ones are way too annoyingly complicated and "anti-cheat" for my tastes. - - if (Playing()) - { - if (cv_kartencore.value == 1 && (gametypes[newgametype]->rules & GTR_ENCORE)) - { - newencore = true; - } - newresetplayers = false; - - if (gamestate == GS_LEVEL) - { - oldmapnum = gamemap-1; - } - else - { - oldmapnum = prevmap; - } - } - else - { - newresetplayers = true; - oldmapnum = -1; - } - - newmapnum = G_RandMap(G_TOLFlag(newgametype), oldmapnum, false, false, NULL) + 1; - D_MapChange(newmapnum, newgametype, newencore, newresetplayers, 0, false, false); + CONS_Printf("randommap is deprecated, please use \"map -random\" instead.\n"); } static void Command_RestartLevel(void) @@ -3029,42 +2986,26 @@ static void Command_RestartLevel(void) static void Handle_MapQueueSend(UINT16 newmapnum, UINT16 newgametype, boolean newencoremode) { - static char buf[1+2+2]; - static char *buf_p = buf; - UINT8 flags = 0; - boolean doclear = (newgametype == ROUNDQUEUE_CLEAR); CONS_Debug(DBG_GAMELOGIC, "Map queue: mapnum=%d newgametype=%d newencoremode=%d\n", newmapnum, newgametype, newencoremode); - buf_p = buf; - if (newencoremode) flags |= 1; - WRITEUINT8(buf_p, flags); - WRITEUINT16(buf_p, newgametype); + netbuffer->packettype = PT_REQMAPQUEUE; - if (client) - { - WRITEUINT16(buf_p, newmapnum); - SendNetXCmd(XD_REQMAPQUEUE, buf, buf_p - buf); - return; - } + reqmapqueue_pak *reqmapqueue = &netbuffer->u.reqmapqueue; - WRITEUINT8(buf_p, roundqueue.size); + reqmapqueue->newmapnum = newmapnum; + reqmapqueue->flags = flags; + reqmapqueue->newgametype = newgametype; + reqmapqueue->source = consoleplayer; - if (doclear == true) - { - memset(&roundqueue, 0, sizeof(struct roundqueue)); - } - else - { - G_MapIntoRoundQueue(newmapnum, newgametype, newencoremode, false); - } + HSendPacket(servernode, false, 0, sizeof(reqmapqueue_pak)); - SendNetXCmd(XD_MAPQUEUE, buf, buf_p - buf); + // See PT_ReqMapQueue and Got_MapQueuecmd for the next stages } static void Command_QueueMap_f(void) @@ -3074,13 +3015,15 @@ static void Command_QueueMap_f(void) size_t option_gametype; size_t option_encore; size_t option_clear; + size_t option_show; + size_t option_random; boolean usingcheats; boolean ischeating; INT32 newmapnum; - char * mapname; + char * mapname = NULL; char *realmapname = NULL; INT32 newgametype = gametype; @@ -3101,23 +3044,32 @@ static void Command_QueueMap_f(void) usingcheats = CV_CheatsEnabled(); ischeating = (!(netgame || multiplayer) || !K_CanChangeRules(false)); - option_clear = COM_CheckParm("-clear"); + // Early check, rather than multiple copypaste. + if (ischeating && !usingcheats) + { + CONS_Printf(M_GetText("Cheats must be enabled.\n")); + return; + } + + option_clear = COM_CheckPartialParm("-c"); if (option_clear) { - if (ischeating && !usingcheats) - { - CONS_Printf(M_GetText("Cheats must be enabled.\n")); - return; - } - if (roundqueue.size == 0) { CONS_Printf(M_GetText("Round queue is already empty!\n")); return; } - Handle_MapQueueSend(0, ROUNDQUEUE_CLEAR, false); + Handle_MapQueueSend(0, ROUNDQUEUE_CMD_CLEAR, false); + return; + } + + option_show = COM_CheckPartialParm("-s"); + + if (option_show) + { + Handle_MapQueueSend(0, ROUNDQUEUE_CMD_SHOW, false); return; } @@ -3130,44 +3082,21 @@ static void Command_QueueMap_f(void) option_force = COM_CheckPartialParm("-f"); option_gametype = COM_CheckPartialParm("-g"); option_encore = COM_CheckPartialParm("-e"); + option_random = COM_CheckPartialParm("-r"); if (!( first_option = COM_FirstOption() )) first_option = COM_Argc(); - if (first_option < 2) + if (!option_random && first_option < 2) { /* I'm going over the fucking lines and I DON'T CAREEEEE */ - CONS_Printf("queuemap [-gametype ] [-force] / [-clear]:\n"); + CONS_Printf("queuemap [-gametype ] [-force] / [-random] / [-clear] / [-show]:\n"); CONS_Printf(M_GetText( "Queue up a map by its name, or by its number (though why would you).\n" "All parameters are case-insensitive and may be abbreviated.\n")); return; } - mapname = ConcatCommandArgv(1, first_option); - - newmapnum = G_FindMapByNameOrCode(mapname, &realmapname); - - if (newmapnum == 0) - { - CONS_Alert(CONS_ERROR, M_GetText("Could not find any map described as '%s'.\n"), mapname); - Z_Free(mapname); - return; - } - - if (/*newmapnum != 1 &&*/ M_MapLocked(newmapnum)) - { - ischeating = true; - } - - if (ischeating && !usingcheats) - { - CONS_Printf(M_GetText("Cheats must be enabled.\n")); - Z_Free(realmapname); - Z_Free(mapname); - return; - } - // new gametype value // use current one by default if (option_gametype) @@ -3195,8 +3124,40 @@ static void Command_QueueMap_f(void) } } + if (option_random) + { + // Unlike map -random, this is a server side RNG roll + newmapnum = NEXTMAP_VOTING + 1; + } + else + { + mapname = ConcatCommandArgv(1, first_option); + + newmapnum = G_FindMapByNameOrCode(mapname, &realmapname); + + if (newmapnum == 0) + { + CONS_Alert(CONS_ERROR, M_GetText("Could not find any map described as '%s'.\n"), mapname); + Z_Free(mapname); + return; + } + + if (M_MapLocked(newmapnum)) + { + ischeating = true; + } + } + + if (ischeating && !usingcheats) + { + CONS_Printf(M_GetText("Cheats must be enabled.\n")); + Z_Free(realmapname); + Z_Free(mapname); + return; + } + // don't use a gametype the map doesn't support - if (cht_debug || option_force || cv_skipmapcheck.value) + if (option_random || cht_debug || option_force || cv_skipmapcheck.value) { // The player wants us to trek on anyway. Do so. } @@ -3221,51 +3182,6 @@ static void Command_QueueMap_f(void) Z_Free(mapname); } -static void Got_RequestMapQueuecmd(const UINT8 **cp, INT32 playernum) -{ - UINT8 flags; - boolean setencore; - UINT16 mapnumber, setgametype; - boolean doclear = false; - - flags = READUINT8(*cp); - - setencore = ((flags & 1) != 0); - - setgametype = READUINT16(*cp); - - mapnumber = READUINT16(*cp); - - if (!IsPlayerAdmin(playernum)) - { - CONS_Alert(CONS_WARNING, M_GetText("Illegal request map queue command received from %s\n"), player_names[playernum]); - if (server && playernum != serverplayer) - SendKick(playernum, KICK_MSG_CON_FAIL); - return; - } - - doclear = (setgametype == ROUNDQUEUE_CLEAR); - - if (doclear == true) - { - if (roundqueue.size == 0) - { - CONS_Alert(CONS_ERROR, "queuemap: Queue is already empty!\n"); - return; - } - } - else if (roundqueue.size >= ROUNDQUEUE_MAX) - { - CONS_Alert(CONS_ERROR, "queuemap: Unable to add map beyond %u\n", roundqueue.size); - return; - } - - if (client) - return; - - Handle_MapQueueSend(mapnumber, setgametype, setencore); -} - static void Got_MapQueuecmd(const UINT8 **cp, INT32 playernum) { UINT8 flags, queueposition, i; @@ -3289,7 +3205,7 @@ static void Got_MapQueuecmd(const UINT8 **cp, INT32 playernum) return; } - doclear = (setgametype == ROUNDQUEUE_CLEAR); + doclear = (setgametype == ROUNDQUEUE_CMD_CLEAR); if (doclear == false && queueposition >= ROUNDQUEUE_MAX) { @@ -3446,166 +3362,204 @@ static void Got_RandomSeed(const UINT8 **cp, INT32 playernum) /** Clears all players' scores in a netgame. * Only the server or a remote admin can use this command, for obvious reasons. * - * \sa XD_CLEARSCORES, Got_Clearscores + * \sa XD_SETSCORE, Got_Setscore * \author SSNTails */ -static void Command_Clearscores_f(void) +static void Command_Setscore_f(void) { - if (!(server || (IsPlayerAdmin(consoleplayer)))) + size_t option_add; + size_t option_clear; + + UINT8 edit_player = UINT8_MAX; + UINT32 desired_score = 0; + + UINT8 buf[1+4]; + UINT8 *p = buf; + + if (!Playing()) + { + CONS_Printf(M_GetText("Scores can only be updated in-game.\n")); + return; + } + + if (client && !IsPlayerAdmin(consoleplayer)) + { + CONS_Printf(M_GetText("Only the server or a remote admin can use this.\n")); + return; + } + + if (K_UsingPowerLevels() != PWRLV_DISABLED) + { + CONS_Printf("PWR is currently active - setscore has no effect.\n"); + return; + } + + if (!K_CanChangeRules(false) && !CV_CheatsEnabled()) + { + CONS_Printf(M_GetText("Cheats must be enabled.\n")); + return; + } + + option_clear = COM_CheckPartialParm("-c"); + + if (option_clear) + { + WRITEUINT8(p, edit_player); + WRITEUINT32(p, desired_score); + + SendNetXCmd(XD_SETSCORE, &buf, p - buf); + return; + } + + option_add = COM_CheckPartialParm("-a"); + + if (COM_Argc() < (option_add ? 3 : 2)) + { + CONS_Printf("setscore [-add] / [-clear]\n"); + return; + } + + size_t work_option = 0; + if (++work_option == option_add) + work_option++; + + { + const char *pid_string = COM_Argv(work_option); + INT32 pid = atoi(pid_string); + + if (pid >= MAXPLAYERS + || pid < 0 + || isdigit(pid_string[0]) == false) + { + CONS_Printf("playernum must be between 0 and %u\n", MAXPLAYERS-1); + return; + } + + edit_player = pid; + } + + if (++work_option == option_add) + work_option++; + + { + const char *score_string = COM_Argv(work_option); + INT32 score = atoi(score_string); + INT32 min_score = (option_add ? -MAXSCORE : 0); + + if (score >= MAXSCORE + || score < min_score + || isdigit(score_string[0]) == false) + { + CONS_Printf("score%s must be between %d and %u\n", (option_add ? " -add" : ""), min_score, MAXSCORE); + return; + } + + if (option_add) + { + score += (INT32)players[edit_player].score; + } + + if (score > MAXSCORE) + { + score = MAXSCORE; + } + else if (score < 0) + { + score = 0; + } + + desired_score = score; + } + + if (edit_player > MAXPLAYERS) return; - SendNetXCmd(XD_CLEARSCORES, NULL, 1); + if (!playeringame[edit_player]) + { + CONS_Printf("playernum must be a valid player\n"); + return; + } + + if (players[edit_player].spectator) + { + CONS_Printf("playernum must not be a spectator\n"); + return; + } + + WRITEUINT8(p, edit_player); + WRITEUINT32(p, desired_score); + + SendNetXCmd(XD_SETSCORE, &buf, p - buf); } -/** Handles an ::XD_CLEARSCORES message, which resets all players' scores in a - * netgame to zero. +/** Handles an ::XD_SETSCORE message, which sets one (or all) player's score(s) * * \param cp Data buffer. * \param playernum Player responsible for the message. Must be ::serverplayer * or ::adminplayer. - * \sa XD_CLEARSCORES, Command_Clearscores_f + * \sa XD_SETSCORE, Command_Setscore_f * \author SSNTails */ -static void Got_Clearscores(const UINT8 **cp, INT32 playernum) +static void Got_Setscore(const UINT8 **cp, INT32 playernum) { - INT32 i; + UINT8 edit_player = READUINT8(*cp); + UINT32 desired_score = READUINT32(*cp); - (void)cp; if (playernum != serverplayer && !IsPlayerAdmin(playernum)) { - CONS_Alert(CONS_WARNING, M_GetText("Illegal clear scores command received from %s\n"), player_names[playernum]); + CONS_Alert(CONS_WARNING, M_GetText("Illegal setscore command received from %s\n"), player_names[playernum]); if (server) SendKick(playernum, KICK_MSG_CON_FAIL); return; } - for (i = 0; i < MAXPLAYERS; i++) - players[i].score = 0; - - CONS_Printf(M_GetText("Scores have been reset by the server.\n")); -} - -// Team changing functions -static void HandleTeamChangeCommand(UINT8 localplayer) -{ - const char *commandname = NULL; - changeteam_union NetPacket; - boolean error = false; - UINT16 usvalue; - NetPacket.value.l = NetPacket.value.b = 0; - - switch (localplayer) + if (K_UsingPowerLevels() != PWRLV_DISABLED) { - case 0: - commandname = "changeteam"; - break; - default: - commandname = va("changeteam%d", localplayer+1); - break; - } - - // 0 1 - // changeteam - - if (COM_Argc() <= 1) - { - if (G_GametypeHasTeams()) - CONS_Printf(M_GetText("%s : switch to a new team (%s)\n"), commandname, "red, blue or spectator"); - else if (G_GametypeHasSpectators()) - CONS_Printf(M_GetText("%s : switch to a new team (%s)\n"), commandname, "spectator or playing"); - else - CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n")); return; } - if (G_GametypeHasTeams()) + if (edit_player == UINT8_MAX) { - if (!strcasecmp(COM_Argv(1), "red") || !strcasecmp(COM_Argv(1), "1")) - NetPacket.packet.newteam = 1; - else if (!strcasecmp(COM_Argv(1), "blue") || !strcasecmp(COM_Argv(1), "2")) - NetPacket.packet.newteam = 2; - else if (!strcasecmp(COM_Argv(1), "spectator") || !strcasecmp(COM_Argv(1), "0")) - NetPacket.packet.newteam = 0; - else - error = true; - } - else if (G_GametypeHasSpectators()) - { - if (!strcasecmp(COM_Argv(1), "spectator") || !strcasecmp(COM_Argv(1), "0")) - NetPacket.packet.newteam = 0; - else if (!strcasecmp(COM_Argv(1), "playing") || !strcasecmp(COM_Argv(1), "1")) - NetPacket.packet.newteam = 3; - else - error = true; - } - else - { - CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n")); + for (edit_player = 0; edit_player < MAXPLAYERS; edit_player++) + { + if (!playeringame[edit_player]) + continue; + + players[edit_player].score = 0; + } + + HU_AddChatText("\x82*All scores have been reset.", false); + return; } - if (error) + if (edit_player >= MAXPLAYERS || !playeringame[edit_player]) { - if (G_GametypeHasTeams()) - CONS_Printf(M_GetText("%s : switch to a new team (%s)\n"), commandname, "red, blue or spectator"); - else if (G_GametypeHasSpectators()) - CONS_Printf(M_GetText("%s : switch to a new team (%s)\n"), commandname, "spectator or playing"); + CONS_Printf("Invalid player ID %u recieved a score set!\n", edit_player); return; } - if (players[g_localplayers[localplayer]].spectator) - error = !(NetPacket.packet.newteam || (players[g_localplayers[localplayer]].pflags & PF_WANTSTOJOIN)); // :lancer: - else if (G_GametypeHasTeams()) - error = (NetPacket.packet.newteam == players[g_localplayers[localplayer]].ctfteam); - else if (G_GametypeHasSpectators() && !players[g_localplayers[localplayer]].spectator) - error = (NetPacket.packet.newteam == 3); -#ifdef PARANOIA - else - I_Error("Invalid gametype after initial checks!"); -#endif - - if (error) + if (players[edit_player].spectator) { - CONS_Alert(CONS_NOTICE, M_GetText("You're already on that team!\n")); + CONS_Printf("%s recieved a score set but is a spectator!\n", player_names[edit_player]); return; } - if (!cv_allowteamchange.value && NetPacket.packet.newteam) // allow swapping to spectator even in locked teams. - { - CONS_Alert(CONS_NOTICE, M_GetText("The server is not allowing team changes at the moment.\n")); - return; - } + players[edit_player].score = desired_score; - usvalue = SHORT(NetPacket.value.l|NetPacket.value.b); - SendNetXCmdForPlayer(localplayer, XD_TEAMCHANGE, &usvalue, sizeof(usvalue)); -} + HU_AddChatText(va("\x82*%s had their score set to %u.", player_names[edit_player], desired_score), false); -static void Command_Teamchange_f(void) -{ - HandleTeamChangeCommand(0); -} - -static void Command_Teamchange2_f(void) -{ - HandleTeamChangeCommand(1); -} - -static void Command_Teamchange3_f(void) -{ - HandleTeamChangeCommand(2); -} - -static void Command_Teamchange4_f(void) -{ - HandleTeamChangeCommand(3); + if (server) + CONS_Printf("This setscore was done by %s (Player %u).\n", player_names[playernum], playernum); } static void Command_ServerTeamChange_f(void) { - changeteam_union NetPacket; - boolean error = false; - UINT16 usvalue; - NetPacket.value.l = NetPacket.value.b = 0; + UINT8 buf[2]; + UINT8 *p = buf; + + UINT8 new_team = TEAM_UNASSIGNED; + UINT8 player_num = consoleplayer; if (!(server || (IsPlayerAdmin(consoleplayer)))) { @@ -3613,96 +3567,63 @@ static void Command_ServerTeamChange_f(void) return; } + if (G_GametypeHasTeams() == false) + { + CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used currently.\n")); + return; + } + // 0 1 2 // serverchangeteam if (COM_Argc() < 3) { - if (G_GametypeHasTeams()) - CONS_Printf(M_GetText("serverchangeteam : switch player to a new team (%s)\n"), "red, blue or spectator"); - else if (G_GametypeHasSpectators()) - CONS_Printf(M_GetText("serverchangeteam : switch player to a new team (%s)\n"), "spectator or playing"); - else - CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n")); + CONS_Printf(M_GetText("serverchangeteam : switch player to a new team (%s)\n"), "orange, blue, or auto"); return; } - if (G_GametypeHasTeams()) + if (!strcasecmp(COM_Argv(2), "orange") || !strcasecmp(COM_Argv(2), "1")) { - if (!strcasecmp(COM_Argv(2), "red") || !strcasecmp(COM_Argv(2), "1")) - NetPacket.packet.newteam = 1; - else if (!strcasecmp(COM_Argv(2), "blue") || !strcasecmp(COM_Argv(2), "2")) - NetPacket.packet.newteam = 2; - else if (!strcasecmp(COM_Argv(2), "spectator") || !strcasecmp(COM_Argv(2), "0")) - NetPacket.packet.newteam = 0; - else - error = true; + new_team = TEAM_ORANGE; } - else if (G_GametypeHasSpectators()) + else if (!strcasecmp(COM_Argv(2), "blue") || !strcasecmp(COM_Argv(2), "2")) { - if (!strcasecmp(COM_Argv(2), "spectator") || !strcasecmp(COM_Argv(2), "0")) - NetPacket.packet.newteam = 0; - else if (!strcasecmp(COM_Argv(2), "playing") || !strcasecmp(COM_Argv(2), "1")) - NetPacket.packet.newteam = 3; - else - error = true; + new_team = TEAM_BLUE; + } + else if (!strcasecmp(COM_Argv(2), "auto") || !strcasecmp(COM_Argv(2), "0")) + { + new_team = TEAM_UNASSIGNED; } else { - CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n")); + CONS_Printf(M_GetText("serverchangeteam : switch player to a new team (%s)\n"), "orange, blue, or auto"); return; } - if (error) + player_num = atoi(COM_Argv(1)); + + if (playeringame[player_num] == false) { - if (G_GametypeHasTeams()) - CONS_Printf(M_GetText("serverchangeteam : switch player to a new team (%s)\n"), "red, blue or spectator"); - else if (G_GametypeHasSpectators()) - CONS_Printf(M_GetText("serverchangeteam : switch player to a new team (%s)\n"), "spectator or playing"); + CONS_Alert(CONS_NOTICE, M_GetText("There is no player %d!\n"), player_num); return; } - NetPacket.packet.playernum = atoi(COM_Argv(1)); - - if (!playeringame[NetPacket.packet.playernum]) - { - CONS_Alert(CONS_NOTICE, M_GetText("There is no player %d!\n"), NetPacket.packet.playernum); - return; - } - - if (G_GametypeHasTeams()) - { - if (NetPacket.packet.newteam == players[NetPacket.packet.playernum].ctfteam || - (players[NetPacket.packet.playernum].spectator && !NetPacket.packet.newteam)) - error = true; - } - else if (G_GametypeHasSpectators()) - { - if ((players[NetPacket.packet.playernum].spectator && !NetPacket.packet.newteam) || - (!players[NetPacket.packet.playernum].spectator && NetPacket.packet.newteam == 3)) - error = true; - } -#ifdef PARANOIA - else - I_Error("Invalid gametype after initial checks!"); -#endif - - if (error) + if (new_team == players[player_num].team) { CONS_Alert(CONS_NOTICE, M_GetText("That player is already on that team!\n")); return; } - NetPacket.packet.verification = true; // This signals that it's a server change + WRITEUINT8(p, new_team); + WRITEUINT8(p, player_num); - usvalue = SHORT(NetPacket.value.l|NetPacket.value.b); - SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue)); + SendNetXCmd(XD_TEAMCHANGE, &buf, p - buf); } void P_SetPlayerSpectator(INT32 playernum) { //Make sure you're in the right gametype. - if (!G_GametypeHasTeams() && !G_GametypeHasSpectators()) + if (!G_GametypeHasSpectators()) return; // Don't duplicate efforts. @@ -3711,148 +3632,105 @@ void P_SetPlayerSpectator(INT32 playernum) players[playernum].spectator = true; players[playernum].pflags &= ~PF_WANTSTOJOIN; + G_AssignTeam(&players[playernum], TEAM_UNASSIGNED); players[playernum].playerstate = PST_REBORN; } -//todo: This and the other teamchange functions are getting too long and messy. Needs cleaning. -static void Got_Teamchange(const UINT8 **cp, INT32 playernum) +static void Got_Spectate(const UINT8 **cp, INT32 playernum) { - changeteam_union NetPacket; - boolean error = false, wasspectator = false; - NetPacket.value.l = NetPacket.value.b = READINT16(*cp); + UINT8 edit_player = READUINT8(*cp); + UINT8 desired_state = READUINT8(*cp); - if (!G_GametypeHasTeams() && !G_GametypeHasSpectators()) //Make sure you're in the right gametype. + if (playeringame[edit_player] == false) { - // this should never happen unless the client is hacked/buggy - CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]); + return; + } + + if (playernum != playerconsole[edit_player] + && playernum != serverplayer + && IsPlayerAdmin(playernum) == false) + { + CONS_Alert(CONS_WARNING, M_GetText("Illegal spectate command received from player %s\n"), player_names[playernum]); if (server) + { SendKick(playernum, KICK_MSG_CON_FAIL); - } - - if (NetPacket.packet.verification) // Special marker that the server sent the request - { - if (playernum != serverplayer && (!IsPlayerAdmin(playernum))) - { - CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]); - if (server) - SendKick(playernum, KICK_MSG_CON_FAIL); - return; - } - playernum = NetPacket.packet.playernum; - } - - // Prevent multiple changes in one go. - if (players[playernum].spectator && !(players[playernum].pflags & PF_WANTSTOJOIN) && !NetPacket.packet.newteam) - return; - else if (G_GametypeHasTeams()) - { - if (NetPacket.packet.newteam && (NetPacket.packet.newteam == (unsigned)players[playernum].ctfteam)) - return; - } - else if (G_GametypeHasSpectators()) - { - if (!players[playernum].spectator && NetPacket.packet.newteam == 3) - return; - } - else - { - if (playernum != serverplayer && (!IsPlayerAdmin(playernum))) - { - CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]); - if (server) - SendKick(playernum, KICK_MSG_CON_FAIL); } return; } - // Don't switch team, just go away, please, go awaayyyy, aaauuauugghhhghgh - if (!LUA_HookTeamSwitch(&players[playernum], NetPacket.packet.newteam, players[playernum].spectator, NetPacket.packet.autobalance, NetPacket.packet.scrambled)) - return; - - //Make sure that the right team number is sent. Keep in mind that normal clients cannot change to certain teams in certain gametypes. -#ifdef PARANOIA - if (!G_GametypeHasTeams() && !G_GametypeHasSpectators()) - I_Error("Invalid gametype after initial checks!"); -#endif - - if (!cv_allowteamchange.value) + if (G_GametypeHasSpectators() == false) { - if (!NetPacket.packet.verification && NetPacket.packet.newteam) - error = true; //Only admin can change status, unless changing to spectator. - } - - if (server && ((NetPacket.packet.newteam < 0 || NetPacket.packet.newteam > 3) || error)) - { - CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]); - SendKick(playernum, KICK_MSG_CON_FAIL); return; } - //Safety first! - // (not respawning spectators here...) - wasspectator = (players[playernum].spectator == true); + player_t *const player = &players[edit_player]; - if (!wasspectator) + // Safety first! + const boolean was_spectator = (player->spectator == true); + if (was_spectator == false) { - if (gamestate == GS_LEVEL && players[playernum].mo) + if (gamestate == GS_LEVEL && player->mo != NULL) { // The following will call P_SetPlayerSpectator if successful - P_DamageMobj(players[playernum].mo, NULL, NULL, 1, DMG_SPECTATOR); + P_DamageMobj(player->mo, NULL, NULL, 1, DMG_SPECTATOR); } //...but because the above could return early under some contexts, we try again here - P_SetPlayerSpectator(playernum); + P_SetPlayerSpectator(edit_player); + HU_AddChatText(va("\x82*%s became a spectator.", player_names[edit_player]), false); } - //Now that we've done our error checking and killed the player - //if necessary, put the player on the correct team/status. - - // This serves us in both teamchange contexts. - if (NetPacket.packet.newteam != 0) + if (desired_state != 0) { - players[playernum].pflags |= PF_WANTSTOJOIN; + player->pflags |= PF_WANTSTOJOIN; } else { - players[playernum].pflags &= ~PF_WANTSTOJOIN; + player->pflags &= ~PF_WANTSTOJOIN; } - if (G_GametypeHasTeams()) + if (gamestate != GS_LEVEL || was_spectator == true) { - // This one is, of course, specific. - players[playernum].ctfteam = NetPacket.packet.newteam; - } - - if (NetPacket.packet.autobalance) - { - if (NetPacket.packet.newteam == 1) - CONS_Printf(M_GetText("%s was autobalanced to the %c%s%c.\n"), player_names[playernum], '\x85', M_GetText("Red Team"), '\x80'); - else if (NetPacket.packet.newteam == 2) - CONS_Printf(M_GetText("%s was autobalanced to the %c%s%c.\n"), player_names[playernum], '\x84', M_GetText("Blue Team"), '\x80'); - } - else if (NetPacket.packet.scrambled) - { - if (NetPacket.packet.newteam == 1) - CONS_Printf(M_GetText("%s was scrambled to the %c%s%c.\n"), player_names[playernum], '\x85', M_GetText("Red Team"), '\x80'); - else if (NetPacket.packet.newteam == 2) - CONS_Printf(M_GetText("%s was scrambled to the %c%s%c.\n"), player_names[playernum], '\x84', M_GetText("Blue Team"), '\x80'); - } - else if (NetPacket.packet.newteam == 1) - { - CONS_Printf(M_GetText("%s switched to the %c%s%c.\n"), player_names[playernum], '\x85', M_GetText("Red Team"), '\x80'); - } - else if (NetPacket.packet.newteam == 2) - { - CONS_Printf(M_GetText("%s switched to the %c%s%c.\n"), player_names[playernum], '\x84', M_GetText("Blue Team"), '\x80'); - } - else if (NetPacket.packet.newteam == 0 && !wasspectator) - HU_AddChatText(va("\x82*%s became a spectator.", player_names[playernum]), false); // "entered the game" text was moved to P_SpectatorJoinGame - - if (gamestate != GS_LEVEL || wasspectator == true) return; + } - FinalisePlaystateChange(playernum); + FinalisePlaystateChange(edit_player); +} + +static void Got_TeamChange(const UINT8 **cp, INT32 playernum) +{ + UINT8 new_team = READUINT8(*cp); + UINT8 edit_player = READUINT8(*cp); + + if (playernum != serverplayer && IsPlayerAdmin(playernum) == false) + { + CONS_Alert(CONS_WARNING, M_GetText("Illegal team change received from player %s\n"), player_names[playernum]); + if (server) + { + SendKick(playernum, KICK_MSG_CON_FAIL); + } + return; + } + + if (G_GametypeHasTeams() == false) + { + return; + } + + if (new_team >= TEAM__MAX) + { + new_team = TEAM_UNASSIGNED; + } + + player_t *const player = &players[edit_player]; + G_AssignTeam(player, new_team); + + if (player->team == TEAM_UNASSIGNED) + { + // auto assign + G_AutoAssignTeam(player); + } } // @@ -5438,22 +5316,6 @@ void D_GameTypeChanged(INT32 lastgametype) if (oldgt && newgt && (lastgametype != gametype)) CONS_Printf(M_GetText("Gametype was changed from %s to %s\n"), oldgt, newgt); } - - // don't retain teams in other modes or between changes from ctf to team match. - // also, stop any and all forms of team scrambling that might otherwise take place. - if (G_GametypeHasTeams()) - { - INT32 i; - for (i = 0; i < MAXPLAYERS; i++) - if (playeringame[i]) - players[i].ctfteam = 0; - - if (server || (IsPlayerAdmin(consoleplayer))) - { - CV_StealthSetValue(&cv_teamscramble, 0); - teamscramble = 0; - } - } } void Gravity_OnChange(void); @@ -5488,186 +5350,141 @@ void SoundTest_OnChange(void) S_StartSound(NULL, cv_soundtest.value); } -void AutoBalance_OnChange(void); -void AutoBalance_OnChange(void) -{ - autobalance = (INT16)cv_autobalance.value; -} - -void TeamScramble_OnChange(void); -void TeamScramble_OnChange(void) -{ - INT16 i = 0, j = 0, playercount = 0; - boolean repick = true; - INT32 blue = 0, red = 0; - INT32 maxcomposition = 0; - INT16 newteam = 0; - INT32 retries = 0; - boolean success = false; - - // Don't trigger outside level or intermission! - if (!(gamestate == GS_LEVEL || gamestate == GS_INTERMISSION || gamestate == GS_VOTING)) - return; - - if (!cv_teamscramble.value) - teamscramble = 0; - - if (!G_GametypeHasTeams() && (server || IsPlayerAdmin(consoleplayer))) - { - CONS_Alert(CONS_NOTICE, M_GetText("This command cannot be used in this gametype.\n")); - CV_StealthSetValue(&cv_teamscramble, 0); - return; - } - - // If a team scramble is already in progress, do not allow another one to be started! - if (teamscramble) - return; - -retryscramble: - - // Clear related global variables. These will get used again in p_tick.c/y_inter.c as the teams are scrambled. - memset(&scrambleplayers, 0, sizeof(scrambleplayers)); - memset(&scrambleteams, 0, sizeof(scrambleplayers)); - scrambletotal = scramblecount = 0; - blue = red = maxcomposition = newteam = playercount = 0; - repick = true; - - // Put each player's node in the array. - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && !players[i].spectator) - { - scrambleplayers[playercount] = i; - playercount++; - } - } - - if (playercount < 2) - { - CV_StealthSetValue(&cv_teamscramble, 0); - return; // Don't scramble one or zero players. - } - - // Randomly place players on teams. - if (cv_teamscramble.value == 1) - { - maxcomposition = playercount / 2; - - // Now randomly assign players to teams. - // If the teams get out of hand, assign the rest to the other team. - for (i = 0; i < playercount; i++) - { - if (repick) - newteam = (INT16)((M_RandomByte() % 2) + 1); - - // One team has the most players they can get, assign the rest to the other team. - if (red == maxcomposition || blue == maxcomposition) - { - if (red == maxcomposition) - newteam = 2; - else //if (blue == maxcomposition) - newteam = 1; - - repick = false; - } - - scrambleteams[i] = newteam; - - if (newteam == 1) - red++; - else - blue++; - } - } - else if (cv_teamscramble.value == 2) // Same as before, except split teams based on current score. - { - // Now, sort the array based on points scored. - for (i = 1; i < playercount; i++) - { - for (j = i; j < playercount; j++) - { - INT16 tempplayer = 0; - - if ((players[scrambleplayers[i-1]].score > players[scrambleplayers[j]].score)) - { - tempplayer = scrambleplayers[i-1]; - scrambleplayers[i-1] = scrambleplayers[j]; - scrambleplayers[j] = tempplayer; - } - } - } - - // Now assign players to teams based on score. Scramble in pairs. - // If there is an odd number, one team will end up with the unlucky slob who has no points. =( - for (i = 0; i < playercount; i++) - { - if (repick) - { - newteam = (INT16)((M_RandomByte() % 2) + 1); - repick = false; - } - // (i != 2) means it does ABBABABA, instead of ABABABAB. - // Team A gets 1st, 4th, 6th, 8th. - // Team B gets 2nd, 3rd, 5th, 7th. - // So 1st on one team, 2nd/3rd on the other, then alternates afterwards. - // Sounds strange on paper, but works really well in practice! - else if (i != 2) - { - // We will only randomly pick the team for the first guy. - // Otherwise, just alternate back and forth, distributing players. - newteam = 3 - newteam; - } - - scrambleteams[i] = newteam; - } - } - - // Check to see if our random selection actually - // changed anybody. If not, we run through and try again. - for (i = 0; i < playercount; i++) - { - if (players[scrambleplayers[i]].ctfteam != scrambleteams[i]) - success = true; - } - - if (!success && retries < 5) - { - retries++; - goto retryscramble; //try again - } - - // Display a witty message, but only during scrambles specifically triggered by an admin. - if (cv_teamscramble.value) - { - scrambletotal = playercount; - teamscramble = (INT16)cv_teamscramble.value; - - if (!(gamestate == GS_INTERMISSION && cv_scrambleonchange.value)) - CONS_Printf(M_GetText("Teams will be scrambled next round.\n")); - } -} - static void Command_Showmap_f(void) { - if (gamestate == GS_LEVEL) + UINT16 printmap = NEXTMAP_INVALID; + + size_t first_option; + size_t option_random; + size_t option_gametype; + + INT32 newgametype = gametype; + + char * mapname = NULL; + char *realmapname = NULL; + + option_gametype = COM_CheckPartialParm("-g"); + option_random = COM_CheckPartialParm("-r"); + + if (!( first_option = COM_FirstOption() )) + first_option = COM_Argc(); + + if (option_gametype) { - if (mapheaderinfo[gamemap-1]->zonttl[0] && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOZONE)) + newgametype = GetGametypeParm(option_gametype); + if (newgametype == -1) { - if (mapheaderinfo[gamemap-1]->actnum > 0) - CONS_Printf("%s (%d): %s %s %d\n", G_BuildMapName(gamemap), gamemap, mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->zonttl, mapheaderinfo[gamemap-1]->actnum); - else - CONS_Printf("%s (%d): %s %s\n", G_BuildMapName(gamemap), gamemap, mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->zonttl); + return; + } + } + + if (option_random) + { + UINT8 numPlayers = 0; + UINT16 oldmapnum = UINT16_MAX; + if (Playing()) + { + UINT8 i; + for (i = 0; i < MAXPLAYERS; ++i) + { + if (!playeringame[i] || players[i].spectator) + { + continue; + } + + extern consvar_t cv_forcebots; // debug + + if (!(gametypes[newgametype]->rules & GTR_BOTS) && players[i].bot && !cv_forcebots.value) + { + // Gametype doesn't support bots + continue; + } + + numPlayers++; + } + + oldmapnum = (gamestate == GS_LEVEL) + ? (gamemap-1) + : prevmap; + } + else if (!option_gametype) + { + CONS_Printf("Can't use -random from the menu without -gametype.\n"); + return; + } + + printmap = G_RandMapPerPlayerCount(G_TOLFlag(newgametype), oldmapnum, false, false, NULL, numPlayers); + } + else if (first_option < 2) + { + if (!Playing()) + { + CONS_Printf(M_GetText("You must be in a game to use this.\n")); + return; + } + + printmap = (gamestate == GS_LEVEL) + ? gamemap-1 + : prevmap; + } + else + { + mapname = ConcatCommandArgv(1, first_option); + + printmap = G_FindMapByNameOrCode(mapname, &realmapname); + + if (printmap == 0) + { + CONS_Alert(CONS_ERROR, M_GetText("Could not find any map described as '%s'.\n"), mapname); + Z_Free(mapname); + return; + } + + printmap--; // i hate the gamemap off-by-one system + } + + if (printmap < nummapheaders && mapheaderinfo[printmap]) + { + char *title = G_BuildMapTitle(printmap + 1); + + if (mapheaderinfo[printmap]->menuttl[0]) + { + CONS_Printf("%s (%d): %s / %s\n", mapheaderinfo[printmap]->lumpname, printmap, title, mapheaderinfo[printmap]->menuttl); } else { - if (mapheaderinfo[gamemap-1]->actnum > 0) - CONS_Printf("%s (%d): %s %d\n", G_BuildMapName(gamemap), gamemap, mapheaderinfo[gamemap-1]->lvlttl, mapheaderinfo[gamemap-1]->actnum); + CONS_Printf("%s (%d): %s\n", mapheaderinfo[printmap]->lumpname, printmap, title); + } + + Z_Free(title); + + if ((option_random || first_option < 2) && !option_gametype) + ; + else if (mapheaderinfo[printmap]->typeoflevel & G_TOLFlag(newgametype)) + { + CONS_Printf(" compatible with this gametype\n"); + } + else + { + newgametype = G_GuessGametypeByTOL(mapheaderinfo[printmap]->typeoflevel); + + if (newgametype == -1) + { + CONS_Printf(" NOT compatible with any known gametype\n"); + } else - CONS_Printf("%s (%d): %s\n", G_BuildMapName(gamemap), gamemap, mapheaderinfo[gamemap-1]->lvlttl); + { + CONS_Printf(" NOT compatible with this gametype (try \"%s\" instead)\n", gametypes[newgametype]->name); + } } } else - CONS_Printf(M_GetText("You must be in a level to use this.\n")); + { + CONS_Printf("Invalid map ID %u\n", printmap); + } + + Z_Free(realmapname); + Z_Free(mapname); } static void Command_Mapmd5_f(void) @@ -6218,7 +6035,7 @@ static void Got_Cheat(const UINT8 **cp, INT32 playernum) player->roundscore = score; - CV_CheaterWarning(targetPlayer, va("score = %u", score)); + CV_CheaterWarning(targetPlayer, va("roundscore = %u", score)); break; } @@ -7254,6 +7071,18 @@ void Mute_OnChange(void) HU_AddChatText(M_GetText("\x82*Chat is no longer muted."), false); } +void VoiceMute_OnChange(void); +void VoiceMute_OnChange(void) +{ + if (leveltime <= 1) + return; // avoid having this notification put in our console / log when we boot the server. + + if (cv_voice_servermute.value) + HU_AddChatText(M_GetText("\x82*Voice chat has been muted."), false); + else + HU_AddChatText(M_GetText("\x82*Voice chat is no longer muted."), false); +} + /** Hack to clear all changed flags after game start. * A lot of code (written by dummies, obviously) uses COM_BufAddText() to run * commands and change consvars, especially on game start. This is problematic @@ -7282,22 +7111,90 @@ void DummyConsvar_OnChange(void) static void Command_ShowScores_f(void) { - UINT8 i; - if (!(netgame || multiplayer)) { - CONS_Printf(M_GetText("This only works in a netgame.\n")); + CONS_Printf("This only works with multiple players.\n"); return; } + if (K_UsingPowerLevels() != PWRLV_DISABLED) + { + CONS_Printf("PWR is currently active - no scores to show!\n"); + return; + } + + UINT8 i, j, numplayers = 0; + UINT8 playerlist[MAXPLAYERS]; + UINT8 pos[MAXPLAYERS]; + UINT16 completed = 0; + + memset(playerlist, 0, sizeof(playerlist)); + memset(pos, 0, sizeof(pos)); + for (i = 0; i < MAXPLAYERS; i++) { - if (playeringame[i]) - // FIXME: %lu? what's wrong with %u? ~Callum (produces warnings...) - CONS_Printf(M_GetText("%s's score is %u\n"), player_names[i], players[i].score); - } - CONS_Printf(M_GetText("The pointlimit is %d\n"), g_pointlimit); + if (playeringame[i] && !players[i].spectator) + { + numplayers++; + continue; + } + completed |= (1 << i); + } + + if (!numplayers) + { + CONS_Printf("No players are currently in-game.\n"); + return; + } + + // This is largely based off of Y_CalculateMatchData. + + for (j = 0; j < numplayers; j++) + { + UINT8 workp = UINT8_MAX; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (completed & (1 << i)) + continue; + if (workp != UINT8_MAX && players[workp].score >= players[i].score) + continue; + workp = i; + } + + completed |= (1 << workp); + + playerlist[j] = workp; + + if (j && players[workp].score == players[playerlist[j-1]].score) + { + pos[j] = pos[j-1]; + } + else + { + pos[j] = j+1; + } + } + + if (roundqueue.size && roundqueue.position) + { + CONS_Printf( + "Rankings %s Round %u:\n", + (gamestate == GS_LEVEL) ? "before" : "as of", + roundqueue.roundnum + ); + } + else + { + CONS_Printf("Total Rankings:\n"); + } + + for (i = 0; i < numplayers; i++) + { + j = playerlist[i]; + CONS_Printf(" %2u - %*s : %9d\n", pos[i], MAXPLAYERNAME-1, player_names[j], players[j].score); // 9 taken from MAXSCORE + } } static void Command_ShowTime_f(void) diff --git a/src/d_netcmd.h b/src/d_netcmd.h index f6d863aac..bff6fe264 100644 --- a/src/d_netcmd.h +++ b/src/d_netcmd.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // @@ -57,17 +57,14 @@ extern UINT32 timelimitintics, extratimeintics, secretextratime; extern UINT32 g_pointlimit; extern consvar_t cv_allowexitlevel; -extern consvar_t cv_autobalance; -extern consvar_t cv_teamscramble; -extern consvar_t cv_scrambleonchange; - extern consvar_t cv_netstat; extern consvar_t cv_countdowntime; extern consvar_t cv_mute; +extern consvar_t cv_voice_servermute; extern consvar_t cv_pause; -extern consvar_t cv_restrictskinchange, cv_allowteamchange, cv_maxplayers; +extern consvar_t cv_restrictskinchange, cv_allowteamchange, cv_maxplayers, cv_shuffleloser; extern consvar_t cv_spectatorreentry, cv_duelspectatorreentry, cv_antigrief; // SRB2kart items @@ -83,6 +80,7 @@ extern consvar_t cv_karthorns; extern consvar_t cv_kartbot; extern consvar_t cv_karteliminatelast; extern consvar_t cv_thunderdome; +extern consvar_t cv_teamplay; extern consvar_t cv_kartusepwrlv; #ifdef DEVELOP extern consvar_t cv_kartencoremap; @@ -155,8 +153,8 @@ typedef enum XD_ADDFILE, // 8 XD_PAUSE, // 9 XD_ADDPLAYER, // 10 - XD_TEAMCHANGE, // 11 - XD_CLEARSCORES, // 12 + XD_SPECTATE, // 11 + XD_SETSCORE, // 12 XD_VERIFIED, // 13 XD_RANDOMSEED, // 14 XD_RUNSOC, // 15 @@ -183,56 +181,19 @@ typedef enum XD_SCHEDULETASK, // 34 XD_SCHEDULECLEAR, // 35 XD_AUTOMATE, // 36 - XD_REQMAPQUEUE, // 37 - XD_MAPQUEUE, // 38 + // 37 is free + XD_MAPQUEUE = XD_AUTOMATE+2, // 38 XD_CALLZVOTE, // 39 XD_SETZVOTE, // 40 + XD_TEAMCHANGE, // 41 + XD_SERVERMUTEPLAYER, // 42 + XD_SERVERDEAFENPLAYER, // 43 MAXNETXCMD } netxcmd_t; extern const char *netxcmdnames[MAXNETXCMD - 1]; -#if defined(_MSC_VER) -#pragma pack(1) -#endif - -#ifdef _MSC_VER -#pragma warning(disable : 4214) -#endif - -//Packet composition for Command_TeamChange_f() ServerTeamChange, etc. -//bitwise structs make packing bits a little easier, but byte alignment harder? -//todo: decide whether to make the other netcommands conform, or just get rid of this experiment. -struct changeteam_packet_t { - UINT32 playernum : 5; // value 0 to 31 - UINT32 newteam : 5; // value 0 to 31 - UINT32 verification : 1; // value 0 to 1 - UINT32 autobalance : 1; // value 0 to 1 - UINT32 scrambled : 1; // value 0 to 1 -} ATTRPACK; - -#ifdef _MSC_VER -#pragma warning(default : 4214) -#endif - -struct changeteam_value_t { - UINT16 l; // liitle endian - UINT16 b; // big enian -} ATTRPACK; - -//Since we do not want other files/modules to know about this data buffer we union it here with a Short Int. -//Other files/modules will hand the INT16 back to us and we will decode it here. -//We don't have to use a union, but we would then send four bytes instead of two. -typedef union { - changeteam_packet_t packet; - changeteam_value_t value; -} ATTRPACK changeteam_union; - -#if defined(_MSC_VER) -#pragma pack() -#endif - // add game commands, needs cleanup void D_RegisterServerCommands(void); void D_RegisterClientCommands(void); diff --git a/src/d_netfil.c b/src/d_netfil.c index 252aaabdf..3deaeeab7 100644 --- a/src/d_netfil.c +++ b/src/d_netfil.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/d_netfil.h b/src/d_netfil.h index c58972e76..ec7c4a73a 100644 --- a/src/d_netfil.h +++ b/src/d_netfil.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/d_player.h b/src/d_player.h index aa1597297..0c3501227 100644 --- a/src/d_player.h +++ b/src/d_player.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -50,6 +50,7 @@ typedef enum SF_MACHINE = 1, // Beep boop. Are you a robot? SF_IRONMAN = 1<<1, // Pick a new skin during POSITION. I main Random! SF_BADNIK = 1<<2, // Explodes on death + SF_HIVOLT = 1<<3, // High power TA ringboxes, like 2.0-2.3! // free up to and including 1<<31 } skinflags_t; @@ -132,6 +133,15 @@ typedef enum PF_NOFASTFALL = (INT32)(1U<<31), // Has already done ebrake/fastfall behavior for this input. Fastfalling needs a new input to prevent unwanted bounces on unexpected airtime. } pflags_t; +typedef enum +{ + PF2_SELFMUTE = 1<<1, + PF2_SELFDEAFEN = 1<<2, + PF2_SERVERMUTE = 1<<3, + PF2_SERVERDEAFEN = 1<<4, + PF2_STRICTFASTFALL = 1<<5, +} pflags2_t; + typedef enum { // Are animation frames playing? @@ -150,6 +160,7 @@ typedef enum CR_SLIDING, CR_ZOOMTUBE, CR_DASHRING, + CR_TRAPBUBBLE, } carrytype_t; // carry /* @@ -397,6 +408,7 @@ struct botvars_t // All entries above persist between rounds and must be recorded in demos fixed_t rubberband; // Bot rubberband value + UINT8 bumpslow; tic_t itemdelay; // Delay before using item at all tic_t itemconfirm; // When high enough, they will use their item @@ -634,6 +646,7 @@ struct player_t // Bit flags. // See pflags_t, above. UINT32 pflags; + UINT32 pflags2; // playing animation. panim_t panim; @@ -671,6 +684,11 @@ struct player_t UINT8 carry; UINT16 dye; + INT32 prefskin; // Queued skin change + UINT16 prefcolor; // Queued color change + INT32 preffollower; // Queued follower change + UINT16 preffollowercolor; // Queued follower color change + // SRB2kart stuff INT32 karthud[NUMKARTHUD]; @@ -678,12 +696,19 @@ struct player_t UINT8 position; // Used for Kart positions, mostly for deterministic stuff UINT8 oldposition; // Used for taunting when you pass someone UINT8 positiondelay; // Used for position number, so it can grow when passing + + UINT8 teamposition; // Position, but only against other teams -- not your own. + UINT8 teamimportance; // Opposite of team position x2, with +1 for being in 1st. + UINT32 distancetofinish; UINT32 distancetofinishprev; + UINT32 lastpickupdistance; // Anti item set farming UINT8 lastpickuptype; + waypoint_t *currentwaypoint; waypoint_t *nextwaypoint; + respawnvars_t respawn; // Respawn info mobj_t *ringShooter; // DEZ respawner object tic_t airtime; // Used to track just air time, but has evolved over time into a general "karted" timer. Rename this variable? @@ -702,9 +727,13 @@ struct player_t UINT8 noEbrakeMagnet; // Briefly disable 2.2 responsive ebrake if you're bumped by another player. UINT8 tumbleBounces; UINT16 tumbleHeight; // In *mobjscaled* fracunits, or mfu, not raw fu + UINT16 stunned; // Number of tics during which rings cannot be picked up + UINT8 stunnedCombo; // Number of hits sustained while stunned, reduces consecutive stun penalties UINT8 justDI; // Turn-lockout timer to briefly prevent unintended turning after DI, resets when actionable or no input boolean flipDI; // Bananas flip the DI direction. Was a bug, but it made bananas much more interesting. + UINT8 cangrabitems; + SINT8 drift; // (-5 to 5) - Drifting Left or Right, plus a bigger counter = sharper turn fixed_t driftcharge; // Charge your drift so you can release a burst of speed UINT16 driftboost; // (0 to 125 baseline) - Boost you get from drifting @@ -755,6 +784,8 @@ struct player_t // Item held stuff SINT8 itemtype; // KITEM_ constant for item number UINT8 itemamount; // Amount of said item + SINT8 backupitemtype; + UINT8 backupitemamount; SINT8 throwdir; // Held dir of controls; 1 = forward, 0 = none, -1 = backward (was "player->heldDir") UINT8 itemscale; // Item scale value, from when an item was taken out. (0 for normal, 1 for grow, 2 for shrink.) @@ -920,15 +951,15 @@ struct player_t tic_t laptime[LAP__MAX]; UINT8 laps; // Number of laps (optional) UINT8 latestlap; - UINT32 lapPoints; // Points given from laps - INT32 exp; + UINT32 exp; // Points given from laps and checkpoints + fixed_t gradingfactor; UINT16 gradingpointnum; // how many grading points, checkpoint and finishline, you've passed INT32 cheatchecknum; // The number of the last cheatcheck you hit INT32 checkpointId; // Players respawn here, objects/checkpoint.cpp INT16 duelscore; - UINT8 ctfteam; // 0 == Spectator, 1 == Red, 2 == Blue + UINT8 team; // 0 == Spectator, 1 == Red, 2 == Blue UINT8 checkskip; // Skipping checkpoints? Oh no no no @@ -983,6 +1014,8 @@ struct player_t UINT8 tripwireReboundDelay; // When failing Tripwire, brieftly lock out speed-based tripwire pass (anti-cheese) UINT16 wavedash; // How long is our chained sliptide? Grant a proportional boost when it's over. + UINT16 wavedashleft; + UINT16 wavedashright; UINT8 wavedashdelay; // How long since the last sliptide? Only boost once you've been straightened out for a bit. UINT16 wavedashboost; // The actual boost granted from wavedash. fixed_t wavedashpower; // Is this a bullshit "tap" wavedash? Weaken lower-charge wavedashes while keeping long sliptides fully rewarding. @@ -1040,11 +1073,12 @@ struct player_t UINT8 ringboxdelay; // Delay until Ring Box auto-activates UINT8 ringboxaward; // Where did we stop? + UINT32 lastringboost; // What was our accumulated boost when locking the award? UINT8 amps; UINT8 amppickup; UINT8 ampspending; - + UINT16 overdrive; UINT16 overshield; fixed_t overdrivepower; @@ -1055,6 +1089,9 @@ struct player_t fixed_t outrun; // Milky Way road effect + fixed_t transfer; // Tired of Ramp Park fastfalls + boolean transfersound; + uint8_t public_key[PUBKEYLENGTH]; #ifdef HWRENDER diff --git a/src/d_think.h b/src/d_think.h index 550ae7a01..97b61e257 100644 --- a/src/d_think.h +++ b/src/d_think.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -38,6 +38,15 @@ typedef union actionf_p1 acp1; } actionf_t; +typedef enum +{ + /// The allocation is standard e.g. Z_Malloc + TAT_MALLOC, + + /// The allocation is in the pool allocator (e.g. Z_LevelPoolCalloc) + TAT_LEVELPOOL +} thinker_alloc_type_e; + // Historically, "think_t" is yet another function pointer to a routine // to handle an actor. typedef actionf_t think_t; @@ -52,7 +61,8 @@ struct thinker_t // killough 11/98: count of how many other objects reference // this one using pointers. Used for garbage collection. INT32 references; - boolean cachable; + INT32 alloctype; + size_t size; #ifdef PARANOIA INT32 debug_mobjtype; diff --git a/src/d_ticcmd.h b/src/d_ticcmd.h index 046488a98..f0c5f4f28 100644 --- a/src/d_ticcmd.h +++ b/src/d_ticcmd.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -34,16 +34,20 @@ typedef enum BT_LOOKBACK = 1<<5, // Look Backward BT_RESPAWN = 1<<6, // Respawn BT_VOTE = 1<<7, // Vote + BT_SPINDASH = 1<<8, // Spindash - BT_EBRAKEMASK = (BT_ACCELERATE|BT_BRAKE), - BT_SPINDASHMASK = (BT_ACCELERATE|BT_BRAKE|BT_DRIFT), + BT_EBRAKEMASK = (BT_ACCELERATE|BT_BRAKE), + BT_SPINDASHMASK = (BT_ACCELERATE|BT_BRAKE|BT_DRIFT), - // free: 1<<8 to 1<<12 + // free: 1<<9 to 1<<12 // Lua garbage, replace with freeslottable buttons some day BT_LUAA = 1<<13, + BT_LUA1 = 1<<13, BT_LUAB = 1<<14, + BT_LUA2 = 1<<14, BT_LUAC = 1<<15, + BT_LUA3 = 1<<15, } buttoncode_t; // The data sampled per tick (single player) diff --git a/src/deh_lua.c b/src/deh_lua.c index 224d0c010..eed99c22b 100644 --- a/src/deh_lua.c +++ b/src/deh_lua.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/deh_lua.h b/src/deh_lua.h index 412e76ff8..87b3e7436 100644 --- a/src/deh_lua.h +++ b/src/deh_lua.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/deh_soc.c b/src/deh_soc.c index 32475d0a6..53da34d0b 100644 --- a/src/deh_soc.c +++ b/src/deh_soc.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // @@ -1494,6 +1494,18 @@ void readlevelheader(MYFILE *f, char * name) deh_warning("Level header %d: invalid lobby size '%s'", num, word2); } } + else if (fastcmp(word, "CAMHEIGHT") || fastcmp(word, "CAMERAHEIGHT")) + { + fixed_t camheight = FloatToFixed(atof(word2)); + + if (camheight < 0) + { + deh_warning("Level header %d: invalid camera height %s", num, word2); + continue; + } + + mapheaderinfo[num]->cameraHeight = camheight;; + } else deh_warning("Level header %d: unknown word '%s'", num, word); } @@ -3533,7 +3545,7 @@ void readmaincfg(MYFILE *f, boolean mainfile) else if (fastcmp(word, "EXECCFG")) { if (strchr(word2, '.')) - COM_BufAddText(va("exec %s\n", word2)); + COM_BufAddText(va("exec \"%s\" -immediate\n", word2)); else { lumpnum_t lumpnum; @@ -3551,22 +3563,6 @@ void readmaincfg(MYFILE *f, boolean mainfile) COM_BufInsertText(W_CacheLumpNum(lumpnum, PU_CACHE)); } } - else if (fastcmp(word, "REDTEAM")) - { - skincolor_redteam = (UINT16)get_number(word2); - } - else if (fastcmp(word, "BLUETEAM")) - { - skincolor_blueteam = (UINT16)get_number(word2); - } - else if (fastcmp(word, "REDRING")) - { - skincolor_redring = (UINT16)get_number(word2); - } - else if (fastcmp(word, "BLUERING")) - { - skincolor_bluering = (UINT16)get_number(word2); - } else if (fastcmp(word, "INVULNTICS")) { invulntics = (UINT16)get_number(word2); @@ -4697,7 +4693,7 @@ preciptype_t get_precip(const char *word) return i; } deh_warning("Couldn't find weather type named 'PRECIP_%s'",word); - return PRECIP_RAIN; + return PRECIP_NONE; } /// \todo Make ANY of this completely over-the-top math craziness obey the order of operations. diff --git a/src/deh_soc.h b/src/deh_soc.h index 1687054e2..248c7d80d 100644 --- a/src/deh_soc.h +++ b/src/deh_soc.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/deh_tables.c b/src/deh_tables.c index 0b2e3a9dc..2d92aac06 100644 --- a/src/deh_tables.c +++ b/src/deh_tables.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // @@ -369,6 +369,7 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_KART_LEFTOVER", "S_KART_LEFTOVER_NOTIRES", + "S_KART_LEFTOVER_CUSTOM", "S_KART_TIRE1", "S_KART_TIRE2", @@ -1575,6 +1576,8 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_AMPAURA", "S_AMPBURST", + "S_GOTIT", + "S_CHARGEAURA", "S_CHARGEFALL", "S_CHARGEFLICKER", @@ -1772,6 +1775,19 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_WIPEOUTTRAIL10", "S_WIPEOUTTRAIL11", + // "Firework" dust trail + "S_FIREWORKTRAIL", + "S_FIREWORKTRAIL2", + "S_FIREWORKTRAIL3", + "S_FIREWORKTRAIL4", + "S_FIREWORKTRAIL5", + "S_FIREWORKTRAIL6", + "S_FIREWORKTRAIL7", + "S_FIREWORKTRAIL8", + "S_FIREWORKTRAIL9", + "S_FIREWORKTRAIL10", + "S_FIREWORKTRAIL11", + // Rocket sneaker "S_ROCKETSNEAKER_L", "S_ROCKETSNEAKER_R", @@ -1979,6 +1995,12 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_LIGHTNINGSHIELD23", "S_LIGHTNINGSHIELD24", + // Lightning Shield Visuals + "S_THNC1", + "S_THNA1", + "S_THNC2", + "S_THNB1", + // Bubble Shield "S_BUBBLESHIELD1", "S_BUBBLESHIELD2", @@ -2014,6 +2036,15 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_BUBBLESHIELDWAVE5", "S_BUBBLESHIELDWAVE6", + // Bubble Shield Visuals + "S_BUBA1", + "S_BUBB1", + "S_BUBB2", + "S_BUBC1", + "S_BUBC2", + "S_BUBD1", + "S_BUBE1", + // Flame Shield "S_FLAMESHIELD1", "S_FLAMESHIELD2", @@ -2034,6 +2065,11 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_FLAMESHIELD17", "S_FLAMESHIELD18", + // Flame Shield Visuals + "S_FLMA1", + "S_FLMA2", + "S_FLMB1", + "S_FLAMESHIELDDASH1", "S_FLAMESHIELDDASH2", "S_FLAMESHIELDDASH3", @@ -2148,6 +2184,8 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_TRIPWIREBOOST_BLAST_TOP", "S_TRIPWIREBOOST_BLAST_BOTTOM", + "S_TRIPWIREAPPROACH", + "S_SMOOTHLANDING", "S_TRICKINDICATOR_OVERLAY", @@ -3065,6 +3103,9 @@ const char *const STATE_LIST[] = { // array length left dynamic for sanity testi "S_BADNIK_EXPLOSION_SHOCKWAVE2", "S_BADNIK_EXPLOSION1", "S_BADNIK_EXPLOSION2", + + // Flybot767 (stun) + "S_FLYBOT767", }; // RegEx to generate this from info.h: ^\tMT_([^,]+), --> \t"MT_\1", @@ -3520,6 +3561,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_AMPAURA", "MT_AMPBURST", + "MT_GOTIT", + "MT_CHARGEAURA", "MT_CHARGEFALL", "MT_CHARGEFLICKER", @@ -3596,8 +3639,11 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_MANTARING", // Juicebox for SPB "MT_LIGHTNINGSHIELD", // Shields + "MT_LIGHTNINGSHIELD_VISUAL", "MT_BUBBLESHIELD", + "MT_BUBBLESHIELD_VISUAL", "MT_FLAMESHIELD", + "MT_FLAMESHIELD_VISUAL", "MT_FLAMESHIELDUNDERLAY", "MT_FLAMESHIELDPAPER", "MT_BUBBLESHIELDTRAP", @@ -3630,6 +3676,7 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_BATTLEBUMPER_BLAST", "MT_TRIPWIREBOOST", + "MT_TRIPWIREAPPROACH", "MT_SMOOTHLANDING", "MT_TRICKINDICATOR", @@ -3956,6 +4003,8 @@ const char *const MOBJTYPE_LIST[] = { // array length left dynamic for sanity t "MT_PULLUPHOOK", "MT_AMPS", + + "MT_FLYBOT767", }; const char *const MOBJFLAG_LIST[] = { @@ -4686,12 +4735,16 @@ struct int_const_s const INT_CONST[] = { // Carrying {"CR_NONE",CR_NONE}, + {"CR_SLIDING",CR_SLIDING}, {"CR_ZOOMTUBE",CR_ZOOMTUBE}, + {"CR_DASHRING",CR_DASHRING}, + {"CR_TRAPBUBBLE",CR_TRAPBUBBLE}, // Character flags (skinflags_t) {"SF_MACHINE",SF_MACHINE}, {"SF_IRONMAN",SF_IRONMAN}, {"SF_BADNIK",SF_BADNIK}, + {"SF_HIVOLT",SF_HIVOLT}, // Sound flags {"SF_TOTALLYSINGLE",SF_TOTALLYSINGLE}, @@ -4985,11 +5038,15 @@ struct int_const_s const INT_CONST[] = { {"BT_LOOKBACK",BT_LOOKBACK}, {"BT_RESPAWN",BT_RESPAWN}, {"BT_VOTE",BT_VOTE}, + {"BT_SPINDASH",BT_SPINDASH}, // Real button now, but triggers the macro same as always. {"BT_EBRAKEMASK",BT_EBRAKEMASK}, // Macro button {"BT_SPINDASHMASK",BT_SPINDASHMASK}, // Macro button {"BT_LUAA",BT_LUAA}, // Lua customizable {"BT_LUAB",BT_LUAB}, // Lua customizable {"BT_LUAC",BT_LUAC}, // Lua customizable + {"BT_LUA1",BT_LUA1}, // Lua customizable + {"BT_LUA2",BT_LUA2}, // Lua customizable + {"BT_LUA3",BT_LUA3}, // Lua customizable // Lua command registration flags {"COM_ADMIN",COM_ADMIN}, @@ -5179,6 +5236,40 @@ struct int_const_s const INT_CONST[] = { {"FOLLOWERMODE_FLOAT",FOLLOWERMODE_FLOAT}, {"FOLLOWERMODE_GROUND",FOLLOWERMODE_GROUND}, + // tripwirepass_t + {"TRIPWIRE_NONE",TRIPWIRE_NONE}, + {"TRIPWIRE_IGNORE",TRIPWIRE_IGNORE}, + {"TRIPWIRE_BOOST",TRIPWIRE_BOOST}, + {"TRIPWIRE_BLASTER",TRIPWIRE_BLASTER}, + {"TRIPWIRE_CONSUME",TRIPWIRE_CONSUME}, + + // trickstate_t + {"TRICKSTATE_NONE",TRICKSTATE_NONE}, + {"TRICKSTATE_READY",TRICKSTATE_READY}, + {"TRICKSTATE_FORWARD",TRICKSTATE_FORWARD}, + {"TRICKSTATE_RIGHT",TRICKSTATE_RIGHT}, + {"TRICKSTATE_LEFT",TRICKSTATE_LEFT}, + {"TRICKSTATE_BACK",TRICKSTATE_BACK}, + + // items + {"GARDENTOP_MAXGRINDTIME",GARDENTOP_MAXGRINDTIME}, + {"BALLHOGINCREMENT",BALLHOGINCREMENT}, + + // kickstart + {"ACCEL_KICKSTART",ACCEL_KICKSTART}, + + // tripwires + {"TRIPWIRETIME",TRIPWIRETIME}, + + // tricks + {"TRICKMOMZRAMP",TRICKMOMZRAMP}, + {"TRICKLAG",TRICKLAG}, + {"TRICKDELAY",TRICKDELAY}, + + // tumble + {"TUMBLEBOUNCES",TUMBLEBOUNCES}, + {"TUMBLEGRAVITY",TUMBLEGRAVITY}, + // tune flags {"TN_INCLUSIVEFADE",TN_INCLUSIVEFADE}, {"TN_USEMAPVOLUME",TN_USEMAPVOLUME}, @@ -5189,6 +5280,11 @@ struct int_const_s const INT_CONST[] = { {"TN_CHANGEPITCH",TN_CHANGEPITCH}, {"TN_LOOPING",TN_LOOPING}, + {"PICKUP_RINGORSPHERE", PICKUP_RINGORSPHERE}, + {"PICKUP_ITEMBOX", PICKUP_ITEMBOX}, + {"PICKUP_EGGBOX", PICKUP_EGGBOX}, + {"PICKUP_PAPERITEM", PICKUP_PAPERITEM}, + {NULL,0} }; diff --git a/src/deh_tables.h b/src/deh_tables.h index 4b8ef69a5..2d44f1f69 100644 --- a/src/deh_tables.h +++ b/src/deh_tables.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/dehacked.c b/src/dehacked.c index 71fa714ae..92d59fe85 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // @@ -378,7 +378,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) { if (i == 0 && word2[0] != '0') // If word2 isn't a number i = get_state(word2); // find a state by name - if (i < NUMSTATES && i >= 0) + if (i < NUMSTATES && i > 0) { if (i < (S_FIRSTFREESLOT+freeslotusage[0][1])) { @@ -389,7 +389,7 @@ static void DEH_LoadDehackedFile(MYFILE *f, boolean mainfile) } else { - deh_warning("Frame %d out of range (0 - %d)", i, NUMSTATES-1); + deh_warning("Frame %d out of range (1 - %d)", i, NUMSTATES-1); ignorelines(f); } } diff --git a/src/dehacked.h b/src/dehacked.h index f78d31612..6fc55accf 100644 --- a/src/dehacked.h +++ b/src/dehacked.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/discord.c b/src/discord.c index 584ccb7b7..dc5ff44cd 100644 --- a/src/discord.c +++ b/src/discord.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/discord.h b/src/discord.h index 0de232c71..2aa2d43c6 100644 --- a/src/discord.h +++ b/src/discord.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/doomdata.h b/src/doomdata.h index bfcd20d39..c81bc059b 100644 --- a/src/doomdata.h +++ b/src/doomdata.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/doomdef.h b/src/doomdef.h index 844be1261..cef04507f 100644 --- a/src/doomdef.h +++ b/src/doomdef.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -544,6 +544,7 @@ typedef enum DBG_LUA = 0x00000800, DBG_RNG = 0x00001000, DBG_DEMO = 0x00002000, + DBG_TEAMS = 0x00004000, } debugFlags_t; struct debugFlagNames_s @@ -558,6 +559,9 @@ extern struct debugFlagNames_s const debug_flag_names[]; // Misc stuff for later... // ======================= +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif #define ANG2RAD(angle) ((float)((angle)*M_PI)/ANGLE_180) // Modifier key variables, accessible anywhere @@ -662,9 +666,6 @@ extern int // None of these that are disabled in the normal build are guaranteed to work perfectly // Compile them at your own risk! -/// Dumps the contents of a network save game upon consistency failure for debugging. -//#define DUMPCONSISTENCY - /// Who put weights on my recycler? ... Inuyasha did. /// \note XMOD port. //#define WEIGHTEDRECYCLER @@ -740,6 +741,14 @@ extern int /// Other karma comeback modes //#define OTHERKARMAMODES +// Amp scaling +#define MAXAMPSCALINGDIST 18000 + +// Exp +#define MINEXP 50 // The min value target +#define TARGETEXP 100 // The target value needed for A rank +#define MAXEXP 125 // The max value displayed by the hud and in the tally screen and GP results screen + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/doomstat.h b/src/doomstat.h index a31a8dc9b..c239fa5f2 100644 --- a/src/doomstat.h +++ b/src/doomstat.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -38,7 +38,9 @@ extern "C" { // ============================= #define ROUNDQUEUE_MAX 10 // sane max? maybe make dynamically allocated later -#define ROUNDQUEUE_CLEAR UINT16_MAX // lives in gametype field of packets +// These two live in gametype field of packets +#define ROUNDQUEUE_CMD_CLEAR UINT16_MAX +#define ROUNDQUEUE_CMD_SHOW UINT16_MAX-1 // The roundqueue itself is resident in g_game.h // Selected by user. @@ -154,6 +156,9 @@ struct skinreference_t #define MV_MYSTICMELODY (1<<4) #define MV_MAX (MV_VISITED|MV_BEATEN|MV_ENCORE|MV_SPBATTACK|MV_MYSTICMELODY) +#define MCAN_INVALID (UINT16_MAX) +#define MCAN_BONUS (UINT16_MAX-1) + struct recordtimes_t { tic_t time; ///< Time in which the level was finished. @@ -162,9 +167,10 @@ struct recordtimes_t struct recorddata_t { - UINT8 mapvisited; + UINT8 mapvisited; ///< Generalised flags recordtimes_t timeattack; ///< Best times for Time Attack recordtimes_t spbattack; ///< Best times for SPB Attack + UINT16 spraycan; ///< Associated spraycan id UINT32 timeplayed; UINT32 netgametimeplayed; UINT32 modetimeplayed[GDGT_MAX]; @@ -237,6 +243,7 @@ extern boolean forceresetplayers, deferencoremode, forcespecialstage; extern boolean sound_disabled; extern boolean digital_disabled; +extern boolean g_voice_disabled; // ========================= // Status flags for refresh. @@ -277,9 +284,6 @@ extern UINT8 tutorialchallenge; #define TUTORIALSKIP_FAILED 1 #define TUTORIALSKIP_INPROGRESS 2 -// CTF colors. -extern UINT16 skincolor_redteam, skincolor_blueteam, skincolor_redring, skincolor_bluering; - extern boolean exitfadestarted; struct scene_t @@ -558,6 +562,8 @@ struct mapheader_t mapheader_lighting_t lighting_encore; ///< Alternative lighting for Encore mode boolean use_encore_lighting; ///< Whether to use separate Encore lighting + fixed_t cameraHeight; ///< Player camera height to use on this map + // Audience information UINT8 numFollowers; ///< Internal. For audience support. INT16 *followers; ///< List of audience followers in this level. Allocated dynamically for space reasons. Be careful. @@ -573,7 +579,6 @@ struct mapheader_t mobjtype_t destroyforchallenge[MAXDESTRUCTIBLES]; ///< Assistive for UCRP_MAPDESTROYOBJECTS UINT8 destroyforchallenge_size; ///< Number for above - UINT16 cache_spraycan; ///< Cached Spraycan ID UINT16 cache_maplock; ///< Cached Unlockable ID // Lua information @@ -762,8 +767,24 @@ extern INT32 nummaprings; //keep track of spawned rings/coins extern UINT8 nummapspraycans; extern UINT16 numchallengedestructibles; -extern UINT32 bluescore; ///< Blue Team Scores -extern UINT32 redscore; ///< Red Team Scores +// Teamplay +typedef enum +{ + TEAM_UNASSIGNED = 0, + TEAM_ORANGE, + TEAM_BLUE, + TEAM__MAX +} team_e; + +struct teaminfo_t +{ + const char *name; + skincolornum_t color; + UINT32 chat_color; +}; + +extern teaminfo_t g_teaminfo[TEAM__MAX]; +extern UINT32 g_teamscores[TEAM__MAX]; // Eliminates unnecessary searching. extern boolean CheckForBustableBlocks; @@ -845,19 +866,12 @@ extern struct maplighting angle_t angle; } maplighting; -//for CTF balancing -extern INT16 autobalance; -extern INT16 teamscramble; -extern INT16 scrambleplayers[MAXPLAYERS]; //for CTF team scramble -extern INT16 scrambleteams[MAXPLAYERS]; //for CTF team scramble -extern INT16 scrambletotal; //for CTF team scramble -extern INT16 scramblecount; //for CTF team scramble - // SRB2kart extern UINT8 numlaps; extern UINT8 gamespeed; extern boolean franticitems; extern boolean encoremode, prevencoremode; +extern boolean g_teamplay; extern tic_t wantedcalcdelay; extern tic_t itemCooldowns[NUMKARTITEMS - 1]; @@ -899,8 +913,7 @@ extern tic_t gametic; // Player spawn spots. extern mapthing_t *playerstarts[MAXPLAYERS]; // Cooperative -extern mapthing_t *bluectfstarts[MAXPLAYERS]; // CTF -extern mapthing_t *redctfstarts[MAXPLAYERS]; // CTF +extern mapthing_t *teamstarts[TEAM__MAX][MAXPLAYERS]; // Teamplay extern mapthing_t *faultstart; // Kart Fault #define TUBEWAYPOINTSEQUENCESIZE 256 diff --git a/src/doomtype.h b/src/doomtype.h index 2d3558551..6f2d80794 100644 --- a/src/doomtype.h +++ b/src/doomtype.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -258,7 +258,7 @@ enum {false = 0, true = 1}; #endif #endif - #if defined (__MINGW32__) && ((__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) // MinGW, >= GCC 3.4 + #if defined (__MINGW32__) && ((__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)) && !defined(__clang__) // MinGW, >= GCC 3.4, not Clang #define ATTRPACK __attribute__((packed, gcc_struct)) #else #define ATTRPACK __attribute__((packed)) @@ -407,8 +407,12 @@ typedef UINT64 precise_t; // that struct and it's fine... // Cast function pointer to (void*) -#define FUNCPTRCAST(p) ((union{void(*f)(void);void*v;})\ - {(void(*)(void))p}).v +typedef union { + void (*f)(void); + void *v; +} func_ptr_cast_union; + +#define FUNCPTRCAST(p) (((func_ptr_cast_union){(void(*)(void))(p)}).v) #include "typedef.h" diff --git a/src/dummy/i_cdmus.c b/src/dummy/i_cdmus.c index eaf11066e..14cf1fda7 100644 --- a/src/dummy/i_cdmus.c +++ b/src/dummy/i_cdmus.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/dummy/i_main.c b/src/dummy/i_main.c index b1ed14fab..617253b48 100644 --- a/src/dummy/i_main.c +++ b/src/dummy/i_main.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/dummy/i_net.c b/src/dummy/i_net.c index cecb6697c..6df06559d 100644 --- a/src/dummy/i_net.c +++ b/src/dummy/i_net.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/dummy/i_sound.c b/src/dummy/i_sound.c index 308d78f56..74b052208 100644 --- a/src/dummy/i_sound.c +++ b/src/dummy/i_sound.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -224,3 +224,30 @@ boolean I_FadeInPlaySong(UINT32 ms, boolean looping) (void)looping; return false; } + +boolean I_SoundInputIsEnabled(void) +{ + return false; +} + +boolean I_SoundInputSetEnabled(boolean enabled) +{ + return false; +} + +UINT32 I_SoundInputDequeueSamples(void *data, UINT32 len) +{ + return 0; +} + +void I_QueueVoiceFrameFromPlayer(INT32 playernum, void *data, UINT32 len, boolean terminal) +{ +} + +void I_SetPlayerVoiceProperties(INT32 playernum, float volume, float sep) +{ +} + +void I_ResetVoiceQueue(INT32 playernum) +{ +} diff --git a/src/dummy/i_system.c b/src/dummy/i_system.c index e44d5efb6..666b61c43 100644 --- a/src/dummy/i_system.c +++ b/src/dummy/i_system.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/dummy/i_video.c b/src/dummy/i_video.c index 171304818..11e44cdbb 100644 --- a/src/dummy/i_video.c +++ b/src/dummy/i_video.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/endian.h b/src/endian.h index 2d8163ddb..2f67ec3b2 100644 --- a/src/endian.h +++ b/src/endian.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/f_finale.c b/src/f_finale.c index 55fec2aa7..fcb5821a8 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -923,6 +923,20 @@ void F_IntroTicker(void) S_StartSound(NULL, sfx_supflk); } + if (skiptype == 5) // Quick Race Menu + { + ResetSkipSequences(); + M_StartControlPanel(); + currentMenu = &PLAY_RaceGamemodesDef; + return; + } + + if (skiptype == 6) // Dev Exec + { + ResetSkipSequences(); + COM_ImmedExecute("exec devexec.cfg"); + } + if (doskip && disclaimerskippable) { if (dc_state == DISCLAIMER_FINAL) { @@ -1013,16 +1027,23 @@ static void AdvanceSkipSequences(UINT8 input) UINT8 s2cheat[] = {1, 1, 1}; UINT8 s3cheat[] = {2, 2, 2}; UINT8 s3kcheat[] = {3, 3, 3}; + UINT8 spincheat[] = {1, 3, 1}; + UINT8 devcheat[] = {4, 4, 4}; #else UINT8 s2cheat[] = {1, 1, 1, 3, 3, 3, 1}; UINT8 s3cheat[] = {1, 1, 3, 3, 1, 1, 1, 1}; UINT8 s3kcheat[] = {4, 4, 4, 2, 2, 2, 1, 1, 1}; + UINT8 spincheat[] = {1, 2, 3, 4, 3, 2, 1}; + UINT8 devcheat[] = {4, 4, 4}; #endif UINT8 nicetry[] = {1, 1, 3, 3, 4, 2, 4, 2}; - UINT8 *cheats[4] = {s2cheat, s3cheat, s3kcheat, nicetry}; - UINT8 cheatlengths[4] = {sizeof(s2cheat), sizeof(s3cheat), sizeof(s3kcheat), sizeof(nicetry)}; - for (UINT8 i = 0; i < 4; i++) // for each cheat... + #define NUMCHEATSPLUSONE 6 + + UINT8 *cheats[NUMCHEATSPLUSONE] = {s2cheat, s3cheat, s3kcheat, nicetry, spincheat, devcheat}; + UINT8 cheatlengths[NUMCHEATSPLUSONE] = {sizeof(s2cheat), sizeof(s3cheat), sizeof(s3kcheat), sizeof(nicetry), sizeof(spincheat), sizeof(devcheat)}; + + for (UINT8 i = 0; i < NUMCHEATSPLUSONE; i++) // for each cheat... { UINT8 cheatsize = cheatlengths[i]; boolean matched = true; @@ -1040,6 +1061,8 @@ static void AdvanceSkipSequences(UINT8 input) skiptype = i+1; } + #undef NUMCHEATSPLUSONE + skipinputindex++; } diff --git a/src/f_finale.h b/src/f_finale.h index 2321748b6..b30572174 100644 --- a/src/f_finale.h +++ b/src/f_finale.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/f_wipe.cpp b/src/f_wipe.cpp index f553aafa6..d583e13da 100644 --- a/src/f_wipe.cpp +++ b/src/f_wipe.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by Kay "Kaito" Sinclaire. // Copyright (C) 2000 by DooM Legacy Team. @@ -319,7 +319,7 @@ static fademask_t *F_GetFadeMask(UINT8 masknum, UINT8 scrnnum) { #endif -static void refresh_wipe_screen_texture(rhi::Rhi& rhi, rhi::Handle ctx, rhi::Handle& tex) +static void refresh_wipe_screen_texture(rhi::Rhi& rhi, rhi::Handle& tex) { bool recreate = false; if (!tex) @@ -357,7 +357,7 @@ void F_WipeStartScreen(void) { #ifndef NOWIPE #ifdef HWRENDER - if(rendermode != render_soft) + if(rendermode == render_opengl) { HWR_StartScreenWipe(); return; @@ -371,24 +371,17 @@ void F_WipeStartScreen(void) return; } - rhi::Handle ctx = srb2::sys::main_graphics_context(); - - if (!ctx) - { - return; - } - hwr2::HardwareState* hw_state = srb2::sys::main_hardware_state(); - refresh_wipe_screen_texture(*rhi, ctx, hw_state->wipe_frames.start); + refresh_wipe_screen_texture(*rhi, hw_state->wipe_frames.start); - hw_state->twodee_renderer->flush(*rhi, ctx, g_2d); + hw_state->twodee_renderer->flush(*rhi, g_2d); rhi::Rect dst_region = {0, 0, static_cast(vid.width), static_cast(vid.height)}; rhi::TextureDetails backbuf_deets = rhi->get_texture_details(hw_state->backbuffer->color()); dst_region.w = std::min(dst_region.w, backbuf_deets.width); dst_region.h = std::min(dst_region.h, backbuf_deets.height); - rhi->copy_framebuffer_to_texture(ctx, hw_state->wipe_frames.start, dst_region, dst_region); + rhi->copy_framebuffer_to_texture(hw_state->wipe_frames.start, dst_region, dst_region); I_FinishUpdate(); #endif @@ -400,7 +393,7 @@ void F_WipeEndScreen(void) { #ifndef NOWIPE #ifdef HWRENDER - if(rendermode != render_soft) + if(rendermode == render_opengl) { HWR_EndScreenWipe(); return; @@ -414,29 +407,22 @@ void F_WipeEndScreen(void) return; } - rhi::Handle ctx = srb2::sys::main_graphics_context(); - - if (!ctx) - { - return; - } - hwr2::HardwareState* hw_state = srb2::sys::main_hardware_state(); - refresh_wipe_screen_texture(*rhi, ctx, hw_state->wipe_frames.end); + refresh_wipe_screen_texture(*rhi, hw_state->wipe_frames.end); - hw_state->twodee_renderer->flush(*rhi, ctx, g_2d); + hw_state->twodee_renderer->flush(*rhi, g_2d); rhi::Rect dst_region = {0, 0, static_cast(vid.width), static_cast(vid.height)}; rhi::TextureDetails backbuf_deets = rhi->get_texture_details(hw_state->backbuffer->color()); dst_region.w = std::min(dst_region.w, backbuf_deets.width); dst_region.h = std::min(dst_region.h, backbuf_deets.height); - rhi->copy_framebuffer_to_texture(ctx, hw_state->wipe_frames.end, dst_region, dst_region); + rhi->copy_framebuffer_to_texture(hw_state->wipe_frames.end, dst_region, dst_region); hw_state->blit_rect->set_output(0, 0, dst_region.w, dst_region.h, false, true); rhi::TextureDetails start_deets = rhi->get_texture_details(hw_state->wipe_frames.start); hw_state->blit_rect->set_texture(hw_state->wipe_frames.start, start_deets.width, start_deets.height); - hw_state->blit_rect->draw(*rhi, ctx); + hw_state->blit_rect->draw(*rhi); I_FinishUpdate(); #endif @@ -535,7 +521,6 @@ void F_RunWipe(UINT8 wipemode, UINT8 wipetype, boolean drawMenu, const char *col g_wipeencorewiggle = 0; } rhi::Rhi* rhi = srb2::sys::get_rhi(srb2::sys::g_current_rhi); - rhi::Handle ctx = srb2::sys::main_graphics_context(); hwr2::HardwareState* hw_state = srb2::sys::main_hardware_state(); if (reverse) @@ -550,7 +535,7 @@ void F_RunWipe(UINT8 wipemode, UINT8 wipetype, boolean drawMenu, const char *col } hw_state->wipe->set_target_size(static_cast(vid.width), static_cast(vid.height)); - hw_state->wipe->draw(*rhi, ctx); + hw_state->wipe->draw(*rhi); } I_OsPolling(); @@ -580,7 +565,7 @@ void F_RunWipe(UINT8 wipemode, UINT8 wipetype, boolean drawMenu, const char *col M_LegacySaveFrame(); else #endif - if (moviemode && rendermode != render_none) + if (moviemode && rendermode == render_soft) I_CaptureVideoFrame(); NetKeepAlive(); // Update the network so we don't cause timeouts diff --git a/src/fastcmp.h b/src/fastcmp.h index 4591a5451..7d89987ee 100644 --- a/src/fastcmp.h +++ b/src/fastcmp.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/filesrch.c b/src/filesrch.c index 9a4c013ef..3f6428bfb 100644 --- a/src/filesrch.c +++ b/src/filesrch.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/filesrch.h b/src/filesrch.h index 650f7cc14..f29360f87 100644 --- a/src/filesrch.h +++ b/src/filesrch.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/font.c b/src/font.c index bf0242cd9..046590efb 100644 --- a/src/font.c +++ b/src/font.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/font.h b/src/font.h index 414e69e6b..d143f6fbc 100644 --- a/src/font.h +++ b/src/font.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2018 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/g_build_ticcmd.cpp b/src/g_build_ticcmd.cpp index b74f48d2d..b8d45e3dc 100644 --- a/src/g_build_ticcmd.cpp +++ b/src/g_build_ticcmd.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -400,7 +400,7 @@ class TiccmdBuilder }; map(gc_drift, BT_DRIFT); // drift - map(gc_spindash, BT_SPINDASHMASK); // C + map(gc_spindash, BT_SPINDASH|BT_SPINDASHMASK); // C map(gc_item, BT_ATTACK); // fire map(gc_lookback, BT_LOOKBACK); // rear view @@ -408,9 +408,9 @@ class TiccmdBuilder map(gc_vote, BT_VOTE); // mp general function button // lua buttons a thru c - map(gc_luaa, BT_LUAA); - map(gc_luab, BT_LUAB); - map(gc_luac, BT_LUAC); + map(gc_lua1, BT_LUA1); + map(gc_lua2, BT_LUA2); + map(gc_lua3, BT_LUA3); } public: @@ -470,11 +470,11 @@ public: regular_input(); } + angle_prediction(); + cmd->angle = localangle[viewnum] >> TICCMD_REDUCE; hook(); - - angle_prediction(); } }; diff --git a/src/g_demo.cpp b/src/g_demo.cpp index 3517795c6..88828fee6 100644 --- a/src/g_demo.cpp +++ b/src/g_demo.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -16,7 +16,6 @@ #include #include -#include #include "doomdef.h" #include "doomtype.h" @@ -50,6 +49,7 @@ #include "md5.h" // demo checksums #include "p_saveg.h" // savebuffer_t #include "g_party.h" +#include "core/json.hpp" // SRB2Kart #include "d_netfil.h" // nameonly @@ -207,6 +207,7 @@ boolean G_CompatLevel(UINT16 level) #define DEMO_BOT 0x08 #define DEMO_AUTOROULETTE 0x10 #define DEMO_AUTORING 0x20 +#define DEMO_STRICTFASTFALL 0x40 // For demos #define ZT_FWD 0x0001 @@ -2336,6 +2337,8 @@ void G_BeginRecording(void) i |= DEMO_SPECTATOR; if (player->pflags & PF_KICKSTARTACCEL) i |= DEMO_KICKSTART; + if (player->pflags & PF2_STRICTFASTFALL) + i |= DEMO_STRICTFASTFALL; if (player->pflags & PF_AUTOROULETTE) i |= DEMO_AUTOROULETTE; if (player->pflags & PF_AUTORING) @@ -2437,17 +2440,18 @@ void G_BeginRecording(void) void srb2::write_current_demo_standings(const srb2::StandingsJson& standings) { using namespace srb2; - using json = nlohmann::json; // TODO populate standings data - std::vector ubjson = json::to_ubjson(standings); + JsonValue value { JsonObject() }; + to_json(value, standings); + Vector ubjson = value.to_ubjson(); uint32_t bytes = ubjson.size(); WRITEUINT8(demobuf.p, DW_STANDING2); WRITEUINT32(demobuf.p, bytes); - WRITEMEM(demobuf.p, ubjson.data(), bytes); + WRITEMEM(demobuf.p, (UINT8*)ubjson.data(), bytes); } void srb2::write_current_demo_end_marker() @@ -2615,12 +2619,12 @@ UINT8 G_CmpDemoTime(char *oldname, char *newname) static bool load_ubjson_standing(menudemo_t* pdemo, tcb::span slice, tcb::span demoskins) { using namespace srb2; - using json = nlohmann::json; StandingsJson js; try { - js = json::from_ubjson(slice).template get(); + JsonValue value { JsonValue::from_ubjson(tcb::as_bytes(slice)) }; + from_json(value, js); } catch (...) { @@ -3452,6 +3456,11 @@ void G_DoPlayDemoEx(const char *defdemoname, lumpnum_t deflumpnum) else players[p].pflags &= ~PF_KICKSTARTACCEL; + if (flags & DEMO_STRICTFASTFALL) + players[p].pflags |= PF2_STRICTFASTFALL; + else + players[p].pflags &= ~PF2_STRICTFASTFALL; + if (flags & DEMO_AUTOROULETTE) players[p].pflags |= PF_AUTOROULETTE; else @@ -3703,7 +3712,7 @@ void G_AddGhost(savebuffer_t *buffer, const char *defdemoname) p += 4; UINT32 num_classes; - if (demo.version <= 0x000D) + if (ghostversion <= 0x000D) { num_classes = PROLDDEMO; } @@ -3943,7 +3952,7 @@ staffbrief_t *G_GetStaffGhostBrief(UINT8 *buffer) temp.lap = READUINT32(p); UINT32 num_classes; - if (demo.version <= 0x000D) + if (ghostversion <= 0x000D) { num_classes = PROLDDEMO; } diff --git a/src/g_demo.h b/src/g_demo.h index a881aba3a..e3c4c74ff 100644 --- a/src/g_demo.h +++ b/src/g_demo.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -21,10 +21,9 @@ #ifdef __cplusplus -#include -#include - -#include +#include "core/json.hpp" +#include "core/string.h" +#include "core/vector.hpp" // Modern json formats namespace srb2 @@ -32,12 +31,12 @@ namespace srb2 struct StandingJson { uint8_t ranking; - std::string name; + String name; uint8_t demoskin; - std::string skincolor; + String skincolor; uint32_t timeorscore; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( StandingJson, ranking, name, @@ -48,9 +47,9 @@ struct StandingJson }; struct StandingsJson { - std::vector standings; + Vector standings; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(StandingsJson, standings) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(StandingsJson, standings) }; void write_current_demo_standings(const StandingsJson& standings); diff --git a/src/g_game.c b/src/g_game.c index af8105920..24e4bd663 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -182,11 +182,6 @@ char * podiummap = NULL; // map to load for podium char * tutorialchallengemap = NULL; // map to load for tutorial skip UINT8 tutorialchallenge = TUTORIALSKIP_NONE; -UINT16 skincolor_redteam = SKINCOLOR_RED; -UINT16 skincolor_blueteam = SKINCOLOR_BLUE; -UINT16 skincolor_redring = SKINCOLOR_RASPBERRY; -UINT16 skincolor_bluering = SKINCOLOR_PERIWINKLE; - boolean exitfadestarted = false; cutscene_t *cutscenes[128]; @@ -222,7 +217,7 @@ INT32 luabanks[NUM_LUABANKS]; // Temporary holding place for nights data for the current map //nightsdata_t ntemprecords; -UINT32 bluescore, redscore; // CTF and Team Match team scores +UINT32 g_teamscores[TEAM__MAX]; // ring count... for PERFECT! INT32 nummaprings = 0; @@ -289,13 +284,6 @@ fixed_t mapobjectscale; struct maplighting maplighting; -INT16 autobalance; //for CTF team balance -INT16 teamscramble; //for CTF team scramble -INT16 scrambleplayers[MAXPLAYERS]; //for CTF team scramble -INT16 scrambleteams[MAXPLAYERS]; //for CTF team scramble -INT16 scrambletotal; //for CTF team scramble -INT16 scramblecount; //for CTF team scramble - // SRB2Kart // Cvars that we don't want changed mid-game UINT8 numlaps; // Removed from Cvar hell @@ -304,6 +292,10 @@ boolean encoremode = false; // Encore Mode currently enabled? boolean prevencoremode; boolean franticitems; // Frantic items currently enabled? +// Server wants to enable teams? +// (Certain gametypes can override this -- prefer using G_GametypeHasTeams().) +boolean g_teamplay; + // Voting system UINT16 g_voteLevels[VOTE_NUM_LEVELS][2]; // Levels that were rolled by the host SINT8 g_votes[VOTE_TOTAL]; // Each player's vote @@ -593,6 +585,11 @@ static void G_UpdateRecordReplays(void) modeprefix = "spb-"; } + if (K_LegacyRingboost(&players[consoleplayer])) + { + modeprefix = "classr-"; + } + if (players[consoleplayer].pflags & PF_NOCONTEST) { players[consoleplayer].realtime = UINT32_MAX; @@ -850,6 +847,15 @@ static INT32 G_GetValueFromControlTable(INT32 deviceID, INT32 deadzone, INT32 *c return failret; } +static void G_SetGamepadPrompts(UINT8 p, boolean prompts) +{ + if (showgamepadprompts[p] != prompts) + { + // CONS_Printf("Setting player %d to gamepadprompts %d\n", p, prompts); + showgamepadprompts[p] = prompts; + } +} + INT32 G_PlayerInputAnalog(UINT8 p, INT32 gc, UINT8 menuPlayers) { const INT32 deadzone = (JOYAXISRANGE * cv_deadzone[p].value) / FRACUNIT; @@ -881,6 +887,7 @@ INT32 G_PlayerInputAnalog(UINT8 p, INT32 gc, UINT8 menuPlayers) // This is only intended for P1. if (main_player == true) { + G_SetGamepadPrompts(p, false); return value; } else @@ -900,6 +907,7 @@ INT32 G_PlayerInputAnalog(UINT8 p, INT32 gc, UINT8 menuPlayers) value = G_GetValueFromControlTable(deviceID, deadzone, &(gamecontrol[p][gc][0])); if (value > 0) { + G_SetGamepadPrompts(p, (deviceID != KEYBOARD_MOUSE_DEVICE)); return value; } if (value != NO_BINDS_REACHABLE) @@ -913,6 +921,7 @@ INT32 G_PlayerInputAnalog(UINT8 p, INT32 gc, UINT8 menuPlayers) value = G_GetValueFromControlTable(KEYBOARD_MOUSE_DEVICE, deadzone, &(gamecontrol[p][gc][0])); if (value > 0) { + G_SetGamepadPrompts(p, false); return value; } if (value != NO_BINDS_REACHABLE) @@ -953,6 +962,7 @@ INT32 G_PlayerInputAnalog(UINT8 p, INT32 gc, UINT8 menuPlayers) value = G_GetValueFromControlTable(tryDevice, deadzone, &(gamecontrol[p][gc][0])); if (value > 0) { + G_SetGamepadPrompts(p, (tryDevice != KEYBOARD_MOUSE_DEVICE)); return value; } if (value != NO_BINDS_REACHABLE) @@ -969,6 +979,7 @@ INT32 G_PlayerInputAnalog(UINT8 p, INT32 gc, UINT8 menuPlayers) value = G_GetValueFromControlTable(deviceID, deadzone, &(gamecontroldefault[gc][0])); if (value > 0) { + G_SetGamepadPrompts(p, (deviceID != KEYBOARD_MOUSE_DEVICE)); return value; } } @@ -1280,7 +1291,7 @@ void G_PreLevelTitleCard(void) M_LegacySaveFrame(); else #endif - if (moviemode && rendermode != render_none) + if (moviemode && rendermode == render_soft) I_CaptureVideoFrame(); while (!((nowtime = I_GetTime()) - lasttime)) @@ -1547,7 +1558,7 @@ boolean G_CouldView(INT32 playernum) // SRB2Kart: we have no team-based modes, YET... if (G_GametypeHasTeams()) { - if (players[consoleplayer].ctfteam && player->ctfteam != players[consoleplayer].ctfteam) + if (players[consoleplayer].spectator == false && player->team != players[consoleplayer].team) return false; } @@ -1780,6 +1791,73 @@ void G_FixCamera(UINT8 view) R_ResetViewInterpolation(view); } +void G_UpdatePlayerPreferences(player_t *const player) +{ + if (demo.playback) + return; + + // set skin + INT32 new_skin = player->prefskin; + if (K_CanChangeRules(true) == true && cv_forceskin.value >= 0) + { + // Server wants everyone to use the same player + new_skin = cv_forceskin.value; + } + + if (player->skin != new_skin) + { + SetPlayerSkinByNum(player - players, new_skin); + } + + // set color + UINT16 new_color = player->prefcolor; + if (new_color == SKINCOLOR_NONE) + { + new_color = skins[player->skin].prefcolor; + } + + if (G_GametypeHasTeams() == true && player->team != TEAM_UNASSIGNED) + { + new_color = g_teaminfo[player->team].color; + } + + if (player->skincolor != new_color) + { + player->skincolor = new_color; + K_KartResetPlayerColor(player); + } + + // set follower + if (player->followerskin != player->preffollower) + { + K_SetFollowerByNum(player - players, player->preffollower); + } + + // set follower color + if (player->followercolor != player->preffollowercolor) + { + // Don't bother doing garbage and kicking if we receive None, + // this is both silly and a waste of time, + // this will be handled properly in K_HandleFollower. + player->followercolor = player->preffollowercolor; + } +} + +void G_UpdateAllPlayerPreferences(void) +{ + INT32 i; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false) + { + continue; + } + + G_UpdatePlayerPreferences(&players[i]); + } +} + // // G_Ticker // Make ticcmd_ts for the players. @@ -1873,6 +1951,13 @@ void G_Ticker(boolean run) K_UpdateAllPlayerPositions(); } } + else if (Playing() && !Y_IntermissionPlayerLock()) + { + if (run) + { + G_UpdateAllPlayerPreferences(); + } + } P_MapEnd(); @@ -2123,8 +2208,9 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) UINT32 followitem; INT32 pflags; + INT32 pflags2; - UINT8 ctfteam; + UINT8 team; INT32 cheatchecknum; INT32 exiting; @@ -2132,8 +2218,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) INT16 totalring; UINT8 laps; UINT8 latestlap; - UINT32 lapPoints; - INT32 exp; + UINT32 exp; + INT32 gradingfactor; UINT16 gradingpointnum; UINT16 skincolor; @@ -2162,6 +2248,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) UINT8 botdiffincrease; boolean botrival; + boolean cangrabitems; + SINT8 xtralife; uint8_t public_key[PUBKEYLENGTH]; @@ -2188,6 +2276,11 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) tic_t laptime[LAP__MAX]; + UINT16 prefcolor; + INT32 prefskin; + UINT16 preffollowercolor; + INT32 preffollower; + INT32 i; // This needs to be first, to permit it to wipe extra information @@ -2200,7 +2293,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) score = players[player].score; lives = players[player].lives; - ctfteam = players[player].ctfteam; + team = players[player].team; splitscreenindex = players[player].splitscreenindex; spectator = players[player].spectator; @@ -2211,6 +2304,11 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) skincolor = players[player].skincolor; skin = players[player].skin; + prefcolor = players[player].prefcolor; + prefskin = players[player].prefskin; + preffollower = players[player].preffollower; + preffollowercolor = players[player].preffollowercolor; + if (betweenmaps) { fakeskin = MAXSKINS; @@ -2253,6 +2351,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) bot = players[player].bot; botdifficulty = players[player].botvars.difficulty; + cangrabitems = players[player].cangrabitems; + botdiffincrease = players[player].botvars.diffincrease; botrival = players[player].botvars.rival; @@ -2260,6 +2360,7 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) xtralife = players[player].xtralife; pflags = (players[player].pflags & (PF_WANTSTOJOIN|PF_KICKSTARTACCEL|PF_SHRINKME|PF_SHRINKACTIVE|PF_AUTOROULETTE|PF_ANALOGSTICK|PF_AUTORING)); + pflags2 = (players[player].pflags2 & (PF2_SELFMUTE | PF2_SELFDEAFEN | PF2_SERVERMUTE | PF2_SERVERDEAFEN | PF2_STRICTFASTFALL)); // SRB2kart memcpy(&itemRoulette, &players[player].itemRoulette, sizeof (itemRoulette)); @@ -2323,8 +2424,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) khudfault = 0; laps = 0; latestlap = 0; - lapPoints = 0; - exp = FRACUNIT; + exp = 0; + gradingfactor = FRACUNIT; gradingpointnum = 0; roundscore = 0; exiting = 0; @@ -2335,6 +2436,15 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) bigwaypointgap = 0; tallyactive = false; + + cangrabitems = 0; + if (gametyperules & GTR_SPHERES + || gametyperules & GTR_CATCHER + || G_TimeAttackStart() + || gametype == GT_TUTORIAL + || !M_NotFreePlay() + || K_GetNumWaypoints() == 0) + cangrabitems = EARLY_ITEM_FLICKER; } else { @@ -2362,8 +2472,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) laps = players[player].laps; latestlap = players[player].latestlap; - lapPoints = players[player].lapPoints; exp = players[player].exp; + gradingfactor = players[player].gradingfactor; gradingpointnum = players[player].gradingpointnum; roundscore = players[player].roundscore; @@ -2451,7 +2561,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->roundscore = roundscore; p->lives = lives; p->pflags = pflags; - p->ctfteam = ctfteam; + p->pflags2 = pflags2; + p->team = team; p->jointime = jointime; p->splitscreenindex = splitscreenindex; p->spectator = spectator; @@ -2465,12 +2576,19 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->skincolor = skincolor; p->skin = skin; + p->prefcolor = prefcolor; + p->prefskin = prefskin; + p->preffollower = preffollower; + p->preffollowercolor = preffollowercolor; + p->fakeskin = fakeskin; p->kartspeed = kartspeed; p->kartweight = kartweight; p->charflags = charflags; p->lastfakeskin = lastfakeskin; + p->cangrabitems = cangrabitems; + memcpy(players[player].availabilities, availabilities, sizeof(availabilities)); p->followitem = followitem; @@ -2480,8 +2598,8 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) p->laps = laps; p->latestlap = latestlap; - p->lapPoints = lapPoints; p->exp = exp; + p->gradingfactor = gradingfactor; p->gradingpointnum = gradingpointnum; p->totalring = totalring; @@ -2547,36 +2665,17 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps) //p->follower = NULL; // respawn a new one with you, it looks better. // ^ Not necessary anyway since it will be respawned regardless considering it doesn't exist anymore. + if (G_GametypeHasTeams() == true + && p->team == TEAM_UNASSIGNED + && p->spectator == false) + { + // No team? + G_AutoAssignTeam(p); + } + p->playerstate = PST_LIVE; p->panim = PA_STILL; // standing animation - // Check to make sure their color didn't change somehow... - if (G_GametypeHasTeams()) - { - if (p->ctfteam == 1 && p->skincolor != skincolor_redteam) - { - for (i = 0; i <= splitscreen; i++) - { - if (p == &players[g_localplayers[i]]) - { - CV_SetValue(&cv_playercolor[i], skincolor_redteam); - break; - } - } - } - else if (p->ctfteam == 2 && p->skincolor != skincolor_blueteam) - { - for (i = 0; i <= splitscreen; i++) - { - if (p == &players[g_localplayers[i]]) - { - CV_SetValue(&cv_playercolor[i], skincolor_blueteam); - break; - } - } - } - } - if (p->spectator == false && !betweenmaps) { if (enteredGame == true) @@ -2686,56 +2785,78 @@ void G_MovePlayerToSpawnOrCheatcheck(INT32 playernum) mapthing_t *G_FindTeamStart(INT32 playernum) { - const boolean doprints = P_IsPartyPlayer(&players[playernum]); - INT32 i,j; + const boolean do_prints = P_IsPartyPlayer(&players[playernum]); + INT32 i, j; - if (!numredctfstarts && !numbluectfstarts) //why even bother, eh? + for (i = 0; i < TEAM__MAX; i++) { - if ((gametyperules & GTR_TEAMSTARTS) && doprints) - CONS_Alert(CONS_WARNING, M_GetText("No CTF starts in this map!\n")); + if (numteamstarts[i] > 0) + { + break; + } + } + + if (i == TEAM__MAX) + { + // No team starts are counted? + // Why even bother, eh? + + if (do_prints == true && (gametyperules & GTR_TEAMSTARTS) == GTR_TEAMSTARTS) + { + CONS_Alert(CONS_WARNING, M_GetText("No team starts in this map!\n")); + } + return NULL; } - if ((!players[playernum].ctfteam && numredctfstarts && (!numbluectfstarts || P_RandomChance(PR_PLAYERSTARTS, FRACUNIT/2))) || players[playernum].ctfteam == 1) //red + UINT8 use_team = players[playernum].team; + if (players[playernum].spectator == true) { - if (!numredctfstarts) + // Spawn at any team start as a spectator. + i = P_RandomKey(PR_PLAYERSTARTS, TEAM__MAX); + + for (j = 0; j < TEAM__MAX; j++) { - if (doprints) - CONS_Alert(CONS_WARNING, M_GetText("No Red Team starts in this map!\n")); - return NULL; + if (numteamstarts[i] > 0) + { + break; + } + + i++; + if (i >= TEAM__MAX) + { + i = 0; + } } - for (j = 0; j < 32; j++) + use_team = i; + } + + if (numteamstarts[use_team] <= 0) + { + if (do_prints == true) { - i = P_RandomKey(PR_PLAYERSTARTS, numredctfstarts); - if (G_CheckSpot(playernum, redctfstarts[i])) - return redctfstarts[i]; + CONS_Alert(CONS_WARNING, M_GetText("No %s Team starts in this map!\n"), g_teaminfo[use_team].name); } - if (doprints) - CONS_Alert(CONS_WARNING, M_GetText("Could not spawn at any Red Team starts!\n")); return NULL; } - else if (!players[playernum].ctfteam || players[playernum].ctfteam == 2) //blue - { - if (!numbluectfstarts) - { - if (doprints) - CONS_Alert(CONS_WARNING, M_GetText("No Blue Team starts in this map!\n")); - return NULL; - } - for (j = 0; j < 32; j++) + for (j = 0; j < 32; j++) + { + i = P_RandomKey(PR_PLAYERSTARTS, numteamstarts[use_team]); + + if (G_CheckSpot(playernum, teamstarts[use_team][i])) { - i = P_RandomKey(PR_PLAYERSTARTS, numbluectfstarts); - if (G_CheckSpot(playernum, bluectfstarts[i])) - return bluectfstarts[i]; + return teamstarts[use_team][i]; } - if (doprints) - CONS_Alert(CONS_WARNING, M_GetText("Could not spawn at any Blue Team starts!\n")); - return NULL; } - //should never be reached but it gets stuff to shut up + + if (do_prints == true) + { + CONS_Alert(CONS_WARNING, M_GetText("Could not spawn at any %s Team starts!\n"), g_teaminfo[use_team].name); + } + return NULL; } @@ -2965,7 +3086,7 @@ mapthing_t *G_FindMapStart(INT32 playernum) // -- CTF -- // Order: CTF->DM->Race - else if ((gametyperules & GTR_TEAMSTARTS) && players[playernum].ctfteam) + else if ((gametyperules & GTR_TEAMSTARTS) && players[playernum].spectator == false) spawnpoint = G_FindTeamStartOrFallback(playernum); // -- DM/Tag/CTF-spectator/etc -- @@ -3077,7 +3198,7 @@ void G_SpectatePlayerOnJoin(INT32 playernum) // This is only ever called shortly after the above. // That calls CL_ClearPlayer, so spectator is false by default - if (!netgame && !G_GametypeHasTeams() && !G_GametypeHasSpectators()) + if (!netgame && !G_GametypeHasSpectators()) return; // These are handled automatically elsewhere @@ -3207,14 +3328,6 @@ void G_FinishExitLevel(void) gameaction = ga_completed; lastdraw = true; - // If you want your teams scrambled on map change, start the process now. - // The teams will scramble at the start of the next round. - if (cv_scrambleonchange.value && G_GametypeHasTeams()) - { - if (server) - CV_SetValue(&cv_teamscramble, cv_scrambleonchange.value); - } - CON_LogMessage(M_GetText("The round has ended.\n")); // Remove CEcho text on round end. @@ -3513,19 +3626,20 @@ boolean G_GametypeAllowsRetrying(void) // boolean G_GametypeHasTeams(void) { - if (gametyperules & GTR_TEAMS) + const UINT32 rules = (gametyperules & (GTR_TEAMS|GTR_NOTEAMS)); + if (rules == GTR_TEAMS) { // Teams forced on by this gametype return true; } - else if (gametyperules & GTR_NOTEAMS) + else if (rules == GTR_NOTEAMS) { // Teams forced off by this gametype return false; } - // Teams are determined by the "teamplay" modifier! - return false; // teamplay + // Teams are determined by the server's preference! + return g_teamplay; } // @@ -4317,13 +4431,28 @@ void G_GetNextMap(void) if (setalready == false) { + UINT8 numPlayers = 0; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + { + continue; + } + numPlayers++; + } + UINT32 tolflag = G_TOLFlag(gametype); register INT16 cm; - if (!(gametyperules & GTR_NOCUPSELECT)) + const boolean cupmode = (!(gametyperules & GTR_NOCUPSELECT)); + + nextmap = NEXTMAP_TITLE; + + if (cupmode) { - cupheader_t *cup = mapheaderinfo[gamemap-1]->cup; - UINT8 gettingresult = 0; + cupheader_t *cup = mapheaderinfo[prevmap]->cup; + boolean gettingresult = false; while (cup) { @@ -4331,7 +4460,7 @@ void G_GetNextMap(void) if (!marathonmode && M_CupLocked(cup)) { cup = cup->next; - gettingresult = 1; + gettingresult = true; continue; } @@ -4348,6 +4477,35 @@ void G_GetNextMap(void) continue; } + // If the map is in multiple cups, only consider the first one valid. + if (mapheaderinfo[cm]->cup != cup) + { + continue; + } + + if (!gettingresult) + { + // Not the map you're on? + if (cm == prevmap) + { + // Ok, this is the current map, time to get the next valid + gettingresult = true; + } + continue; + } + + if ((mapheaderinfo[cm]->menuflags & LF2_HIDEINMENU) == LF2_HIDEINMENU) + { + // Not intended to be accessed in multiplayer. + continue; + } + + if (numPlayers > mapheaderinfo[cm]->playerLimit) + { + // Too many players for this map. + continue; + } + // Only care about restrictions if the host is a listen server. if (!dedicated && !marathonmode) { @@ -4373,32 +4531,13 @@ void G_GetNextMap(void) continue; } - // If the map is in multiple cups, only consider the first one valid. - if (mapheaderinfo[cm]->cup != cup) - { - continue; - } - // Grab the first valid after the map you're on - if (gettingresult) - { - nextmap = cm; - gettingresult = 2; - break; - } - - // Not the map you're on? - if (cm != prevmap) - { - continue; - } - - // Ok, this is the current map, time to get the next - gettingresult = 1; + nextmap = cm; + break; } // We have a good nextmap? - if (gettingresult == 2) + if (nextmap < NEXTMAP_SPECIAL) { break; } @@ -4406,22 +4545,25 @@ void G_GetNextMap(void) // Ok, iterate to the next cup = cup->next; } - - // Didn't get a nextmap before reaching the end? - if (gettingresult != 2) - { - nextmap = NEXTMAP_CEREMONY; // ceremonymap - } } - else + + // Haven't grabbed a nextmap yet? + if (nextmap >= NEXTMAP_SPECIAL) { - cm = prevmap; - - do + if (cupmode && mapheaderinfo[prevmap]->cup) { - if (++cm >= nummapheaders) - cm = 0; + // Special case - looking for Lost & Found #1. + // Could be anywhere in mapheaderinfo. + cm = 0; + } + else + { + // All subsequent courses in load order. + cm = prevmap+1; + } + for (; cm < nummapheaders; cm++) + { if (!mapheaderinfo[cm] || mapheaderinfo[cm]->lumpnum == LUMPERROR || !(mapheaderinfo[cm]->typeoflevel & tolflag) @@ -4430,6 +4572,24 @@ void G_GetNextMap(void) continue; } + if (cupmode && mapheaderinfo[cm]->cup) + { + // Only Lost & Found this loop around. + continue; + } + + if ((mapheaderinfo[cm]->menuflags & LF2_HIDEINMENU) == LF2_HIDEINMENU) + { + // Not intended to be accessed in multiplayer. + continue; + } + + if (numPlayers > mapheaderinfo[cm]->playerLimit) + { + // Too many players for this map. + continue; + } + // Only care about restrictions if the host is a listen server. if (!dedicated && !marathonmode) { @@ -4458,10 +4618,9 @@ void G_GetNextMap(void) continue; } + nextmap = cm; break; - } while (cm != prevmap); - - nextmap = cm; + } } if (K_CanChangeRules(true)) @@ -4474,24 +4633,14 @@ void G_GetNextMap(void) nextmap = prevmap; break; case 3: // Voting screen. + if (numPlayers != 0) { - for (i = 0; i < MAXPLAYERS; i++) - { - if (!playeringame[i]) - continue; - if (players[i].spectator) - continue; - break; - } - if (i != MAXPLAYERS) - { - nextmap = NEXTMAP_VOTING; - break; - } + nextmap = NEXTMAP_VOTING; + break; } /* FALLTHRU */ case 2: // Go to random map. - nextmap = G_RandMap(G_TOLFlag(gametype), prevmap, false, false, NULL); + nextmap = G_RandMapPerPlayerCount(G_TOLFlag(gametype), prevmap, false, false, NULL, numPlayers); break; default: if (nextmap >= NEXTMAP_SPECIAL) // Loop back around @@ -4576,8 +4725,8 @@ static void G_DoCompleted(void) if (grandprixinfo.eventmode == GPEVENT_NONE) { - grandprixinfo.rank.winPoints += K_CalculateGPRankPoints(player->position, grandprixinfo.rank.totalPlayers); - grandprixinfo.rank.laps += player->lapPoints; + grandprixinfo.rank.winPoints += K_CalculateGPRankPoints(player->exp, grandprixinfo.rank.position, grandprixinfo.rank.totalPlayers); + grandprixinfo.rank.exp += player->exp; } else if (grandprixinfo.eventmode == GPEVENT_SPECIAL) { @@ -4707,9 +4856,13 @@ static void G_DoCompleted(void) { Y_StartIntermission(); } - else if (grandprixinfo.gp == true) + else { - K_UpdateGPRank(&grandprixinfo.rank); + Y_MidIntermission(); + if (grandprixinfo.gp == true) + { + K_UpdateGPRank(&grandprixinfo.rank); + } } G_UpdateVisited(); @@ -5243,9 +5396,14 @@ void G_InitNew(UINT8 pencoremode, INT32 map, boolean resetplayer, boolean skippr } // Clear a bunch of variables - redscore = bluescore = lastmap = 0; + lastmap = 0; racecountdown = exitcountdown = musiccountdown = mapreset = exitfadestarted = 0; + for (i = 0; i < TEAM__MAX; i++) + { + g_teamscores[i] = 0; + } + for (i = 0; i < MAXPLAYERS; i++) { players[i].playerstate = PST_REBORN; @@ -5718,3 +5876,204 @@ INT32 G_TicsToMilliseconds(tic_t tics) { return (INT32)((tics%TICRATE) * (1000.00f/TICRATE)); } + +teaminfo_t g_teaminfo[TEAM__MAX] = +{ + // TEAM_UNASSIGNED + // These values should not be reached most of the time, + // but it is a necessary evil for this to exist. + { + "Unassigned", + SKINCOLOR_NONE, + 0, + }, + // TEAM_ORANGE + { + "Orange", + SKINCOLOR_TANGERINE, + V_ORANGEMAP, + }, + // TEAM_BLUE + { + "Blue", + SKINCOLOR_SAPPHIRE, + V_BLUEMAP, + }, +}; + +void G_AssignTeam(player_t *const p, UINT8 new_team) +{ + if (p->team != new_team) + { + CONS_Debug(DBG_TEAMS, "%s >> Changed from team %s to team %s.\n", player_names[p - players], g_teaminfo[p->team].name, g_teaminfo[new_team].name); + } + + p->team = new_team; + + if (new_team && p->skincolor != g_teaminfo[new_team].color) + { + p->skincolor = g_teaminfo[new_team].color; + if (G_GamestateUsesLevel()) + { + K_KartResetPlayerColor(p); + } + } +} + +boolean G_SameTeam(const player_t *a, const player_t *b) +{ + if (a == NULL || b == NULL) + { + return false; + } + + if (G_GametypeHasTeams() == true) + { + if (a->team == TEAM_UNASSIGNED || b->team == TEAM_UNASSIGNED) + { + // Unassigned is not a real team. + // Treat them as lone wolves. + return false; + } + + // You share a team! + return (a->team == b->team); + } + + // Free for all. + return false; +} + +UINT8 G_CountTeam(UINT8 team) +{ + UINT8 count = 0; + + for (UINT8 i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false || players[i].spectator == true) + { + continue; + } + + if (players[i].team == team) + { + count++; + } + } + + return count; +} + +void G_AutoAssignTeam(player_t *const p) +{ + if (G_GametypeHasTeams() == false) + { + CONS_Debug(DBG_TEAMS, "%s >> Teams are disabled.\n", player_names[p - players]); + G_AssignTeam(p, TEAM_UNASSIGNED); + return; + } + + if (p->spectator == true) + { + CONS_Debug(DBG_TEAMS, "%s >> Why are you giving a spectator a team?\n", player_names[p - players]); + G_AssignTeam(p, TEAM_UNASSIGNED); + return; + } + + if (p->team != TEAM_UNASSIGNED) + { + CONS_Debug(DBG_TEAMS, "%s >> Already assigned a team.\n", player_names[p - players]); + return; + } + + const UINT8 orange_count = G_CountTeam(TEAM_ORANGE); + const UINT8 blue_count = G_CountTeam(TEAM_BLUE); + + if (orange_count == blue_count) + { + CONS_Debug(DBG_TEAMS, "%s >> Team assigned randomly.\n", player_names[p - players]); + G_AssignTeam(p, (P_Random(PR_TEAMS) & 1) ? TEAM_BLUE : TEAM_ORANGE); + return; + } + + CONS_Debug(DBG_TEAMS, "%s >> Team imbalance.\n", player_names[p - players]); + + if (blue_count < orange_count) + { + G_AssignTeam(p, TEAM_BLUE); + } + else + { + G_AssignTeam(p, TEAM_ORANGE); + } +} + +void G_AddTeamScore(UINT8 team, INT32 amount, player_t *source) +{ + if (team == TEAM_UNASSIGNED || G_GametypeHasTeams() == false) + { + return; + } + + if ((gametyperules & GTR_POINTLIMIT) == 0) + { + return; + } + +#if 1 + if (amount <= 0) + { + // Don't allow players to intentionally + // tank the team score. Might not be necessary? + return; + } +#endif + + (void)source; // Just included in case we need the scorer later. + + // Don't underflow. + // Don't go above MAXSCORE. + if (amount < 0 && (UINT32)-amount > g_teamscores[team]) + { + g_teamscores[team] = 0; + } + else if (g_teamscores[team] + amount < MAXSCORE) + { + if (g_teamscores[team] < g_pointlimit + && g_pointlimit <= g_teamscores[team] + amount) + { + INT32 i; + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *const p = &players[i]; + + if (playeringame[i] == false || p->spectator == true) + { + continue; + } + + if (p->team == team) + { + HU_DoTitlecardCEchoForDuration(p, "K.O. READY!", true, 5*TICRATE/2); + } + } + } + + g_teamscores[team] += amount; + } + else + { + g_teamscores[team] = MAXSCORE; + } +} + +UINT32 G_TeamOrIndividualScore(const player_t *player) +{ + if (G_GametypeHasTeams() == true && player->team != TEAM_UNASSIGNED) + { + return g_teamscores[player->team]; + } + + return player->roundscore; +} + diff --git a/src/g_game.h b/src/g_game.h index 5d33303c8..14ee6494a 100644 --- a/src/g_game.h +++ b/src/g_game.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -100,11 +100,14 @@ extern consvar_t cv_pauseifunfocused; extern consvar_t cv_kickstartaccel[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_autoroulette[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_litesteer[MAXSPLITSCREENPLAYERS]; +extern consvar_t cv_strictfastfall[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_autoring[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_shrinkme[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_deadzone[MAXSPLITSCREENPLAYERS]; +extern consvar_t cv_descriptiveinput[MAXSPLITSCREENPLAYERS]; + extern consvar_t cv_ghost_besttime, cv_ghost_bestlap, cv_ghost_last, cv_ghost_guest, cv_ghost_staff; // mouseaiming (looking up/down with the mouse or keyboard) @@ -232,6 +235,9 @@ void G_UpdateTimeStickerMedals(UINT16 map, boolean showownrecord); void G_TickTimeStickerMedals(void); void G_UpdateRecords(void); +void G_UpdatePlayerPreferences(player_t *const player); +void G_UpdateAllPlayerPreferences(void); + void G_Ticker(boolean run); boolean G_Responder(event_t *ev); @@ -288,6 +294,13 @@ void G_AddMapToBuffer(UINT16 map); void G_UpdateVisited(void); +boolean G_SameTeam(const player_t *a, const player_t *b); +UINT8 G_CountTeam(UINT8 team); +void G_AssignTeam(player_t *const p, UINT8 new_team); +void G_AutoAssignTeam(player_t *const p); +void G_AddTeamScore(UINT8 team, INT32 amount, player_t *source); +UINT32 G_TeamOrIndividualScore(const player_t *player); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/g_gamedata.cpp b/src/g_gamedata.cpp index 66269392d..556def12a 100644 --- a/src/g_gamedata.cpp +++ b/src/g_gamedata.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -27,10 +27,11 @@ #include "z_zone.h" namespace fs = std::filesystem; -using json = nlohmann::json; #define GD_VERSION_MAJOR (0xBA5ED321) -#define GD_VERSION_MINOR (1) +#define GD_VERSION_MINOR (2) + +#define GD_MINIMUM_SPRAYCANSV2 (2) void srb2::save_ng_gamedata() { @@ -137,16 +138,28 @@ void srb2::save_ng_gamedata() skin_t& memskin = skins[i]; auto skin = skintojson(&memskin.records); - std::string name = std::string(memskin.name); + srb2::String name { memskin.name }; ng.skins[name] = std::move(skin); } for (auto unloadedskin = unloadedskins; unloadedskin; unloadedskin = unloadedskin->next) { auto skin = skintojson(&unloadedskin->records); - std::string name = std::string(unloadedskin->name); + srb2::String name { unloadedskin->name }; ng.skins[name] = std::move(skin); } + for (int i = 0; i < gamedata->numspraycans; i++) + { + uint16_t col = gamedata->spraycans[i].col; + + if (col >= SKINCOLOR_FIRSTFREESLOT) + { + col = SKINCOLOR_NONE; + } + + ng.spraycans_v2.emplace_back(String(skincolors[col].name)); + } + auto maptojson = [](recorddata_t *records) { srb2::GamedataMapJson map {}; @@ -168,6 +181,7 @@ void srb2::save_ng_gamedata() map.stats.time.custom = records->modetimeplayed[GDGT_CUSTOM]; map.stats.time.timeattack = records->timeattacktimeplayed; map.stats.time.spbattack = records->spbattacktimeplayed; + map.spraycan = records->spraycan; return map; }; @@ -175,47 +189,15 @@ void srb2::save_ng_gamedata() for (int i = 0; i < nummapheaders; i++) { auto map = maptojson(&mapheaderinfo[i]->records); - std::string lumpname = std::string(mapheaderinfo[i]->lumpname); + srb2::String lumpname { mapheaderinfo[i]->lumpname }; ng.maps[lumpname] = std::move(map); } for (auto unloadedmap = unloadedmapheaders; unloadedmap; unloadedmap = unloadedmap->next) { auto map = maptojson(&unloadedmap->records); - std::string lumpname = std::string(unloadedmap->lumpname); + srb2::String lumpname { unloadedmap->lumpname }; ng.maps[lumpname] = std::move(map); } - for (int i = 0; i < gamedata->numspraycans; i++) - { - srb2::GamedataSprayCanJson spraycan {}; - - candata_t* can = &gamedata->spraycans[i]; - - if (can->col >= numskincolors) - { - continue; - } - spraycan.color = std::string(skincolors[can->col].name); - - if (can->map == NEXTMAP_INVALID) - { - spraycan.map = ""; - ng.spraycans.emplace_back(std::move(spraycan)); - continue; - } - - if (can->map >= nummapheaders) - { - continue; - } - - mapheader_t* mapheader = mapheaderinfo[can->map]; - if (!mapheader) - { - continue; - } - spraycan.map = std::string(mapheader->lumpname); - ng.spraycans.emplace_back(std::move(spraycan)); - } auto cuptojson = [](cupwindata_t *windata) { @@ -230,11 +212,11 @@ void srb2::save_ng_gamedata() skinreference_t& skinref = windata[i].best_skin; if (skinref.unloaded) { - newrecords.bestskin = std::string(skinref.unloaded->name); + newrecords.bestskin = String(skinref.unloaded->name); } else { - newrecords.bestskin = std::string(skins[skinref.id].name); + newrecords.bestskin = String(skins[skinref.id].name); } newrecords.gotemerald = windata[i].got_emerald; @@ -252,7 +234,7 @@ void srb2::save_ng_gamedata() } auto cupdata = cuptojson(cup->windata); - cupdata.name = std::string(cup->name); + cupdata.name = String(cup->name); ng.cups[cupdata.name] = std::move(cupdata); } for (auto unloadedcup = unloadedcupheaders; unloadedcup; unloadedcup = unloadedcup->next) @@ -263,7 +245,7 @@ void srb2::save_ng_gamedata() } auto cupdata = cuptojson(unloadedcup->windata); - cupdata.name = std::string(unloadedcup->name); + cupdata.name = String(unloadedcup->name); ng.cups[cupdata.name] = std::move(cupdata); } @@ -273,17 +255,19 @@ void srb2::save_ng_gamedata() cupheader_t* cup = gamedata->sealedswaps[i]; - sealedswap.name = std::string(cup->name); + sealedswap.name = String(cup->name); ng.sealedswaps.emplace_back(std::move(sealedswap)); } - std::string gamedataname_s {gamedatafilename}; - fs::path savepath {fmt::format("{}/{}", srb2home, gamedataname_s)}; - fs::path baksavepath {fmt::format("{}/{}.bak", srb2home, gamedataname_s)}; - - json ngdata_json = ng; + String gamedataname_s {gamedatafilename}; + String savepath_string = srb2::format("{}/{}", srb2home, gamedataname_s); + String baksavepath_string = srb2::format("{}/{}.bak", srb2home, gamedataname_s); + fs::path savepath { static_cast(savepath_string) }; + fs::path baksavepath { static_cast(srb2::format("{}/{}.bak", srb2home, gamedataname_s)) }; + JsonValue ngdata_json { JsonObject() }; + to_json(ngdata_json, ng); if (fs::exists(savepath)) { @@ -300,7 +284,7 @@ void srb2::save_ng_gamedata() try { - std::string savepathstring = savepath.string(); + String savepathstring = savepath.string(); srb2::io::FileStream file {savepathstring, srb2::io::FileStreamMode::kWrite}; // The header is necessary to validate during loading. @@ -308,7 +292,7 @@ void srb2::save_ng_gamedata() srb2::io::write(static_cast(GD_VERSION_MINOR), file); // minor/flags srb2::io::write(static_cast(gamedata->evercrashed), file); // dirty (crash recovery) - std::vector ubjson = json::to_ubjson(ng); + srb2::Vector ubjson = ngdata_json.to_ubjson(); srb2::io::write_exact(file, tcb::as_bytes(tcb::make_span(ubjson))); file.close(); } @@ -382,7 +366,7 @@ void srb2::load_ng_gamedata() return; } - std::string datapath {fmt::format("{}/{}", srb2home, gamedatafilename)}; + String datapath {srb2::format("{}/{}", srb2home, gamedatafilename)}; srb2::io::BufferedInputStream bis; try @@ -400,6 +384,7 @@ void srb2::load_ng_gamedata() uint32_t majorversion; uint8_t minorversion; uint8_t dirty; + bool converted = false; try { majorversion = srb2::io::read_uint32(bis); @@ -419,15 +404,13 @@ void srb2::load_ng_gamedata() return; } - std::vector remainder = srb2::io::read_to_vec(bis); + srb2::Vector remainder = srb2::io::read_to_vec(bis); GamedataJson js; try { - // safety: std::byte repr is always uint8_t 1-byte aligned - tcb::span remainder_as_u8 = tcb::span((uint8_t*)remainder.data(), remainder.size()); - json parsed = json::from_ubjson(remainder_as_u8); - js = parsed.template get(); + JsonValue parsed = JsonValue::from_ubjson(remainder); + from_json(parsed, js); } catch (const std::exception& ex) { @@ -520,29 +503,17 @@ void srb2::load_ng_gamedata() gamedata->achieved[i] = js.conditionsets[i]; } - if (M_CheckParm("-resetchallengegrid")) - { - gamedata->challengegridwidth = 0; - if (gamedata->challengegrid) - { - Z_Free(gamedata->challengegrid); - gamedata->challengegrid = nullptr; - } - } - else +#ifdef DEVELOP + if (!M_CheckParm("-resetchallengegrid")) +#endif { gamedata->challengegridwidth = std::max(js.challengegrid.width, (uint32_t)0); - if (gamedata->challengegrid) - { - Z_Free(gamedata->challengegrid); - gamedata->challengegrid = nullptr; - } if (gamedata->challengegridwidth) { gamedata->challengegrid = static_cast(Z_Malloc( (gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT * sizeof(UINT16)), PU_STATIC, NULL)); - for (size_t i = 0; i < std::min((size_t)(gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT), js.challengegrid.grid.size()); i++) + for (size_t i = 0; i < std::min((size_t)(gamedata->challengegridwidth * CHALLENGEGRIDHEIGHT), js.challengegrid.grid.size()); i++) { uint16_t gridvalue = js.challengegrid.grid[i]; gamedata->challengegrid[i] = gridvalue; @@ -550,10 +521,6 @@ void srb2::load_ng_gamedata() M_SanitiseChallengeGrid(); } - else - { - gamedata->challengegrid = nullptr; - } } gamedata->timesBeaten = js.timesBeaten; @@ -610,9 +577,37 @@ void srb2::load_ng_gamedata() } } + std::vector tempcans; + +#ifdef DEVELOP + if (M_CheckParm("-resetspraycans")) + ; + else +#endif + for (auto& cancolor : js.spraycans_v2) + { + // Version 2 behaviour - spraycans_v2, not spraycans! + + candata_t tempcan; + tempcan.col = SKINCOLOR_NONE; + tempcan.map = NEXTMAP_INVALID; + + // Find the skin color index for the name + for (size_t i = 0; i < SKINCOLOR_FIRSTFREESLOT; i++) + { + if (cancolor != skincolors[i].name) + continue; + + tempcan.col = i; + break; + } + + tempcans.emplace_back(std::move(tempcan)); + } + for (auto& mappair : js.maps) { - UINT16 mapnum = G_MapNumber(mappair.first.c_str()); + uint16_t mapnum = G_MapNumber(mappair.first.c_str()); recorddata_t dummyrecord {}; dummyrecord.mapvisited |= mappair.second.visited.visited ? MV_VISITED : 0; dummyrecord.mapvisited |= mappair.second.visited.beaten ? MV_BEATEN : 0; @@ -633,15 +628,37 @@ void srb2::load_ng_gamedata() dummyrecord.timeattacktimeplayed = mappair.second.stats.time.timeattack; dummyrecord.spbattacktimeplayed = mappair.second.stats.time.spbattack; + dummyrecord.spraycan = (minorversion >= GD_MINIMUM_SPRAYCANSV2) + ? mappair.second.spraycan + : MCAN_INVALID; + if (mapnum < nummapheaders && mapheaderinfo[mapnum]) { // Valid mapheader, time to populate with record data. + // Infill Spray Can info + if ( + dummyrecord.spraycan < tempcans.size() + && (mapnum < basenummapheaders) + && (tempcans[dummyrecord.spraycan].map >= basenummapheaders) + ) + { + // Assign map ID. + tempcans[dummyrecord.spraycan].map = mapnum; + } + + if (dummyrecord.spraycan != MCAN_INVALID) + { + // Yes, even if it's valid. We reassign later. + dummyrecord.spraycan = MCAN_BONUS; + } + mapheaderinfo[mapnum]->records = dummyrecord; } else if (dummyrecord.mapvisited & MV_BEATEN || dummyrecord.timeattack.time != 0 || dummyrecord.timeattack.lap != 0 - || dummyrecord.spbattack.time != 0 || dummyrecord.spbattack.lap != 0) + || dummyrecord.spbattack.time != 0 || dummyrecord.spbattack.lap != 0 + || dummyrecord.spraycan != MCAN_INVALID) { // Invalid, but we don't want to lose all the juicy statistics. // Instead, update a FILO linked list of "unloaded mapheaders". @@ -660,81 +677,98 @@ void srb2::load_ng_gamedata() unloadedmap->next = unloadedmapheaders; unloadedmapheaders = unloadedmap; + if (dummyrecord.spraycan != MCAN_INVALID) + { + // Invalidate non-bonus spraycans. + dummyrecord.spraycan = MCAN_BONUS; + } + // Finally, copy into. unloadedmap->records = dummyrecord; } } - gamedata->gotspraycans = 0; - gamedata->numspraycans = js.spraycans.size(); - if (gamedata->spraycans) + if ((minorversion < GD_MINIMUM_SPRAYCANSV2) && (js.spraycans.size() > 1)) { - Z_Free(gamedata->spraycans); - } - if (gamedata->numspraycans) - { - gamedata->spraycans = static_cast(Z_Malloc( - (gamedata->numspraycans * sizeof(candata_t)), - PU_STATIC, NULL)); + // Deprecated behaviour! Look above for spraycans_v2 handling - for (size_t i = 0; i < gamedata->numspraycans; i++) + converted = true; + + for (auto& deprecatedcan : js.spraycans) { - auto& can = js.spraycans[i]; + candata_t tempcan; + tempcan.col = SKINCOLOR_NONE; // Find the skin color index for the name - bool foundcolor = false; - for (size_t j = 0; j < numskincolors; j++) + for (size_t i = 0; i < SKINCOLOR_FIRSTFREESLOT; i++) { - if (can.color == skincolors[j].name) - { - gamedata->spraycans[i].col = j; - skincolors[j].cache_spraycan = i; - foundcolor = true; - break; - } - } - if (!foundcolor) - { - // Invalid color name? Ignore the spraycan - gamedata->numspraycans -= 1; - i -= 1; - continue; - } + if (deprecatedcan.color != skincolors[i].name) + continue; - gamedata->spraycans[i].map = NEXTMAP_INVALID; + tempcan.col = i; + break; + } UINT16 mapnum = NEXTMAP_INVALID; - if (!can.map.empty()) + if (!deprecatedcan.map.empty()) { - mapnum = G_MapNumber(can.map.c_str()); - } - gamedata->spraycans[i].map = mapnum; - if (mapnum >= nummapheaders) - { - // Can has not been grabbed on any map, this is intentional. - continue; + mapnum = G_MapNumber(deprecatedcan.map.c_str()); } + tempcan.map = mapnum; - if (gamedata->gotspraycans != i) - { - //CONS_Printf("LOAD - Swapping gotten can %u, color %s with prior ungotten can %u\n", i, skincolors[col].name, gamedata->gotspraycans); - - // All grabbed cans should be at the head of the list. - // Let's swap with the can the disjoint occoured at. - // This will prevent a gap from occouring on reload. - candata_t copycan = gamedata->spraycans[gamedata->gotspraycans]; - gamedata->spraycans[gamedata->gotspraycans] = gamedata->spraycans[i]; - gamedata->spraycans[i] = copycan; - - mapheaderinfo[copycan.map]->cache_spraycan = i; - } - mapheaderinfo[mapnum]->cache_spraycan = gamedata->gotspraycans; - gamedata->gotspraycans++; + tempcans.emplace_back(std::move(tempcan)); } } - else + { - gamedata->spraycans = nullptr; + // Post-process of Spray Cans - a component of both v1 and v2 spraycans behaviour + + // Determine sizes. + for (auto& tempcan : tempcans) + { + if (tempcan.col == SKINCOLOR_NONE) + continue; + + gamedata->numspraycans++; + + if (tempcan.map >= nummapheaders) + continue; + + gamedata->gotspraycans++; + } + + if (gamedata->numspraycans) + { + // Arrange with collected first + std::stable_sort(tempcans.begin(), tempcans.end(), [ ]( auto& lhs, auto& rhs ) + { + return (rhs.map >= basenummapheaders && lhs.map < basenummapheaders); + }); + + gamedata->spraycans = static_cast(Z_Malloc( + (gamedata->numspraycans * sizeof(candata_t)), + PU_STATIC, NULL)); + + // Finally, fill can data. + size_t i = 0; + for (auto& tempcan : tempcans) + { + if (tempcan.col == SKINCOLOR_NONE) + continue; + + skincolors[tempcan.col].cache_spraycan = i; + + if (tempcan.map < basenummapheaders) + mapheaderinfo[tempcan.map]->records.spraycan = i; + + gamedata->spraycans[i] = tempcan; + + if (++i < gamedata->numspraycans) + continue; + + break; + } + } } for (auto& cuppair : js.cups) @@ -745,7 +779,7 @@ void srb2::load_ng_gamedata() // Find the loaded cup for (cup = kartcupheaders; cup; cup = cup->next) { - std::string cupname = std::string(cup->name); + String cupname { cup->name }; if (cupname == cuppair.first) { break; @@ -770,7 +804,7 @@ void srb2::load_ng_gamedata() } for (auto unloadedskin = unloadedskins; unloadedskin; unloadedskin = unloadedskin->next) { - std::string skinname = std::string(unloadedskin->name); + String skinname { unloadedskin->name }; if (skinname == cuppair.second.records[j].bestskin) { skinreference_t ref {}; @@ -826,7 +860,7 @@ void srb2::load_ng_gamedata() break; } - std::string cupname = std::string(cup->name); + String cupname { cup->name }; if (cupname == js.sealedswaps[i].name) { break; @@ -839,7 +873,6 @@ void srb2::load_ng_gamedata() } } - bool converted = false; UINT32 chao_key_rounds = GDCONVERT_ROUNDSTOKEY; UINT32 start_keys = GDINIT_CHAOKEYS; diff --git a/src/g_gamedata.h b/src/g_gamedata.h index c8899c84f..0027ef582 100644 --- a/src/g_gamedata.h +++ b/src/g_gamedata.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -13,13 +13,12 @@ #ifdef __cplusplus -#include #include -#include -#include -#include -#include +#include "core/json.hpp" +#include "core/hash_map.hpp" +#include "core/string.h" +#include "core/vector.hpp" namespace srb2 { @@ -39,7 +38,7 @@ struct GamedataPlaytimeJson final uint32_t statistics; uint32_t tumble; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataPlaytimeJson, total, netgame, @@ -60,7 +59,7 @@ struct GamedataRingsJson final { uint32_t total; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataRingsJson, total) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataRingsJson, total) }; struct GamedataRoundsJson final @@ -71,7 +70,7 @@ struct GamedataRoundsJson final uint32_t special; uint32_t custom; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataRoundsJson, race, battle, prisons, special, custom) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataRoundsJson, race, battle, prisons, special, custom) }; struct GamedataChallengeKeysJson final @@ -81,7 +80,7 @@ struct GamedataChallengeKeysJson final uint16_t keyspending; uint16_t chaokeys; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataChallengeKeysJson, pendingkeyrounds, pendingkeyroundoffset, keyspending, chaokeys) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataChallengeKeysJson, pendingkeyrounds, pendingkeyroundoffset, keyspending, chaokeys) }; struct GamedataMilestonesJson final @@ -98,7 +97,7 @@ struct GamedataMilestonesJson final bool sealedswapalerted; bool tutorialdone; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataMilestonesJson, gonerlevel, everloadedaddon, @@ -119,15 +118,15 @@ struct GamedataPrisonEggPickupsJson final uint16_t thisprisoneggpickup; uint16_t prisoneggstothispickup; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataPrisonEggPickupsJson, thisprisoneggpickup, prisoneggstothispickup) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataPrisonEggPickupsJson, thisprisoneggpickup, prisoneggstothispickup) }; struct GamedataChallengeGridJson final { uint32_t width; - std::vector grid; + Vector grid; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataChallengeGridJson, width, grid) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataChallengeGridJson, width, grid) }; struct GamedataSkinRecordsPlaytimeJson final @@ -140,7 +139,7 @@ struct GamedataSkinRecordsPlaytimeJson final uint32_t custom; uint32_t tumble; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataSkinRecordsPlaytimeJson, total, race, @@ -158,7 +157,7 @@ struct GamedataSkinRecordsJson final uint32_t rounds; GamedataSkinRecordsPlaytimeJson time; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataSkinRecordsJson, wins, rounds, @@ -170,7 +169,7 @@ struct GamedataSkinJson final { GamedataSkinRecordsJson records; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataSkinJson, records) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataSkinJson, records) }; struct GamedataMapVisitedJson final @@ -181,7 +180,7 @@ struct GamedataMapVisitedJson final bool spbattack; bool mysticmelody; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapVisitedJson, visited, beaten, encore, spbattack, mysticmelody) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapVisitedJson, visited, beaten, encore, spbattack, mysticmelody) }; struct GamedataMapStatsTimeAttackJson final @@ -189,7 +188,7 @@ struct GamedataMapStatsTimeAttackJson final uint32_t besttime; uint32_t bestlap; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapStatsTimeAttackJson, besttime, bestlap) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapStatsTimeAttackJson, besttime, bestlap) }; struct GamedataMapStatsSpbAttackJson final @@ -197,7 +196,7 @@ struct GamedataMapStatsSpbAttackJson final uint32_t besttime; uint32_t bestlap; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapStatsSpbAttackJson, besttime, bestlap) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapStatsSpbAttackJson, besttime, bestlap) }; struct GamedataMapStatsPlaytimeJson final @@ -212,7 +211,7 @@ struct GamedataMapStatsPlaytimeJson final uint32_t timeattack; uint32_t spbattack; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataMapStatsPlaytimeJson, total, netgame, @@ -232,7 +231,7 @@ struct GamedataMapStatsJson final GamedataMapStatsSpbAttackJson spbattack; GamedataMapStatsPlaytimeJson time; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataMapStatsJson, timeattack, spbattack, @@ -244,16 +243,18 @@ struct GamedataMapJson final { GamedataMapVisitedJson visited; GamedataMapStatsJson stats; + uint16_t spraycan; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapJson, visited, stats) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataMapJson, visited, stats, spraycan) }; +// Deprecated struct GamedataSprayCanJson final { - std::string map; - std::string color; + String map; + String color; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataSprayCanJson, map, color) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataSprayCanJson, map, color) }; struct GamedataCupRecordsJson final @@ -261,24 +262,24 @@ struct GamedataCupRecordsJson final uint8_t bestplacement; uint8_t bestgrade; bool gotemerald; - std::string bestskin; + String bestskin; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataCupRecordsJson, bestplacement, bestgrade, gotemerald, bestskin) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataCupRecordsJson, bestplacement, bestgrade, gotemerald, bestskin) }; struct GamedataCupJson final { - std::string name; - std::vector records; + String name; + Vector records; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataCupJson, name, records) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataCupJson, name, records) }; struct GamedataSealedSwapJson final { - std::string name; + String name; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataSealedSwapJson, name) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(GamedataSealedSwapJson, name) }; struct GamedataJson final @@ -290,19 +291,20 @@ struct GamedataJson final GamedataMilestonesJson milestones; GamedataPrisonEggPickupsJson prisons; uint32_t tafolderhash; - std::vector emblems; - std::vector unlockables; - std::vector unlockpending; - std::vector conditionsets; + Vector emblems; + Vector unlockables; + Vector unlockpending; + Vector conditionsets; GamedataChallengeGridJson challengegrid; uint32_t timesBeaten; - std::unordered_map skins; - std::unordered_map maps; - std::vector spraycans; - std::unordered_map cups; - std::vector sealedswaps; + HashMap skins; + Vector spraycans_v2; + HashMap maps; + Vector spraycans; // Deprecated + HashMap cups; + Vector sealedswaps; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( GamedataJson, playtime, rings, @@ -318,6 +320,7 @@ struct GamedataJson final challengegrid, timesBeaten, skins, + spraycans_v2, maps, spraycans, cups, diff --git a/src/g_input.c b/src/g_input.c index e99f35499..3aa69c5ce 100644 --- a/src/g_input.c +++ b/src/g_input.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // @@ -33,6 +33,7 @@ INT32 gamekeydown[MAXDEVICES][NUMINPUTS]; // two key codes (or virtual key) per game control INT32 gamecontrol[MAXSPLITSCREENPLAYERS][num_gamecontrols][MAXINPUTMAPPING]; UINT8 gamecontrolflags[MAXSPLITSCREENPLAYERS]; +UINT8 showgamepadprompts[MAXSPLITSCREENPLAYERS]; INT32 gamecontroldefault[num_gamecontrols][MAXINPUTMAPPING]; // default control storage INT32 menucontrolreserved[num_gamecontrols][MAXINPUTMAPPING]; @@ -759,6 +760,129 @@ static keyname_t keynames[] = {KEY_AXIS1+9, "R TRIGGER"}, }; +static keyname_t shortkeynames[] = +{ + {KEY_SPACE, "SPC"}, + {KEY_CAPSLOCK, "CAPS"}, + {KEY_ENTER, "ENTER"}, + {KEY_TAB, "TAB"}, + {KEY_ESCAPE, "ESC"}, + {KEY_BACKSPACE, "BKSP"}, + + {KEY_NUMLOCK, "NLOK"}, + {KEY_SCROLLLOCK, "SLOK"}, + + // bill gates keys + {KEY_LEFTWIN, "LWIN"}, + {KEY_RIGHTWIN, "RWIN"}, + {KEY_MENU, "MENU"}, + + {KEY_LSHIFT, "LSFT"}, + {KEY_RSHIFT, "RSFT"}, + {KEY_LSHIFT, "SFT"}, + {KEY_LCTRL, "LCTRL"}, + {KEY_RCTRL, "RCTRL"}, + {KEY_LCTRL, "CTRL"}, + {KEY_LALT, "LALT"}, + {KEY_RALT, "RALT"}, + {KEY_LALT, "ALT"}, + + // keypad keys + {KEY_KPADSLASH, "/"}, + {KEY_KEYPAD7, "7"}, + {KEY_KEYPAD8, "8"}, + {KEY_KEYPAD9, "9"}, + {KEY_MINUSPAD, "-"}, + {KEY_KEYPAD4, "4"}, + {KEY_KEYPAD5, "5"}, + {KEY_KEYPAD6, "6"}, + {KEY_PLUSPAD, "+"}, + {KEY_KEYPAD1, "1"}, + {KEY_KEYPAD2, "2"}, + {KEY_KEYPAD3, "3"}, + {KEY_KEYPAD0, "0"}, + {KEY_KPADDEL, "."}, + + // extended keys (not keypad) + {KEY_HOME, "HOME"}, + {KEY_UPARROW, "UP"}, + {KEY_PGUP, "PGUP"}, + {KEY_LEFTARROW, "LEFT"}, + {KEY_RIGHTARROW, "RIGHT"}, + {KEY_END, "END"}, + {KEY_DOWNARROW, "DOWN"}, + {KEY_PGDN, "PGDN"}, + {KEY_INS, "INS"}, + {KEY_DEL, "DEL"}, + + // other keys + {KEY_F1, "F1"}, + {KEY_F2, "F2"}, + {KEY_F3, "F3"}, + {KEY_F4, "F4"}, + {KEY_F5, "F5"}, + {KEY_F6, "F6"}, + {KEY_F7, "F7"}, + {KEY_F8, "F8"}, + {KEY_F9, "F9"}, + {KEY_F10, "F10"}, + {KEY_F11, "F11"}, + {KEY_F12, "F12"}, + + // KEY_CONSOLE has an exception in the keyname code + {'`', "TILDE"}, + {KEY_PAUSE, "PAUSE"}, + + // virtual keys for mouse buttons and joystick buttons + {KEY_MOUSE1+0,"M1"}, + {KEY_MOUSE1+1,"M2"}, + {KEY_MOUSE1+2,"M3"}, + {KEY_MOUSE1+3,"M4"}, + {KEY_MOUSE1+4,"M5"}, + {KEY_MOUSE1+5,"M6"}, + {KEY_MOUSE1+6,"M7"}, + {KEY_MOUSE1+7,"M8"}, + {KEY_MOUSEMOVE+0,"Mouse Up"}, + {KEY_MOUSEMOVE+1,"Mouse Down"}, + {KEY_MOUSEMOVE+2,"Mouse Left"}, + {KEY_MOUSEMOVE+3,"Mouse Right"}, + {KEY_MOUSEWHEELUP, "Wheel Up"}, + {KEY_MOUSEWHEELDOWN, "Wheel Down"}, + + {KEY_JOY1+0, "A"}, + {KEY_JOY1+1, "B"}, + {KEY_JOY1+2, "X"}, + {KEY_JOY1+3, "Y"}, + {KEY_JOY1+4, "BACK"}, + {KEY_JOY1+5, "GUIDE"}, + {KEY_JOY1+6, "START"}, + {KEY_JOY1+7, "LS"}, + {KEY_JOY1+8, "RS"}, + {KEY_JOY1+9, "LB"}, + {KEY_JOY1+10, "RB"}, + {KEY_JOY1+11, "D-UP"}, + {KEY_JOY1+12, "D-DOWN"}, + {KEY_JOY1+13, "D-LEFT"}, + {KEY_JOY1+14, "D-RIGHT"}, + {KEY_JOY1+15, "MISC."}, + {KEY_JOY1+16, "PADDLE1"}, + {KEY_JOY1+17, "PADDLE2"}, + {KEY_JOY1+18, "PADDLE3"}, + {KEY_JOY1+19, "PADDLE4"}, + {KEY_JOY1+20, "TOUCHPAD"}, + + {KEY_AXIS1+0, "LS LEFT"}, + {KEY_AXIS1+1, "LS RIGHT"}, + {KEY_AXIS1+2, "LS UP"}, + {KEY_AXIS1+3, "LS DOWN"}, + {KEY_AXIS1+4, "RS LEFT"}, + {KEY_AXIS1+5, "RS RIGHT"}, + {KEY_AXIS1+6, "RS UP"}, + {KEY_AXIS1+7, "RS DOWN"}, + {KEY_AXIS1+8, "LT"}, + {KEY_AXIS1+9, "RT"}, +}; + static const char *gamecontrolname[num_gamecontrols] = { "null", // a key/button mapped to gc_null has no effect @@ -786,6 +910,7 @@ static const char *gamecontrolname[num_gamecontrols] = "screenshot", "startmovie", "startlossless", + "voicepushtotalk" }; #define NUMKEYNAMES (sizeof (keynames)/sizeof (keyname_t)) @@ -879,7 +1004,7 @@ const char *G_KeynumToString(INT32 keynum) // return a string with the ascii char if displayable if (keynum > ' ' && keynum <= 'z' && keynum != KEY_CONSOLE) { - keynamestr[0] = (char)keynum; + keynamestr[0] = toupper(keynum); // Uppercase looks better! keynamestr[1] = '\0'; return keynamestr; } @@ -894,6 +1019,30 @@ const char *G_KeynumToString(INT32 keynum) return keynamestr; } +const char *G_KeynumToShortString(INT32 keynum) +{ + static char keynamestr[8]; + + UINT32 j; + + // return a string with the ascii char if displayable + if (keynum > ' ' && keynum <= 'z' && keynum != KEY_CONSOLE) + { + keynamestr[0] = toupper(keynum); // Uppercase looks better! + keynamestr[1] = '\0'; + return keynamestr; + } + + // find a description for special keys + for (j = 0; j < NUMKEYNAMES; j++) + if (shortkeynames[j].keynum == keynum) + return shortkeynames[j].name; + + // create a name for unknown keys + sprintf(keynamestr, "KEY%d", keynum); + return keynamestr; +} + INT32 G_KeyStringtoNum(const char *keystr) { UINT32 j; @@ -1121,6 +1270,72 @@ INT32 G_CheckDoubleUsage(INT32 keynum, INT32 playernum, boolean modify) return result; } +INT32 G_FindPlayerBindForGameControl(INT32 player, gamecontrols_e control) +{ + INT32 device = showgamepadprompts[player] ? 1 : KEYBOARD_MOUSE_DEVICE; + + INT32 bestbind = -1; // Bind that matches our input device + INT32 anybind = -1; // Bind that doesn't match, but is at least for this control + + INT32 bindindex = MAXINPUTMAPPING-1; + + // CONS_Printf("Check bind %d for player %d device %d\n", control, player, device); + + // PASS 1: Binds that are directly in our active control mapping. + while (bindindex >= 0) // Prefer earlier binds + { + INT32 possiblecontrol = gamecontrol[player][control][bindindex]; + + bindindex--; + + if (possiblecontrol == 0) + continue; + + // if (device is gamepad) == (bound control is in gamepad range) - e.g. if bind matches device + if ((device != KEYBOARD_MOUSE_DEVICE) == (possiblecontrol >= KEY_JOY1 && possiblecontrol < JOYINPUTEND)) + { + // CONS_Printf("PASS1 found %s\n", G_KeynumToShortString(possiblecontrol)); + bestbind = possiblecontrol; + anybind = possiblecontrol; + } + else + { + // CONS_Printf("PASS1 considering %s\n", G_KeynumToShortString(possiblecontrol)); + anybind = possiblecontrol; + } + } + + // PASS 3: "Safety" binds that are reserved by the menu system. + if (bestbind == -1) + { + bindindex = MAXINPUTMAPPING-1; + + while (bindindex >= 0) + { + INT32 possiblecontrol = menucontrolreserved[control][bindindex]; + + bindindex--; + + if (possiblecontrol == 0) + continue; + + if ((device != KEYBOARD_MOUSE_DEVICE) == (possiblecontrol >= KEY_JOY1 && possiblecontrol < JOYINPUTEND)) + { + // CONS_Printf("PASS2 found %s\n", G_KeynumToShortString(possiblecontrol)); + bestbind = possiblecontrol; + anybind = possiblecontrol; + } + else + { + // CONS_Printf("PASS2 considering %s\n", G_KeynumToShortString(possiblecontrol)); + anybind = possiblecontrol; + } + } + } + + return (bestbind != -1) ? bestbind : anybind; // If we couldn't find a device-appropriate bind, try to at least use something +} + static void setcontrol(UINT8 player) { INT32 numctrl; diff --git a/src/g_input.h b/src/g_input.h index cc04f0047..d2f2d535c 100644 --- a/src/g_input.h +++ b/src/g_input.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // @@ -56,6 +56,33 @@ typedef enum NUMINPUTS = MOUSEINPUTEND, } key_input_e; +// Helper to keep descriptive input setup slightly more readable +typedef enum +{ + nc_a = KEY_JOY1, + nc_b, + nc_x, + nc_y, + nc_back, + nc_guide, + nc_start, + nc_ls, + nc_rs, + nc_lb, + nc_rb, + nc_hatup, + nc_hatdown, + nc_hatleft, + nc_hatright, + nc_touch = KEY_JOY1+20, + nc_lsleft = KEY_AXIS1+0, + nc_lsright, + nc_lsup, + nc_lsdown, + nc_lt = KEY_AXIS1+8, + nc_rt, +} named_controls_e; + typedef enum { gc_null = 0, // a key/button mapped to gc_null has no effect @@ -77,9 +104,9 @@ typedef enum // special keys gc_abc, - gc_luaa, - gc_luab, - gc_luac, + gc_lua1, + gc_lua2, + gc_lua3, gc_console, gc_talk, gc_teamtalk, @@ -87,6 +114,7 @@ typedef enum gc_screenshot, gc_startmovie, gc_startlossless, + gc_voicepushtotalk, num_gamecontrols, @@ -120,6 +148,7 @@ extern INT32 gamekeydown[MAXDEVICES][NUMINPUTS]; // several key codes (or virtual key) per game control extern INT32 gamecontrol[MAXSPLITSCREENPLAYERS][num_gamecontrols][MAXINPUTMAPPING]; extern UINT8 gamecontrolflags[MAXSPLITSCREENPLAYERS]; +extern UINT8 showgamepadprompts[MAXSPLITSCREENPLAYERS]; extern INT32 gamecontroldefault[num_gamecontrols][MAXINPUTMAPPING]; // default control storage extern INT32 menucontrolreserved[num_gamecontrols][MAXINPUTMAPPING]; @@ -187,6 +216,7 @@ void G_MapEventsToControls(event_t *ev); // returns the name of a key const char *G_KeynumToString(INT32 keynum); +const char *G_KeynumToShortString(INT32 keynum); INT32 G_KeyStringtoNum(const char *keystr); boolean G_KeyBindIsNecessary(INT32 gc); @@ -205,6 +235,8 @@ void G_ApplyControlScheme(UINT8 splitplayer, INT32 (*fromcontrols)[MAXINPUTMAPPI void G_SaveKeySetting(FILE *f, INT32 (*fromcontrolsa)[MAXINPUTMAPPING], INT32 (*fromcontrolsb)[MAXINPUTMAPPING], INT32 (*fromcontrolsc)[MAXINPUTMAPPING], INT32 (*fromcontrolsd)[MAXINPUTMAPPING]); INT32 G_CheckDoubleUsage(INT32 keynum, INT32 playernum, boolean modify); +INT32 G_FindPlayerBindForGameControl(INT32 player, gamecontrols_e control); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/g_party.cpp b/src/g_party.cpp index 95d260d72..a5dd79270 100644 --- a/src/g_party.cpp +++ b/src/g_party.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/g_party.h b/src/g_party.h index 5b16c1f2b..b5fd5c2b0 100644 --- a/src/g_party.h +++ b/src/g_party.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/g_state.h b/src/g_state.h index 9bdadf52c..89c5beef8 100644 --- a/src/g_state.h +++ b/src/g_state.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/hardware/hw3dsdrv.h b/src/hardware/hw3dsdrv.h index 963d6f577..3753a7095 100644 --- a/src/hardware/hw3dsdrv.h +++ b/src/hardware/hw3dsdrv.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2001 by DooM Legacy Team. // diff --git a/src/hardware/hw3sound.c b/src/hardware/hw3sound.c index bdba00841..6bd9a9837 100644 --- a/src/hardware/hw3sound.c +++ b/src/hardware/hw3sound.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2001 by DooM Legacy Team. // diff --git a/src/hardware/hw3sound.h b/src/hardware/hw3sound.h index d426ac07a..3bed85f56 100644 --- a/src/hardware/hw3sound.h +++ b/src/hardware/hw3sound.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2001 by DooM Legacy Team. // diff --git a/src/hardware/hw_batching.c b/src/hardware/hw_batching.c index 25595e512..9c0e2f6e9 100644 --- a/src/hardware/hw_batching.c +++ b/src/hardware/hw_batching.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the @@ -130,8 +130,8 @@ void HWR_ProcessPolygon(FSurfaceInfo *pSurf, FOutVector *pOutVerts, FUINT iNumPt polygonArray[polygonArraySize].vertsIndex = unsortedVertexArraySize; polygonArray[polygonArraySize].numVerts = iNumPts; polygonArray[polygonArraySize].polyFlags = PolyFlags; - polygonArray[polygonArraySize].texture = current_texture; polygonArray[polygonArraySize].brightmap = current_brightmap; + polygonArray[polygonArraySize].texture = current_texture; polygonArray[polygonArraySize].shader = shader; polygonArray[polygonArraySize].horizonSpecial = horizonSpecial; polygonArraySize++; @@ -203,9 +203,9 @@ static int comparePolygons(const void *p1, const void *p2) diff = poly1->surf.LightInfo.fade_start - poly2->surf.LightInfo.fade_start; if (diff != 0) return diff; diff = poly1->surf.LightInfo.fade_end - poly2->surf.LightInfo.fade_end; + if (diff != 0) return diff; diff = poly1->surf.LightInfo.directional - poly2->surf.LightInfo.directional; - if (diff != 0) return diff; return diff; } diff --git a/src/hardware/hw_batching.h b/src/hardware/hw_batching.h index ddb4f101d..48f256daa 100644 --- a/src/hardware/hw_batching.h +++ b/src/hardware/hw_batching.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/hardware/hw_bsp.c b/src/hardware/hw_bsp.c index a9dec5666..0b036ead0 100644 --- a/src/hardware/hw_bsp.c +++ b/src/hardware/hw_bsp.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/hardware/hw_cache.c b/src/hardware/hw_cache.c index 1ef482cf9..aee197ab5 100644 --- a/src/hardware/hw_cache.c +++ b/src/hardware/hw_cache.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/hardware/hw_clip.h b/src/hardware/hw_clip.h index 687839d12..1942f426b 100644 --- a/src/hardware/hw_clip.h +++ b/src/hardware/hw_clip.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/hardware/hw_data.h b/src/hardware/hw_data.h index 0b6df9ae9..69b5d897e 100644 --- a/src/hardware/hw_data.h +++ b/src/hardware/hw_data.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/hardware/hw_defs.h b/src/hardware/hw_defs.h index ad3bb0369..1ce225f19 100644 --- a/src/hardware/hw_defs.h +++ b/src/hardware/hw_defs.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/hardware/hw_dll.h b/src/hardware/hw_dll.h index c93093679..c967e96ce 100644 --- a/src/hardware/hw_dll.h +++ b/src/hardware/hw_dll.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/hardware/hw_draw.c b/src/hardware/hw_draw.c index 1bb294cff..d4ba78614 100644 --- a/src/hardware/hw_draw.c +++ b/src/hardware/hw_draw.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // @@ -267,8 +267,8 @@ void HWR_DrawStretchyFixedPatch(patch_t *gpatch, fixed_t x, fixed_t y, fixed_t p cx = cx1; cy = cy1; - fwidth = cx2 - cx1; - fheight = cy2 - cy1; + fwidth = fmaxf(0.0f, cx2 - cx1); + fheight = fmaxf(0.0f, cy2 - cy1); } // positions of the cx, cy, are between 0 and vid.width/vid.height now, we need them to be between -1 and 1 diff --git a/src/hardware/hw_drv.h b/src/hardware/hw_drv.h index 00243fd0d..c4396c8e5 100644 --- a/src/hardware/hw_drv.h +++ b/src/hardware/hw_drv.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/hardware/hw_glob.h b/src/hardware/hw_glob.h index a8f2477cf..07dfb7a16 100644 --- a/src/hardware/hw_glob.h +++ b/src/hardware/hw_glob.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/hardware/hw_light.c b/src/hardware/hw_light.c index 3ee0a2f4e..93b19179d 100644 --- a/src/hardware/hw_light.c +++ b/src/hardware/hw_light.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/hardware/hw_light.h b/src/hardware/hw_light.h index f29a59ecd..d35edc5dc 100644 --- a/src/hardware/hw_light.h +++ b/src/hardware/hw_light.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index ed62ac236..a9a90fbed 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // @@ -1762,7 +1762,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom if (rover->alpha < 256 || rover->blend) { blendmode = HWR_GetBlendModeFlag(rover->blend); - Surf.PolyColor.s.alpha = (UINT8)(rover->alpha-1); + Surf.PolyColor.s.alpha = max(0, min(rover->alpha, 255)); } } @@ -1891,7 +1891,7 @@ static void HWR_ProcessSeg(void) // Sort of like GLWall::Process in GZDoom if (rover->alpha < 256 || rover->blend) { blendmode = HWR_GetBlendModeFlag(rover->blend); - Surf.PolyColor.s.alpha = (UINT8)(rover->alpha-1); + Surf.PolyColor.s.alpha = max(0, min(rover->alpha, 255)); } } @@ -2636,7 +2636,7 @@ static void HWR_Subsector(size_t num) false, *rover->bottomheight, *gl_frontsector->lightlist[light].lightlevel, - rover->alpha-1, rover->master->frontsector, blendmode, + max(0, min(rover->alpha, 255)), rover->master->frontsector, blendmode, false, *gl_frontsector->lightlist[light].extra_colormap); } else @@ -2684,7 +2684,7 @@ static void HWR_Subsector(size_t num) true, *rover->topheight, *gl_frontsector->lightlist[light].lightlevel, - rover->alpha-1, rover->master->frontsector, blendmode, + max(0, min(rover->alpha, 255)), rover->master->frontsector, blendmode, false, *gl_frontsector->lightlist[light].extra_colormap); } else diff --git a/src/hardware/hw_main.h b/src/hardware/hw_main.h index 2d5a530a7..652b40947 100644 --- a/src/hardware/hw_main.h +++ b/src/hardware/hw_main.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c index c6c25cb10..977db93b6 100644 --- a/src/hardware/hw_md2.c +++ b/src/hardware/hw_md2.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // @@ -826,10 +826,9 @@ static void HWR_CreateBlendedTexture(patch_t *gpatch, patch_t *blendgpatch, GLMi } } } - } - if (translen > 0) colorbrightnesses[translen] = colorbrightnesses[translen-1]; + } if (skinnum == TC_BLINK) blendcolor = V_GetColor(skincolors[color].ramp[3]); diff --git a/src/hardware/hw_md2.h b/src/hardware/hw_md2.h index ce2850493..dbfa84aa1 100644 --- a/src/hardware/hw_md2.h +++ b/src/hardware/hw_md2.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/hardware/hw_md2load.c b/src/hardware/hw_md2load.c index 3ff9274e3..7530fc91c 100644 --- a/src/hardware/hw_md2load.c +++ b/src/hardware/hw_md2load.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2020 by Spaddlewit Inc. // diff --git a/src/hardware/hw_md2load.h b/src/hardware/hw_md2load.h index ed80602dd..fd17fbe85 100644 --- a/src/hardware/hw_md2load.h +++ b/src/hardware/hw_md2load.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2020 by Spaddlewit Inc. // diff --git a/src/hardware/hw_md3load.c b/src/hardware/hw_md3load.c index 1c9ca600f..6d2667cd4 100644 --- a/src/hardware/hw_md3load.c +++ b/src/hardware/hw_md3load.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2020 by Spaddlewit Inc. // diff --git a/src/hardware/hw_md3load.h b/src/hardware/hw_md3load.h index d53f20643..7d53efd69 100644 --- a/src/hardware/hw_md3load.h +++ b/src/hardware/hw_md3load.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2020 by Spaddlewit Inc. // diff --git a/src/hardware/hw_model.c b/src/hardware/hw_model.c index dff088097..b60439930 100644 --- a/src/hardware/hw_model.c +++ b/src/hardware/hw_model.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2020 by Spaddlewit Inc. // diff --git a/src/hardware/hw_model.h b/src/hardware/hw_model.h index 3af928fa3..e352f3971 100644 --- a/src/hardware/hw_model.h +++ b/src/hardware/hw_model.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2020 by Spaddlewit Inc. // diff --git a/src/hardware/hws_data.h b/src/hardware/hws_data.h index 711beb21f..24014638d 100644 --- a/src/hardware/hws_data.h +++ b/src/hardware/hws_data.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/hardware/r_minigl/r_minigl.c b/src/hardware/r_minigl/r_minigl.c index 58644d5b1..c0d077e64 100644 --- a/src/hardware/r_minigl/r_minigl.c +++ b/src/hardware/r_minigl/r_minigl.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/hardware/r_opengl/ogl_win.c b/src/hardware/r_opengl/ogl_win.c index dfc9e58a6..02c785e78 100644 --- a/src/hardware/r_opengl/ogl_win.c +++ b/src/hardware/r_opengl/ogl_win.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/hardware/r_opengl/r_opengl.c b/src/hardware/r_opengl/r_opengl.c index 41e6cb6ec..941808f60 100644 --- a/src/hardware/r_opengl/r_opengl.c +++ b/src/hardware/r_opengl/r_opengl.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/hardware/r_opengl/r_opengl.h b/src/hardware/r_opengl/r_opengl.h index 74545af6d..a15f2854a 100644 --- a/src/hardware/r_opengl/r_opengl.h +++ b/src/hardware/r_opengl/r_opengl.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/hardware/r_opengl/r_vbo.h b/src/hardware/r_opengl/r_vbo.h index 13e3a8f36..5ba6be5b7 100644 --- a/src/hardware/r_opengl/r_vbo.h +++ b/src/hardware/r_opengl/r_vbo.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2020 by Spaddlewit Inc. // diff --git a/src/hardware/s_ds3d/s_ds3d.c b/src/hardware/s_ds3d/s_ds3d.c index 99d44b6ac..5f2dc4449 100644 --- a/src/hardware/s_ds3d/s_ds3d.c +++ b/src/hardware/s_ds3d/s_ds3d.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2001 by DooM Legacy Team. // diff --git a/src/hardware/s_fmod/s_fmod.c b/src/hardware/s_fmod/s_fmod.c index adadb9ba9..10ff21f4e 100644 --- a/src/hardware/s_fmod/s_fmod.c +++ b/src/hardware/s_fmod/s_fmod.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2001 by DooM Legacy Team. // diff --git a/src/hardware/s_openal/s_openal.c b/src/hardware/s_openal/s_openal.c index c4a83170a..800374344 100644 --- a/src/hardware/s_openal/s_openal.c +++ b/src/hardware/s_openal/s_openal.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2001 by DooM Legacy Team. // diff --git a/src/hardware/u_list.c b/src/hardware/u_list.c index 10e9a4e67..4d58af011 100644 --- a/src/hardware/u_list.c +++ b/src/hardware/u_list.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2020 by Spaddlewit Inc. // diff --git a/src/hardware/u_list.h b/src/hardware/u_list.h index c8d7b4259..a9ac950f0 100644 --- a/src/hardware/u_list.h +++ b/src/hardware/u_list.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2020 by Spaddlewit Inc. // diff --git a/src/http-mserv.c b/src/http-mserv.c index 622d051dc..4f9f9cb39 100644 --- a/src/http-mserv.c +++ b/src/http-mserv.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James R. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James R. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/hu_stuff.c b/src/hu_stuff.c index c866184b4..04d8fdaec 100644 --- a/src/hu_stuff.c +++ b/src/hu_stuff.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -86,6 +86,7 @@ patch_t *frameslash; // framerate stuff. Used in screen.c static player_t *plr; boolean hu_keystrokes; // :) boolean chat_on; // entering a chat message? +boolean g_voicepushtotalk_on; // holding PTT? static char w_chat[HU_MAXMSGLEN + 1]; static size_t c_input = 0; // let's try to make the chat input less shitty. static boolean headsupactive = false; @@ -621,7 +622,7 @@ static void Command_Sayteam_f(void) return; } - if (G_GametypeHasTeams()) // revert to normal say if we don't have teams in this gametype. + if (G_GametypeHasTeams()) // revert to normal say if we don't have teams in this gametype. DoSayPacketFromCommand(-1, 1, 0); else DoSayPacketFromCommand(0, 1, 0); @@ -779,16 +780,8 @@ static void Got_Saycmd(const UINT8 **p, INT32 playernum) } else if (target == -1) // say team { - if (players[playernum].ctfteam == 1) - { - // red text - cstart = textcolor = "\x85"; - } - else - { - // blue text - cstart = textcolor = "\x84"; - } + sprintf(color_prefix, "%c", '\x80' + (g_teaminfo[ players[playernum].team ].chat_color >> V_CHARCOLORSHIFT)); + cstart = textcolor = color_prefix; } else { @@ -1110,6 +1103,24 @@ void HU_clearChatChars(void) // boolean HU_Responder(event_t *ev) { + // Handle Push-to-Talk + if (ev->data1 == gamecontrol[0][gc_voicepushtotalk][0] + || ev->data1 == gamecontrol[0][gc_voicepushtotalk][1] + || ev->data1 == gamecontrol[0][gc_voicepushtotalk][2] + || ev->data1 == gamecontrol[0][gc_voicepushtotalk][3]) + { + if (ev->type == ev_keydown) + { + g_voicepushtotalk_on = true; + return true; + } + else if (ev->type == ev_keyup) + { + g_voicepushtotalk_on = false; + return true; + } + } + if (ev->type != ev_keydown) return false; @@ -1593,12 +1604,7 @@ static void HU_DrawChat(void) if (teamtalk) { talk = ttalk; -#if 0 - if (players[consoleplayer].ctfteam == 1) - t = '\0x85'; // Red - else if (players[consoleplayer].ctfteam == 2) - t = '\0x84'; // Blue -#endif + //t = '\x80' + (g_teaminfo[ players[consoleplayer].team ].chat_color >> V_CHARCOLORSHIFT); } typelines = 1; @@ -1925,25 +1931,25 @@ static void HU_DrawTitlecardCEcho(size_t num) { INT32 ofs; INT32 timer = (INT32)(elapsed - timeroffset); - + if (timer <= 0) return; // we don't care. - + line = strchr(echoptr, '\\'); - + if (line == NULL) break; *line = '\0'; - + ofs = V_CenteredTitleCardStringOffset(echoptr, p4); V_DrawTitleCardString(x - ofs, y, echoptr, 0, false, timer, fadeout, p4); y += p4 ? 18 : 32; - + // offset the timer for the next line. timeroffset += strlen(echoptr); - + // set the ptr to the \0 we made and advance it because we don't want an empty string. echoptr = line; echoptr++; @@ -2138,7 +2144,7 @@ void HU_Erase(void) con_hudupdate = false; // if it was set.. } #ifdef HWRENDER - else if (rendermode != render_none) + else if (rendermode == render_opengl) { // refresh just what is needed from the view borders HWR_DrawViewBorder(secondframelines); @@ -2569,8 +2575,6 @@ static void HU_DrawRankings(void) completed[i] = true; - standings.character[standings.numplayers] = players[i].skin; - standings.color[standings.numplayers] = players[i].skincolor; standings.pos[standings.numplayers] = players[i].position; #define strtime standings.strval[standings.numplayers] @@ -2608,6 +2612,8 @@ static void HU_DrawRankings(void) standings.numplayers++; } + standings.halfway = (standings.numplayers-1)/2; + // Returns early if there's no players to draw Y_PlayerStandingsDrawer(&standings, 0); diff --git a/src/hu_stuff.h b/src/hu_stuff.h index 5747cd85e..58f2ef1ae 100644 --- a/src/hu_stuff.h +++ b/src/hu_stuff.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -125,6 +125,9 @@ void HU_AddChatText(const char *text, boolean playsound); // set true when entering a chat message extern boolean chat_on; +// set true when push-to-talk is held +extern boolean g_voicepushtotalk_on; + // keystrokes in the console or chat window extern boolean hu_keystrokes; diff --git a/src/hud/emerald-win.cpp b/src/hud/emerald-win.cpp index 44d8d1f68..0c108b371 100644 --- a/src/hud/emerald-win.cpp +++ b/src/hud/emerald-win.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/hud/input-display.cpp b/src/hud/input-display.cpp index 31779a7ef..c3dab0a53 100644 --- a/src/hud/input-display.cpp +++ b/src/hud/input-display.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -85,8 +85,8 @@ void K_DrawInputDisplay(float x, float y, INT32 flags, char mode, UINT8 pid, boo const ticcmd_t& cmd = players[displayplayers[pid]].cmd; const boolean analog = (mode == '4' || mode == '5') ? players[displayplayers[pid]].analoginput : false; - const std::string prefix = fmt::format("PR{}", mode); - auto gfx = [&](auto format, auto&&... args) { return prefix + fmt::format(format, args...); }; + srb2::String prefix = srb2::format("PR{}", mode); + auto gfx = [&](auto format, auto&&... args) { return prefix + srb2::format(format, args...); }; auto but = [&](char key, INT32 gc, UINT32 bt) { bool press = local ? G_PlayerInputAnalog(pid, gc, guessinput) : ((cmd.buttons & bt) == bt); @@ -111,7 +111,7 @@ void K_DrawInputDisplay(float x, float y, INT32 flags, char mode, UINT8 pid, boo box.patch(gfx("PAD{}", analog ? "N" : dpad_suffix(dpad))); box.patch(but('A', gc_a, BT_ACCELERATE)); box.patch(but('B', gc_b, BT_LOOKBACK)); - box.patch(but('C', gc_c, BT_SPINDASHMASK)); + box.patch(but('C', gc_c, BT_SPINDASH)); box.patch(but('X', gc_x, BT_BRAKE)); box.patch(but('Y', gc_y, BT_RESPAWN)); box.patch(but('Z', gc_z, BT_VOTE)); diff --git a/src/hud/powerup.cpp b/src/hud/powerup.cpp index 0877fc0a3..64cfe6090 100644 --- a/src/hud/powerup.cpp +++ b/src/hud/powerup.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/hud/spectator.cpp b/src/hud/spectator.cpp index 194f41785..f0606378a 100644 --- a/src/hud/spectator.cpp +++ b/src/hud/spectator.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -27,6 +27,7 @@ #include "../k_hud.h" #include "../p_local.h" #include "../r_fps.h" +#include "../s_sound.h" extern "C" consvar_t cv_maxplayers; @@ -39,10 +40,9 @@ struct List { struct Field { - Field(const char* label, Draw::Button button, std::optional pressed = {}) : + Field(const char* label, const char* button = {}) : label_(Draw::TextElement(label).font(Draw::Font::kThin)), - button_(button), - pressed_(pressed) + btlabel_(Draw::TextElement().parse(button).font(Draw::Font::kThin)) { } @@ -55,29 +55,7 @@ struct List col.text(label_); col = col.x(left ? -(kButtonWidth + kButtonMargin) : width() - (kButtonWidth + kFieldSpacing)); - //if (r_splitscreen) - { - auto small_button_offset = [&] - { - switch (button_) - { - case Draw::Button::l: - case Draw::Button::r: - return -4; - - default: - return -2; - } - }; - - col.y(small_button_offset()).small_button(button_, pressed_); - } -#if 0 - else - { - col.y(-4).button(button_, pressed_); - } -#endif + col.text(btlabel_); } private: @@ -86,8 +64,7 @@ struct List static constexpr int kFieldSpacing = 8; Draw::TextElement label_; - Draw::Button button_; - std::optional pressed_; + Draw::TextElement btlabel_; }; List(int x, int y) : row_(split_draw(x, y, left_)) {} @@ -188,7 +165,7 @@ void K_drawSpectatorHUD(boolean director) if (player) { - std::string label = [player] + srb2::String label = [player] { if (player->flashing) { @@ -206,10 +183,10 @@ void K_drawSpectatorHUD(boolean director) if (cv_maxplayers.value) { - label += fmt::format(" [{}/{}]", numingame, cv_maxplayers.value); + label += srb2::format(" [{}/{}]", numingame, cv_maxplayers.value); } - list.insert({{label.c_str(), Draw::Button::l}}); + list.insert({{label.c_str(), ""}}); } if (director || camera[viewnum].freecam) @@ -217,14 +194,14 @@ void K_drawSpectatorHUD(boolean director) // Not locked into freecam -- can toggle it. if (director) { - list.insert({{"Freecam", Draw::Button::c}}); + list.insert({{"Freecam", ""}}); } else { bool press = D_LocalTiccmd(viewnum)->buttons & BT_RESPAWN; const char* label = (press && I_GetTime() % 16 < 8) ? "> <" : ">< "; - list.insert({{label, Draw::Button::y, press}, {"Exit", Draw::Button::c}}); + list.insert({{label, press ? "" : ""}, {"Exit", ""}}); } } @@ -232,19 +209,19 @@ void K_drawSpectatorHUD(boolean director) { if (numingame > 1) { - list.insert({{"+", Draw::Button::a}, {"-", Draw::Button::x}}); + list.insert({{"+", ""}, {"-", ""}}); } if (player) { - list.insert({{K_DirectorIsEnabled(viewnum) ? "\x82" "Director" : "Director", Draw::Button::r}}); + list.insert({{K_DirectorIsEnabled(viewnum) ? "\x82" "Director" : "Director", ""}}); } } else { auto bt = D_LocalTiccmd(viewnum)->buttons; - list.insert({{"", Draw::Button::r, bt & BT_DRIFT}, {"Pivot", Draw::Button::b, bt & BT_LOOKBACK}}); - list.insert({{"+", Draw::Button::a, bt & BT_ACCELERATE}, {"-", Draw::Button::x, bt & BT_BRAKE}}); + list.insert({{"", bt & BT_DRIFT ? "" : ""}, {"Pivot", bt & BT_LOOKBACK ? "" : ""}}); + list.insert({{"+", bt & BT_ACCELERATE ? "" : ""}, {"-", bt & BT_BRAKE ? "" : ""}}); } } diff --git a/src/hud/timer.cpp b/src/hud/timer.cpp index d37f79d7c..8f0ccc504 100644 --- a/src/hud/timer.cpp +++ b/src/hud/timer.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/hwr2/CMakeLists.txt b/src/hwr2/CMakeLists.txt index fa068990f..879f4e669 100644 --- a/src/hwr2/CMakeLists.txt +++ b/src/hwr2/CMakeLists.txt @@ -5,14 +5,10 @@ target_sources(SRB2SDL2 PRIVATE blit_rect.cpp blit_rect.hpp hardware_state.hpp - pass_imgui.cpp - pass_imgui.hpp - pass_manager.cpp - pass_manager.hpp + imgui_renderer.cpp + imgui_renderer.hpp pass_resource_managers.cpp pass_resource_managers.hpp - pass.cpp - pass.hpp patch_atlas.cpp patch_atlas.hpp postprocess_wipe.cpp diff --git a/src/hwr2/blendmode.hpp b/src/hwr2/blendmode.hpp index 2b0e8268d..e5f73999f 100644 --- a/src/hwr2/blendmode.hpp +++ b/src/hwr2/blendmode.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/hwr2/blit_postimg_screens.cpp b/src/hwr2/blit_postimg_screens.cpp index 13a9e8213..f1804c9d4 100644 --- a/src/hwr2/blit_postimg_screens.cpp +++ b/src/hwr2/blit_postimg_screens.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -10,11 +10,14 @@ #include "blit_postimg_screens.hpp" +#include + #include #include #include #include #include +#include #include "../p_tick.h" #include "../i_time.h" @@ -36,34 +39,6 @@ struct BlitVertex }; } // namespace -static const PipelineDesc kPostimgPipelineDesc = -{ - PipelineProgram::kPostimg, - {{{sizeof(BlitVertex)}}, {{VertexAttributeName::kPosition, 0, 0}, {VertexAttributeName::kTexCoord0, 0, 12}}}, - {{{{UniformName::kTime, UniformName::kProjection, UniformName::kModelView, UniformName::kTexCoord0Transform, UniformName::kTexCoord0Min, UniformName::kTexCoord0Max, UniformName::kPostimgWater, UniformName::kPostimgHeat}}}}, - {{SamplerName::kSampler0}}, - std::nullopt, - {std::nullopt, {true, true, true, true}}, - PrimitiveType::kTriangles, - CullMode::kNone, - FaceWinding::kCounterClockwise, - {0.0, 0.0, 0.0, 1.0} -}; - -static const PipelineDesc kPostimgIndexedPipelineDesc = -{ - PipelineProgram::kPostimg, - {{{sizeof(BlitVertex)}}, {{VertexAttributeName::kPosition, 0, 0}, {VertexAttributeName::kTexCoord0, 0, 12}}}, - {{{{UniformName::kTime, UniformName::kProjection, UniformName::kModelView, UniformName::kTexCoord0Transform, UniformName::kTexCoord0Min, UniformName::kTexCoord0Max, UniformName::kPostimgWater, UniformName::kPostimgHeat}}}}, - {{SamplerName::kSampler0, SamplerName::kSampler1}}, - std::nullopt, - {std::nullopt, {true, true, true, true}}, - PrimitiveType::kTriangles, - CullMode::kNone, - FaceWinding::kCounterClockwise, - {0.0, 0.0, 0.0, 1.0} -}; - static const BlitVertex kVerts[] = {{-.5f, -.5f, 0.f, 0.f, 0.f}, {.5f, -.5f, 0.f, 1.f, 0.f}, {-.5f, .5f, 0.f, 0.f, 1.f}, {.5f, .5f, 0.f, 1.f, 1.f}}; @@ -99,105 +74,15 @@ BlitPostimgScreens::BlitPostimgScreens(PaletteManager* palette_mgr) { } -void BlitPostimgScreens::draw(Rhi& rhi, Handle ctx) +void BlitPostimgScreens::draw(Rhi& rhi) { prepass(rhi); - transfer(rhi, ctx); - - for (uint32_t i = 0; i < screens_; i++) - { - BlitPostimgScreens::ScreenData& data = screen_data_[i]; - - rhi.bind_pipeline(ctx, data.pipeline); - rhi.set_viewport(ctx, get_screen_viewport(i, screens_, target_width_, target_height_)); - rhi.bind_uniform_set(ctx, 0, data.uniform_set); - rhi.bind_binding_set(ctx, data.binding_set); - rhi.bind_index_buffer(ctx, quad_ibo_); - rhi.draw_indexed(ctx, 6, 0); - } -} - -void BlitPostimgScreens::prepass(Rhi& rhi) -{ - if (!renderpass_) - { - renderpass_ = rhi.create_render_pass( - { - false, - AttachmentLoadOp::kClear, - AttachmentStoreOp::kStore, - AttachmentLoadOp::kDontCare, - AttachmentStoreOp::kDontCare, - AttachmentLoadOp::kDontCare, - AttachmentStoreOp::kDontCare - } - ); - } - - if (!pipeline_) - { - pipeline_ = rhi.create_pipeline(kPostimgPipelineDesc); - } - - if (!indexed_pipeline_) - { - indexed_pipeline_ = rhi.create_pipeline(kPostimgIndexedPipelineDesc); - } - - if (!quad_vbo_) - { - quad_vbo_ = rhi.create_buffer({sizeof(kVerts), BufferType::kVertexBuffer, BufferUsage::kImmutable}); - upload_quad_buffer_ = true; - } - - if (!quad_ibo_) - { - quad_ibo_ = rhi.create_buffer({sizeof(kIndices), BufferType::kIndexBuffer, BufferUsage::kImmutable}); - upload_quad_buffer_ = true; - } - - screen_data_.clear(); -} - -void BlitPostimgScreens::transfer(Rhi& rhi, Handle ctx) -{ - // Upload needed buffers - if (upload_quad_buffer_) - { - rhi.update_buffer(ctx, quad_vbo_, 0, tcb::as_bytes(tcb::span(kVerts))); - rhi.update_buffer(ctx, quad_ibo_, 0, tcb::as_bytes(tcb::span(kIndices))); - upload_quad_buffer_ = false; - } + transfer(rhi); for (uint32_t i = 0; i < screens_; i++) { BlitPostimgScreens::ScreenConfig& screen_config = screen_configs_[i]; - BlitPostimgScreens::ScreenData data {}; - - if (screen_config.indexed) - { - data.pipeline = indexed_pipeline_; - } - else - { - data.pipeline = pipeline_; - } - - VertexAttributeBufferBinding vertex_bindings[] = {{0, quad_vbo_}}; - TextureBinding sampler_bindings[] = - { - {SamplerName::kSampler0, screen_config.source}, - {SamplerName::kSampler1, palette_mgr_->palette()} - }; - - data.binding_set = rhi.create_binding_set( - ctx, - data.pipeline, - { - vertex_bindings, - tcb::span(sampler_bindings, screen_config.indexed ? 2 : 1) - } - ); + BlitPostimgScreens::ScreenData& data = screen_data_[i]; glm::mat4 projection = glm::scale(glm::identity(), glm::vec3(2.f, -2.f, 1.f)); glm::mat4 modelview = glm::identity(); @@ -222,19 +107,95 @@ void BlitPostimgScreens::transfer(Rhi& rhi, Handle ctx) glm::vec2 texcoord_min = screen_config.uv_offset; glm::vec2 texcoord_max = screen_config.uv_offset + screen_config.uv_size; - UniformVariant uniforms[] = - { - static_cast(leveltime), - projection, - modelview, - texcoord_transform, - texcoord_min, - texcoord_max, - screen_config.post.water, - screen_config.post.heat - }; + RasterizerStateDesc r_state {}; + r_state.cull = CullMode::kNone; - data.uniform_set = rhi.create_uniform_set(ctx, {uniforms}); + rhi.bind_program(data.program); + rhi.set_rasterizer_state(r_state); + rhi.set_viewport(get_screen_viewport(i, screens_, target_width_, target_height_)); + rhi.bind_vertex_attrib("a_position", quad_vbo_, rhi::VertexAttributeFormat::kFloat3, offsetof(BlitVertex, x), sizeof(BlitVertex)); + rhi.bind_vertex_attrib("a_texcoord0", quad_vbo_, rhi::VertexAttributeFormat::kFloat2, offsetof(BlitVertex, u), sizeof(BlitVertex)); + rhi.bind_index_buffer(quad_ibo_); + rhi.set_uniform("u_time", static_cast(leveltime)); + rhi.set_uniform("u_projection", projection); + rhi.set_uniform("u_modelview", modelview); + rhi.set_uniform("u_texcoord0_transform", texcoord_transform); + rhi.set_uniform("u_texcoord0_min", texcoord_min); + rhi.set_uniform("u_texcoord0_max", texcoord_max); + rhi.set_uniform("u_postimg_water", screen_config.post.water); + rhi.set_uniform("u_postimg_heat", screen_config.post.heat); + rhi.set_sampler("s_sampler0", 0, screen_config.source); + if (screen_config.indexed) + { + rhi.set_sampler("s_sampler1", 1, palette_mgr_->palette()); + } + rhi.draw_indexed(6, 0); + } +} + +void BlitPostimgScreens::prepass(Rhi& rhi) +{ + if (!program_) + { + static const char* defines[1] = { + "ENABLE_S_SAMPLER0" + }; + ProgramDesc desc {}; + desc.name = "postimg"; + desc.defines = tcb::make_span(defines); + program_ = rhi.create_program(desc); + } + + if (!indexed_program_) + { + static const char* defines[2] = { + "ENABLE_S_SAMPLER0", + "ENABLE_S_SAMPLER1" + }; + ProgramDesc desc {}; + desc.name = "postimg"; + desc.defines = tcb::make_span(defines); + indexed_program_ = rhi.create_program(desc); + } + + if (!quad_vbo_) + { + quad_vbo_ = rhi.create_buffer({sizeof(kVerts), BufferType::kVertexBuffer, BufferUsage::kImmutable}); + upload_quad_buffer_ = true; + } + + if (!quad_ibo_) + { + quad_ibo_ = rhi.create_buffer({sizeof(kIndices), BufferType::kIndexBuffer, BufferUsage::kImmutable}); + upload_quad_buffer_ = true; + } + + screen_data_.clear(); +} + +void BlitPostimgScreens::transfer(Rhi& rhi) +{ + // Upload needed buffers + if (upload_quad_buffer_) + { + rhi.update_buffer(quad_vbo_, 0, tcb::as_bytes(tcb::span(kVerts))); + rhi.update_buffer(quad_ibo_, 0, tcb::as_bytes(tcb::span(kIndices))); + upload_quad_buffer_ = false; + } + + for (uint32_t i = 0; i < screens_; i++) + { + BlitPostimgScreens::ScreenConfig& screen_config = screen_configs_[i]; + BlitPostimgScreens::ScreenData data {}; + + if (screen_config.indexed) + { + data.program = indexed_program_; + } + else + { + data.program = program_; + } screen_data_[i] = std::move(data); } diff --git a/src/hwr2/blit_postimg_screens.hpp b/src/hwr2/blit_postimg_screens.hpp index c91b65ca9..88438c248 100644 --- a/src/hwr2/blit_postimg_screens.hpp +++ b/src/hwr2/blit_postimg_screens.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -46,14 +46,11 @@ public: private: struct ScreenData { - rhi::Handle pipeline; - rhi::Handle binding_set; - rhi::Handle uniform_set; + rhi::Handle program; }; - rhi::Handle pipeline_; - rhi::Handle indexed_pipeline_; - rhi::Handle renderpass_; + rhi::Handle program_; + rhi::Handle indexed_program_; rhi::Handle quad_vbo_; rhi::Handle quad_ibo_; bool upload_quad_buffer_; @@ -67,12 +64,12 @@ private: PaletteManager* palette_mgr_; void prepass(rhi::Rhi& rhi); - void transfer(rhi::Rhi& rhi, rhi::Handle ctx); + void transfer(rhi::Rhi& rhi); public: explicit BlitPostimgScreens(PaletteManager* palette_mgr); - void draw(rhi::Rhi& rhi, rhi::Handle ctx); + void draw(rhi::Rhi& rhi); void set_num_screens(uint32_t screens) noexcept { diff --git a/src/hwr2/blit_rect.cpp b/src/hwr2/blit_rect.cpp index 1e6edec2f..189177ec5 100644 --- a/src/hwr2/blit_rect.cpp +++ b/src/hwr2/blit_rect.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -11,6 +11,7 @@ #include "blit_rect.hpp" #include +#include #include #include @@ -38,95 +39,42 @@ static const BlitVertex kVerts[] = static const uint16_t kIndices[] = {0, 1, 2, 1, 3, 2}; -/// @brief Pipeline used for non-paletted source textures. -static const PipelineDesc kUnshadedPipelineDescription = { - PipelineProgram::kUnshaded, - {{{sizeof(BlitVertex)}}, {{VertexAttributeName::kPosition, 0, 0}, {VertexAttributeName::kTexCoord0, 0, 12}}}, - {{{UniformName::kProjection}, {{UniformName::kModelView, UniformName::kTexCoord0Transform}}}}, - {{// RGB/A texture - SamplerName::kSampler0}}, - std::nullopt, - {std::nullopt, {true, true, true, true}}, - PrimitiveType::kTriangles, - CullMode::kNone, - FaceWinding::kCounterClockwise, - {0.f, 0.f, 0.f, 1.f}}; - -/// @brief Pipeline used for sharp bilinear special blit. -static const PipelineDesc kSharpBilinearPipelineDescription = { - PipelineProgram::kSharpBilinear, - {{{sizeof(BlitVertex)}}, {{VertexAttributeName::kPosition, 0, 0}, {VertexAttributeName::kTexCoord0, 0, 12}}}, - {{{UniformName::kProjection}, {{UniformName::kModelView, UniformName::kTexCoord0Transform, UniformName::kSampler0Size}}}}, - {{// RGB/A texture - SamplerName::kSampler0}}, - std::nullopt, - {std::nullopt, {true, true, true, true}}, - PrimitiveType::kTriangles, - CullMode::kNone, - FaceWinding::kCounterClockwise, - {0.f, 0.f, 0.f, 1.f}}; - -/// @brief Pipeline used for CRT special blit -static const PipelineDesc kCrtPipelineDescription = { - PipelineProgram::kCrt, - {{{sizeof(BlitVertex)}}, {{VertexAttributeName::kPosition, 0, 0}, {VertexAttributeName::kTexCoord0, 0, 12}}}, - {{{UniformName::kProjection}, {{UniformName::kModelView, UniformName::kTexCoord0Transform, UniformName::kSampler0Size}}}}, - {{// RGB/A texture - SamplerName::kSampler0, SamplerName::kSampler1}}, - std::nullopt, - {std::nullopt, {true, true, true, true}}, - PrimitiveType::kTriangles, - CullMode::kNone, - FaceWinding::kCounterClockwise, - {0.f, 0.f, 0.f, 1.f}}; - -/// @brief Pipeline used for CRT special blit (sharp) -static const PipelineDesc kCrtSharpPipelineDescription = { - PipelineProgram::kCrtSharp, - {{{sizeof(BlitVertex)}}, {{VertexAttributeName::kPosition, 0, 0}, {VertexAttributeName::kTexCoord0, 0, 12}}}, - {{{UniformName::kProjection}, {{UniformName::kModelView, UniformName::kTexCoord0Transform, UniformName::kSampler0Size}}}}, - {{// RGB/A texture - SamplerName::kSampler0, SamplerName::kSampler1}}, - std::nullopt, - {std::nullopt, {true, true, true, true}}, - PrimitiveType::kTriangles, - CullMode::kNone, - FaceWinding::kCounterClockwise, - {0.f, 0.f, 0.f, 1.f}}; - BlitRectPass::BlitRectPass() : BlitRectPass(BlitRectPass::BlitMode::kNearest) {} BlitRectPass::BlitRectPass(BlitRectPass::BlitMode blit_mode) : blit_mode_(blit_mode) {} BlitRectPass::~BlitRectPass() = default; -void BlitRectPass::draw(Rhi& rhi, Handle ctx) +void BlitRectPass::draw(Rhi& rhi) { prepass(rhi); - transfer(rhi, ctx); - graphics(rhi, ctx); + transfer(rhi); + graphics(rhi); } void BlitRectPass::prepass(Rhi& rhi) { - if (!pipeline_) + if (!program_) { + ProgramDesc desc {}; + const char* defines[1] = {"ENABLE_VA_TEXCOORD0"}; + desc.defines = tcb::make_span(defines); switch (blit_mode_) { - case BlitRectPass::BlitMode::kNearest: - pipeline_ = rhi.create_pipeline(kUnshadedPipelineDescription); - break; - case BlitRectPass::BlitMode::kSharpBilinear: - pipeline_ = rhi.create_pipeline(kSharpBilinearPipelineDescription); - break; - case BlitRectPass::BlitMode::kCrt: - pipeline_ = rhi.create_pipeline(kCrtPipelineDescription); - break; - case BlitRectPass::BlitMode::kCrtSharp: - pipeline_ = rhi.create_pipeline(kCrtSharpPipelineDescription); - break; - default: - std::terminate(); + case BlitRectPass::BlitMode::kNearest: + desc.name = "unshaded"; + break; + case BlitRectPass::BlitMode::kSharpBilinear: + desc.name = "sharpbilinear"; + break; + case BlitRectPass::BlitMode::kCrt: + desc.name = "crt"; + break; + case BlitRectPass::BlitMode::kCrtSharp: + desc.name = "crtsharp"; + break; + default: + std::terminate(); } - + program_ = rhi.create_program(desc); } if (!quad_vbo_) @@ -156,17 +104,17 @@ void BlitRectPass::prepass(Rhi& rhi) } } -void BlitRectPass::transfer(Rhi& rhi, Handle ctx) +void BlitRectPass::transfer(Rhi& rhi) { if (quad_vbo_needs_upload_ && quad_vbo_) { - rhi.update_buffer(ctx, quad_vbo_, 0, tcb::as_bytes(tcb::span(kVerts))); + rhi.update_buffer(quad_vbo_, 0, tcb::as_bytes(tcb::span(kVerts))); quad_vbo_needs_upload_ = false; } if (quad_ibo_needs_upload_ && quad_ibo_) { - rhi.update_buffer(ctx, quad_ibo_, 0, tcb::as_bytes(tcb::span(kIndices))); + rhi.update_buffer(quad_ibo_, 0, tcb::as_bytes(tcb::span(kIndices))); quad_ibo_needs_upload_ = false; } @@ -227,9 +175,21 @@ void BlitRectPass::transfer(Rhi& rhi, Handle ctx) 0, 0, 255, 255, 0, 0, 0, 255, }; - rhi.update_texture(ctx, dot_pattern_, {0, 0, 12, 4}, PixelFormat::kRGBA8, tcb::as_bytes(tcb::span(kDotPattern))); + rhi.update_texture(dot_pattern_, {0, 0, 12, 4}, PixelFormat::kRGBA8, tcb::as_bytes(tcb::span(kDotPattern))); dot_pattern_needs_upload_ = false; } +} + +void BlitRectPass::graphics(Rhi& rhi) +{ + rhi.bind_program(program_); + + RasterizerStateDesc rs {}; + rs.cull = CullMode::kNone; + + rhi.set_rasterizer_state(rs); + rhi.bind_vertex_attrib("a_position", quad_vbo_, VertexAttributeFormat::kFloat3, offsetof(BlitVertex, x), sizeof(BlitVertex)); + rhi.bind_vertex_attrib("a_texcoord0", quad_vbo_, VertexAttributeFormat::kFloat2, offsetof(BlitVertex, u), sizeof(BlitVertex)); float aspect = 1.0; float output_aspect = 1.0; @@ -242,122 +202,46 @@ void BlitRectPass::transfer(Rhi& rhi, Handle ctx) rhi::TextureDetails texture_details = rhi.get_texture_details(texture_); - std::array g1_uniforms = {{ - // Projection - glm::scale( - glm::identity(), - glm::vec3(taller ? 1.f : 1.f / output_aspect, taller ? -1.f / (1.f / output_aspect) : -1.f, 1.f) - ) - }}; + glm::mat4 projection = glm::scale( + glm::identity(), + glm::vec3(taller ? 1.f : 1.f / output_aspect, taller ? -1.f / (1.f / output_aspect) : -1.f, 1.f) + ); + glm::mat4 modelview = glm::scale( + glm::identity(), + glm::vec3(taller ? 2.f : 2.f * aspect, taller ? 2.f * (1.f / aspect) : 2.f, 1.f) + );; + glm::mat3 texcoord0_transform = glm::mat3( + glm::vec3(1.f, 0.f, 0.f), + glm::vec3(0.f, output_flip_ ? -1.f : 1.f, 0.f), + glm::vec3(0.f, output_flip_ ? 1.f : 0.f, 1.f) + ); + glm::vec2 sampler0_size = glm::vec2(texture_details.width, texture_details.height); - uniform_sets_[0] = rhi.create_uniform_set(ctx, {g1_uniforms}); + rhi.set_uniform("u_projection", projection); + rhi.set_uniform("u_modelview", modelview); + rhi.set_uniform("u_texcoord0_transform", texcoord0_transform); switch (blit_mode_) { - case BlitRectPass::BlitMode::kCrt: - { - std::array g2_uniforms = { - // ModelView - glm::scale( - glm::identity(), - glm::vec3(taller ? 2.f : 2.f * aspect, taller ? 2.f * (1.f / aspect) : 2.f, 1.f) - ), - // Texcoord0 Transform - glm::mat3( - glm::vec3(1.f, 0.f, 0.f), - glm::vec3(0.f, output_flip_ ? -1.f : 1.f, 0.f), - glm::vec3(0.f, output_flip_ ? 1.f : 0.f, 1.f) - ), - // Sampler 0 Size - glm::vec2(texture_details.width, texture_details.height) - }; - uniform_sets_[1] = rhi.create_uniform_set(ctx, {g2_uniforms}); - - std::array vbs = {{{0, quad_vbo_}}}; - std::array tbs = {{{rhi::SamplerName::kSampler0, texture_}, {rhi::SamplerName::kSampler1, dot_pattern_}}}; - binding_set_ = rhi.create_binding_set(ctx, pipeline_, {vbs, tbs}); + case BlitRectPass::BlitMode::kNearest: break; - } - case BlitRectPass::BlitMode::kCrtSharp: - { - std::array g2_uniforms = { - // ModelView - glm::scale( - glm::identity(), - glm::vec3(taller ? 2.f : 2.f * aspect, taller ? 2.f * (1.f / aspect) : 2.f, 1.f) - ), - // Texcoord0 Transform - glm::mat3( - glm::vec3(1.f, 0.f, 0.f), - glm::vec3(0.f, output_flip_ ? -1.f : 1.f, 0.f), - glm::vec3(0.f, output_flip_ ? 1.f : 0.f, 1.f) - ), - // Sampler 0 Size - glm::vec2(texture_details.width, texture_details.height) - }; - uniform_sets_[1] = rhi.create_uniform_set(ctx, {g2_uniforms}); - - std::array vbs = {{{0, quad_vbo_}}}; - std::array tbs = {{{rhi::SamplerName::kSampler0, texture_}, {rhi::SamplerName::kSampler1, dot_pattern_}}}; - binding_set_ = rhi.create_binding_set(ctx, pipeline_, {vbs, tbs}); - break; - } - case BlitRectPass::BlitMode::kSharpBilinear: - { - std::array g2_uniforms = { - // ModelView - glm::scale( - glm::identity(), - glm::vec3(taller ? 2.f : 2.f * aspect, taller ? 2.f * (1.f / aspect) : 2.f, 1.f) - ), - // Texcoord0 Transform - glm::mat3( - glm::vec3(1.f, 0.f, 0.f), - glm::vec3(0.f, output_flip_ ? -1.f : 1.f, 0.f), - glm::vec3(0.f, output_flip_ ? 1.f : 0.f, 1.f) - ), - // Sampler0 size - glm::vec2(texture_details.width, texture_details.height) - }; - uniform_sets_[1] = rhi.create_uniform_set(ctx, {g2_uniforms}); - - std::array vbs = {{{0, quad_vbo_}}}; - std::array tbs = {{{rhi::SamplerName::kSampler0, texture_}}}; - binding_set_ = rhi.create_binding_set(ctx, pipeline_, {vbs, tbs}); - break; - } default: - { - std::array g2_uniforms = { - // ModelView - glm::scale( - glm::identity(), - glm::vec3(taller ? 2.f : 2.f * aspect, taller ? 2.f * (1.f / aspect) : 2.f, 1.f) - ), - // Texcoord0 Transform - glm::mat3( - glm::vec3(1.f, 0.f, 0.f), - glm::vec3(0.f, output_flip_ ? -1.f : 1.f, 0.f), - glm::vec3(0.f, output_flip_ ? 1.f : 0.f, 1.f) - ) - }; - uniform_sets_[1] = rhi.create_uniform_set(ctx, {g2_uniforms}); - - std::array vbs = {{{0, quad_vbo_}}}; - std::array tbs = {{{rhi::SamplerName::kSampler0, texture_}}}; - binding_set_ = rhi.create_binding_set(ctx, pipeline_, {vbs, tbs}); + rhi.set_uniform("u_sampler0_size", sampler0_size); break; } + rhi.set_sampler("s_sampler0", 0, texture_); + switch (blit_mode_) + { + case BlitRectPass::BlitMode::kCrt: + rhi.set_sampler("s_sampler1", 1, dot_pattern_); + break; + case BlitRectPass::BlitMode::kCrtSharp: + rhi.set_sampler("s_sampler1", 1, dot_pattern_); + break; + default: + break; } -} - -void BlitRectPass::graphics(Rhi& rhi, Handle ctx) -{ - rhi.bind_pipeline(ctx, pipeline_); - rhi.set_viewport(ctx, output_position_); - rhi.bind_uniform_set(ctx, 0, uniform_sets_[0]); - rhi.bind_uniform_set(ctx, 1, uniform_sets_[1]); - rhi.bind_binding_set(ctx, binding_set_); - rhi.bind_index_buffer(ctx, quad_ibo_); - rhi.draw_indexed(ctx, 6, 0); + rhi.set_viewport(output_position_); + rhi.bind_index_buffer(quad_ibo_); + rhi.draw_indexed(6, 0); } diff --git a/src/hwr2/blit_rect.hpp b/src/hwr2/blit_rect.hpp index d93841fe9..3d1c32d5f 100644 --- a/src/hwr2/blit_rect.hpp +++ b/src/hwr2/blit_rect.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -32,7 +32,7 @@ public: }; private: - rhi::Handle pipeline_; + rhi::Handle program_; rhi::Handle texture_; uint32_t texture_width_ = 0; uint32_t texture_height_ = 0; @@ -42,8 +42,6 @@ private: bool output_flip_ = false; rhi::Handle quad_vbo_; rhi::Handle quad_ibo_; - std::array, 2> uniform_sets_; - rhi::Handle binding_set_; BlitMode blit_mode_; rhi::Handle dot_pattern_; @@ -52,8 +50,8 @@ private: bool dot_pattern_needs_upload_ = false; void prepass(rhi::Rhi& rhi); - void transfer(rhi::Rhi& rhi, rhi::Handle ctx); - void graphics(rhi::Rhi& rhi, rhi::Handle ctx); + void transfer(rhi::Rhi& rhi); + void graphics(rhi::Rhi& rhi); public: @@ -61,7 +59,7 @@ public: BlitRectPass(); ~BlitRectPass(); - void draw(rhi::Rhi& rhi, rhi::Handle ctx); + void draw(rhi::Rhi& rhi); /// @brief Set the next blit texture. Don't call during graphics phase! /// @param texture the texture to use when blitting diff --git a/src/hwr2/hardware_state.hpp b/src/hwr2/hardware_state.hpp index e78f6daf2..607e58a2d 100644 --- a/src/hwr2/hardware_state.hpp +++ b/src/hwr2/hardware_state.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -13,6 +13,7 @@ #include "blit_postimg_screens.hpp" #include "blit_rect.hpp" +#include "imgui_renderer.hpp" #include "postprocess_wipe.hpp" #include "resource_management.hpp" #include "screen_capture.hpp" @@ -44,6 +45,7 @@ struct HardwareState std::unique_ptr crtsharp_blit_rect; std::unique_ptr screen_capture; std::unique_ptr backbuffer; + std::unique_ptr imgui_renderer; WipeFrames wipe_frames; }; diff --git a/src/hwr2/pass_imgui.cpp b/src/hwr2/imgui_renderer.cpp similarity index 56% rename from src/hwr2/pass_imgui.cpp rename to src/hwr2/imgui_renderer.cpp index b11e724cf..934dffc02 100644 --- a/src/hwr2/pass_imgui.cpp +++ b/src/hwr2/imgui_renderer.cpp @@ -1,16 +1,17 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -#include "pass_imgui.hpp" +#include "imgui_renderer.hpp" #include +#include #include "../v_video.h" @@ -18,39 +19,25 @@ using namespace srb2; using namespace srb2::hwr2; using namespace srb2::rhi; -static const PipelineDesc kPipelineDesc = { - PipelineProgram::kUnshaded, - {{{sizeof(ImDrawVert)}}, - {{VertexAttributeName::kPosition, 0, 0}, - {VertexAttributeName::kTexCoord0, 0, 12}, - {VertexAttributeName::kColor, 0, 24}}}, - {{{UniformName::kProjection}, {{UniformName::kModelView, UniformName::kTexCoord0Transform}}}}, - {{SamplerName::kSampler0}}, - PipelineDepthStencilStateDesc {true, true, CompareFunc::kAlways, false, {}, {}}, - {BlendDesc { - BlendFactor::kSourceAlpha, - BlendFactor::kOneMinusSourceAlpha, - BlendFunction::kAdd, - BlendFactor::kOne, - BlendFactor::kOneMinusSourceAlpha, - BlendFunction::kAdd}, - {true, true, true, true}}, - PrimitiveType::kTriangles, - CullMode::kNone, - FaceWinding::kCounterClockwise, - {0.f, 0.f, 0.f, 1.f}}; - -ImguiPass::ImguiPass() : Pass() +ImguiRenderer::ImguiRenderer() { } -ImguiPass::~ImguiPass() = default; +ImguiRenderer::~ImguiRenderer() = default; -void ImguiPass::prepass(Rhi& rhi) +void ImguiRenderer::render(Rhi& rhi) { - if (!pipeline_) + + if (!program_) { - pipeline_ = rhi.create_pipeline(kPipelineDesc); + const char* defines[2] = { + "ENABLE_VA_TEXCOORD0", + "ENABLE_VA_COLOR" + }; + ProgramDesc desc; + desc.name = "unshaded"; + desc.defines = tcb::make_span(defines); + program_ = rhi.create_program(desc); } ImGuiIO& io = ImGui::GetIO(); @@ -75,6 +62,19 @@ void ImguiPass::prepass(Rhi& rhi) io.Fonts->SetTexID(font_atlas_); } + if (!default_tex_) + { + uint32_t pixel = 0xFFFFFFFF; + default_tex_ = rhi.create_texture({ + TextureFormat::kRGBA, + 1, + 1, + TextureWrapMode::kRepeat, + TextureWrapMode::kRepeat + }); + rhi.update_texture(default_tex_, {0, 0, 1, 1}, rhi::PixelFormat::kRGBA8, tcb::as_bytes(tcb::span(&pixel, 1))); + } + ImGui::Render(); ImDrawData* data = ImGui::GetDrawData(); @@ -113,7 +113,14 @@ void ImguiPass::prepass(Rhi& rhi) DrawCmd draw_cmd; ImTextureID tex_id = cmd.GetTexID(); - draw_cmd.tex = tex_id; + if (tex_id == 0) + { + draw_cmd.tex = default_tex_; + } + else + { + draw_cmd.tex = tex_id; + } draw_cmd.v_offset = cmd.VtxOffset; draw_cmd.i_offset = cmd.IdxOffset; draw_cmd.elems = cmd.ElemCount; @@ -126,18 +133,12 @@ void ImguiPass::prepass(Rhi& rhi) } draw_lists_.push_back(std::move(hwr2_list)); } -} - -void ImguiPass::transfer(Rhi& rhi, Handle ctx) -{ - ImGuiIO& io = ImGui::GetIO(); { unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); rhi.update_texture( - ctx, font_atlas_, {0, 0, static_cast(width), static_cast(height)}, rhi::PixelFormat::kRGBA8, @@ -162,74 +163,70 @@ void ImguiPass::transfer(Rhi& rhi, Handle ctx) } tcb::span vert_span = tcb::span(im_list->VtxBuffer.Data, im_list->VtxBuffer.size()); - rhi.update_buffer(ctx, vbo, 0, tcb::as_bytes(vert_span)); + rhi.update_buffer(vbo, 0, tcb::as_bytes(vert_span)); tcb::span index_span = tcb::span(im_list->IdxBuffer.Data, im_list->IdxBuffer.size()); - rhi.update_buffer(ctx, ibo, 0, tcb::as_bytes(index_span)); - - // Uniform sets - std::array g1_uniforms = { - // Projection - glm::mat4( - glm::vec4(2.f / vid.realwidth, 0.f, 0.f, 0.f), - glm::vec4(0.f, 2.f / vid.realheight, 0.f, 0.f), - glm::vec4(0.f, 0.f, 1.f, 0.f), - glm::vec4(-1.f, 1.f, 0.f, 1.f) - ) - }; - std::array g2_uniforms = { - // ModelView - glm::mat4( - glm::vec4(1.f, 0.f, 0.f, 0.f), - glm::vec4(0.f, -1.f, 0.f, 0.f), - glm::vec4(0.f, 0.f, 1.f, 0.f), - glm::vec4(0.f, 0, 0.f, 1.f) - ), - // Texcoord0 Transform - glm::mat3( - glm::vec3(1.f, 0.f, 0.f), - glm::vec3(0.f, 1.f, 0.f), - glm::vec3(0.f, 0.f, 1.f) - ) - }; - Handle us_1 = rhi.create_uniform_set(ctx, {g1_uniforms}); - Handle us_2 = rhi.create_uniform_set(ctx, {g2_uniforms}); - - draw_list.us_1 = us_1; - draw_list.us_2 = us_2; + rhi.update_buffer(ibo, 0, tcb::as_bytes(index_span)); for (auto& draw_cmd : draw_list.cmds) { - // Binding set - std::array vbos = {{{0, vbo}}}; - std::array tbs = {{{rhi::SamplerName::kSampler0, draw_cmd.tex}}}; - rhi::Handle binding_set = rhi.create_binding_set(ctx, pipeline_, {vbos, tbs}); - draw_cmd.binding_set = binding_set; + draw_cmd.vbo = vbo; + draw_cmd.ibo = ibo; + draw_cmd.tex = draw_cmd.tex; } } -} -void ImguiPass::graphics(Rhi& rhi, Handle ctx) -{ - rhi.begin_default_render_pass(ctx, false); - rhi.bind_pipeline(ctx, pipeline_); + rhi.bind_program(program_); + RasterizerStateDesc rs; + + rs.cull = CullMode::kNone; + rs.winding = FaceWinding::kCounterClockwise; + rs.depth_test = true; + rs.depth_func = CompareFunc::kAlways; + rs.blend_enabled = true; + rs.blend_source_factor_color = BlendFactor::kSourceAlpha; + rs.blend_dest_factor_color = BlendFactor::kOneMinusSourceAlpha; + rs.blend_color_function = BlendFunction::kAdd; + rs.blend_source_factor_alpha = BlendFactor::kOne; + rs.blend_dest_factor_alpha = BlendFactor::kOneMinusSourceAlpha; + rs.blend_alpha_function = BlendFunction::kAdd; + for (auto& draw_list : draw_lists_) { - rhi.bind_uniform_set(ctx, 0, draw_list.us_1); - rhi.bind_uniform_set(ctx, 1, draw_list.us_2); + glm::mat4 projection = glm::mat4( + glm::vec4(2.f / vid.realwidth, 0.f, 0.f, 0.f), + glm::vec4(0.f, 2.f / vid.realheight, 0.f, 0.f), + glm::vec4(0.f, 0.f, 1.f, 0.f), + glm::vec4(-1.f, 1.f, 0.f, 1.f) + ); + glm::mat4 modelview = glm::mat4( + glm::vec4(1.f, 0.f, 0.f, 0.f), + glm::vec4(0.f, -1.f, 0.f, 0.f), + glm::vec4(0.f, 0.f, 1.f, 0.f), + glm::vec4(0.f, 0, 0.f, 1.f) + ); + glm::mat3 texcoord0_transform = glm::mat3( + glm::vec3(1.f, 0.f, 0.f), + glm::vec3(0.f, 1.f, 0.f), + glm::vec3(0.f, 0.f, 1.f) + ); + rhi.set_uniform("u_projection", projection); + rhi.set_uniform("u_modelview", modelview); + rhi.set_uniform("u_texcoord0_transform", texcoord0_transform); for (auto& cmd : draw_list.cmds) { - rhi.bind_binding_set(ctx, cmd.binding_set); - rhi.bind_index_buffer(ctx, draw_list.ibo); - rhi.set_scissor(ctx, cmd.clip); - rhi.draw_indexed(ctx, cmd.elems, cmd.i_offset); + rs.scissor_test = true; + rs.scissor = cmd.clip; + rhi.set_rasterizer_state(rs); + rhi.bind_vertex_attrib("a_position", cmd.vbo, VertexAttributeFormat::kFloat3, offsetof(ImDrawVert, pos), sizeof(ImDrawVert)); + rhi.bind_vertex_attrib("a_texcoord0", cmd.vbo, VertexAttributeFormat::kFloat2, offsetof(ImDrawVert, uv), sizeof(ImDrawVert)); + rhi.bind_vertex_attrib("a_color", cmd.vbo, VertexAttributeFormat::kFloat4, offsetof(ImDrawVert, colf), sizeof(ImDrawVert)); + rhi.set_sampler("s_sampler0", 0, cmd.tex); + rhi.bind_index_buffer(draw_list.ibo); + rhi.draw_indexed(cmd.elems, cmd.i_offset); } } - rhi.end_render_pass(ctx); -} -void ImguiPass::postpass(Rhi& rhi) -{ for (auto& list : draw_lists_) { rhi.destroy_buffer(list.vbo); diff --git a/src/hwr2/pass_imgui.hpp b/src/hwr2/imgui_renderer.hpp similarity index 60% rename from src/hwr2/pass_imgui.hpp rename to src/hwr2/imgui_renderer.hpp index d0fa44db5..9d985293d 100644 --- a/src/hwr2/pass_imgui.hpp +++ b/src/hwr2/imgui_renderer.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,48 +14,41 @@ #include #include "../rhi/rhi.hpp" -#include "pass.hpp" namespace srb2::hwr2 { -class ImguiPass final : public Pass +class ImguiRenderer final { struct DrawCmd { - rhi::Handle tex; uint32_t v_offset; uint32_t elems; uint32_t i_offset; rhi::Rect clip; - rhi::Handle binding_set; + rhi::Handle vbo; + rhi::Handle ibo; + rhi::Handle tex; }; struct DrawList { void* list; rhi::Handle vbo; rhi::Handle ibo; - rhi::Handle us_1; - rhi::Handle us_2; std::vector cmds; }; - rhi::Handle pipeline_; + rhi::Handle program_; rhi::Handle font_atlas_; + rhi::Handle default_tex_; std::vector draw_lists_; public: - ImguiPass(); - virtual ~ImguiPass(); + ImguiRenderer(); + virtual ~ImguiRenderer(); - virtual void prepass(rhi::Rhi& rhi) override; - - virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; - - virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; - - virtual void postpass(rhi::Rhi& rhi) override; + void render(rhi::Rhi& rhi); }; } // namespace srb2::hwr2 diff --git a/src/hwr2/pass.cpp b/src/hwr2/pass.cpp deleted file mode 100644 index 25c88ca52..000000000 --- a/src/hwr2/pass.cpp +++ /dev/null @@ -1,16 +0,0 @@ -// DR. ROBOTNIK'S RING RACERS -//----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew -// -// This program is free software distributed under the -// terms of the GNU General Public License, version 2. -// See the 'LICENSE' file for more details. -//----------------------------------------------------------------------------- - -#include "pass.hpp" - -using namespace srb2; -using namespace srb2::hwr2; - -Pass::~Pass() = default; diff --git a/src/hwr2/pass.hpp b/src/hwr2/pass.hpp deleted file mode 100644 index 716049de3..000000000 --- a/src/hwr2/pass.hpp +++ /dev/null @@ -1,47 +0,0 @@ -// DR. ROBOTNIK'S RING RACERS -//----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew -// -// This program is free software distributed under the -// terms of the GNU General Public License, version 2. -// See the 'LICENSE' file for more details. -//----------------------------------------------------------------------------- - -#ifndef __SRB2_HWR2_PASS_HPP__ -#define __SRB2_HWR2_PASS_HPP__ - -#include "../rhi/rhi.hpp" - -namespace srb2::hwr2 -{ - -/// @brief A rendering pass which performs logic during each phase of a frame render. -/// During rendering, all registered Pass's individual stages will be run together. -class Pass -{ -public: - virtual ~Pass(); - - /// @brief Perform rendering logic and create necessary GPU resources. - /// @param rhi - virtual void prepass(rhi::Rhi& rhi) = 0; - - /// @brief Upload contents for needed GPU resources. Passes must implement but this will be removed soon. - /// @param rhi - /// @param ctx - virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) = 0; - - /// @brief Issue draw calls. - /// @param rhi - /// @param ctx - virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) = 0; - - /// @brief Cleanup GPU resources. Transient resources should be cleaned up here. - /// @param rhi - virtual void postpass(rhi::Rhi& rhi) = 0; -}; - -} // namespace srb2::hwr2 - -#endif // __SRB2_HWR2_PASS_HPP__ diff --git a/src/hwr2/pass_manager.cpp b/src/hwr2/pass_manager.cpp deleted file mode 100644 index d846e3770..000000000 --- a/src/hwr2/pass_manager.cpp +++ /dev/null @@ -1,189 +0,0 @@ -// DR. ROBOTNIK'S RING RACERS -//----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew -// -// This program is free software distributed under the -// terms of the GNU General Public License, version 2. -// See the 'LICENSE' file for more details. -//----------------------------------------------------------------------------- - -#include "pass_manager.hpp" - -using namespace srb2; -using namespace srb2::hwr2; -using namespace srb2::rhi; - -namespace -{ - -class LambdaPass final : public Pass -{ - PassManager* mgr_; - std::function prepass_func_; - std::function postpass_func_; - -public: - LambdaPass(PassManager* mgr, std::function prepass_func); - LambdaPass( - PassManager* mgr, - std::function prepass_func, - std::function postpass_func - ); - virtual ~LambdaPass(); - - virtual void prepass(rhi::Rhi& rhi) override; - virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void postpass(rhi::Rhi& rhi) override; -}; - -} // namespace - -LambdaPass::LambdaPass(PassManager* mgr, std::function prepass_func) - : mgr_(mgr), prepass_func_(prepass_func) -{ -} - -LambdaPass::LambdaPass( - PassManager* mgr, - std::function prepass_func, - std::function postpass_func -) - : mgr_(mgr), prepass_func_(prepass_func), postpass_func_(postpass_func) -{ -} - -LambdaPass::~LambdaPass() = default; - -void LambdaPass::prepass(Rhi& rhi) -{ - if (prepass_func_) - { - (prepass_func_)(*mgr_, rhi); - } -} - -void LambdaPass::transfer(Rhi&, Handle) -{ -} - -void LambdaPass::graphics(Rhi&, Handle) -{ -} - -void LambdaPass::postpass(Rhi& rhi) -{ - if (postpass_func_) - { - (postpass_func_)(*mgr_, rhi); - } -} - -PassManager::PassManager() = default; -PassManager::PassManager(const PassManager&) = default; -PassManager& PassManager::operator=(const PassManager&) = default; - -void PassManager::insert(const std::string& name, std::shared_ptr pass) -{ - SRB2_ASSERT(pass_by_name_.find(name) == pass_by_name_.end()); - - std::size_t index = passes_.size(); - passes_.push_back(PassManagerEntry {name, pass, true}); - pass_by_name_.insert({name, index}); -} - -void PassManager::insert(const std::string& name, std::function prepass_func) -{ - insert(std::forward(name), std::make_shared(LambdaPass {this, prepass_func})); -} - -void PassManager::insert( - const std::string& name, - std::function prepass_func, - std::function postpass_func -) -{ - insert( - std::forward(name), - std::make_shared(LambdaPass {this, prepass_func, postpass_func}) - ); -} - -void PassManager::set_pass_enabled(const std::string& name, bool enabled) -{ - SRB2_ASSERT(pass_by_name_.find(name) != pass_by_name_.end()); - - passes_[pass_by_name_[name]].enabled = enabled; -} - -std::weak_ptr PassManager::for_name(const std::string& name) -{ - auto itr = pass_by_name_.find(name); - if (itr == pass_by_name_.end()) - { - return std::weak_ptr(); - } - return passes_[itr->second].pass; -} - -void PassManager::prepass(Rhi& rhi) -{ - for (auto& pass : passes_) - { - if (pass.enabled) - { - pass.pass->prepass(rhi); - } - } -} - -void PassManager::transfer(Rhi& rhi, Handle ctx) -{ - for (auto& pass : passes_) - { - if (pass.enabled) - { - pass.pass->transfer(rhi, ctx); - } - } -} - -void PassManager::graphics(Rhi& rhi, Handle ctx) -{ - for (auto& pass : passes_) - { - if (pass.enabled) - { - pass.pass->graphics(rhi, ctx); - } - } -} - -void PassManager::postpass(Rhi& rhi) -{ - for (auto& pass : passes_) - { - if (pass.enabled) - { - pass.pass->postpass(rhi); - } - } -} - -void PassManager::render(Rhi& rhi) -{ - if (passes_.empty()) - { - return; - } - - prepass(rhi); - - Handle gc = rhi.begin_graphics(); - transfer(rhi, gc); - graphics(rhi, gc); - rhi.end_graphics(gc); - - postpass(rhi); -} diff --git a/src/hwr2/pass_manager.hpp b/src/hwr2/pass_manager.hpp deleted file mode 100644 index 12c40187b..000000000 --- a/src/hwr2/pass_manager.hpp +++ /dev/null @@ -1,66 +0,0 @@ -// DR. ROBOTNIK'S RING RACERS -//----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew -// -// This program is free software distributed under the -// terms of the GNU General Public License, version 2. -// See the 'LICENSE' file for more details. -//----------------------------------------------------------------------------- - -#ifndef __SRB2_HWR2_PASS_MANAGER_HPP__ -#define __SRB2_HWR2_PASS_MANAGER_HPP__ - -#include -#include -#include -#include -#include -#include - -#include "../rhi/rhi.hpp" -#include "pass.hpp" - -namespace srb2::hwr2 -{ - -class PassManager final : public Pass -{ - struct PassManagerEntry - { - std::string name; - std::shared_ptr pass; - bool enabled; - }; - - std::unordered_map pass_by_name_; - std::vector passes_; - -public: - PassManager(); - PassManager(const PassManager&); - PassManager(PassManager&&) = delete; - PassManager& operator=(const PassManager&); - PassManager& operator=(PassManager&&) = delete; - - virtual void prepass(rhi::Rhi& rhi) override; - virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void postpass(rhi::Rhi& rhi) override; - - void insert(const std::string& name, std::shared_ptr pass); - void insert(const std::string& name, std::function prepass_func); - void insert( - const std::string& name, - std::function prepass_func, - std::function postpass_func - ); - std::weak_ptr for_name(const std::string& name); - void set_pass_enabled(const std::string& name, bool enabled); - - void render(rhi::Rhi& rhi); -}; - -} // namespace srb2::hwr2 - -#endif // __SRB2_HWR2_PASS_MANAGER_HPP__ diff --git a/src/hwr2/pass_resource_managers.cpp b/src/hwr2/pass_resource_managers.cpp index d7ace5d9b..c239db01f 100644 --- a/src/hwr2/pass_resource_managers.cpp +++ b/src/hwr2/pass_resource_managers.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -21,7 +21,7 @@ using namespace srb2; using namespace srb2::hwr2; using namespace srb2::rhi; -FramebufferManager::FramebufferManager() : Pass() +FramebufferManager::FramebufferManager() { } @@ -120,11 +120,11 @@ void FramebufferManager::prepass(Rhi& rhi) } } -void FramebufferManager::transfer(Rhi& rhi, Handle ctx) +void FramebufferManager::transfer(Rhi& rhi) { } -void FramebufferManager::graphics(Rhi& rhi, Handle ctx) +void FramebufferManager::graphics(Rhi& rhi) { } @@ -161,28 +161,28 @@ void MainPaletteManager::prepass(Rhi& rhi) } } -void MainPaletteManager::upload_palette(Rhi& rhi, Handle ctx) +void MainPaletteManager::upload_palette(Rhi& rhi) { std::array palette_32; for (std::size_t i = 0; i < kPaletteSize; i++) { palette_32[i] = V_GetColor(i).s; } - rhi.update_texture(ctx, palette_, {0, 0, kPaletteSize, 1}, PixelFormat::kRGBA8, tcb::as_bytes(tcb::span(palette_32))); + rhi.update_texture(palette_, {0, 0, kPaletteSize, 1}, PixelFormat::kRGBA8, tcb::as_bytes(tcb::span(palette_32))); } -void MainPaletteManager::upload_lighttables(Rhi& rhi, Handle ctx) +void MainPaletteManager::upload_lighttables(Rhi& rhi) { if (colormaps != nullptr) { tcb::span colormap_bytes = tcb::as_bytes(tcb::span(colormaps, kPaletteSize * kLighttableRows)); - rhi.update_texture(ctx, lighttable_, {0, 0, kPaletteSize, kLighttableRows}, PixelFormat::kR8, colormap_bytes); + rhi.update_texture(lighttable_, {0, 0, kPaletteSize, kLighttableRows}, PixelFormat::kR8, colormap_bytes); } if (encoremap != nullptr) { tcb::span encoremap_bytes = tcb::as_bytes(tcb::span(encoremap, kPaletteSize * kLighttableRows)); - rhi.update_texture(ctx, encore_lighttable_, {0, 0, kPaletteSize, kLighttableRows}, PixelFormat::kR8, encoremap_bytes); + rhi.update_texture(encore_lighttable_, {0, 0, kPaletteSize, kLighttableRows}, PixelFormat::kR8, encoremap_bytes); } if (!lighttables_to_upload_.empty()) @@ -192,23 +192,23 @@ void MainPaletteManager::upload_lighttables(Rhi& rhi, Handle ct Handle lighttable_tex = find_extra_lighttable(lighttable); SRB2_ASSERT(lighttable_tex != kNullHandle); tcb::span lighttable_bytes = tcb::as_bytes(tcb::span(lighttable, kPaletteSize * kLighttableRows)); - rhi.update_texture(ctx, lighttable_tex, {0, 0, kPaletteSize, kLighttableRows}, PixelFormat::kR8, lighttable_bytes); + rhi.update_texture(lighttable_tex, {0, 0, kPaletteSize, kLighttableRows}, PixelFormat::kR8, lighttable_bytes); } lighttables_to_upload_.clear(); } } -void MainPaletteManager::upload_default_colormap(Rhi& rhi, Handle ctx) +void MainPaletteManager::upload_default_colormap(Rhi& rhi) { std::array data; for (std::size_t i = 0; i < kPaletteSize; i++) { data[i] = i; } - rhi.update_texture(ctx, default_colormap_, {0, 0, kPaletteSize, 1}, PixelFormat::kR8, tcb::as_bytes(tcb::span(data))); + rhi.update_texture(default_colormap_, {0, 0, kPaletteSize, 1}, PixelFormat::kR8, tcb::as_bytes(tcb::span(data))); } -void MainPaletteManager::upload_colormaps(Rhi& rhi, Handle ctx) +void MainPaletteManager::upload_colormaps(Rhi& rhi) { for (auto to_upload : colormaps_to_upload_) { @@ -218,7 +218,7 @@ void MainPaletteManager::upload_colormaps(Rhi& rhi, Handle ctx) rhi::Handle map_texture = colormaps_.at(to_upload); tcb::span map_bytes = tcb::as_bytes(tcb::span(to_upload, kPaletteSize)); - rhi.update_texture(ctx, map_texture, {0, 0, kPaletteSize, 1}, PixelFormat::kR8, map_bytes); + rhi.update_texture(map_texture, {0, 0, kPaletteSize, 1}, PixelFormat::kR8, map_bytes); } colormaps_to_upload_.clear(); } @@ -271,15 +271,15 @@ rhi::Handle MainPaletteManager::find_extra_lighttable(srb2::NotNul return lighttables_.at(lighttable); } -void MainPaletteManager::transfer(Rhi& rhi, Handle ctx) +void MainPaletteManager::transfer(Rhi& rhi) { - upload_palette(rhi, ctx); - upload_lighttables(rhi, ctx); - upload_default_colormap(rhi, ctx); - upload_colormaps(rhi, ctx); + upload_palette(rhi); + upload_lighttables(rhi); + upload_default_colormap(rhi); + upload_colormaps(rhi); } -void MainPaletteManager::graphics(Rhi& rhi, Handle ctx) +void MainPaletteManager::graphics(Rhi& rhi) { } @@ -319,7 +319,7 @@ void CommonResourcesManager::prepass(Rhi& rhi) } } -void CommonResourcesManager::transfer(Rhi& rhi, Handle ctx) +void CommonResourcesManager::transfer(Rhi& rhi) { if (!init_) { @@ -330,13 +330,13 @@ void CommonResourcesManager::transfer(Rhi& rhi, Handle ctx) uint8_t transparent[4] = {0, 0, 0, 0}; tcb::span transparent_bytes = tcb::as_bytes(tcb::span(transparent, 4)); - rhi.update_texture(ctx, black_, {0, 0, 1, 1}, PixelFormat::kRGBA8, black_bytes); - rhi.update_texture(ctx, white_, {0, 0, 1, 1}, PixelFormat::kRGBA8, white_bytes); - rhi.update_texture(ctx, transparent_, {0, 0, 1, 1}, PixelFormat::kRGBA8, transparent_bytes); + rhi.update_texture(black_, {0, 0, 1, 1}, PixelFormat::kRGBA8, black_bytes); + rhi.update_texture(white_, {0, 0, 1, 1}, PixelFormat::kRGBA8, white_bytes); + rhi.update_texture(transparent_, {0, 0, 1, 1}, PixelFormat::kRGBA8, transparent_bytes); } } -void CommonResourcesManager::graphics(Rhi& rhi, Handle ctx) +void CommonResourcesManager::graphics(Rhi& rhi) { } diff --git a/src/hwr2/pass_resource_managers.hpp b/src/hwr2/pass_resource_managers.hpp index 30e93279e..9636480dd 100644 --- a/src/hwr2/pass_resource_managers.hpp +++ b/src/hwr2/pass_resource_managers.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -13,15 +13,15 @@ #include #include -#include -#include -#include "pass.hpp" +#include "../core/hash_map.hpp" +#include "../core/vector.hpp" +#include "../rhi/rhi.hpp" namespace srb2::hwr2 { -class FramebufferManager final : public Pass +class FramebufferManager final { rhi::Handle main_color_; std::array, 2> post_colors_; @@ -36,10 +36,10 @@ public: FramebufferManager(); virtual ~FramebufferManager(); - virtual void prepass(rhi::Rhi& rhi) override; - virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void postpass(rhi::Rhi& rhi) override; + void prepass(rhi::Rhi& rhi); + void transfer(rhi::Rhi& rhi); + void graphics(rhi::Rhi& rhi); + void postpass(rhi::Rhi& rhi); /// @brief Swap the current and previous postprocess FB textures. Use between pass prepass phases to alternate. void swap_post() noexcept @@ -70,31 +70,31 @@ public: std::size_t height() const noexcept { return height_; } }; -class MainPaletteManager final : public Pass +class MainPaletteManager final { rhi::Handle palette_; rhi::Handle lighttable_; rhi::Handle encore_lighttable_; rhi::Handle default_colormap_; - std::unordered_map> colormaps_; - std::unordered_map> lighttables_; - std::vector colormaps_to_upload_; - std::vector lighttables_to_upload_; + srb2::HashMap> colormaps_; + srb2::HashMap> lighttables_; + srb2::Vector colormaps_to_upload_; + srb2::Vector lighttables_to_upload_; - void upload_palette(rhi::Rhi& rhi, rhi::Handle ctx); - void upload_lighttables(rhi::Rhi& rhi, rhi::Handle ctx); - void upload_default_colormap(rhi::Rhi& rhi, rhi::Handle ctx); - void upload_colormaps(rhi::Rhi& rhi, rhi::Handle ctx); + void upload_palette(rhi::Rhi& rhi); + void upload_lighttables(rhi::Rhi& rhi); + void upload_default_colormap(rhi::Rhi& rhi); + void upload_colormaps(rhi::Rhi& rhi); public: MainPaletteManager(); virtual ~MainPaletteManager(); - virtual void prepass(rhi::Rhi& rhi) override; - virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void postpass(rhi::Rhi& rhi) override; + void prepass(rhi::Rhi& rhi); + void transfer(rhi::Rhi& rhi); + void graphics(rhi::Rhi& rhi); + void postpass(rhi::Rhi& rhi); rhi::Handle palette() const noexcept { return palette_; } rhi::Handle lighttable() const noexcept { return lighttable_; } @@ -107,7 +107,7 @@ public: rhi::Handle find_extra_lighttable(srb2::NotNull lighttable) const; }; -class CommonResourcesManager final : public Pass +class CommonResourcesManager final { bool init_ = false; rhi::Handle black_; @@ -118,10 +118,10 @@ public: CommonResourcesManager(); virtual ~CommonResourcesManager(); - virtual void prepass(rhi::Rhi& rhi) override; - virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void postpass(rhi::Rhi& rhi) override; + void prepass(rhi::Rhi& rhi); + void transfer(rhi::Rhi& rhi); + void graphics(rhi::Rhi& rhi); + void postpass(rhi::Rhi& rhi); rhi::Handle black() const noexcept { return black_; } rhi::Handle white() const noexcept { return white_; } diff --git a/src/hwr2/patch_atlas.cpp b/src/hwr2/patch_atlas.cpp index b3b1d218d..5dad04e5f 100644 --- a/src/hwr2/patch_atlas.cpp +++ b/src/hwr2/patch_atlas.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -72,7 +72,7 @@ rhi::Rect srb2::hwr2::trimmed_patch_dimensions(const patch_t* patch) return {minx, miny, static_cast(maxx - minx), static_cast(maxy - miny)}; } -void srb2::hwr2::convert_patch_to_trimmed_rg8_pixels(const patch_t* patch, std::vector& out) +void srb2::hwr2::convert_patch_to_trimmed_rg8_pixels(const patch_t* patch, srb2::Vector& out) { Rect trimmed_rect = srb2::hwr2::trimmed_patch_dimensions(patch); if (trimmed_rect.w % 2 > 0) @@ -216,7 +216,7 @@ static PatchAtlas create_atlas(Rhi& rhi, uint32_t size) return new_atlas; } -void PatchAtlasCache::pack(Rhi& rhi, Handle ctx) +void PatchAtlasCache::pack(Rhi& rhi) { // Prepare stbrp rects for patches to be loaded. std::vector rects; @@ -299,7 +299,7 @@ void PatchAtlasCache::pack(Rhi& rhi, Handle ctx) SRB2_ASSERT(ready_for_lookup()); // Upload atlased patches - std::vector patch_data; + srb2::Vector patch_data; for (const patch_t* patch_to_upload : patches_to_upload_) { srb2::NotNull atlas = find_patch(patch_to_upload); @@ -310,7 +310,6 @@ void PatchAtlasCache::pack(Rhi& rhi, Handle ctx) convert_patch_to_trimmed_rg8_pixels(patch_to_upload, patch_data); rhi.update_texture( - ctx, atlas->tex_, {static_cast(entry->x), static_cast(entry->y), entry->w, entry->h}, PixelFormat::kRG8, diff --git a/src/hwr2/patch_atlas.hpp b/src/hwr2/patch_atlas.hpp index f66bf7c36..46e39740d 100644 --- a/src/hwr2/patch_atlas.hpp +++ b/src/hwr2/patch_atlas.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,14 +14,14 @@ #include #include #include -#include -#include -#include #include -#include "pass.hpp" +#include "../core/hash_map.hpp" +#include "../core/hash_set.hpp" +#include "../core/vector.hpp" #include "../r_defs.h" +#include "../rhi/rhi.hpp" extern "C" { @@ -55,7 +55,7 @@ private: rhi::Handle tex_; uint32_t size_; - std::unordered_map entries_; + srb2::HashMap entries_; std::unique_ptr rp_ctx {nullptr}; std::unique_ptr rp_nodes {nullptr}; @@ -84,11 +84,11 @@ public: /// drawing things like sprites and 2D elements. class PatchAtlasCache { - std::vector atlases_; - std::unordered_map patch_lookup_; + srb2::Vector atlases_; + srb2::HashMap patch_lookup_; - std::unordered_set patches_to_pack_; - std::unordered_set patches_to_upload_; + srb2::HashSet patches_to_pack_; + srb2::HashSet patches_to_upload_; uint32_t tex_size_ = 2048; size_t max_textures_ = 2; @@ -113,7 +113,7 @@ public: void queue_patch(srb2::NotNull patch); /// @brief Pack queued patches, allowing them to be looked up with find_patch. - void pack(rhi::Rhi& rhi, rhi::Handle ctx); + void pack(rhi::Rhi& rhi); /// @brief Find the atlas a patch belongs to, or nullopt if it is not cached. /// This may not be called if there are still patches that need to be packed. @@ -135,7 +135,7 @@ rhi::Rect trimmed_patch_dimensions(const patch_t* patch); /// during upload, but required for the RHI device's Unpack Alignment of 4 bytes. /// @param patch the patch to convert /// @param out the output vector, cleared before writing. -void convert_patch_to_trimmed_rg8_pixels(const patch_t* patch, std::vector& out); +void convert_patch_to_trimmed_rg8_pixels(const patch_t* patch, srb2::Vector& out); } // namespace srb2::hwr2 diff --git a/src/hwr2/postprocess_wipe.cpp b/src/hwr2/postprocess_wipe.cpp index 4b5f3ced7..86364ab23 100644 --- a/src/hwr2/postprocess_wipe.cpp +++ b/src/hwr2/postprocess_wipe.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -10,12 +10,11 @@ #include "postprocess_wipe.hpp" -#include - #include #include #include +#include "../core/string.h" #include "../f_finale.h" #include "../w_wad.h" @@ -41,41 +40,28 @@ static const uint16_t kPostprocessIndices[] = {0, 1, 2, 1, 3, 2}; } // namespace -static const PipelineDesc kWipePipelineDesc = { - PipelineProgram::kPostprocessWipe, - {{{sizeof(PostprocessVertex)}}, - { - {VertexAttributeName::kPosition, 0, 0}, - {VertexAttributeName::kTexCoord0, 0, 12}, - }}, - {{{{UniformName::kProjection, UniformName::kWipeColorizeMode, UniformName::kWipeEncoreSwizzle}}}}, - {{SamplerName::kSampler0, SamplerName::kSampler1, SamplerName::kSampler2}}, - std::nullopt, - {std::nullopt, {true, true, true, true}}, - PrimitiveType::kTriangles, - CullMode::kNone, - FaceWinding::kCounterClockwise, - {0.f, 0.f, 0.f, 1.f}}; - PostprocessWipePass::PostprocessWipePass() { } PostprocessWipePass::~PostprocessWipePass() = default; -void PostprocessWipePass::draw(Rhi& rhi, Handle ctx) +void PostprocessWipePass::draw(Rhi& rhi) { prepass(rhi); - transfer(rhi, ctx); - graphics(rhi, ctx); + transfer(rhi); + graphics(rhi); postpass(rhi); } void PostprocessWipePass::prepass(Rhi& rhi) { - if (!pipeline_) + if (!program_) { - pipeline_ = rhi.create_pipeline(kWipePipelineDesc); + ProgramDesc desc; + desc.name = "postprocesswipe"; + desc.defines = tcb::span(); + program_ = rhi.create_program(desc); } if (!vbo_) @@ -119,7 +105,7 @@ void PostprocessWipePass::prepass(Rhi& rhi) return; } - std::string lumpname = fmt::format(FMT_STRING("FADE{:02d}{:02d}"), wipe_type, wipe_frame); + String lumpname = format(FMT_STRING("FADE{:02d}{:02d}"), wipe_type, wipe_frame); lumpnum_t mask_lump = W_CheckNumForName(lumpname.c_str()); if (mask_lump == LUMPERROR) { @@ -169,7 +155,7 @@ void PostprocessWipePass::prepass(Rhi& rhi) }); } -void PostprocessWipePass::transfer(Rhi& rhi, Handle ctx) +void PostprocessWipePass::transfer(Rhi& rhi) { if (wipe_tex_ == kNullHandle) { @@ -183,47 +169,44 @@ void PostprocessWipePass::transfer(Rhi& rhi, Handle ctx) if (upload_vbo_) { - rhi.update_buffer(ctx, vbo_, 0, tcb::as_bytes(tcb::span(kPostprocessVerts))); + rhi.update_buffer(vbo_, 0, tcb::as_bytes(tcb::span(kPostprocessVerts))); upload_vbo_ = false; } if (upload_ibo_) { - rhi.update_buffer(ctx, ibo_, 0, tcb::as_bytes(tcb::span(kPostprocessIndices))); + rhi.update_buffer(ibo_, 0, tcb::as_bytes(tcb::span(kPostprocessIndices))); upload_ibo_ = false; } tcb::span data = tcb::as_bytes(tcb::span(mask_data_)); - rhi.update_texture(ctx, wipe_tex_, {0, 0, mask_w_, mask_h_}, PixelFormat::kR8, data); - - UniformVariant uniforms[] = { - glm::scale(glm::identity(), glm::vec3(2.f, 2.f, 1.f)), - static_cast(wipe_color_mode_), - static_cast(wipe_swizzle_) - }; - us_ = rhi.create_uniform_set(ctx, {tcb::span(uniforms)}); - - VertexAttributeBufferBinding vbos[] = {{0, vbo_}}; - TextureBinding tx[] = { - {SamplerName::kSampler0, start_}, - {SamplerName::kSampler1, end_}, - {SamplerName::kSampler2, wipe_tex_}}; - bs_ = rhi.create_binding_set(ctx, pipeline_, {vbos, tx}); + rhi.update_texture(wipe_tex_, {0, 0, mask_w_, mask_h_}, PixelFormat::kR8, data); } -void PostprocessWipePass::graphics(Rhi& rhi, Handle ctx) +void PostprocessWipePass::graphics(Rhi& rhi) { if (wipe_tex_ == kNullHandle) { return; } - rhi.bind_pipeline(ctx, pipeline_); - rhi.set_viewport(ctx, {0, 0, width_, height_}); - rhi.bind_uniform_set(ctx, 0, us_); - rhi.bind_binding_set(ctx, bs_); - rhi.bind_index_buffer(ctx, ibo_); - rhi.draw_indexed(ctx, 6, 0); + rhi.bind_program(program_); + + RasterizerStateDesc desc {}; + desc.cull = CullMode::kNone; + + rhi.set_rasterizer_state(desc); + rhi.set_uniform("u_projection", glm::scale(glm::identity(), glm::vec3(2.f, 2.f, 1.f))); + rhi.set_uniform("u_wipe_colorize_mode", static_cast(wipe_color_mode_)); + rhi.set_uniform("u_wipe_encore_swizzle", static_cast(wipe_swizzle_)); + rhi.set_sampler("s_sampler0", 0, start_); + rhi.set_sampler("s_sampler1", 1, end_); + rhi.set_sampler("s_sampler2", 2, wipe_tex_); + rhi.bind_vertex_attrib("a_position", vbo_, VertexAttributeFormat::kFloat3, offsetof(PostprocessVertex, x), sizeof(PostprocessVertex)); + rhi.bind_vertex_attrib("a_texcoord0", vbo_, VertexAttributeFormat::kFloat2, offsetof(PostprocessVertex, u), sizeof(PostprocessVertex)); + rhi.set_viewport({0, 0, width_, height_}); + rhi.bind_index_buffer(ibo_); + rhi.draw_indexed(6, 0); } void PostprocessWipePass::postpass(Rhi& rhi) diff --git a/src/hwr2/postprocess_wipe.hpp b/src/hwr2/postprocess_wipe.hpp index f1d13b4fd..c8beca864 100644 --- a/src/hwr2/postprocess_wipe.hpp +++ b/src/hwr2/postprocess_wipe.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -21,13 +21,11 @@ namespace srb2::hwr2 class PostprocessWipePass final { // Internal RHI resources - rhi::Handle pipeline_; + rhi::Handle program_; rhi::Handle vbo_; bool upload_vbo_ = false; rhi::Handle ibo_; bool upload_ibo_ = false; - rhi::Handle us_; - rhi::Handle bs_; rhi::Handle wipe_tex_; int wipe_color_mode_ = 0; int wipe_swizzle_ = 0; @@ -44,15 +42,15 @@ class PostprocessWipePass final uint32_t mask_h_ = 0; void prepass(rhi::Rhi& rhi); - void transfer(rhi::Rhi& rhi, rhi::Handle ctx); - void graphics(rhi::Rhi& rhi, rhi::Handle ctx); + void transfer(rhi::Rhi& rhi); + void graphics(rhi::Rhi& rhi); void postpass(rhi::Rhi& rhi); public: PostprocessWipePass(); virtual ~PostprocessWipePass(); - void draw(rhi::Rhi& rhi, rhi::Handle ctx); + void draw(rhi::Rhi& rhi); void set_start(rhi::Handle start) noexcept { start_ = start; } diff --git a/src/hwr2/resource_management.cpp b/src/hwr2/resource_management.cpp index e338c66dd..0ac7d7358 100644 --- a/src/hwr2/resource_management.cpp +++ b/src/hwr2/resource_management.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -26,7 +26,7 @@ PaletteManager& PaletteManager::operator=(PaletteManager&&) = default; constexpr std::size_t kPaletteSize = 256; constexpr std::size_t kLighttableRows = LIGHTLEVELS; -void PaletteManager::update(Rhi& rhi, Handle ctx) +void PaletteManager::update(Rhi& rhi) { if (!palette_) { @@ -57,7 +57,7 @@ void PaletteManager::update(Rhi& rhi, Handle ctx) { palette_32[i] = V_GetColor(i).s; } - rhi.update_texture(ctx, palette_, {0, 0, kPaletteSize, 1}, PixelFormat::kRGBA8, tcb::as_bytes(tcb::span(palette_32))); + rhi.update_texture(palette_, {0, 0, kPaletteSize, 1}, PixelFormat::kRGBA8, tcb::as_bytes(tcb::span(palette_32))); } #if 0 @@ -66,7 +66,7 @@ void PaletteManager::update(Rhi& rhi, Handle ctx) if (colormaps != nullptr) { tcb::span colormap_bytes = tcb::as_bytes(tcb::span(colormaps, kPaletteSize * kLighttableRows)); - rhi.update_texture(ctx, lighttable_, {0, 0, kPaletteSize, kLighttableRows}, PixelFormat::kR8, colormap_bytes); + rhi.update_texture(lighttable_, {0, 0, kPaletteSize, kLighttableRows}, PixelFormat::kR8, colormap_bytes); } // FIXME: This is broken, encoremap should not be used directly. @@ -74,7 +74,7 @@ void PaletteManager::update(Rhi& rhi, Handle ctx) if (encoremap != nullptr) { tcb::span encoremap_bytes = tcb::as_bytes(tcb::span(encoremap, kPaletteSize * kLighttableRows)); - rhi.update_texture(ctx, encore_lighttable_, {0, 0, kPaletteSize, kLighttableRows}, PixelFormat::kR8, encoremap_bytes); + rhi.update_texture(encore_lighttable_, {0, 0, kPaletteSize, kLighttableRows}, PixelFormat::kR8, encoremap_bytes); } } #endif @@ -86,7 +86,7 @@ void PaletteManager::update(Rhi& rhi, Handle ctx) { data[i] = i; } - rhi.update_texture(ctx, default_colormap_, {0, 0, kPaletteSize, 1}, PixelFormat::kR8, tcb::as_bytes(tcb::span(data))); + rhi.update_texture(default_colormap_, {0, 0, kPaletteSize, 1}, PixelFormat::kR8, tcb::as_bytes(tcb::span(data))); } } @@ -107,7 +107,7 @@ void PaletteManager::destroy_per_frame_resources(Rhi& rhi) lighttables_.clear(); } -Handle PaletteManager::find_or_create_colormap(Rhi& rhi, rhi::Handle ctx, srb2::NotNull colormap) +Handle PaletteManager::find_or_create_colormap(Rhi& rhi, srb2::NotNull colormap) { if (colormaps_.find(colormap) != colormaps_.end()) { @@ -117,13 +117,13 @@ Handle PaletteManager::find_or_create_colormap(Rhi& rhi, rhi::Handle texture = rhi.create_texture({TextureFormat::kLuminance, kPaletteSize, 1, TextureWrapMode::kClamp, TextureWrapMode::kClamp}); tcb::span map_bytes = tcb::as_bytes(tcb::span(colormap.get(), kPaletteSize)); - rhi.update_texture(ctx, texture, {0, 0, kPaletteSize, 1}, PixelFormat::kR8, map_bytes); + rhi.update_texture(texture, {0, 0, kPaletteSize, 1}, PixelFormat::kR8, map_bytes); colormaps_.insert_or_assign(colormap, texture); return texture; } -Handle PaletteManager::find_or_create_extra_lighttable(Rhi& rhi, rhi::Handle ctx, srb2::NotNull lighttable) +Handle PaletteManager::find_or_create_extra_lighttable(Rhi& rhi, srb2::NotNull lighttable) { if (lighttables_.find(lighttable) != lighttables_.end()) { @@ -133,7 +133,7 @@ Handle PaletteManager::find_or_create_extra_lighttable(Rhi& rhi, rhi::H Handle texture = rhi.create_texture({TextureFormat::kLuminance, kPaletteSize, kLighttableRows, TextureWrapMode::kClamp, TextureWrapMode::kClamp}); tcb::span lighttable_bytes = tcb::as_bytes(tcb::span(lighttable.get(), kPaletteSize * kLighttableRows)); - rhi.update_texture(ctx, texture, {0, 0, kPaletteSize, kLighttableRows}, PixelFormat::kR8, lighttable_bytes); + rhi.update_texture(texture, {0, 0, kPaletteSize, kLighttableRows}, PixelFormat::kR8, lighttable_bytes); lighttables_.insert_or_assign(lighttable, texture); return texture; @@ -161,7 +161,7 @@ static uint32_t get_flat_size(lumpnum_t lump) return lumpsize; } -Handle FlatTextureManager::find_or_create_indexed(Rhi& rhi, Handle ctx, lumpnum_t lump) +Handle FlatTextureManager::find_or_create_indexed(Rhi& rhi, lumpnum_t lump) { SRB2_ASSERT(lump != LUMPERROR); @@ -181,7 +181,7 @@ Handle FlatTextureManager::find_or_create_indexed(Rhi& rhi, Handle> flat_data; + srb2::Vector> flat_data; std::size_t lump_length = W_LumpLength(lump); flat_data.reserve(flat_size * flat_size); @@ -206,7 +206,7 @@ Handle FlatTextureManager::find_or_create_indexed(Rhi& rhi, Handle data_bytes = tcb::as_bytes(tcb::span(flat_data)); - rhi.update_texture(ctx, new_tex, {0, 0, flat_size, flat_size}, rhi::PixelFormat::kRG8, data_bytes); + rhi.update_texture(new_tex, {0, 0, flat_size, flat_size}, rhi::PixelFormat::kRG8, data_bytes); return new_tex; } diff --git a/src/hwr2/resource_management.hpp b/src/hwr2/resource_management.hpp index 965e276c1..3fd6666cf 100644 --- a/src/hwr2/resource_management.hpp +++ b/src/hwr2/resource_management.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -11,10 +11,10 @@ #ifndef __SRB2_HWR2_RESOURCE_MANAGEMENT_HPP__ #define __SRB2_HWR2_RESOURCE_MANAGEMENT_HPP__ +#include "../core/hash_map.hpp" +#include "../core/vector.hpp" #include "../rhi/rhi.hpp" -#include - namespace srb2::hwr2 { @@ -27,8 +27,8 @@ class PaletteManager #endif rhi::Handle default_colormap_; - std::unordered_map> colormaps_; - std::unordered_map> lighttables_; + srb2::HashMap> colormaps_; + srb2::HashMap> lighttables_; public: PaletteManager(); @@ -45,11 +45,11 @@ public: #endif rhi::Handle default_colormap() const noexcept { return default_colormap_; } - void update(rhi::Rhi& rhi, rhi::Handle ctx); + void update(rhi::Rhi& rhi); void destroy_per_frame_resources(rhi::Rhi& rhi); - rhi::Handle find_or_create_colormap(rhi::Rhi& rhi, rhi::Handle ctx, srb2::NotNull colormap); - rhi::Handle find_or_create_extra_lighttable(rhi::Rhi& rhi, rhi::Handle ctx, srb2::NotNull lighttable); + rhi::Handle find_or_create_colormap(rhi::Rhi& rhi, srb2::NotNull colormap); + rhi::Handle find_or_create_extra_lighttable(rhi::Rhi& rhi, srb2::NotNull lighttable); }; /* @@ -67,9 +67,9 @@ from patch_t. /// @brief Manages textures corresponding to specific flats indexed by lump number. class FlatTextureManager { - std::unordered_map> flats_; - std::vector to_upload_; - std::vector> disposed_textures_; + srb2::HashMap> flats_; + srb2::Vector to_upload_; + srb2::Vector> disposed_textures_; public: FlatTextureManager(); @@ -79,7 +79,7 @@ public: /// in prepass. /// @param flat_lump /// @return - rhi::Handle find_or_create_indexed(rhi::Rhi& rhi, rhi::Handle ctx, lumpnum_t flat_lump); + rhi::Handle find_or_create_indexed(rhi::Rhi& rhi, lumpnum_t flat_lump); }; } // namespace srb2::hwr2 diff --git a/src/hwr2/screen_capture.cpp b/src/hwr2/screen_capture.cpp index 69e950650..5fe68d636 100644 --- a/src/hwr2/screen_capture.cpp +++ b/src/hwr2/screen_capture.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -21,7 +21,7 @@ using namespace srb2::rhi; ScreenshotPass::ScreenshotPass() = default; ScreenshotPass::~ScreenshotPass() = default; -void ScreenshotPass::capture(Rhi& rhi, Handle ctx) +void ScreenshotPass::capture(Rhi& rhi) { bool doing_screenshot = takescreenshot || moviemode != MM_OFF || g_takemapthumbnail != TMT_NO; @@ -39,7 +39,7 @@ void ScreenshotPass::capture(Rhi& rhi, Handle ctx) packed_data_.resize(stride * height_); tcb::span data_bytes = tcb::as_writable_bytes(tcb::span(pixel_data_)); - rhi.read_pixels(ctx, {0, 0, width_, height_}, PixelFormat::kRGB8, data_bytes); + rhi.read_pixels({0, 0, width_, height_}, PixelFormat::kRGB8, data_bytes); for (uint32_t row = 0; row < height_; row++) { diff --git a/src/hwr2/screen_capture.hpp b/src/hwr2/screen_capture.hpp index 2b455adf2..dfb7932d1 100644 --- a/src/hwr2/screen_capture.hpp +++ b/src/hwr2/screen_capture.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -21,7 +21,6 @@ namespace srb2::hwr2 class ScreenshotPass { - rhi::Handle render_pass_; std::vector pixel_data_; std::vector packed_data_; uint32_t width_ = 0; @@ -31,7 +30,7 @@ public: ScreenshotPass(); ~ScreenshotPass(); - void capture(rhi::Rhi& rhi, rhi::Handle ctx); + void capture(rhi::Rhi& rhi); void set_source(uint32_t width, uint32_t height) { diff --git a/src/hwr2/software_screen_renderer.cpp b/src/hwr2/software_screen_renderer.cpp index 021f609bc..4e8e9116b 100644 --- a/src/hwr2/software_screen_renderer.cpp +++ b/src/hwr2/software_screen_renderer.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -20,7 +20,7 @@ using namespace srb2::rhi; SoftwareScreenRenderer::SoftwareScreenRenderer() = default; SoftwareScreenRenderer::~SoftwareScreenRenderer() = default; -void SoftwareScreenRenderer::draw(Rhi& rhi, Handle ctx) +void SoftwareScreenRenderer::draw(Rhi& rhi) { // Render the player views... or not yet? Needs to be moved out of D_Display in d_main.c // Assume it's already been done and vid.buffer contains the composited splitscreen view. @@ -71,5 +71,5 @@ void SoftwareScreenRenderer::draw(Rhi& rhi, Handle ctx) screen_span = tcb::as_bytes(tcb::span(vid.buffer, width_ * height_)); } - rhi.update_texture(ctx, screen_texture_, {0, 0, width_, height_}, PixelFormat::kR8, screen_span); + rhi.update_texture(screen_texture_, {0, 0, width_, height_}, PixelFormat::kR8, screen_span); } diff --git a/src/hwr2/software_screen_renderer.hpp b/src/hwr2/software_screen_renderer.hpp index e95027eb2..7d3416059 100644 --- a/src/hwr2/software_screen_renderer.hpp +++ b/src/hwr2/software_screen_renderer.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -34,7 +34,7 @@ public: SoftwareScreenRenderer(); ~SoftwareScreenRenderer(); - void draw(rhi::Rhi& rhi, rhi::Handle ctx); + void draw(rhi::Rhi& rhi); rhi::Handle screen() const { return screen_texture_; } }; diff --git a/src/hwr2/twodee.cpp b/src/hwr2/twodee.cpp index 2edb1b103..60a78abcb 100644 --- a/src/hwr2/twodee.cpp +++ b/src/hwr2/twodee.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/hwr2/twodee.hpp b/src/hwr2/twodee.hpp index 0c4d10201..4886fa6f5 100644 --- a/src/hwr2/twodee.hpp +++ b/src/hwr2/twodee.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/hwr2/twodee_renderer.cpp b/src/hwr2/twodee_renderer.cpp index b79e5063e..a7e8fe761 100644 --- a/src/hwr2/twodee_renderer.cpp +++ b/src/hwr2/twodee_renderer.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -11,11 +11,11 @@ #include "twodee_renderer.hpp" #include -#include #include #include +#include "../core/hash_set.hpp" #include "blendmode.hpp" #include "../r_patch.h" #include "../v_video.h" @@ -43,78 +43,59 @@ static TwodeePipelineKey pipeline_key_for_cmd(const Draw2dCmd& cmd) return {hwr2::get_blend_mode(cmd), hwr2::is_draw_lines(cmd)}; } -static PipelineDesc make_pipeline_desc(TwodeePipelineKey key) +static void set_blend_state(RasterizerStateDesc& desc, BlendMode blend) { - constexpr const VertexInputDesc kTwodeeVertexInput = { - {{sizeof(TwodeeVertex)}}, - {{VertexAttributeName::kPosition, 0, 0}, - {VertexAttributeName::kTexCoord0, 0, 12}, - {VertexAttributeName::kColor, 0, 20}}}; - BlendDesc blend_desc; - switch (key.blend) + switch (blend) { case BlendMode::kAlphaTransparent: - blend_desc.source_factor_color = BlendFactor::kSourceAlpha; - blend_desc.dest_factor_color = BlendFactor::kOneMinusSourceAlpha; - blend_desc.color_function = BlendFunction::kAdd; - blend_desc.source_factor_alpha = BlendFactor::kOne; - blend_desc.dest_factor_alpha = BlendFactor::kOneMinusSourceAlpha; - blend_desc.alpha_function = BlendFunction::kAdd; + desc.blend_source_factor_color = BlendFactor::kSourceAlpha; + desc.blend_dest_factor_color = BlendFactor::kOneMinusSourceAlpha; + desc.blend_color_function = BlendFunction::kAdd; + desc.blend_source_factor_alpha = BlendFactor::kOne; + desc.blend_dest_factor_alpha = BlendFactor::kOneMinusSourceAlpha; + desc.blend_alpha_function = BlendFunction::kAdd; break; case BlendMode::kModulate: - blend_desc.source_factor_color = BlendFactor::kDest; - blend_desc.dest_factor_color = BlendFactor::kZero; - blend_desc.color_function = BlendFunction::kAdd; - blend_desc.source_factor_alpha = BlendFactor::kDestAlpha; - blend_desc.dest_factor_alpha = BlendFactor::kZero; - blend_desc.alpha_function = BlendFunction::kAdd; + desc.blend_source_factor_color = BlendFactor::kDest; + desc.blend_dest_factor_color = BlendFactor::kZero; + desc.blend_color_function = BlendFunction::kAdd; + desc.blend_source_factor_alpha = BlendFactor::kDestAlpha; + desc.blend_dest_factor_alpha = BlendFactor::kZero; + desc.blend_alpha_function = BlendFunction::kAdd; break; case BlendMode::kAdditive: - blend_desc.source_factor_color = BlendFactor::kSourceAlpha; - blend_desc.dest_factor_color = BlendFactor::kOne; - blend_desc.color_function = BlendFunction::kAdd; - blend_desc.source_factor_alpha = BlendFactor::kOne; - blend_desc.dest_factor_alpha = BlendFactor::kOneMinusSourceAlpha; - blend_desc.alpha_function = BlendFunction::kAdd; + desc.blend_source_factor_color = BlendFactor::kSourceAlpha; + desc.blend_dest_factor_color = BlendFactor::kOne; + desc.blend_color_function = BlendFunction::kAdd; + desc.blend_source_factor_alpha = BlendFactor::kOne; + desc.blend_dest_factor_alpha = BlendFactor::kOneMinusSourceAlpha; + desc.blend_alpha_function = BlendFunction::kAdd; break; case BlendMode::kSubtractive: - blend_desc.source_factor_color = BlendFactor::kSourceAlpha; - blend_desc.dest_factor_color = BlendFactor::kOne; - blend_desc.color_function = BlendFunction::kSubtract; - blend_desc.source_factor_alpha = BlendFactor::kOne; - blend_desc.dest_factor_alpha = BlendFactor::kOneMinusSourceAlpha; - blend_desc.alpha_function = BlendFunction::kAdd; + desc.blend_source_factor_color = BlendFactor::kSourceAlpha; + desc.blend_dest_factor_color = BlendFactor::kOne; + desc.blend_color_function = BlendFunction::kSubtract; + desc.blend_source_factor_alpha = BlendFactor::kOne; + desc.blend_dest_factor_alpha = BlendFactor::kOneMinusSourceAlpha; + desc.blend_alpha_function = BlendFunction::kAdd; break; case BlendMode::kReverseSubtractive: - blend_desc.source_factor_color = BlendFactor::kSourceAlpha; - blend_desc.dest_factor_color = BlendFactor::kOne; - blend_desc.color_function = BlendFunction::kReverseSubtract; - blend_desc.source_factor_alpha = BlendFactor::kOne; - blend_desc.dest_factor_alpha = BlendFactor::kOneMinusSourceAlpha; - blend_desc.alpha_function = BlendFunction::kAdd; + desc.blend_source_factor_color = BlendFactor::kSourceAlpha; + desc.blend_dest_factor_color = BlendFactor::kOne; + desc.blend_color_function = BlendFunction::kReverseSubtract; + desc.blend_source_factor_alpha = BlendFactor::kOne; + desc.blend_dest_factor_alpha = BlendFactor::kOneMinusSourceAlpha; + desc.blend_alpha_function = BlendFunction::kAdd; break; case BlendMode::kInvertDest: - blend_desc.source_factor_color = BlendFactor::kOne; - blend_desc.dest_factor_color = BlendFactor::kOne; - blend_desc.color_function = BlendFunction::kSubtract; - blend_desc.source_factor_alpha = BlendFactor::kZero; - blend_desc.dest_factor_alpha = BlendFactor::kDestAlpha; - blend_desc.alpha_function = BlendFunction::kAdd; + desc.blend_source_factor_color = BlendFactor::kOne; + desc.blend_dest_factor_color = BlendFactor::kOne; + desc.blend_color_function = BlendFunction::kSubtract; + desc.blend_source_factor_alpha = BlendFactor::kZero; + desc.blend_dest_factor_alpha = BlendFactor::kDestAlpha; + desc.blend_alpha_function = BlendFunction::kAdd; break; } - - return { - PipelineProgram::kUnshadedPaletted, - kTwodeeVertexInput, - {{{UniformName::kProjection}, - {{UniformName::kModelView, UniformName::kTexCoord0Transform, UniformName::kSampler0IsIndexedAlpha}}}}, - {{SamplerName::kSampler0, SamplerName::kSampler1, SamplerName::kSampler2}}, - std::nullopt, - {blend_desc, {true, true, true, true}}, - key.lines ? PrimitiveType::kLines : PrimitiveType::kTriangles, - CullMode::kNone, - FaceWinding::kCounterClockwise, - {0.f, 0.f, 0.f, 1.f}}; } void TwodeeRenderer::rewrite_patch_quad_vertices(Draw2dList& list, const Draw2dPatchQuad& cmd) const @@ -149,10 +130,10 @@ void TwodeeRenderer::rewrite_patch_quad_vertices(Draw2dList& list, const Draw2dP const float cmd_xrange = cmd.xmax - cmd.xmin; const float cmd_yrange = cmd.ymax - cmd.ymin; - const float clipped_xmin = cmd.clip ? std::clamp(cmd.xmin, cmd.clip_xmin, cmd.clip_xmax) : cmd.xmin; - const float clipped_xmax = cmd.clip ? std::clamp(cmd.xmax, cmd.clip_xmin, cmd.clip_xmax) : cmd.xmax; - const float clipped_ymin = cmd.clip ? std::clamp(cmd.ymin, cmd.clip_ymin, cmd.clip_ymax) : cmd.ymin; - const float clipped_ymax = cmd.clip ? std::clamp(cmd.ymax, cmd.clip_ymin, cmd.clip_ymax) : cmd.ymax; + const float clipped_xmin = cmd.clip ? std::clamp(cmd.xmin, cmd.clip_xmin, std::max(cmd.clip_xmax, cmd.clip_xmin)) : cmd.xmin; + const float clipped_xmax = cmd.clip ? std::clamp(cmd.xmax, cmd.clip_xmin, std::max(cmd.clip_xmax, cmd.clip_xmin)) : cmd.xmax; + const float clipped_ymin = cmd.clip ? std::clamp(cmd.ymin, cmd.clip_ymin, std::max(cmd.clip_ymax, cmd.clip_ymin)) : cmd.ymin; + const float clipped_ymax = cmd.clip ? std::clamp(cmd.ymax, cmd.clip_ymin, std::max(cmd.clip_ymax, cmd.clip_ymin)) : cmd.ymax; const float trimmed_left = cmd.flip ? (1.f - trim_umax) : trim_umin; const float trimmed_right = cmd.flip ? trim_umin : (1.f - trim_umax); @@ -231,34 +212,18 @@ void TwodeeRenderer::rewrite_patch_quad_vertices(Draw2dList& list, const Draw2dP list.vertices[vtx_offs + 3].v = clipped_vmax; } -void TwodeeRenderer::initialize(Rhi& rhi, Handle ctx) +void TwodeeRenderer::initialize(Rhi& rhi) { - { - TwodeePipelineKey alpha_transparent_tris = {BlendMode::kAlphaTransparent, false}; - TwodeePipelineKey modulate_tris = {BlendMode::kModulate, false}; - TwodeePipelineKey additive_tris = {BlendMode::kAdditive, false}; - TwodeePipelineKey subtractive_tris = {BlendMode::kSubtractive, false}; - TwodeePipelineKey revsubtractive_tris = {BlendMode::kReverseSubtractive, false}; - TwodeePipelineKey invertdest_tris = {BlendMode::kInvertDest, false}; - TwodeePipelineKey alpha_transparent_lines = {BlendMode::kAlphaTransparent, true}; - TwodeePipelineKey modulate_lines = {BlendMode::kModulate, true}; - TwodeePipelineKey additive_lines = {BlendMode::kAdditive, true}; - TwodeePipelineKey subtractive_lines = {BlendMode::kSubtractive, true}; - TwodeePipelineKey revsubtractive_lines = {BlendMode::kReverseSubtractive, true}; - TwodeePipelineKey invertdest_lines = {BlendMode::kInvertDest, true}; - pipelines_.insert({alpha_transparent_tris, rhi.create_pipeline(make_pipeline_desc(alpha_transparent_tris))}); - pipelines_.insert({modulate_tris, rhi.create_pipeline(make_pipeline_desc(modulate_tris))}); - pipelines_.insert({additive_tris, rhi.create_pipeline(make_pipeline_desc(additive_tris))}); - pipelines_.insert({subtractive_tris, rhi.create_pipeline(make_pipeline_desc(subtractive_tris))}); - pipelines_.insert({revsubtractive_tris, rhi.create_pipeline(make_pipeline_desc(revsubtractive_tris))}); - pipelines_.insert({invertdest_tris, rhi.create_pipeline(make_pipeline_desc(invertdest_tris))}); - pipelines_.insert({alpha_transparent_lines, rhi.create_pipeline(make_pipeline_desc(alpha_transparent_lines))}); - pipelines_.insert({modulate_lines, rhi.create_pipeline(make_pipeline_desc(modulate_lines))}); - pipelines_.insert({additive_lines, rhi.create_pipeline(make_pipeline_desc(additive_lines))}); - pipelines_.insert({subtractive_lines, rhi.create_pipeline(make_pipeline_desc(subtractive_lines))}); - pipelines_.insert({revsubtractive_lines, rhi.create_pipeline(make_pipeline_desc(revsubtractive_lines))}); - pipelines_.insert({invertdest_lines, rhi.create_pipeline(make_pipeline_desc(revsubtractive_lines))}); - } + ProgramDesc prog_desc; + prog_desc.name = "unshadedpaletted"; + const char* defines[] = { + "ENABLE_U_SAMPLER0_IS_INDEXED_ALPHA", + "ENABLE_S_SAMPLER2", + "ENABLE_VA_TEXCOORD0", + "ENABLE_VA_COLOR" + }; + prog_desc.defines = tcb::make_span(defines); + program_ = rhi.create_program(prog_desc); { default_tex_ = rhi.create_texture({ @@ -269,21 +234,21 @@ void TwodeeRenderer::initialize(Rhi& rhi, Handle ctx) TextureWrapMode::kClamp }); std::array data = {0, 255, 0, 255}; - rhi.update_texture(ctx, default_tex_, {0, 0, 2, 1}, PixelFormat::kRG8, tcb::as_bytes(tcb::span(data))); + rhi.update_texture(default_tex_, {0, 0, 2, 1}, PixelFormat::kRG8, tcb::as_bytes(tcb::span(data))); } initialized_ = true; } -void TwodeeRenderer::flush(Rhi& rhi, Handle ctx, Twodee& twodee) +void TwodeeRenderer::flush(Rhi& rhi, Twodee& twodee) { if (!initialized_) { - initialize(rhi, ctx); + initialize(rhi); } // Stage 1 - command list patch detection - std::unordered_set found_patches; + srb2::HashSet found_patches; for (const auto& list : twodee) { for (const auto& cmd : list.cmds) @@ -297,7 +262,7 @@ void TwodeeRenderer::flush(Rhi& rhi, Handle ctx, Twodee& twodee } if (cmd.colormap != nullptr) { - palette_manager_->find_or_create_colormap(rhi, ctx, cmd.colormap); + palette_manager_->find_or_create_colormap(rhi, cmd.colormap); } }, [&](const Draw2dVertices& cmd) {}}; @@ -309,7 +274,7 @@ void TwodeeRenderer::flush(Rhi& rhi, Handle ctx, Twodee& twodee { patch_atlas_cache_->queue_patch(patch); } - patch_atlas_cache_->pack(rhi, ctx); + patch_atlas_cache_->pack(rhi); size_t list_index = 0; for (auto& list : twodee) @@ -377,6 +342,8 @@ void TwodeeRenderer::flush(Rhi& rhi, Handle ctx, Twodee& twodee new_cmd.colormap = nullptr; // safety: a command list is required to have at least 1 command new_cmd.pipeline_key = pipeline_key_for_cmd(list.cmds[0]); + new_cmd.primitive = new_cmd.pipeline_key.lines ? PrimitiveType::kLines : PrimitiveType::kTriangles; + new_cmd.blend_mode = new_cmd.pipeline_key.blend; merged_list.cmds.push_back(std::move(new_cmd)); for (auto& cmd : list.cmds) @@ -445,7 +412,7 @@ void TwodeeRenderer::flush(Rhi& rhi, Handle ctx, Twodee& twodee { if (cmd.flat_lump != LUMPERROR) { - flat_manager_->find_or_create_indexed(rhi, ctx, cmd.flat_lump); + flat_manager_->find_or_create_indexed(rhi, cmd.flat_lump); std::optional t = MergedTwodeeCommandFlatTexture {cmd.flat_lump}; the_new_one.texture = t; } @@ -458,6 +425,8 @@ void TwodeeRenderer::flush(Rhi& rhi, Handle ctx, Twodee& twodee }}; std::visit(tex_visitor_again, cmd); the_new_one.pipeline_key = pipeline_key_for_cmd(cmd); + the_new_one.primitive = the_new_one.pipeline_key.lines ? PrimitiveType::kLines : PrimitiveType::kTriangles; + the_new_one.blend_mode = the_new_one.pipeline_key.blend; merged_list.cmds.push_back(std::move(the_new_one)); } @@ -492,26 +461,21 @@ void TwodeeRenderer::flush(Rhi& rhi, Handle ctx, Twodee& twodee tcb::span vertex_data = tcb::as_bytes(tcb::span(orig_list.vertices)); tcb::span index_data = tcb::as_bytes(tcb::span(orig_list.indices)); - rhi.update_buffer(ctx, merged_list.vbo, 0, vertex_data); - rhi.update_buffer(ctx, merged_list.ibo, 0, index_data); + rhi.update_buffer(merged_list.vbo, 0, vertex_data); + rhi.update_buffer(merged_list.ibo, 0, index_data); - // Update the binding sets for each individual merged command - VertexAttributeBufferBinding vbos[] = {{0, merged_list.vbo}}; for (auto& mcmd : merged_list.cmds) { - TextureBinding tx[3]; auto tex_visitor = srb2::Overload { [&](Handle texture) { - tx[0] = {SamplerName::kSampler0, texture}; - tx[1] = {SamplerName::kSampler1, palette_tex}; + mcmd.texture_handle = texture; }, [&](const MergedTwodeeCommandFlatTexture& tex) { - Handle th = flat_manager_->find_or_create_indexed(rhi, ctx, tex.lump); + Handle th = flat_manager_->find_or_create_indexed(rhi, tex.lump); SRB2_ASSERT(th != kNullHandle); - tx[0] = {SamplerName::kSampler0, th}; - tx[1] = {SamplerName::kSampler1, palette_tex}; + mcmd.texture_handle = th; }}; if (mcmd.texture) { @@ -519,49 +483,39 @@ void TwodeeRenderer::flush(Rhi& rhi, Handle ctx, Twodee& twodee } else { - tx[0] = {SamplerName::kSampler0, default_tex_}; - tx[1] = {SamplerName::kSampler1, palette_tex}; + mcmd.texture_handle = default_tex_; } const uint8_t* colormap = mcmd.colormap; Handle colormap_h = palette_manager_->default_colormap(); if (colormap) { - colormap_h = palette_manager_->find_or_create_colormap(rhi, ctx, colormap); + colormap_h = palette_manager_->find_or_create_colormap(rhi, colormap); SRB2_ASSERT(colormap_h != kNullHandle); } - tx[2] = {SamplerName::kSampler2, colormap_h}; - mcmd.binding_set = - rhi.create_binding_set(ctx, pipelines_[mcmd.pipeline_key], {tcb::span(vbos), tcb::span(tx)}); + mcmd.colormap_handle = colormap_h; } ctx_list_itr++; } - // Uniform sets - std::array g1_uniforms = { - // Projection - glm::mat4( - glm::vec4(2.f / vid.width, 0.f, 0.f, 0.f), - glm::vec4(0.f, -2.f / vid.height, 0.f, 0.f), - glm::vec4(0.f, 0.f, 1.f, 0.f), - glm::vec4(-1.f, 1.f, 0.f, 1.f) - ), - }; - std::array g2_uniforms = { - // ModelView - glm::identity(), - // Texcoord0 Transform - glm::identity(), - // Sampler 0 Is Indexed Alpha (yes, it always is) - static_cast(1) - }; - Handle us_1 = rhi.create_uniform_set(ctx, {tcb::span(g1_uniforms)}); - Handle us_2 = rhi.create_uniform_set(ctx, {tcb::span(g2_uniforms)}); - // Presumably, we're already in a renderpass when flush is called + rhi.bind_program(program_); + rhi.set_uniform("u_projection", glm::mat4( + glm::vec4(2.f / vid.width, 0.f, 0.f, 0.f), + glm::vec4(0.f, -2.f / vid.height, 0.f, 0.f), + glm::vec4(0.f, 0.f, 1.f, 0.f), + glm::vec4(-1.f, 1.f, 0.f, 1.f) + )); + rhi.set_uniform("u_modelview", glm::identity()); + rhi.set_uniform("u_texcoord0_transform", glm::identity()); + rhi.set_uniform("u_sampler0_is_indexed_alpha", static_cast(1)); + rhi.set_sampler("s_sampler1", 1, palette_tex); for (auto& list : cmd_lists_) { + rhi.bind_vertex_attrib("a_position", list.vbo, VertexAttributeFormat::kFloat3, offsetof(TwodeeVertex, x), sizeof(TwodeeVertex)); + rhi.bind_vertex_attrib("a_texcoord0", list.vbo, VertexAttributeFormat::kFloat2, offsetof(TwodeeVertex, u), sizeof(TwodeeVertex)); + rhi.bind_vertex_attrib("a_color", list.vbo, VertexAttributeFormat::kFloat4, offsetof(TwodeeVertex, r), sizeof(TwodeeVertex)); for (auto& cmd : list.cmds) { if (cmd.elements == 0) @@ -570,15 +524,18 @@ void TwodeeRenderer::flush(Rhi& rhi, Handle ctx, Twodee& twodee // This shouldn't happen, but, just in case... continue; } - SRB2_ASSERT(pipelines_.find(cmd.pipeline_key) != pipelines_.end()); - Handle pl = pipelines_[cmd.pipeline_key]; - rhi.bind_pipeline(ctx, pl); - rhi.set_viewport(ctx, {0, 0, static_cast(vid.width), static_cast(vid.height)}); - rhi.bind_uniform_set(ctx, 0, us_1); - rhi.bind_uniform_set(ctx, 1, us_2); - rhi.bind_binding_set(ctx, cmd.binding_set); - rhi.bind_index_buffer(ctx, list.ibo); - rhi.draw_indexed(ctx, cmd.elements, cmd.index_offset); + RasterizerStateDesc desc; + desc.cull = CullMode::kNone; + desc.primitive = cmd.primitive; + desc.blend_enabled = true; + set_blend_state(desc, cmd.blend_mode); + // Set blend and primitives + rhi.set_rasterizer_state(desc); + rhi.set_viewport({0, 0, static_cast(vid.width), static_cast(vid.height)}); + rhi.set_sampler("s_sampler0", 0, cmd.texture_handle); + rhi.set_sampler("s_sampler2", 2, cmd.colormap_handle); + rhi.bind_index_buffer(list.ibo); + rhi.draw_indexed(cmd.elements, cmd.index_offset); } } diff --git a/src/hwr2/twodee_renderer.hpp b/src/hwr2/twodee_renderer.hpp index 9b3a895c9..9c836ce0b 100644 --- a/src/hwr2/twodee_renderer.hpp +++ b/src/hwr2/twodee_renderer.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -11,10 +11,8 @@ #ifndef __SRB2_HWR2_PASS_TWODEE_HPP__ #define __SRB2_HWR2_PASS_TWODEE_HPP__ -#include #include #include -#include #include #include @@ -65,10 +63,13 @@ struct MergedTwodeeCommandFlatTexture struct MergedTwodeeCommand { using Texture = std::variant, MergedTwodeeCommandFlatTexture>; - TwodeePipelineKey pipeline_key = {}; - rhi::Handle binding_set = {}; + rhi::PrimitiveType primitive; + BlendMode blend_mode; std::optional texture; + TwodeePipelineKey pipeline_key; + rhi::Handle texture_handle; const uint8_t* colormap; + rhi::Handle colormap_handle; uint32_t index_offset = 0; uint32_t elements = 0; }; @@ -94,14 +95,13 @@ class TwodeeRenderer final std::vector cmd_lists_; std::vector, std::size_t>> vbos_; std::vector, std::size_t>> ibos_; - rhi::Handle render_pass_; rhi::Handle output_; rhi::Handle default_tex_; - std::unordered_map> pipelines_; + rhi::Handle program_; void rewrite_patch_quad_vertices(Draw2dList& list, const Draw2dPatchQuad& cmd) const; - void initialize(rhi::Rhi& rhi, rhi::Handle ctx); + void initialize(rhi::Rhi& rhi); public: TwodeeRenderer( @@ -118,7 +118,7 @@ public: /// @brief Flush accumulated Twodee state and perform draws. /// @param rhi /// @param ctx - void flush(rhi::Rhi& rhi, rhi::Handle ctx, Twodee& twodee); + void flush(rhi::Rhi& rhi, Twodee& twodee); }; } // namespace srb2::hwr2 diff --git a/src/hwr2/upscale_backbuffer.cpp b/src/hwr2/upscale_backbuffer.cpp index 790f5b332..3a14c5254 100644 --- a/src/hwr2/upscale_backbuffer.cpp +++ b/src/hwr2/upscale_backbuffer.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -27,7 +27,7 @@ static bool size_equal(Rhi& rhi, Handle tex, uint32_t width, uint32_t h return deets.width == width && deets.height == height; } -void UpscaleBackbuffer::begin_pass(Rhi& rhi, Handle ctx) +void UpscaleBackbuffer::begin_pass(Rhi& rhi) { uint32_t vid_width = static_cast(vid.width); uint32_t vid_height = static_cast(vid.height); @@ -38,19 +38,6 @@ void UpscaleBackbuffer::begin_pass(Rhi& rhi, Handle ctx) remake = true; } - auto new_renderpass = [&rhi = rhi](AttachmentLoadOp load_op, AttachmentStoreOp store_op) - { - RenderPassDesc desc {}; - desc.use_depth_stencil = false; - desc.color_load_op = load_op; - desc.color_store_op = store_op; - desc.depth_load_op = load_op; - desc.depth_store_op = store_op; - desc.stencil_load_op = load_op; - desc.stencil_store_op = store_op; - return rhi.create_render_pass(desc); - }; - if (remake) { if (color_) @@ -70,23 +57,16 @@ void UpscaleBackbuffer::begin_pass(Rhi& rhi, Handle ctx) RenderbufferDesc depth_tex {}; depth_tex.width = vid_width; depth_tex.height = vid_height; - - if (!renderpass_clear_) - { - renderpass_clear_ = new_renderpass(AttachmentLoadOp::kClear, AttachmentStoreOp::kStore); - } - } - else - { - if (!renderpass_) - { - renderpass_ = new_renderpass(AttachmentLoadOp::kLoad, AttachmentStoreOp::kStore); - } } RenderPassBeginInfo begin_info {}; - begin_info.render_pass = remake ? renderpass_clear_ : renderpass_; begin_info.clear_color = {0, 0, 0, 1}; begin_info.color_attachment = color_; - rhi.begin_render_pass(ctx, begin_info); + begin_info.color_load_op = rhi::AttachmentLoadOp::kLoad; + begin_info.color_store_op = rhi::AttachmentStoreOp::kStore; + begin_info.depth_load_op = rhi::AttachmentLoadOp::kLoad; + begin_info.depth_store_op = rhi::AttachmentStoreOp::kStore; + begin_info.stencil_load_op = rhi::AttachmentLoadOp::kLoad; + begin_info.stencil_store_op = rhi::AttachmentStoreOp::kStore; + rhi.push_render_pass(begin_info); } diff --git a/src/hwr2/upscale_backbuffer.hpp b/src/hwr2/upscale_backbuffer.hpp index a60f30172..f2a4c2699 100644 --- a/src/hwr2/upscale_backbuffer.hpp +++ b/src/hwr2/upscale_backbuffer.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -19,8 +19,6 @@ namespace srb2::hwr2 class UpscaleBackbuffer { rhi::Handle color_; - rhi::Handle renderpass_; - rhi::Handle renderpass_clear_; public: UpscaleBackbuffer(); @@ -31,7 +29,7 @@ public: UpscaleBackbuffer& operator=(const UpscaleBackbuffer&) = delete; UpscaleBackbuffer& operator=(UpscaleBackbuffer&&); - void begin_pass(rhi::Rhi& rhi, rhi::Handle ctx); + void begin_pass(rhi::Rhi& rhi); rhi::Handle color() const noexcept { return color_; } }; diff --git a/src/i_addrinfo.c b/src/i_addrinfo.c index 095731338..8997a47e9 100644 --- a/src/i_addrinfo.c +++ b/src/i_addrinfo.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/i_addrinfo.h b/src/i_addrinfo.h index b435cfaa5..b334112dd 100644 --- a/src/i_addrinfo.h +++ b/src/i_addrinfo.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/i_joy.h b/src/i_joy.h index 5b0362eb0..d7b4d1d8c 100644 --- a/src/i_joy.h +++ b/src/i_joy.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/i_net.h b/src/i_net.h index efe325081..26af8e0d2 100644 --- a/src/i_net.h +++ b/src/i_net.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/i_sound.h b/src/i_sound.h index ea346b859..f0c4814e1 100644 --- a/src/i_sound.h +++ b/src/i_sound.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -114,6 +114,8 @@ void I_UpdateSoundParams(INT32 handle, UINT8 vol, UINT8 sep, UINT8 pitch); */ void I_SetSfxVolume(int volume); +void I_SetVoiceVolume(int volume); + /// ------------------------ // MUSIC SYSTEM /// ------------------------ @@ -246,6 +248,22 @@ boolean I_FadeSong(UINT8 target_volume, UINT32 ms, void (*callback)(void)); boolean I_FadeOutStopSong(UINT32 ms); boolean I_FadeInPlaySong(UINT32 ms, boolean looping); +// AUDIO INPUT (Microphones) +boolean I_SoundInputIsEnabled(void); +boolean I_SoundInputSetEnabled(boolean enabled); +UINT32 I_SoundInputDequeueSamples(void *data, UINT32 len); + +// VOICE CHAT + +/// Queue a frame of samples of voice data from a player. Voice format is MONO F32 SYSTEM ENDIANNESS. +/// If there is too much data being queued, old samples will be truncated +void I_QueueVoiceFrameFromPlayer(INT32 playernum, void *data, UINT32 len, boolean terminal); + +void I_SetPlayerVoiceProperties(INT32 playernum, float volume, float sep); + +/// Reset the voice queue for the given player. Use when server connection ends +void I_ResetVoiceQueue(INT32 playernum); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/i_system.h b/src/i_system.h index a55640017..ec58097fb 100644 --- a/src/i_system.h +++ b/src/i_system.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/i_tcp.c b/src/i_tcp.c index 25e98b9d0..ce4abb740 100644 --- a/src/i_tcp.c +++ b/src/i_tcp.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/i_tcp.h b/src/i_tcp.h index 305d1e590..dec1a6c4b 100644 --- a/src/i_tcp.h +++ b/src/i_tcp.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/i_tcp_detail.h b/src/i_tcp_detail.h index 037b46fa6..b56b1c1dc 100644 --- a/src/i_tcp_detail.h +++ b/src/i_tcp_detail.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // @@ -40,8 +40,6 @@ extern "C" { #include #endif -#include "doomdef.h" - #ifdef USE_WINSOCK1 #include #else @@ -115,9 +113,11 @@ extern "C" { static UINT8 UPNP_support = TRUE; #endif // HAVE_MINIUPNC -#include "d_net.h" -#include "doomtype.h" #include "i_tcp.h" +#include "d_net.h" + +#include "doomdef.h" +#include "doomtype.h" union mysockaddr_t { diff --git a/src/i_threads.h b/src/i_threads.h index a0c210e24..ac16ee520 100644 --- a/src/i_threads.h +++ b/src/i_threads.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. -// Copyright (C) 2024 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/i_time.c b/src/i_time.c index 50d443a6b..bb39070b5 100644 --- a/src/i_time.c +++ b/src/i_time.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2022 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/i_time.h b/src/i_time.h index b3cccfe1b..2459d4d38 100644 --- a/src/i_time.h +++ b/src/i_time.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/i_video.h b/src/i_video.h index 1617a7305..12150f41e 100644 --- a/src/i_video.h +++ b/src/i_video.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -28,7 +28,6 @@ extern rhi::Handle g_current_rhi; rhi::Rhi* get_rhi(rhi::Handle handle); -rhi::Handle main_graphics_context(); hwr2::HardwareState* main_hardware_state(); } // namespace srb2::sys diff --git a/src/i_video_common.cpp b/src/i_video_common.cpp index 82ab995c3..f52f9298e 100644 --- a/src/i_video_common.cpp +++ b/src/i_video_common.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -56,7 +56,6 @@ using namespace srb2::rhi; static Rhi* g_last_known_rhi = nullptr; static bool g_imgui_frame_active = false; -static Handle g_main_graphics_context; static HardwareState g_hw_state; Handle srb2::sys::g_current_rhi = kNullHandle; @@ -88,6 +87,7 @@ static void reset_hardware_state(Rhi* rhi) g_hw_state.crtsharp_blit_rect = std::make_unique(BlitRectPass::BlitMode::kCrtSharp); g_hw_state.screen_capture = std::make_unique(); g_hw_state.backbuffer = std::make_unique(); + g_hw_state.imgui_renderer = std::make_unique(); g_hw_state.wipe_frames = {}; g_last_known_rhi = rhi; @@ -98,9 +98,7 @@ static void new_imgui_frame(); static void preframe_update(Rhi& rhi) { - SRB2_ASSERT(g_main_graphics_context != kNullHandle); - - g_hw_state.palette_manager->update(rhi, g_main_graphics_context); + g_hw_state.palette_manager->update(rhi); new_twodee_frame(); new_imgui_frame(); } @@ -196,11 +194,6 @@ static void new_imgui_frame() g_imgui_frame_active = true; } -rhi::Handle sys::main_graphics_context() -{ - return g_main_graphics_context; -} - HardwareState* sys::main_hardware_state() { return &g_hw_state; @@ -209,11 +202,10 @@ HardwareState* sys::main_hardware_state() void I_CaptureVideoFrame() { rhi::Rhi* rhi = srb2::sys::get_rhi(srb2::sys::g_current_rhi); - rhi::Handle ctx = srb2::sys::main_graphics_context(); hwr2::HardwareState* hw_state = srb2::sys::main_hardware_state(); hw_state->screen_capture->set_source(static_cast(vid.width), static_cast(vid.height)); - hw_state->screen_capture->capture(*rhi, ctx); + hw_state->screen_capture->capture(*rhi); } void I_StartDisplayUpdate(void) @@ -244,12 +236,9 @@ void I_StartDisplayUpdate(void) reset_hardware_state(rhi); } - rhi::Handle ctx = rhi->begin_graphics(); HardwareState* hw_state = &g_hw_state; - hw_state->backbuffer->begin_pass(*rhi, ctx); - - g_main_graphics_context = ctx; + hw_state->backbuffer->begin_pass(*rhi); preframe_update(*rhi); } @@ -283,68 +272,63 @@ void I_FinishUpdate(void) return; } - rhi::Handle ctx = g_main_graphics_context; + // better hope the drawing code left the context in a render pass, I guess + g_hw_state.twodee_renderer->flush(*rhi, g_2d); + rhi->pop_render_pass(); - if (ctx != kNullHandle) + rhi->push_default_render_pass(true); + + // Upscale draw the backbuffer (with postprocessing maybe?) + if (cv_scr_scale.value != FRACUNIT) { - // better hope the drawing code left the context in a render pass, I guess - g_hw_state.twodee_renderer->flush(*rhi, ctx, g_2d); - rhi->end_render_pass(ctx); + float f = std::max(FixedToFloat(cv_scr_scale.value), 0.f); + float w = vid.realwidth * f; + float h = vid.realheight * f; + float x = (vid.realwidth - w) * (0.5f + (FixedToFloat(cv_scr_x.value) * 0.5f)); + float y = (vid.realheight - h) * (0.5f + (FixedToFloat(cv_scr_y.value) * 0.5f)); - rhi->begin_default_render_pass(ctx, true); - - // Upscale draw the backbuffer (with postprocessing maybe?) - if (cv_scr_scale.value != FRACUNIT) - { - float f = std::max(FixedToFloat(cv_scr_scale.value), 0.f); - float w = vid.realwidth * f; - float h = vid.realheight * f; - float x = (vid.realwidth - w) * (0.5f + (FixedToFloat(cv_scr_x.value) * 0.5f)); - float y = (vid.realheight - h) * (0.5f + (FixedToFloat(cv_scr_y.value) * 0.5f)); - - g_hw_state.blit_rect->set_output(x, y, w, h, true, true); - g_hw_state.sharp_bilinear_blit_rect->set_output(x, y, w, h, true, true); - g_hw_state.crt_blit_rect->set_output(x, y, w, h, true, true); - g_hw_state.crtsharp_blit_rect->set_output(x, y, w, h, true, true); - } - else - { - g_hw_state.blit_rect->set_output(0, 0, vid.realwidth, vid.realheight, true, true); - g_hw_state.sharp_bilinear_blit_rect->set_output(0, 0, vid.realwidth, vid.realheight, true, true); - g_hw_state.crt_blit_rect->set_output(0, 0, vid.realwidth, vid.realheight, true, true); - g_hw_state.crtsharp_blit_rect->set_output(0, 0, vid.realwidth, vid.realheight, true, true); - } - g_hw_state.blit_rect->set_texture(g_hw_state.backbuffer->color(), static_cast(vid.width), static_cast(vid.height)); - g_hw_state.sharp_bilinear_blit_rect->set_texture(g_hw_state.backbuffer->color(), static_cast(vid.width), static_cast(vid.height)); - g_hw_state.crt_blit_rect->set_texture(g_hw_state.backbuffer->color(), static_cast(vid.width), static_cast(vid.height)); - g_hw_state.crtsharp_blit_rect->set_texture(g_hw_state.backbuffer->color(), static_cast(vid.width), static_cast(vid.height)); - - switch (cv_scr_effect.value) - { - case 1: - rhi->update_texture_settings(ctx, g_hw_state.backbuffer->color(), TextureWrapMode::kClamp, TextureWrapMode::kClamp, TextureFilterMode::kLinear, TextureFilterMode::kLinear); - g_hw_state.sharp_bilinear_blit_rect->draw(*rhi, ctx); - break; - case 2: - rhi->update_texture_settings(ctx, g_hw_state.backbuffer->color(), TextureWrapMode::kClamp, TextureWrapMode::kClamp, TextureFilterMode::kLinear, TextureFilterMode::kLinear); - g_hw_state.crt_blit_rect->draw(*rhi, ctx); - break; - case 3: - rhi->update_texture_settings(ctx, g_hw_state.backbuffer->color(), TextureWrapMode::kClamp, TextureWrapMode::kClamp, TextureFilterMode::kLinear, TextureFilterMode::kLinear); - g_hw_state.crtsharp_blit_rect->draw(*rhi, ctx); - break; - default: - rhi->update_texture_settings(ctx, g_hw_state.backbuffer->color(), TextureWrapMode::kClamp, TextureWrapMode::kClamp, TextureFilterMode::kNearest, TextureFilterMode::kNearest); - g_hw_state.blit_rect->draw(*rhi, ctx); - break; - } - rhi->end_render_pass(ctx); - - rhi->end_graphics(ctx); - g_main_graphics_context = kNullHandle; - - postframe_update(*rhi); + g_hw_state.blit_rect->set_output(x, y, w, h, true, true); + g_hw_state.sharp_bilinear_blit_rect->set_output(x, y, w, h, true, true); + g_hw_state.crt_blit_rect->set_output(x, y, w, h, true, true); + g_hw_state.crtsharp_blit_rect->set_output(x, y, w, h, true, true); } + else + { + g_hw_state.blit_rect->set_output(0, 0, vid.realwidth, vid.realheight, true, true); + g_hw_state.sharp_bilinear_blit_rect->set_output(0, 0, vid.realwidth, vid.realheight, true, true); + g_hw_state.crt_blit_rect->set_output(0, 0, vid.realwidth, vid.realheight, true, true); + g_hw_state.crtsharp_blit_rect->set_output(0, 0, vid.realwidth, vid.realheight, true, true); + } + g_hw_state.blit_rect->set_texture(g_hw_state.backbuffer->color(), static_cast(vid.width), static_cast(vid.height)); + g_hw_state.sharp_bilinear_blit_rect->set_texture(g_hw_state.backbuffer->color(), static_cast(vid.width), static_cast(vid.height)); + g_hw_state.crt_blit_rect->set_texture(g_hw_state.backbuffer->color(), static_cast(vid.width), static_cast(vid.height)); + g_hw_state.crtsharp_blit_rect->set_texture(g_hw_state.backbuffer->color(), static_cast(vid.width), static_cast(vid.height)); + + switch (cv_scr_effect.value) + { + case 1: + rhi->update_texture_settings(g_hw_state.backbuffer->color(), TextureWrapMode::kClamp, TextureWrapMode::kClamp, TextureFilterMode::kLinear, TextureFilterMode::kLinear); + g_hw_state.sharp_bilinear_blit_rect->draw(*rhi); + break; + case 2: + rhi->update_texture_settings(g_hw_state.backbuffer->color(), TextureWrapMode::kClamp, TextureWrapMode::kClamp, TextureFilterMode::kLinear, TextureFilterMode::kLinear); + g_hw_state.crt_blit_rect->draw(*rhi); + break; + case 3: + rhi->update_texture_settings(g_hw_state.backbuffer->color(), TextureWrapMode::kClamp, TextureWrapMode::kClamp, TextureFilterMode::kLinear, TextureFilterMode::kLinear); + g_hw_state.crtsharp_blit_rect->draw(*rhi); + break; + default: + rhi->update_texture_settings(g_hw_state.backbuffer->color(), TextureWrapMode::kClamp, TextureWrapMode::kClamp, TextureFilterMode::kNearest, TextureFilterMode::kNearest); + g_hw_state.blit_rect->draw(*rhi); + break; + } + + g_hw_state.imgui_renderer->render(*rhi); + + rhi->pop_render_pass(); + + postframe_update(*rhi); rhi->present(); rhi->finish(); diff --git a/src/info.c b/src/info.c index 40cc9949f..c33e6b943 100644 --- a/src/info.c +++ b/src/info.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -50,6 +50,7 @@ char sprnames[NUMSPRITES + 1][5] = "BSPH", // Sphere "EMBM", "SPCN", // Spray Can + "SBON", // Spray Can replacement bonus "MMSH", // Ancient Shrine "MORB", // One Morbillion "EMRC", // Chaos Emeralds @@ -375,9 +376,20 @@ char sprnames[NUMSPRITES + 1][5] = "TRIS", // SPB Manta Ring start "TRNQ", // SPB Manta Ring loop "THNS", // Lightning Shield + "THNC", // Lightning Shield Top Flash + "THNA", // Lightning Shield Top Swoosh + "THNB", // Lightning Shield Bottom Swoosh "BUBS", // Bubble Shield (not Bubs) + "BUBT", // Bubble Shield trap + "BUBA", // Bubble Shield Outline + "BUBB", // Bubble Shield Top Wave + "BUBC", // Bubble Shield Bottom Wave + "BUBD", // Bubble Shield Reflection + "BUBE", // Bubble Shield Underline "BWVE", // Bubble Shield waves "FLMS", // Flame Shield + "FLMA", // Flame Shield Top Layer + "FLMB", // Flame Shield Bottom Layer "FLMD", // Flame Shield dash "FLMP", // Flame Shield paper sprites "FLML", // Flame Shield speed lines @@ -399,6 +411,7 @@ char sprnames[NUMSPRITES + 1][5] = "BEXB", // Battle Bumper Explosion: Blast "TWBS", // Tripwire Boost "TWBT", // Tripwire BLASTER + "TWBP", // Tripwire approach "SMLD", // Smooth landing // Trick Effects @@ -583,6 +596,8 @@ char sprnames[NUMSPRITES + 1][5] = "AMPC", "AMPD", + "SOR_", + "WTRL", // Water Trail "GCHA", // follower: generic chao @@ -770,6 +785,9 @@ char sprnames[NUMSPRITES + 1][5] = "DIEM", // smoke "DIEN", // explosion + // Flybot767 (stun) + "STUN", + // Pulley "HCCH", "HCHK", @@ -801,6 +819,7 @@ char spr2names[NUMPLAYERSPRITES][5] = "SIGN", "SIGL", "SSIG", // Finish signpost "XTRA", // Three Faces of Darkness "TALK", // Dialogue + "DKRT", // Kart husk }; playersprite_t free_spr2 = SPR2_FIRSTFREESLOT; @@ -845,13 +864,14 @@ playersprite_t spr2defaults[NUMPLAYERSPRITES] = { SPR2_SIGN, // SPR2_SSIG 0, // SPR2_XTRA 0, // SPR2_TALK + 0, // SPR2_DKRT }; // Doesn't work with g++, needs actionf_p1 (don't modify this comment) state_t states[NUMSTATES] = { // frame is masked through FF_FRAMEMASK - // FF_ANIMATE makes simple state animations (var1 #frames, var2 tic delay) + // FF_ANIMATE makes simple state animations (var1 #frames, var2 tic delay) (var1 is ignored in P_SetupStateAnimation() if sprite is SPR_PLAY) // FF_FULLBRIGHT activates the fullbright colormap // use FF_TRANS10 - FF_TRANS90 for easy translucency // (or tr_trans10< -#include #include -#include #include #include -#include #include #include +#include "../core/string.h" +#include "../core/vector.hpp" + namespace srb2::io { @@ -484,13 +484,13 @@ inline void read_exact(SpanStream& stream, tcb::span buffer) } class VecStream { - std::vector vec_; + srb2::Vector vec_; std::size_t head_ {0}; public: VecStream() = default; - VecStream(const std::vector& vec) : vec_(vec) {} - VecStream(std::vector&& vec) : vec_(std::move(vec)) {} + VecStream(const srb2::Vector& vec) : vec_(vec) {} + VecStream(srb2::Vector&& vec) : vec_(std::move(vec)) {} VecStream(const VecStream& rhs) = default; VecStream(VecStream&& rhs) = default; @@ -549,7 +549,7 @@ public: return head_; } - std::vector& vector() { return vec_; } + srb2::Vector& vector() { return vec_; } friend void read_exact(VecStream& stream, tcb::span buffer); }; @@ -674,7 +674,7 @@ template buf_; + srb2::Vector buf_; std::size_t buf_head_; bool zstream_initialized_; bool zstream_ended_; @@ -820,7 +820,7 @@ template buf_; + srb2::Vector buf_; tcb::span::size_type cap_; public: @@ -872,7 +872,7 @@ template buf_; + srb2::Vector buf_; tcb::span::size_type cap_; public: @@ -933,7 +933,7 @@ extern template class BufferedInputStream; template StreamSize pipe_all(I& input, O& output) { - std::vector buf; + srb2::Vector buf; StreamSize total_written = 0; StreamSize read_this_time = 0; @@ -951,7 +951,7 @@ StreamSize pipe_all(I& input, O& output) { } template -std::vector read_to_vec(I& input) { +srb2::Vector read_to_vec(I& input) { VecStream out; pipe_all(input, out); return std::move(out.vector()); diff --git a/src/k_bans.cpp b/src/k_bans.cpp index bf5e38ec1..a47902c21 100644 --- a/src/k_bans.cpp +++ b/src/k_bans.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by AJ "Tyron" Martinez -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by AJ "Tyron" Martinez +// Copyright (C) 2025 by Kart Krew // Copyright (C) 2020 by Sonic Team Junior // Copyright (C) 2000 by DooM Legacy Team // @@ -12,14 +12,16 @@ /// \file k_bans.c /// \brief replacement for DooM Legacy ban system -#include #include -#include #include -#include +#include #include "i_tcp_detail.h" // clientaddress + +#include "core/json.hpp" +#include "core/string.h" +#include "io/streams.hpp" #include "k_bans.h" #include "byteptr.h" // READ/WRITE macros #include "command.h" @@ -34,7 +36,9 @@ #include "z_zone.h" #include "i_addrinfo.h" // I_getaddrinfo -using nlohmann::json; +using srb2::JsonArray; +using srb2::JsonObject; +using srb2::JsonValue; static mysockaddr_t *DuplicateSockAddr(const mysockaddr_t *source) { @@ -43,22 +47,22 @@ static mysockaddr_t *DuplicateSockAddr(const mysockaddr_t *source) return dup; } -static std::vector bans; +static srb2::Vector bans; static uint8_t allZero[PUBKEYLENGTH]; -static void load_bans_array_v1(json& array) +static void load_bans_array_v1(const JsonArray& array) { - for (json& object : array) + for (const JsonValue& object : array) { uint8_t public_key_bin[PUBKEYLENGTH]; - std::string public_key = object.at("public_key"); - std::string ip_address = object.at("ip_address"); - time_t expires = object.at("expires"); - UINT8 subnet_mask = object.at("subnet_mask"); - std::string username = object.at("username"); - std::string reason = object.at("reason"); + srb2::String public_key = object.at("public_key").get(); + srb2::String ip_address = object.at("ip_address").get(); + time_t expires = object.at("expires").get(); + UINT8 subnet_mask = object.at("subnet_mask").get(); + srb2::String username = object.at("username").get(); + srb2::String reason = object.at("reason").get(); if (!FromPrettyRRID(public_key_bin, public_key.c_str())) { @@ -86,16 +90,31 @@ void SV_LoadBans(void) if (!server) return; - json object; + JsonValue object; + + srb2::String banspath { srb2::format("{}/{}", srb2home, BANFILE) }; + srb2::io::BufferedInputStream bis; + try + { + srb2::io::FileStream fs { banspath, srb2::io::FileStreamMode::kRead }; + bis = srb2::io::BufferedInputStream(std::move(fs)); + } + catch (const srb2::io::FileStreamException& ex) + { + // file didn't open, likely doesn't exist + return; + } try { - std::ifstream f(va(pandf, srb2home, BANFILE)); - - if (f.is_open()) + srb2::Vector data = srb2::io::read_to_vec(bis); + srb2::String data_s; + data_s.reserve(data.size()); + for (auto b : data) { - f >> object; + data_s.push_back(std::to_integer(b)); } + object = JsonValue::from_json_string(data_s); } catch (const std::exception& ex) { @@ -115,11 +134,11 @@ void SV_LoadBans(void) { if (object.value("version", 1) == 1) { - json& array = object.at("bans"); + JsonValue& array = object.at("bans"); if (array.is_array()) { - load_bans_array_v1(array); + load_bans_array_v1(array.as_array()); } } } @@ -131,31 +150,36 @@ void SV_LoadBans(void) void SV_SaveBans(void) { - json object = json::object(); + JsonValue object = JsonValue(JsonObject()); object["version"] = 1; - json& array = object["bans"]; + JsonValue& array_value = object["bans"]; - array = json::array(); + array_value = JsonValue(JsonArray()); + JsonArray& array = array_value.as_array(); for (banrecord_t& ban : bans) { if (ban.deleted) continue; - array.push_back({ + array.push_back(JsonObject { {"public_key", GetPrettyRRID(ban.public_key, false)}, {"ip_address", SOCK_AddrToStr(ban.address)}, {"subnet_mask", ban.mask}, - {"expires", ban.expires}, + {"expires", static_cast(ban.expires)}, {"username", ban.username}, {"reason", ban.reason}, }); } + srb2::String json_string = object.to_json_string(); + srb2::String banfile_path = srb2::format("{}/{}", srb2home, BANFILE); + try { - std::ofstream(va(pandf, srb2home, BANFILE)) << object; + srb2::io::FileStream fs { banfile_path, srb2::io::FileStreamMode::kWrite }; + srb2::io::write_exact(fs, tcb::as_bytes(tcb::span(json_string))); } catch (const std::exception& ex) { @@ -345,10 +369,10 @@ static void SV_BanSearch(boolean remove) const char* stringaddress = SOCK_AddrToStr(ban.address); const char* stringkey = GetPrettyRRID(ban.public_key, true); - std::string recordprint = fmt::format( + srb2::String recordprint = srb2::format( "{}{} - {} [{}] - {}", stringaddress, - ban.mask && ban.mask != 32 ? fmt::format("/{}", ban.mask) : "", + ban.mask && ban.mask != 32 ? srb2::format("/{}", ban.mask) : "", ban.username, stringkey, ban.reason @@ -359,7 +383,7 @@ static void SV_BanSearch(boolean remove) if (ban.expires < now) recordprint += " - EXPIRED"; else - recordprint += fmt::format(" - expires {}m", (ban.expires - now)/60); + recordprint += srb2::format(" - expires {}m", (ban.expires - now)/60); } CONS_Printf("%s\n", recordprint.c_str()); diff --git a/src/k_bans.h b/src/k_bans.h index f10579674..ca4ddd2f6 100644 --- a/src/k_bans.h +++ b/src/k_bans.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by AJ "Tyron" Martinez. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by AJ "Tyron" Martinez. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/k_battle.c b/src/k_battle.c index 9c82c38dc..8d3477da4 100644 --- a/src/k_battle.c +++ b/src/k_battle.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -105,6 +105,9 @@ void K_SpawnBattlePoints(player_t *source, player_t *victim, UINT8 amount) if (!source || !source->mo) return; + if (source->exiting) + return; + if (amount == 1) st = S_BATTLEPOINT1A; else if (amount == 2) @@ -985,7 +988,16 @@ boolean K_EndBattleRound(player_t *victor) if (gametyperules & GTR_POINTLIMIT) { // Lock the winner in before the round ends. + + // TODO: a "won the round" bool used for sorting + // position / intermission, so we aren't completely + // clobbering the individual scoring. victor->roundscore = 100; + + if (G_GametypeHasTeams() == true && victor->team != TEAM_UNASSIGNED) + { + g_teamscores[victor->team] = 100; + } } } diff --git a/src/k_battle.h b/src/k_battle.h index c813efb3c..8f0b5db3a 100644 --- a/src/k_battle.h +++ b/src/k_battle.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_bheap.c b/src/k_bheap.c index b29f022dd..ff2e5bf0c 100644 --- a/src/k_bheap.c +++ b/src/k_bheap.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sean "Sryder" Ryder -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sean "Sryder" Ryder +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -394,7 +394,7 @@ boolean K_BHeapPush(bheap_t *const heap, void *const item, UINT32 value, updatei if (heap->count >= heap->capacity) { size_t newarraycapacity = heap->capacity * 2; - heap->array = Z_Realloc(heap->array, newarraycapacity, PU_STATIC, NULL); + heap->array = Z_Realloc(heap->array, newarraycapacity * sizeof(bheapitem_t), PU_STATIC, NULL); if (heap->array == NULL) { diff --git a/src/k_bheap.h b/src/k_bheap.h index 107aacd0f..3f406e808 100644 --- a/src/k_bheap.h +++ b/src/k_bheap.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sean "Sryder" Ryder -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sean "Sryder" Ryder +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_boss.c b/src/k_boss.c index ecdf6a209..314566f8e 100644 --- a/src/k_boss.c +++ b/src/k_boss.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Vivian "toastergrl" Grannell +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_boss.h b/src/k_boss.h index fe42652b1..2a5d94a70 100644 --- a/src/k_boss.h +++ b/src/k_boss.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Vivian "toastergrl" Grannell +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_bot.cpp b/src/k_bot.cpp index 5e00814ff..041a0be38 100644 --- a/src/k_bot.cpp +++ b/src/k_bot.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -174,16 +174,20 @@ void K_SetBot(UINT8 newplayernum, UINT8 skinnum, UINT8 difficulty, botStyle_e st break; } } - players[newplayernum].skincolor = color; - K_SetNameForBot(newplayernum, realname); - SetPlayerSkinByNum(newplayernum, skinnum); + K_SetNameForBot(newplayernum, realname); for (UINT8 i = 0; i < PWRLV_NUMTYPES; i++) { clientpowerlevels[newplayernum][i] = 0; } + players[newplayernum].prefcolor = color; + players[newplayernum].prefskin = skinnum; + players[newplayernum].preffollower = -1; + players[newplayernum].preffollowercolor = SKINCOLOR_NONE; + G_UpdatePlayerPreferences(&players[newplayernum]); + if (netgame) { HU_AddChatText(va("\x82*Bot %d has been added to the game", newplayernum+1), false); @@ -630,6 +634,12 @@ static UINT32 K_BotRubberbandDistance(const player_t *player) continue; } + if (G_SameTeam(player, &players[i]) == true) + { + // Don't consider friendlies with your rubberbanding. + continue; + } + // First check difficulty levels, then score, then settle it with port priority! if (player->botvars.difficulty < players[i].botvars.difficulty) { @@ -716,6 +726,12 @@ fixed_t K_BotRubberband(const player_t *player) continue; } + // Don't rubberband to friendlies... + if (G_SameTeam(player, &players[i]) == true) + { + continue; + } + #if 0 // Only rubberband up to players. if (players[i].bot) @@ -787,8 +803,13 @@ fixed_t K_UpdateRubberband(player_t *player) fixed_t dest = K_BotRubberband(player); fixed_t ret = player->botvars.rubberband; + UINT8 ease_soften = 8; + + if (player->botvars.bumpslow && dest > ret) + ease_soften *= 10; + // Ease into the new value. - ret += (dest - player->botvars.rubberband) / 8; + ret += (dest - player->botvars.rubberband) / ease_soften; return ret; } diff --git a/src/k_bot.h b/src/k_bot.h index 296617e68..8a4cfe893 100644 --- a/src/k_bot.h +++ b/src/k_bot.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_botitem.cpp b/src/k_botitem.cpp index 227e55be7..e3bb56c33 100644 --- a/src/k_botitem.cpp +++ b/src/k_botitem.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -89,6 +89,7 @@ static boolean K_BotUseItemNearPlayer(const player_t *player, ticcmd_t *cmd, fix if (target->mo == NULL || P_MobjWasRemoved(target->mo) || player == target || target->spectator + || G_SameTeam(player, target) || target->flashing) { continue; @@ -144,6 +145,7 @@ static player_t *K_PlayerNearSpot(const player_t *player, fixed_t x, fixed_t y, if (target->mo == NULL || P_MobjWasRemoved(target->mo) || player == target || target->spectator + || G_SameTeam(player, target) || target->flashing) { continue; @@ -222,6 +224,7 @@ static player_t *K_PlayerInCone(const player_t *player, fixed_t radius, UINT16 c if (target->mo == NULL || P_MobjWasRemoved(target->mo) || player == target || target->spectator + || G_SameTeam(player, target) || target->flashing || !P_CheckSight(player->mo, target->mo)) { @@ -1142,28 +1145,31 @@ static void K_BotItemJawz(const player_t *player, ticcmd_t *cmd) && players[lastTarg].mo != NULL && P_MobjWasRemoved(players[lastTarg].mo) == false) { - mobj_t *targMo = players[lastTarg].mo; - mobj_t *mobj = NULL, *next = NULL; - boolean targettedAlready = false; - target = &players[lastTarg]; - // Make sure no other Jawz are targetting this player. - for (mobj = trackercap; mobj; mobj = next) + if (G_SameTeam(player, target) == false) { - next = mobj->itnext; + mobj_t *targMo = players[lastTarg].mo; + mobj_t *mobj = NULL, *next = NULL; + boolean targettedAlready = false; - if (mobj->type == MT_JAWZ && mobj->target == targMo) + // Make sure no other Jawz are targetting this player. + for (mobj = trackercap; mobj; mobj = next) { - targettedAlready = true; - break; - } - } + next = mobj->itnext; - if (targettedAlready == false) - { - K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty * snipeMul); - throwdir = 1; + if (mobj->type == MT_JAWZ && mobj->target == targMo) + { + targettedAlready = true; + break; + } + } + + if (targettedAlready == false) + { + K_ItemConfirmForTarget(player, cmd, target, player->botvars.difficulty * snipeMul); + throwdir = 1; + } } } @@ -1253,6 +1259,7 @@ static void K_BotItemBubble(const player_t *player, ticcmd_t *cmd) if (target->mo == NULL || P_MobjWasRemoved(target->mo) || player == target || target->spectator + || G_SameTeam(player, target) || target->flashing) { continue; @@ -1528,6 +1535,7 @@ static void K_BotItemInstashield(const player_t *player, ticcmd_t *cmd) if (P_MobjWasRemoved(target->mo) == true || player == target || target->spectator == true + || G_SameTeam(player, target) == true || target->flashing != 0) { continue; diff --git a/src/k_botsearch.cpp b/src/k_botsearch.cpp index 6d1e2c85c..e60b6c85d 100644 --- a/src/k_botsearch.cpp +++ b/src/k_botsearch.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -353,13 +353,14 @@ static void K_AddDodgeObject(mobj_t *thing, UINT8 side, UINT8 weight) } /*-------------------------------------------------- - static boolean K_PlayerAttackSteer(mobj_t *thing, UINT8 side, UINT8 weight, boolean attackCond, boolean dodgeCond) + static boolean K_PlayerAttackSteer(mobj_t *thing, boolean friendly_fire, UINT8 side, UINT8 weight, boolean attackCond, boolean dodgeCond) Checks two conditions to determine if the object should be attacked or dodged. Input Arguments:- thing - Object to move towards/away from. + friendly_fire - If the attack would be against a friendly player. side - Which side -- 0 for left, 1 for right weight - How important this object is. attackCond - If this is true, and dodgeCond isn't, then we go towards the object. @@ -368,8 +369,15 @@ static void K_AddDodgeObject(mobj_t *thing, UINT8 side, UINT8 weight) Return:- true if either condition is successful. --------------------------------------------------*/ -static boolean K_PlayerAttackSteer(mobj_t *thing, UINT8 side, UINT8 weight, boolean attackCond, boolean dodgeCond) +static boolean K_PlayerAttackSteer(mobj_t *thing, boolean friendly_fire, UINT8 side, UINT8 weight, boolean attackCond, boolean dodgeCond) { + if (friendly_fire == true && attackCond == true && dodgeCond == false) + { + // Dodge, don't attack. + attackCond = false; + dodgeCond = true; + } + if (attackCond == true && dodgeCond == false) { K_AddAttackObject(thing, side, weight); @@ -489,7 +497,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) break; } - if (P_CanPickupItem(g_nudgeSearch.botmo->player, 1)) + if (P_CanPickupItem(g_nudgeSearch.botmo->player, PICKUP_ITEMBOX)) { K_AddAttackObject(thing, side, ((thing->extravalue1 < RINGBOX_TIME) ? 10 : 20)); } @@ -500,7 +508,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) break; } - if (P_CanPickupItem(g_nudgeSearch.botmo->player, 1)) // Can pick up an actual item + if (P_CanPickupItem(g_nudgeSearch.botmo->player, PICKUP_ITEMBOX)) // Can pick up an actual item { const UINT8 stealth = K_EggboxStealth(thing->x, thing->y); const UINT8 requiredstealth = (g_nudgeSearch.botmo->player->botvars.difficulty * g_nudgeSearch.botmo->player->botvars.difficulty); @@ -521,7 +529,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) break; } - if (P_CanPickupItem(g_nudgeSearch.botmo->player, 3)) + if (P_CanPickupItem(g_nudgeSearch.botmo->player, PICKUP_PAPERITEM)) { K_AddAttackObject(thing, side, 20); } @@ -533,8 +541,8 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) break; } - if ((RINGTOTAL(g_nudgeSearch.botmo->player) < 20 && !(g_nudgeSearch.botmo->player->pflags & PF_RINGLOCK) - && P_CanPickupItem(g_nudgeSearch.botmo->player, 0)) + if ((RINGTOTAL(g_nudgeSearch.botmo->player) < 20 + && P_CanPickupItem(g_nudgeSearch.botmo->player, PICKUP_RINGORSPHERE)) && !thing->extravalue1 && (g_nudgeSearch.botmo->player->itemtype != KITEM_LIGHTNINGSHIELD)) { @@ -547,9 +555,11 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) && !thing->player->hyudorotimer && !g_nudgeSearch.botmo->player->hyudorotimer) { + const boolean same_team = G_SameTeam(g_nudgeSearch.botmo->player, thing->player); + // There REALLY ought to be a better way to handle this logic, right?! // Squishing - if (K_PlayerAttackSteer(thing, side, 20, + if (K_PlayerAttackSteer(thing, same_team, side, 20, K_IsBigger(g_nudgeSearch.botmo, thing), K_IsBigger(thing, g_nudgeSearch.botmo) )) @@ -557,7 +567,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) break; } // Invincibility - else if (K_PlayerAttackSteer(thing, side, 20, + else if (K_PlayerAttackSteer(thing, same_team, side, 20, g_nudgeSearch.botmo->player->invincibilitytimer, thing->player->invincibilitytimer )) @@ -565,7 +575,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) break; } // Lightning Shield - else if (K_PlayerAttackSteer(thing, side, 20, + else if (K_PlayerAttackSteer(thing, same_team, side, 20, g_nudgeSearch.botmo->player->itemtype == KITEM_LIGHTNINGSHIELD, thing->player->itemtype == KITEM_LIGHTNINGSHIELD )) @@ -573,7 +583,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) break; } // Bubble Shield - else if (K_PlayerAttackSteer(thing, side, 20, + else if (K_PlayerAttackSteer(thing, same_team, side, 20, g_nudgeSearch.botmo->player->itemtype == KITEM_BUBBLESHIELD, thing->player->itemtype == KITEM_BUBBLESHIELD )) @@ -581,7 +591,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) break; } // Flame Shield - else if (K_PlayerAttackSteer(thing, side, 20, + else if (K_PlayerAttackSteer(thing, same_team, side, 20, g_nudgeSearch.botmo->player->itemtype == KITEM_FLAMESHIELD, thing->player->itemtype == KITEM_FLAMESHIELD )) @@ -589,7 +599,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) break; } // Has held item shield - else if (K_PlayerAttackSteer(thing, side, 20, + else if (K_PlayerAttackSteer(thing, same_team, side, 20, (thing->player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT)), (g_nudgeSearch.botmo->player->itemflags & (IF_ITEMOUT|IF_EGGMANOUT)) )) @@ -597,7 +607,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) break; } // Ring Sting - else if (K_PlayerAttackSteer(thing, side, 20, + else if (K_PlayerAttackSteer(thing, same_team, side, 20, thing->player->rings <= 0, g_nudgeSearch.botmo->player->rings <= 0 )) @@ -620,7 +630,7 @@ static BlockItReturn_t K_FindObjectsForNudging(mobj_t *thing) weightdiff = ourweight - theirweight; } - if (weightdiff > mapobjectscale) + if (weightdiff > mapobjectscale && same_team == false) { K_AddAttackObject(thing, side, 20); } diff --git a/src/k_brightmap.c b/src/k_brightmap.c index 25e9e47c9..1c6a9b6b8 100644 --- a/src/k_brightmap.c +++ b/src/k_brightmap.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_brightmap.h b/src/k_brightmap.h index 1fee05f12..53bda00e0 100644 --- a/src/k_brightmap.h +++ b/src/k_brightmap.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_collide.cpp b/src/k_collide.cpp index b73c0fd5b..4e1a3451b 100644 --- a/src/k_collide.cpp +++ b/src/k_collide.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -72,6 +72,12 @@ boolean K_BananaBallhogCollide(mobj_t *t1, mobj_t *t2) if (t1->type == MT_BALLHOG && t2->type == MT_BALLHOG) return true; // Ballhogs don't collide with eachother + if (t1->type == MT_BALLHOGBOOM && t2->type == MT_BALLHOGBOOM) + return true; // Ballhogs don't collide with eachother + + if (K_TryPickMeUp(t1, t2)) + return true; + if (t2->player) { if (t2->player->flashing > 0 && t2->hitlag == 0) @@ -172,7 +178,10 @@ boolean K_EggItemCollide(mobj_t *t1, mobj_t *t2) if (t1->health <= 0 || t2->health <= 0) return true; - if (!P_CanPickupItem(t2->player, 2)) + if (K_TryPickMeUp(t1, t2)) + return true; + + if (!P_CanPickupItem(t2->player, PICKUP_EGGBOX)) return true; K_DropItems(t2->player); @@ -425,6 +434,9 @@ boolean K_LandMineCollide(mobj_t *t1, mobj_t *t2) if (t1->health <= 0 || t2->health <= 0) return true; + if (K_TryPickMeUp(t1, t2)) + return true; + if (t2->player) { const INT32 oldhitlag = t2->hitlag; @@ -532,6 +544,9 @@ boolean K_DropTargetCollide(mobj_t *t1, mobj_t *t2) if (t2->player && (t2->player->hyudorotimer || t2->player->justbumped)) return true; + if (K_TryPickMeUp(t1, t2)) + return true; + if (draggeddroptarget && P_MobjWasRemoved(draggeddroptarget)) draggeddroptarget = NULL; // Beware order-of-execution on crushers, I guess?! @@ -1053,6 +1068,9 @@ boolean K_KitchenSinkCollide(mobj_t *t1, mobj_t *t2) if (((t1->target == t2) || (!(t2->flags & (MF_ENEMY|MF_BOSS)) && (t1->target == t2->target))) && (t1->threshold > 0 || (t2->type != MT_PLAYER && t2->threshold > 0))) return true; + if (K_TryPickMeUp(t1, t2)) + return true; + if (t2->player) { if (t2->player->flashing > 0 && t2->hitlag == 0) @@ -1206,7 +1224,7 @@ boolean K_PvPTouchDamage(mobj_t *t1, mobj_t *t2) // Ring sting, this is a bit more unique auto doSting = [](mobj_t *t1, mobj_t *t2) { - if (K_GetShieldFromItem(t2->player->itemtype) != KSHIELD_NONE) + if (t2->player->curshield != KSHIELD_NONE) { return false; } diff --git a/src/k_collide.h b/src/k_collide.h index d72b30fd6..770b4af67 100644 --- a/src/k_collide.h +++ b/src/k_collide.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_color.c b/src/k_color.c index dcf6e0fa7..4355d607a 100644 --- a/src/k_color.c +++ b/src/k_color.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_color.h b/src/k_color.h index 3d922ce5f..1ff7f9fb6 100644 --- a/src/k_color.h +++ b/src/k_color.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_credits.cpp b/src/k_credits.cpp index de2565b86..5b9023c19 100644 --- a/src/k_credits.cpp +++ b/src/k_credits.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -12,11 +12,8 @@ #include "k_credits.h" -#include -#include #include #include -#include #include "doomdef.h" #include "doomstat.h" @@ -59,7 +56,9 @@ #include "r_main.h" #include "m_easing.h" -using nlohmann::json; +using srb2::JsonArray; +using srb2::JsonObject; +using srb2::JsonValue; enum credits_slide_types_e { @@ -75,14 +74,14 @@ enum credits_slide_types_e struct credits_slide_s { credits_slide_types_e type; - std::string label; - std::vector strings; + srb2::String label; + srb2::Vector strings; size_t strings_height; boolean play_demo_afterwards; int fade_out_music; }; -static std::vector g_credits_slides; +static srb2::Vector g_credits_slides; struct credits_star_s { @@ -94,10 +93,10 @@ struct credits_star_s static struct credits_s { size_t current_slide; - std::vector demo_maps; + srb2::Vector demo_maps; boolean skip; - std::vector stars; + srb2::Vector stars; fixed_t transition; fixed_t transition_prev; @@ -105,7 +104,7 @@ static struct credits_s tic_t animation_timer; - std::vector> split_slide_strings; + srb2::Vector> split_slide_strings; size_t split_slide_id; tic_t split_slide_delay; @@ -142,12 +141,14 @@ void F_LoadCreditsDefinitions(void) size_t credits_lump_len = W_LumpLength(credits_lump_id); const char *credits_lump = static_cast( W_CacheLumpNum(credits_lump_id, PU_CACHE) ); - json credits_array = json::parse(credits_lump, credits_lump + credits_lump_len); - if (credits_array.is_array() == false) + srb2::String json_string { credits_lump, credits_lump_len }; + JsonValue credits_parsed = JsonValue::from_json_string(json_string); + if (credits_parsed.is_array() == false) { I_Error("credits_def parse error: Not a JSON array"); return; } + JsonArray credits_array = credits_parsed.as_array(); if (credits_array.size() == 0) { @@ -156,11 +157,11 @@ void F_LoadCreditsDefinitions(void) try { - for (json& slide_obj : credits_array) + for (JsonValue& slide_obj : credits_array) { struct credits_slide_s slide; - std::string type_str = slide_obj.value("type", "scroll"); + srb2::String type_str = slide_obj.value("type", srb2::String("scroll")); if (type_str == "scroll") { @@ -196,18 +197,19 @@ void F_LoadCreditsDefinitions(void) throw std::runtime_error("unexpected type name '" + type_str + "'"); } - slide.label = slide_obj.value("label", ""); + slide.label = slide_obj.value("label", srb2::String("")); slide.strings_height = 0; if (slide_obj.contains("strings")) { - json strings_array = slide_obj.at("strings"); - if (strings_array.is_array() == true) + JsonValue strings_value = slide_obj.at("strings"); + if (strings_value.is_array() == true) { + JsonArray& strings_array = strings_value.as_array(); for (size_t i = 0; i < strings_array.size(); i++) { - slide.strings.push_back( strings_array.at(i) ); + slide.strings.push_back( strings_array.at(i).get() ); if (slide.type == CRED_TYPE_SCROLL) { @@ -289,7 +291,7 @@ static void F_InitCreditsSlide(void) size_t max_strings_per_screen = (num_strings - 1) / num_sub_screens + 1; size_t str_id = 0; - std::vector screen_strings; + srb2::Vector screen_strings; if (max_strings_per_screen == kMaxSlideStrings && num_strings % kMaxSlideStrings == 1) @@ -342,24 +344,24 @@ static void F_InitCreditsSlide(void) #endif ) { - slide->strings.push_back("#" + std::string(def->title)); + slide->strings.push_back("#" + srb2::String(def->title)); slide->strings_height += 12; if (def->author) { - slide->strings.push_back("by " + std::string(def->author)); + slide->strings.push_back("by " + srb2::String(def->author)); slide->strings_height += 12; } if (def->source) { - slide->strings.push_back("from " + std::string(def->source)); + slide->strings.push_back("from " + srb2::String(def->source)); slide->strings_height += 12; } if (def->composers) { - slide->strings.push_back("originally by " + std::string(def->composers)); + slide->strings.push_back("originally by " + srb2::String(def->composers)); slide->strings_height += 12; } @@ -478,7 +480,7 @@ void F_ContinueCredits(void) static UINT16 F_PickRandomCreditsDemoMap(void) { - std::vector allowedMaps; + srb2::Vector allowedMaps; for (INT32 i = 0; i < basenummapheaders; i++) // Only take from the base game. { @@ -911,7 +913,7 @@ static void F_DrawCreditsScroll(void) } else { - std::string new_str = str; + srb2::String new_str = str; if (new_str.at(0) == '*') { @@ -1017,7 +1019,7 @@ static void F_DrawCreditsSlide(void) return; } - const std::vector *slide_strings = &g_credits.split_slide_strings[ g_credits.split_slide_id ]; + const srb2::Vector *slide_strings = &g_credits.split_slide_strings[ g_credits.split_slide_id ]; const fixed_t strings_height = slide_strings->size() * 30 * FRACUNIT; fixed_t y = 0; @@ -1113,7 +1115,7 @@ static void F_DrawCreditsTyler52(void) V_DrawFadeScreen(0xFF00, 31 - (g_credits.tyler_fade * 4)); } - std::string memory_str = "In memory of"; + srb2::String memory_str = "In memory of"; const fixed_t memory_width = V_StringScaledWidth( FRACUNIT, FRACUNIT, FRACUNIT, 0, LSLOW_FONT, @@ -1126,7 +1128,7 @@ static void F_DrawCreditsTyler52(void) memory_str.c_str() ); - std::string tyler_str = "Tyler52"; + srb2::String tyler_str = "Tyler52"; const fixed_t tyler_width = V_StringScaledWidth( FRACUNIT, FRACUNIT, FRACUNIT, 0, LSHI_FONT, diff --git a/src/k_credits.h b/src/k_credits.h index 6e080dc9c..85ab252ac 100644 --- a/src/k_credits.h +++ b/src/k_credits.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_dialogue.cpp b/src/k_dialogue.cpp index 74abfa72e..10b7f8796 100644 --- a/src/k_dialogue.cpp +++ b/src/k_dialogue.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // Copyright (C) 2020 by Sonic Team Junior // // This program is free software distributed under the @@ -14,9 +14,10 @@ #include "k_dialogue.hpp" #include "k_dialogue.h" -#include #include +#include +#include "core/string.h" #include "info.h" #include "sounds.h" #include "g_game.h" @@ -43,7 +44,7 @@ void Dialogue::Typewriter::ClearText(void) textDest.clear(); } -void Dialogue::Typewriter::NewText(std::string newText) +void Dialogue::Typewriter::NewText(const srb2::String& newText) { text.clear(); @@ -174,7 +175,7 @@ void Dialogue::SetSpeaker(void) typewriter.voiceSfx = sfx_ktalk; } -void Dialogue::SetSpeaker(std::string skinName, int portraitID) +void Dialogue::SetSpeaker(srb2::String skinName, int portraitID) { Init(); @@ -215,7 +216,7 @@ void Dialogue::SetSpeaker(std::string skinName, int portraitID) } } -void Dialogue::SetSpeaker(std::string name, patch_t *patch, UINT8 *colormap, sfxenum_t voice) +void Dialogue::SetSpeaker(srb2::String name, patch_t *patch, UINT8 *colormap, sfxenum_t voice) { Init(); @@ -241,7 +242,7 @@ void Dialogue::NewText(std::string_view rawText) Init(); char* newText = V_ScaledWordWrap( - 290 << FRACBITS, + 275 << FRACBITS, FRACUNIT, FRACUNIT, FRACUNIT, 0, HU_FONT, srb2::Draw::TextElement().parse(rawText).string().c_str() // parse special characters @@ -472,32 +473,25 @@ void Dialogue::Draw(void) .flags(V_VFLIP|V_FLIP) .patch(patchCache["TUTDIAGE"]); + srb2::String intertext = ""; + drawer .xy(10 - BASEVIDWIDTH, -3-32) .font(srb2::Draw::Font::kConsole) .text( typewriter.text.c_str() ); - if (Dismissable()) + if (TextDone()) { - if (TextDone()) - { - drawer - .xy(-14, -7-5) - .patch(patchCache["TUTDIAG2"]); - } - - auto bt_translate_press = [this]() -> std::optional - { - if (Held()) - return true; - if (TextDone()) - return {}; - return false; - }; - drawer - .xy(17-14 - BASEVIDWIDTH, -39-16) - .button(srb2::Draw::Button::z, bt_translate_press()); + .xy(-18 - 3, -7-5) + .patch(patchCache["TUTDIAG2"]); + + if (Held()) + intertext += ""; + else + intertext += ""; + + drawer.xy(-18, -7-8 - 14).align(Draw::Align::kCenter).font(Draw::Font::kMenu).text(srb2::Draw::TextElement().parse(intertext).string()); } } diff --git a/src/k_dialogue.h b/src/k_dialogue.h index b5894f7ee..fca78c627 100644 --- a/src/k_dialogue.h +++ b/src/k_dialogue.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // Copyright (C) 2020 by Sonic Team Junior // // This program is free software distributed under the diff --git a/src/k_dialogue.hpp b/src/k_dialogue.hpp index 530748a9c..678cc36b0 100644 --- a/src/k_dialogue.hpp +++ b/src/k_dialogue.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // Copyright (C) 2020 by Sonic Team Junior // // This program is free software distributed under the @@ -14,10 +14,10 @@ #ifndef __K_DIALOGUE_HPP__ #define __K_DIALOGUE_HPP__ -#include #include -#include +#include "core/hash_map.hpp" +#include "core/string.h" #include "doomdef.h" #include "doomtype.h" #include "typedef.h" @@ -33,8 +33,8 @@ public: static constexpr fixed_t kSlideSpeed = FRACUNIT / (TICRATE / 5); void SetSpeaker(void); - void SetSpeaker(std::string skinName, int portraitID); - void SetSpeaker(std::string name, patch_t *patch, UINT8 *colormap, sfxenum_t voice); + void SetSpeaker(srb2::String skinName, int portraitID); + void SetSpeaker(srb2::String name, patch_t *patch, UINT8 *colormap, sfxenum_t voice); void NewText(std::string_view newText); @@ -60,8 +60,8 @@ public: static constexpr fixed_t kTextSpeedDefault = FRACUNIT; static constexpr fixed_t kTextPunctPause = (FRACUNIT * TICRATE * 2) / 5; - std::string text; - std::string textDest; + srb2::String text; + srb2::String textDest; fixed_t textTimer; fixed_t textSpeed; @@ -71,7 +71,7 @@ public: sfxenum_t voiceSfx; bool syllable; - void NewText(std::string newText); + void NewText(const srb2::String& newText); void ClearText(void); void WriteText(void); @@ -86,11 +86,11 @@ private: patch_t *bgPatch; patch_t *confirmPatch; - std::string speaker; + srb2::String speaker; patch_t *portrait; UINT8 *portraitColormap; - std::unordered_map patchCache; + srb2::HashMap patchCache; bool active; fixed_t slide; diff --git a/src/k_director.cpp b/src/k_director.cpp index 00035f023..0a7d221c2 100644 --- a/src/k_director.cpp +++ b/src/k_director.cpp @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by AJ "Tyron" Martinez. -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by AJ "Tyron" Martinez. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_director.h b/src/k_director.h index 47d7ea9be..16a8ea937 100644 --- a/src/k_director.h +++ b/src/k_director.h @@ -1,9 +1,9 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by AJ "Tyron" Martinez. -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. -// +// Copyright (C) 2025 by AJ "Tyron" Martinez. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. +// // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. diff --git a/src/k_endcam.cpp b/src/k_endcam.cpp index 378e00cc0..3fb7f65cb 100644 --- a/src/k_endcam.cpp +++ b/src/k_endcam.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_endcam.h b/src/k_endcam.h index e321e6e5b..7807cc88d 100644 --- a/src/k_endcam.h +++ b/src/k_endcam.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_follower.c b/src/k_follower.c index dc02685b6..aee769b95 100644 --- a/src/k_follower.c +++ b/src/k_follower.c @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'". -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by "Lat'". +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_follower.h b/src/k_follower.h index edab92f8e..dc5ca9a72 100644 --- a/src/k_follower.h +++ b/src/k_follower.h @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'". -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by "Lat'". +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_grandprix.c b/src/k_grandprix.cpp similarity index 76% rename from src/k_grandprix.c rename to src/k_grandprix.cpp index 8991a9606..d4b96a257 100644 --- a/src/k_grandprix.c +++ b/src/k_grandprix.cpp @@ -1,16 +1,20 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -/// \file k_grandprix.c +/// \file k_grandprix.cpp /// \brief Grand Prix mode game logic & bot behaviors #include "k_grandprix.h" + +#include +#include + #include "k_specialstage.h" #include "doomdef.h" #include "d_player.h" @@ -47,11 +51,11 @@ UINT8 K_BotStartingDifficulty(SINT8 value) } /*-------------------------------------------------- - INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers) + INT16 K_CalculateGPRankPoints(UINT16 diplayexp, UINT8 position, UINT8 numplayers) See header file for description. --------------------------------------------------*/ -INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers) +INT16 K_CalculateGPRankPoints(UINT16 exp, UINT8 position, UINT8 numplayers) { INT16 points; @@ -61,7 +65,7 @@ INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers) return 0; } - points = numplayers - position; + points = exp; // Give bonus to high-ranking players, depending on player count // This rounds out the point gain when you get 1st every race, @@ -75,16 +79,16 @@ INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers) case 0: case 1: case 2: // 1v1 break; // No bonus needed. case 3: case 4: // 3-4P - if (position == 1) { points += 1; } // 1st gets +1 extra point + if (position == 1) { points += 5; } // 1st gets +1 extra point break; case 5: case 6: // 5-6P - if (position == 1) { points += 3; } // 1st gets +3 extra points - else if (position == 2) { points += 1; } // 2nd gets +1 extra point + if (position == 1) { points += 10; } // 1st gets +3 extra points + // else if (position == 2) { points += 4; } // 2nd gets +1 extra point break; default: // Normal matches - if (position == 1) { points += 5; } // 1st gets +5 extra points - else if (position == 2) { points += 3; } // 2nd gets +3 extra points - else if (position == 3) { points += 1; } // 3rd gets +1 extra point + if (position == 1) { points += 10; } // 1st gets +5 extra points + // else if (position == 2) { points += 5; } // 2nd gets +3 extra points + // else if (position == 3) { points += 2; } // 3rd gets +1 extra point break; } @@ -108,7 +112,7 @@ UINT8 K_GetGPPlayerCount(UINT8 humans) // 2P -> 8 total // 3P -> 12 total // 4P -> 16 total - return max(min(humans * 4, MAXPLAYERS), 8); + return std::clamp(humans * 4, 8, MAXPLAYERS); } /*-------------------------------------------------- @@ -160,22 +164,22 @@ void K_InitGrandPrixBots(void) else { // init difficulty levels list - difficultylevels[0] = max(1, startingdifficulty); - difficultylevels[1] = max(1, startingdifficulty-1); - difficultylevels[2] = max(1, startingdifficulty-2); - difficultylevels[3] = max(1, startingdifficulty-3); - difficultylevels[4] = max(1, startingdifficulty-3); - difficultylevels[5] = max(1, startingdifficulty-4); - difficultylevels[6] = max(1, startingdifficulty-4); - difficultylevels[7] = max(1, startingdifficulty-4); - difficultylevels[8] = max(1, startingdifficulty-5); - difficultylevels[9] = max(1, startingdifficulty-5); - difficultylevels[10] = max(1, startingdifficulty-5); - difficultylevels[11] = max(1, startingdifficulty-6); - difficultylevels[12] = max(1, startingdifficulty-6); - difficultylevels[13] = max(1, startingdifficulty-7); - difficultylevels[14] = max(1, startingdifficulty-7); - difficultylevels[15] = max(1, startingdifficulty-8); + difficultylevels[ 0] = std::max(1, startingdifficulty); + difficultylevels[ 1] = std::max(1, startingdifficulty-1); + difficultylevels[ 2] = std::max(1, startingdifficulty-2); + difficultylevels[ 3] = std::max(1, startingdifficulty-3); + difficultylevels[ 4] = std::max(1, startingdifficulty-3); + difficultylevels[ 5] = std::max(1, startingdifficulty-4); + difficultylevels[ 6] = std::max(1, startingdifficulty-4); + difficultylevels[ 7] = std::max(1, startingdifficulty-4); + difficultylevels[ 8] = std::max(1, startingdifficulty-5); + difficultylevels[ 9] = std::max(1, startingdifficulty-5); + difficultylevels[10] = std::max(1, startingdifficulty-5); + difficultylevels[11] = std::max(1, startingdifficulty-6); + difficultylevels[12] = std::max(1, startingdifficulty-6); + difficultylevels[13] = std::max(1, startingdifficulty-7); + difficultylevels[14] = std::max(1, startingdifficulty-7); + difficultylevels[15] = std::max(1, startingdifficulty-8); } for (i = 0; i < MAXPLAYERS; i++) @@ -661,6 +665,42 @@ void K_IncreaseBotDifficulty(player_t *bot) bot->botvars.diffincrease = increase; } +static boolean CompareJoiners(player_t *a, player_t *b) +{ + if (a->spectatorReentry != b->spectatorReentry) + { + // Push low re-entry cooldown to the back. + return (a->spectatorReentry > b->spectatorReentry); + } + + if (a->spectatewait != b->spectatewait) + { + // Push high waiting time to the back. + return (a->spectatewait < b->spectatewait); + } + + // They are equals, so just randomize + return (P_Random(PR_BOTS) & 1); +} + +static boolean CompareReplacements(player_t *a, player_t *b) +{ + if ((a->pflags & PF_NOCONTEST) != (b->pflags & PF_NOCONTEST)) + { + // Push NO CONTEST to the back. + return ((a->pflags & PF_NOCONTEST) == 0); + } + + if (a->position != b->position) + { + // Push bad position to the back. + return (a->position < b->position); + } + + // They are equals, so just randomize + return (P_Random(PR_BOTS) & 1); +} + /*-------------------------------------------------- void K_RetireBots(void) @@ -764,41 +804,146 @@ void K_RetireBots(void) } } + // Duel Shuffle: + // In games with limited player count, shuffle the NO CONTEST + // player with a spectator that wants to play. Intended for 1v1 + // servers, but really this can help any server with a lower + // player count than connection count. + std::vector joining; + std::vector humans; + std::vector bots; + for (i = 0; i < MAXPLAYERS; i++) { - player_t *bot = NULL; - - if (!playeringame[i] || !players[i].bot || players[i].spectator) + if (playeringame[i] == false) { continue; } - bot = &players[i]; + player_t *player = &players[i]; - if (bot->pflags & PF_NOCONTEST) + if (player->spectator == true) { - UINT8 skinnum = defaultbotskin; - - if (usableskins > 0) + if (player->bot == false && (player->pflags & PF_WANTSTOJOIN) == PF_WANTSTOJOIN) { - UINT8 index = P_RandomKey(PR_BOTS, usableskins); - skinnum = grabskins[index]; - grabskins[index] = grabskins[--usableskins]; + joining.push_back(player); + } + } + else + { + if (player->bot == true) + { + bots.push_back(player); + } + else + { + humans.push_back(player); + } + } + } + + const size_t max_lobby_size = static_cast(cv_maxplayers.value); + + size_t num_joining = joining.size(); + size_t num_playing = humans.size() + bots.size(); + + std::stable_sort(joining.begin(), joining.end(), CompareJoiners); + std::stable_sort(humans.begin(), humans.end(), CompareReplacements); + std::stable_sort(bots.begin(), bots.end(), CompareReplacements); + + boolean did_replacement = false; + + if (G_GametypeHasSpectators() == true && grandprixinfo.gp == false && cv_shuffleloser.value != 0) + { + // While joiners and players still exist, insert joiners. + + //UINT8 replacements = max_lobby_size / 2; // Only replace bottom half + UINT8 replacements = 1; // Only replace a single player. + + while (replacements > 0) + { + if (joining.size() == 0 || (humans.size() + bots.size()) == 0) + { + // No one to replace or to join. + break; } - memcpy(&bot->availabilities, R_GetSkinAvailabilities(false, skinnum), MAXAVAILABILITY*sizeof(UINT8)); + if (num_playing + num_joining <= max_lobby_size) + { + // We can fit everyone, so we don't need to do manual replacement. + break; + } - bot->botvars.difficulty = newDifficulty; - bot->botvars.diffincrease = 0; + player_t *joiner = joining.back(); + joining.pop_back(); - SetPlayerSkinByNum(i, skinnum); - bot->skincolor = skins[skinnum].prefcolor; - K_SetNameForBot(i, skins[skinnum].realname); + player_t *replace = nullptr; + if (bots.size() > 0) + { + replace = bots.back(); - bot->score = 0; - bot->pflags &= ~PF_NOCONTEST; + // A bot is taking up this slot? Remove it entirely. + CL_RemovePlayer(replace - players, KR_LEAVE); + + bots.pop_back(); + } + else + { + replace = humans.back(); + + // A human is taking up this slot? Spectate them. + replace->spectator = true; + replace->pflags |= PF_WANTSTOJOIN; // We were are spectator against our will, we want to play ASAP. + + humans.pop_back(); + } + + // Add our waiting-to-play spectator to the game. + P_SpectatorJoinGame(joiner); + + did_replacement = true; + replacements--; + num_joining--; } } + + if (did_replacement == true) + { + // No need to run the bot swapping code, + // we already replaced the loser. + return; + } + + // Replace last place bot. + if (bots.size() > 0) + { + player_t *bot = bots.back(); + + UINT8 skinnum = defaultbotskin; + + if (usableskins > 0) + { + UINT8 index = P_RandomKey(PR_BOTS, usableskins); + skinnum = grabskins[index]; + grabskins[index] = grabskins[--usableskins]; + } + + memcpy(&bot->availabilities, R_GetSkinAvailabilities(false, skinnum), MAXAVAILABILITY*sizeof(UINT8)); + + bot->botvars.difficulty = newDifficulty; + bot->botvars.diffincrease = 0; + + K_SetNameForBot(bot - players, skins[skinnum].realname); + + bot->prefskin = skinnum; + bot->prefcolor = skins[skinnum].prefcolor; + bot->preffollower = -1; + bot->preffollowercolor = SKINCOLOR_NONE; + G_UpdatePlayerPreferences(bot); + + bot->score = 0; + bot->pflags &= ~PF_NOCONTEST; + } } /*-------------------------------------------------- diff --git a/src/k_grandprix.h b/src/k_grandprix.h index d16f0eb03..8b62187dc 100644 --- a/src/k_grandprix.h +++ b/src/k_grandprix.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -60,7 +60,7 @@ UINT8 K_BotStartingDifficulty(SINT8 value); /*-------------------------------------------------- - INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers); + INT16 K_CalculateGPRankPoints(player_t* player, UINT8 numplayers); Calculates the number of points that a player would recieve if they won the round. @@ -73,7 +73,7 @@ UINT8 K_BotStartingDifficulty(SINT8 value); Number of points to give. --------------------------------------------------*/ -INT16 K_CalculateGPRankPoints(UINT8 position, UINT8 numplayers); +INT16 K_CalculateGPRankPoints(UINT16 exp, UINT8 position, UINT8 numplayers); /*-------------------------------------------------- diff --git a/src/k_hitlag.c b/src/k_hitlag.c index 27f8dc7e6..9110f1845 100644 --- a/src/k_hitlag.c +++ b/src/k_hitlag.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_hitlag.h b/src/k_hitlag.h index 62ea19bda..c5c0944d5 100644 --- a/src/k_hitlag.h +++ b/src/k_hitlag.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_hud.cpp b/src/k_hud.cpp index 88088915e..d0e6da19c 100644 --- a/src/k_hud.cpp +++ b/src/k_hud.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -11,11 +11,12 @@ #include #include -#include #include -#include "v_draw.hpp" +#include +#include "core/string.h" +#include "core/vector.hpp" #include "k_hud.h" #include "k_kart.h" #include "k_battle.h" @@ -54,6 +55,7 @@ #include "k_dialogue.h" #include "f_finale.h" #include "m_easing.h" +#include "v_draw.hpp" //{ Patch Definitions static patch_t *kp_nodraw; @@ -207,6 +209,7 @@ static patch_t *kp_alagles[10]; static patch_t *kp_blagles[6]; static patch_t *kp_cpu[2]; +patch_t *kp_pickmeup[2]; static patch_t *kp_nametagstem; @@ -215,6 +218,23 @@ static patch_t *kp_bossret[4]; static patch_t *kp_trickcool[2]; +static patch_t *kp_voice_localactive[16]; +static patch_t *kp_voice_localactiveoverlay[16]; +static patch_t *kp_voice_localopen; +static patch_t *kp_voice_localmuted; +static patch_t *kp_voice_localdeafened; +static patch_t *kp_voice_remoteactive; +static patch_t *kp_voice_remoteopen; +static patch_t *kp_voice_remotemuted; +static patch_t *kp_voice_remotedeafened; +static patch_t *kp_voice_tagactive[3]; + +static patch_t *kp_team_bar[2]; +static patch_t *kp_team_sticker[2]; +static patch_t *kp_team_underlay[2][2]; +static patch_t *kp_team_minihead; +static patch_t *kp_team_you; + patch_t *kp_autoroulette; patch_t *kp_autoring; @@ -235,19 +255,49 @@ patch_t *kp_button_c[2][2]; patch_t *kp_button_x[2][2]; patch_t *kp_button_y[2][2]; patch_t *kp_button_z[2][2]; -patch_t *kp_button_start[2]; -patch_t *kp_button_l[2]; -patch_t *kp_button_r[2]; -patch_t *kp_button_up[2]; -patch_t *kp_button_down[2]; -patch_t *kp_button_right[2]; -patch_t *kp_button_left[2]; -patch_t *kp_button_dpad[2]; +patch_t *kp_button_start[2][2]; +patch_t *kp_button_l[2][2]; +patch_t *kp_button_r[2][2]; +patch_t *kp_button_up[2][2]; +patch_t *kp_button_down[2][2]; +patch_t *kp_button_right[2][2]; +patch_t *kp_button_left[2][2]; +patch_t *kp_button_lua1[2][2]; +patch_t *kp_button_lua2[2][2]; +patch_t *kp_button_lua3[2][2]; -static void K_LoadButtonGraphics(patch_t *kp[2], int letter) +patch_t *gen_button_a[2][2]; +patch_t *gen_button_b[2][2]; +patch_t *gen_button_x[2][2]; +patch_t *gen_button_y[2][2]; +patch_t *gen_button_lb[2][2]; +patch_t *gen_button_rb[2][2]; +patch_t *gen_button_lt[2][2]; +patch_t *gen_button_rt[2][2]; +patch_t *gen_button_start[2][2]; +patch_t *gen_button_back[2][2]; +patch_t *gen_button_ls[2][2]; +patch_t *gen_button_rs[2][2]; +patch_t *gen_button_dpad[2][2]; + +patch_t *gen_button_keyleft[2]; +patch_t *gen_button_keyright[2]; +patch_t *gen_button_keycenter[2]; + +static void K_LoadButtonGraphics(patch_t *kp[2][2], const char* code) { - HU_UpdatePatch(&kp[0], "TLB_%c", letter); - HU_UpdatePatch(&kp[1], "TLB_%cB", letter); + HU_UpdatePatch(&kp[0][0], "TLB_%s", code); + HU_UpdatePatch(&kp[0][1], "TLB_%sB", code); + HU_UpdatePatch(&kp[1][0], "TLBS%s", code); + HU_UpdatePatch(&kp[1][1], "TLBS%sB", code); +} + +static void K_LoadGenericButtonGraphics(patch_t *kp[2][2], const char* code) +{ + HU_UpdatePatch(&kp[0][0], "TLG_%s", code); + HU_UpdatePatch(&kp[0][1], "TLG_%sB", code); + HU_UpdatePatch(&kp[1][0], "TLGS%s", code); + HU_UpdatePatch(&kp[1][1], "TLGS%sB", code); } void K_LoadKartHUDGraphics(void) @@ -435,7 +485,7 @@ void K_LoadKartHUDGraphics(void) buffer[6] = '0'+((j) / 10); buffer[7] = '0'+((j) % 10); HU_UpdatePatch(&kp_amps[i][j], "%s", buffer); - } + } } // Level 7 @@ -824,6 +874,9 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_cpu[0], "K_CPU1"); HU_UpdatePatch(&kp_cpu[1], "K_CPU2"); + HU_UpdatePatch(&kp_pickmeup[0], "K_PMU1"); + HU_UpdatePatch(&kp_pickmeup[1], "K_PMU2"); + HU_UpdatePatch(&kp_nametagstem, "K_NAMEST"); HU_UpdatePatch(&kp_trickcool[0], "K_COOL1"); @@ -942,26 +995,72 @@ void K_LoadKartHUDGraphics(void) HU_UpdatePatch(&kp_spraycantarget_near[1][i], "%s", buffer); } - K_LoadButtonGraphics(kp_button_a[0], 'A'); - K_LoadButtonGraphics(kp_button_a[1], 'N'); - K_LoadButtonGraphics(kp_button_b[0], 'B'); - K_LoadButtonGraphics(kp_button_b[1], 'O'); - K_LoadButtonGraphics(kp_button_c[0], 'C'); - K_LoadButtonGraphics(kp_button_c[1], 'P'); - K_LoadButtonGraphics(kp_button_x[0], 'D'); - K_LoadButtonGraphics(kp_button_x[1], 'Q'); - K_LoadButtonGraphics(kp_button_y[0], 'E'); - K_LoadButtonGraphics(kp_button_y[1], 'R'); - K_LoadButtonGraphics(kp_button_z[0], 'F'); - K_LoadButtonGraphics(kp_button_z[1], 'S'); - K_LoadButtonGraphics(kp_button_start, 'G'); - K_LoadButtonGraphics(kp_button_l, 'H'); - K_LoadButtonGraphics(kp_button_r, 'I'); - K_LoadButtonGraphics(kp_button_up, 'J'); - K_LoadButtonGraphics(kp_button_down, 'K'); - K_LoadButtonGraphics(kp_button_right, 'L'); - K_LoadButtonGraphics(kp_button_left, 'M'); - K_LoadButtonGraphics(kp_button_dpad, 'T'); + K_LoadButtonGraphics(kp_button_a, "A"); + K_LoadButtonGraphics(kp_button_b, "B"); + K_LoadButtonGraphics(kp_button_c, "C"); + K_LoadButtonGraphics(kp_button_x, "X"); + K_LoadButtonGraphics(kp_button_y, "Y"); + K_LoadButtonGraphics(kp_button_z, "Z"); + K_LoadButtonGraphics(kp_button_l, "L1"); + K_LoadButtonGraphics(kp_button_r, "R1"); + K_LoadButtonGraphics(kp_button_up, "ARU"); + K_LoadButtonGraphics(kp_button_down, "ARD"); + K_LoadButtonGraphics(kp_button_right, "ARR"); + K_LoadButtonGraphics(kp_button_left, "ARL"); + K_LoadButtonGraphics(kp_button_start, "S"); + + K_LoadGenericButtonGraphics(kp_button_lua1, "LU1"); + K_LoadGenericButtonGraphics(kp_button_lua2, "LU2"); + K_LoadGenericButtonGraphics(kp_button_lua3, "LU3"); + + HU_UpdatePatch(&gen_button_keyleft[0], "TLK_L"); + HU_UpdatePatch(&gen_button_keyleft[1], "TLK_LB"); + HU_UpdatePatch(&gen_button_keyright[0], "TLK_R"); + HU_UpdatePatch(&gen_button_keyright[1], "TLK_RB"); + HU_UpdatePatch(&gen_button_keycenter[0], "TLK_M"); + HU_UpdatePatch(&gen_button_keycenter[1], "TLK_MB"); + + K_LoadGenericButtonGraphics(gen_button_dpad, "DP"); + K_LoadGenericButtonGraphics(gen_button_a, "A"); + K_LoadGenericButtonGraphics(gen_button_b, "B"); + K_LoadGenericButtonGraphics(gen_button_x, "X"); + K_LoadGenericButtonGraphics(gen_button_y, "Y"); + K_LoadGenericButtonGraphics(gen_button_lb, "L1"); + K_LoadGenericButtonGraphics(gen_button_rb, "R1"); + K_LoadGenericButtonGraphics(gen_button_lt, "L2"); + K_LoadGenericButtonGraphics(gen_button_rt, "R2"); + K_LoadGenericButtonGraphics(gen_button_ls, "L3"); + K_LoadGenericButtonGraphics(gen_button_rs, "R3"); + K_LoadGenericButtonGraphics(gen_button_start, "S"); + K_LoadGenericButtonGraphics(gen_button_back, "I"); + + HU_UpdatePatch(&kp_voice_localopen, "VOXCLO"); + for (i = 0; i < 16; i++) + { + HU_UpdatePatch(&kp_voice_localactive[i], "VOXCLA%d", i); + HU_UpdatePatch(&kp_voice_localactiveoverlay[i], "VOXCLB%d", i); + } + HU_UpdatePatch(&kp_voice_localmuted, "VOXCLM"); + HU_UpdatePatch(&kp_voice_localdeafened, "VOXCLD"); + HU_UpdatePatch(&kp_voice_remoteopen, "VOXCRO"); + HU_UpdatePatch(&kp_voice_remoteactive, "VOXCRA"); + HU_UpdatePatch(&kp_voice_remotemuted, "VOXCRM"); + HU_UpdatePatch(&kp_voice_remotedeafened, "VOXCRD"); + for (i = 0; i < 3; i++) + { + HU_UpdatePatch(&kp_voice_tagactive[i], "VOXCTA%d", i); + } + + HU_UpdatePatch(&kp_team_bar[0], "TEAM_B"); + HU_UpdatePatch(&kp_team_sticker[0], "TEAM_S"); + HU_UpdatePatch(&kp_team_bar[1], "TEAM4B"); + HU_UpdatePatch(&kp_team_sticker[1], "TEAM4S"); + HU_UpdatePatch(&kp_team_underlay[0][0], "TEAM_UL"); + HU_UpdatePatch(&kp_team_underlay[0][1], "TEAM_UR"); + HU_UpdatePatch(&kp_team_underlay[1][0], "TEAM4UL"); + HU_UpdatePatch(&kp_team_underlay[1][1], "TEAM4UR"); + HU_UpdatePatch(&kp_team_minihead, "TEAM4H"); + HU_UpdatePatch(&kp_team_you, "TEAM_YOU"); } // For the item toggle menu @@ -1641,7 +1740,7 @@ static void K_drawKartItem(void) } UINT8 *boxmap = NULL; - if (stplyr->itemRoulette.active && (stplyr->itemRoulette.speed - stplyr->itemRoulette.tics < 3) && stplyr->itemRoulette.index == 0) + if (stplyr->itemRoulette.active && (stplyr->itemRoulette.speed - stplyr->itemRoulette.tics < 3) && stplyr->itemRoulette.index == 0 && stplyr->itemRoulette.itemListLen > 1) { boxmap = R_GetTranslationColormap(TC_ALLWHITE, SKINCOLOR_WHITE, GTC_CACHE); } @@ -1811,6 +1910,125 @@ static void K_drawKartItem(void) } } +// So, like, we've already established that HUD code is unsavable, right? +// == SHITGARBAGE UNLIMITED 3: HACKS GONE WILD == +static void K_drawBackupItem(void) +{ + bool tiny = r_splitscreen > 1; + patch_t *localpatch[3] = { kp_nodraw, kp_nodraw, kp_nodraw }; + patch_t *localbg = (kp_itembg[2]); + patch_t *localinv = kp_invincibility[((leveltime % (6*3)) / 3) + 7 + tiny]; + INT32 fx = 0, fy = 0, fflags = 0, tx = 0, ty = 0; // final coords for hud and flags... + const INT32 numberdisplaymin = 2; + skincolornum_t localcolor[3] = { static_cast(stplyr->skincolor) }; + SINT8 colormode[3] = { TC_RAINBOW }; + boolean flipamount = false; // Used for 3P/4P splitscreen to flip item amount stuff + + if (stplyr->backupitemamount <= 0) + return; + + switch (stplyr->backupitemtype) + { + case KITEM_INVINCIBILITY: + localpatch[1] = localinv; + localbg = kp_itembg[2]; + break; + + case KITEM_ORBINAUT: + localpatch[1] = kp_orbinaut[tiny+4]; + break; + + case KITEM_SPB: + case KITEM_LIGHTNINGSHIELD: + case KITEM_BUBBLESHIELD: + case KITEM_FLAMESHIELD: + localbg = kp_itembg[2]; + /*FALLTHRU*/ + + default: + localpatch[1] = K_GetCachedItemPatch(stplyr->backupitemtype, 1 + tiny); + + if (localpatch[1] == NULL) + localpatch[1] = kp_nodraw; // diagnose underflows + break; + } + + // pain and suffering defined below + if (!(R_GetViewNumber() & 1) || (!tiny)) // If we are P1 or P3... + { + fx = ITEM_X; + fy = ITEM_Y; + fflags = V_SNAPTOLEFT|V_SNAPTOTOP|V_SPLITSCREEN; + } + else // else, that means we're P2 or P4. + { + fx = ITEM2_X; + fy = ITEM2_Y; + fflags = V_SNAPTORIGHT|V_SNAPTOTOP|V_SPLITSCREEN; + flipamount = true; + } + + if (r_splitscreen == 1) + { + fy -= 5; + } + + // final fudge - vegeta 2025 + if (tiny && !(R_GetViewNumber() & 1)) // P1/P3 4P + { + fx += 26; + fy += 5; + tx += 10; + ty += 18; + } + else if (tiny && (R_GetViewNumber() & 1)) // P2/P4 4P + { + fx += -4; + fy += 5; + tx += 1; + ty += 18; + } + else // 1P/2P + { + fx += 30; + fy += -10; + tx += 25; + ty += 30; + } + + boolean transflag = V_HUDTRANS; + + // I feel like the cardinal sin of all evolving HUDcode is, like, assuming the old offsets do something that makes sense. + + if (stplyr->backupitemamount >= numberdisplaymin && stplyr->itemRoulette.active == false) + { + /* + // Then, the numbers: + V_DrawScaledPatch( + fx + (flipamount ? 48 : 0), fy, + V_HUDTRANS|V_SLIDEIN|fflags|(flipamount ? V_FLIP : 0), + kp_itemmulsticker[offset] + ); // flip this graphic for p2 and p4 in split and shift it. + */ + + V_DrawFixedPatch( + fx<backupitemamount)); + } + else + { + V_DrawFixedPatch( + fx<roundscore; } + UINT32 top_score() const { return G_TeamOrIndividualScore( top() ); } bool near_goal() const { @@ -2500,7 +2718,7 @@ void PositionFacesInfo::draw_1p() } // Draw GOAL - bool skull = g_pointlimit && (g_pointlimit <= stplyr->roundscore); + bool skull = g_pointlimit && (g_pointlimit <= G_TeamOrIndividualScore(stplyr)); INT32 height = i*18; INT32 GOAL_Y = Y-height; @@ -2663,6 +2881,30 @@ void PositionFacesInfo::draw_1p() ); } + // Voice speaking indicator + if (netgame && !players[rankplayer[i]].bot && cv_voice_servermute.value == 0) + { + patch_t *voxmic; + if (S_IsPlayerVoiceActive(rankplayer[i])) + { + voxmic = kp_voice_remoteactive; + } + else if (players[rankplayer[i]].pflags2 & (PF2_SELFDEAFEN | PF2_SERVERDEAFEN)) + { + voxmic = kp_voice_remotedeafened; + } + else if (players[rankplayer[i]].pflags2 & (PF2_SELFMUTE | PF2_SERVERMUTE)) + { + voxmic = kp_voice_remotemuted; + } + else + { + voxmic = kp_voice_remoteopen; + } + + V_DrawScaledPatch(FACE_X + 10, Y - 4, V_HUDTRANS|V_SLIDEIN|V_SNAPTOLEFT, voxmic); + } + Y -= 18; } } @@ -2963,13 +3205,13 @@ static void K_drawKartEmeralds(void) INT32 K_GetTransFlagFromFixed(fixed_t value) { value = std::clamp(value, FRACUNIT/2, FRACUNIT*3/2); - + // Calculate distance from 1.0 fixed_t distance = abs(FRACUNIT - value); - + // Map the distance to 0-10 range (10 = closest to 1.0, 0 = farthest from 1.0) INT32 transLevel = 10 - ((distance * 10) / (FRACUNIT/2)); - + // Map 0-10 to V_TRANS flags switch (transLevel) { case 10: return V_70TRANS; // Most transparent (closest to 1.0) @@ -2987,12 +3229,316 @@ INT32 K_GetTransFlagFromFixed(fixed_t value) } } -static void K_drawKartLaps(void) +static INT32 easedallyscore = 0; +static tic_t scorechangecooldown = 0; +// Mildly ugly. Don't want to export this to khud when it's so nicely handled here, +// but HUD hooks run at variable timing based on your actual framerate. +static tic_t lastleveltime = 0; + +static void K_drawKartTeamScores(void) +{ + if (G_GametypeHasTeams() == false) + { + return; + } + + if (TEAM__MAX != 3) + return; // "maybe someday" - the magic conch + + // I get to write HUD code from scratch, so it's going to be horribly + // verbose and obnoxious. + + UINT8 use4p = (r_splitscreen > 1) ? 1 : 0; + + INT32 basex = BASEVIDWIDTH/2 + 20; + INT32 basey = 0; + INT32 flags = V_HUDTRANS|V_SLIDEIN; + INT32 snapflags = V_SNAPTOTOP|V_SNAPTORIGHT; + if (use4p) + snapflags = V_SNAPTOTOP; + flags |= snapflags; + + // bar stuff, relative to base + INT32 barwidth = 124; + INT32 barheight = 4; + INT32 barx = 5; // 58 + INT32 bary = 12; // -8 + + // team score stuff, relative to base + INT32 scorex = 67; + INT32 scorey = 20; + INT32 scoregap = 8; + INT32 scoreoffset = 1; + + // your score, relative to base + INT32 youx = 108; + INT32 youy = 26; // youy you arrive at the rising sus + + // minimap team leaders, relative to BARS + INT32 facex = -4; + INT32 facey = -4; + INT32 faceoff = 7; + INT32 facesize = 2; + + if (use4p) + { + basex = 127; + basey = 5; + + barwidth = 62; + barheight = 2; + barx = 2; + bary = 6; + + scorex = 33; + scorey = 10; + scoregap = 2; + + facex = -2; + facey = -5; + faceoff = 4; + } + + UINT8 allies = stplyr->team; + UINT8 enemies = (allies == TEAM_ORANGE) ? TEAM_BLUE : TEAM_ORANGE; + + UINT8 *allycolor = R_GetTranslationColormap(TC_RAINBOW, g_teaminfo[allies].color, GTC_CACHE); + UINT8 *enemycolor = R_GetTranslationColormap(TC_RAINBOW, g_teaminfo[enemies].color, GTC_CACHE); + + UINT16 allyscore = g_teamscores[allies]; + UINT16 enemyscore = g_teamscores[enemies]; + UINT16 totalscore = allyscore + enemyscore; + + using srb2::Draw; + srb2::Draw::Font scorefont = Draw::Font::kTimer; + + if (totalscore > 99) + { + scorefont = Draw::Font::kThinTimer; + scoreoffset--; + } + + if (use4p) + { + if (totalscore > 99) + { + scorefont = Draw::Font::kPing; + } + else + { + scorefont = Draw::Font::kZVote; + } + } + + UINT32 youscore = stplyr->teamimportance; + if (gametyperules & GTR_POINTLIMIT) + { + youscore = stplyr->roundscore; + } + + UINT8 ri = 6; // "ramp index", picks drawfill color from team skincolors + INT32 allyfill = skincolors[g_teaminfo[allies].color].ramp[ri]; + INT32 enemyfill = skincolors[g_teaminfo[enemies].color].ramp[ri]; + + INT32 winning = 0; + if (allyscore > enemyscore) + winning = 1; + else if (enemyscore > allyscore) + winning = -1; + + UINT8 *winnercolor = (winning == 1) ? + R_GetTranslationColormap(TC_RAINBOW, g_teaminfo[allies].color, GTC_CACHE) : + R_GetTranslationColormap(TC_RAINBOW, g_teaminfo[enemies].color, GTC_CACHE); + + if (scorechangecooldown) + scorechangecooldown--; + + // prevent giga flicker on team scoring + if (easedallyscore == allyscore) + { + // :O + } + else + { + if (lastleveltime != leveltime) // Timing consistency + { + INT32 delta = abs(easedallyscore - allyscore); // how wrong is display score? + + if (scorechangecooldown == 0) + { + if (allyscore > easedallyscore) + { + easedallyscore++; + if (!cv_reducevfx.value) + allycolor = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_WHITE, GTC_CACHE); + } + else + { + easedallyscore--; + if (!cv_reducevfx.value) + enemycolor = R_GetTranslationColormap(TC_BLINK, SKINCOLOR_WHITE, GTC_CACHE); + } + scorechangecooldown = TICRATE/delta; + } + } + + // replace scores with eased scores + allyscore = easedallyscore; + enemyscore = totalscore - allyscore; + } + + lastleveltime = leveltime; + + fixed_t enemypercent = FixedDiv(enemyscore*FRACUNIT, totalscore*FRACUNIT); + // fixed_t allypercent = FixedDiv(allyscore*FRACUNIT, totalscore*FRACUNIT); + INT32 enemywidth = FixedInt(FixedMul(enemypercent, barwidth*FRACUNIT)); + INT32 allywidth = barwidth - enemywidth; + + player_t *bestenemy = NULL; + INT32 bestenemyscore = -1; + player_t *bestally = NULL; + INT32 bestallyscore = -1; + + boolean useroundscore = false; + if (gametyperules & GTR_POINTLIMIT) + useroundscore = true; + + for (UINT8 i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator || players[i].team == TEAM_UNASSIGNED) + continue; + + INT32 score = useroundscore ? players[i].roundscore : players[i].teamimportance; + + if (players[i].team == allies) + { + if (bestallyscore < score || (bestallyscore == score && useroundscore == false && players[i].teamposition < bestally->teamposition)) + { + bestallyscore = score; + bestally = &players[i]; + } + } + else + { + if (bestenemyscore < score || (bestenemyscore == score && useroundscore == false && players[i].teamposition < bestenemy->teamposition)) + { + bestenemyscore = score; + bestenemy = &players[i]; + } + } + } + + // Draw at the top and bottom of the screen in 4P. + boolean goagain = use4p; + + draw: + + V_DrawScaledPatch(basex, basey, flags, kp_team_sticker[use4p]); + V_DrawMappedPatch(basex, basey, flags, kp_team_underlay[use4p][0], enemycolor); + V_DrawMappedPatch(basex, basey, flags, kp_team_underlay[use4p][1], allycolor); + V_DrawMappedPatch(basex, basey, flags, kp_team_bar[use4p], winnercolor); + if (!use4p) + V_DrawScaledPatch(basex, basey, flags, kp_team_you); + + if (V_GetHUDTranslucency(0) != 10) + return; + + V_DrawFill(basex+barx, basey+bary, enemywidth, barheight, enemyfill|flags); + V_DrawFill(basex+barx+enemywidth, basey+bary, allywidth, barheight, allyfill|flags); + + // Goofy, but we want the winning team to draw on top + boolean drawally = (winning != 1); + for (UINT8 drew = 0; drew < 2; drew++) + { + if (use4p) + { + if (bestenemy && !drawally) + { + V_DrawMappedPatch(basex+barx+facex+enemywidth-facesize+faceoff, basey+bary+facey, flags|V_FLIP, kp_team_minihead, enemycolor); + } + if (bestally && drawally) + { + V_DrawMappedPatch(basex+barx+facex+enemywidth+facesize, basey+bary+facey, flags, kp_team_minihead, allycolor); + } + } + else + { + if (bestenemy && !drawally) + { + UINT8 *colormap = R_GetTranslationColormap(bestenemy->skin, static_cast(bestenemy->skincolor), GTC_CACHE); + V_DrawMappedPatch(basex+barx+facex+enemywidth-facesize+faceoff, basey+bary+facey, flags|V_FLIP, faceprefix[bestenemy->skin][FACE_MINIMAP], colormap); + } + if (bestally && drawally) + { + UINT8 *colormap = R_GetTranslationColormap(bestally->skin, static_cast(bestally->skincolor), GTC_CACHE); + V_DrawMappedPatch(basex+barx+facex+enemywidth+facesize, basey+bary+facey, flags, faceprefix[bestally->skin][FACE_MINIMAP], colormap); + } + } + + + drawally = !drawally; + } + + Draw enemynum = Draw(basex+scorex-scoregap, basey+scorey).flags(flags).font(scorefont).align(Draw::Align::kRight).colorize(g_teaminfo[enemies].color); + Draw allynum = Draw(basex+scorex+scoregap+scoreoffset, basey+scorey).flags(flags).font(scorefont).align(Draw::Align::kLeft).colorize(g_teaminfo[allies].color); + + if (totalscore > 99) + { + enemynum.text("{:03}", enemyscore); + allynum.text("{:03}", allyscore); + } + else + { + enemynum.text("{:02}", enemyscore); + allynum.text("{:02}", allyscore); + } + + if (!use4p) + { + Draw you = Draw(basex+youx, basey+youy).flags(flags).font(Draw::Font::kZVote).colorize(g_teaminfo[allies].color); + you.text("{:02}", youscore); + } + + if (goagain) + { + goagain = false; + flags |= V_SNAPTOBOTTOM; + flags &= ~V_SNAPTOTOP; + basey = 170; + goto draw; + } + + /* + for (INT32 i = TEAM_UNASSIGNED+1; i < TEAM__MAX; i++) + { + INT32 x = BASEVIDWIDTH/2; + + x += -12 + (24 * (i - 1)); + + V_DrawCenteredString(x, 5, g_teaminfo[i].chat_color, va("%d", g_teamscores[i])); + + if (stplyr->team == i) + { + UINT32 individual_score = stplyr->teamimportance; + if (gametyperules & GTR_POINTLIMIT) + { + individual_score = stplyr->roundscore; + } + + V_DrawCenteredString(x, 15, g_teaminfo[i].chat_color, va("+%d", individual_score)); + } + } + */ +} + +static boolean K_drawKartLaps(void) { INT32 splitflags = V_SNAPTOBOTTOM|V_SNAPTOLEFT|V_SPLITSCREEN; INT32 bump = 0; boolean drewsticker = false; + UINT16 displayEXP = stplyr->exp; + // Jesus Christ. // I do not understand the way this system of offsets is laid out at all, // so it's probably going to be pretty bad to maintain. Sorry. @@ -3010,7 +3556,7 @@ static void K_drawKartLaps(void) V_DrawCenteredString(BASEVIDWIDTH/2, 5, flashflag, va("%d", stplyr->duelscore)); } - boolean drawinglaps = (numlaps != 1 && !K_InRaceDuel()); + boolean drawinglaps = (numlaps != 1 && !K_InRaceDuel() && displayEXP != UINT16_MAX); if (drawinglaps) { @@ -3082,10 +3628,12 @@ static void K_drawKartLaps(void) } } - UINT16 displayEXP = std::clamp(FixedMul(std::max(stplyr->exp, FRACUNIT/2), (500/K_GetNumGradingPoints())*stplyr->gradingpointnum), 0, 999); - // EXP - if (r_splitscreen > 1) + if (displayEXP == UINT16_MAX) + { + ; + } + else if (r_splitscreen > 1) { INT32 fx = 0, fy = 0, fr = 0; INT32 flipflag = 0; @@ -3123,8 +3671,8 @@ static void K_drawKartLaps(void) // WHAT IS THIS? // WHAT ARE YOU FUCKING TALKING ABOUT? V_DrawMappedPatch(fr, fy, V_HUDTRANS|V_SLIDEIN|splitflags, kp_exp[1], R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_MUSTARD, GTC_CACHE)); - auto transflag = K_GetTransFlagFromFixed(stplyr->exp); - skincolornum_t overlaycolor = stplyr->exp < FRACUNIT ? SKINCOLOR_RUBY : SKINCOLOR_ULTRAMARINE ; + auto transflag = K_GetTransFlagFromFixed(stplyr->gradingfactor); + skincolornum_t overlaycolor = stplyr->gradingfactor < FRACUNIT ? SKINCOLOR_RUBY : SKINCOLOR_ULTRAMARINE ; auto colormap = R_GetTranslationColormap(TC_RAINBOW, overlaycolor, GTC_CACHE); V_DrawMappedPatch(fr, fy, transflag|V_SLIDEIN|splitflags, kp_exp[1], colormap); @@ -3140,8 +3688,8 @@ static void K_drawKartLaps(void) V_DrawMappedPatch(LAPS_X+bump, LAPS_Y, V_HUDTRANS|V_SLIDEIN|splitflags, kp_exp[0], R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_MUSTARD, GTC_CACHE)); - auto transflag = K_GetTransFlagFromFixed(stplyr->exp); - skincolornum_t overlaycolor = stplyr->exp < FRACUNIT ? SKINCOLOR_RUBY : SKINCOLOR_ULTRAMARINE ; + auto transflag = K_GetTransFlagFromFixed(stplyr->gradingfactor); + skincolornum_t overlaycolor = stplyr->gradingfactor < FRACUNIT ? SKINCOLOR_RUBY : SKINCOLOR_ULTRAMARINE ; auto colormap = R_GetTranslationColormap(TC_RAINBOW, overlaycolor, GTC_CACHE); V_DrawMappedPatch(LAPS_X+bump, LAPS_Y, transflag|V_SLIDEIN|splitflags, kp_exp[0], colormap); @@ -3149,6 +3697,8 @@ static void K_drawKartLaps(void) Draw row = Draw(LAPS_X+23+bump, LAPS_Y+3).flags(V_HUDTRANS|V_SLIDEIN|splitflags).font(Draw::Font::kThinTimer); row.text("{:03}", displayEXP); } + + return drewsticker; } #define RINGANIM_FLIPFRAME (RINGANIM_NUMFRAMES/2) @@ -3197,6 +3747,11 @@ static void K_drawRingCounter(boolean gametypeinfoshown) ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_CRIMSON, GTC_CACHE); colorring = true; } + else if (K_LegacyRingboost(stplyr) && (leveltime%2)) + { + ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_BLUEBERRY, GTC_CACHE); + colorring = true; + } else if (stplyr->hudrings >= 20) // Maxed out ringmap = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_YELLOW, GTC_CACHE); @@ -3318,7 +3873,7 @@ static void K_drawRingCounter(boolean gametypeinfoshown) .align(Draw::Align::kCenter) .width(uselives ? (stplyr->lives >= 10 ? 70 : 64) : 33) .small_sticker(); - + if (stplyr->overdrive) { V_DrawMappedPatch(LAPS_X+7-8, fy-5-8, V_HUDTRANS|V_SLIDEIN|splitflags, kp_overdrive[leveltime%32], R_GetTranslationColormap(TC_RAINBOW, static_cast(stplyr->skincolor), GTC_CACHE)); @@ -3364,7 +3919,7 @@ static void K_drawRingCounter(boolean gametypeinfoshown) V_DrawScaledPatch(LAPS_X-5, fy-17, V_HUDTRANS|V_SLIDEIN|splitflags, kp_ringspblock[stplyr->karthud[khud_ringspblock]]); UINT32 greyout = V_HUDTRANS; - + if (stplyr->superringdisplay) { greyout = V_HUDTRANSHALF; @@ -4045,15 +4600,23 @@ static void K_DrawTypingDot(fixed_t x, fixed_t y, UINT8 duration, player_t *p, I static void K_DrawTypingNotifier(fixed_t x, fixed_t y, player_t *p, INT32 flags) { - if (p->cmd.flags & TICCMD_TYPING) + int playernum = p - players; + if (p->cmd.flags & TICCMD_TYPING || S_IsPlayerVoiceActive(playernum)) { V_DrawFixedPatch(x, y, FRACUNIT, V_SPLITSCREEN|flags, kp_talk, NULL); - + } + if (p->cmd.flags & TICCMD_TYPING) + { /* spacing closer with the last two looks a better most of the time */ K_DrawTypingDot(x + 3*FRACUNIT, y, 15, p, flags); K_DrawTypingDot(x + 6*FRACUNIT - FRACUNIT/3, y, 31, p, flags); K_DrawTypingDot(x + 9*FRACUNIT - FRACUNIT/3, y, 47, p, flags); } + else if (S_IsPlayerVoiceActive(playernum)) + { + patch_t* voxmic = kp_voice_tagactive[(leveltime / 3) % 3]; + V_DrawFixedPatch(x + 6*FRACUNIT, y - 12*FRACUNIT, FRACUNIT, V_SPLITSCREEN|flags, voxmic, NULL); + } } // see also K_drawKartItem @@ -4071,7 +4634,7 @@ static void K_DrawNameTagItemSpy(INT32 x, INT32 y, player_t *p, INT32 flags) flip = P_MobjFlip(p->mo); flipboxoffset = 8; } - + Draw bar = Draw(x, y).flags(V_NOSCALESTART|flags); Draw box = tiny ? bar.xy(-22 * vid.dupx, (-17+flipboxoffset) * vid.dupy) : bar.xy(-40 * vid.dupx, (-26+flipboxoffset) * vid.dupy); @@ -4336,8 +4899,8 @@ playertagtype_t K_WhichPlayerTag(player_t *p) void K_DrawPlayerTag(fixed_t x, fixed_t y, player_t *p, playertagtype_t type, boolean foreground) { - INT32 flags = P_IsObjectFlipped(p->mo) ? V_VFLIP : 0; - + INT32 flags = P_IsObjectFlipped(p->mo) ? V_VFLIP : 0; + switch (type) { case PLAYERTAG_LOCAL: @@ -4573,13 +5136,13 @@ static void K_drawKartProgressionMinimapIcon(UINT32 distancetofinish, INT32 hudx position_t K_GetKartObjectPosToMinimapPos(fixed_t objx, fixed_t objy) { fixed_t amnumxpos, amnumypos; - + amnumxpos = (FixedMul(objx, minimapinfo.zoom) - minimapinfo.offs_x); amnumypos = -(FixedMul(objy, minimapinfo.zoom) - minimapinfo.offs_y); if (encoremode) amnumxpos = -amnumxpos; - + return (position_t){amnumxpos, amnumypos}; } @@ -4591,10 +5154,10 @@ static void K_drawKartMinimapIcon(fixed_t objx, fixed_t objy, INT32 hudx, INT32 // am xpos & ypos are the icon's starting position. Withouht // it, they wouldn't 'spawn' on the top-right side of the HUD. - + position_t amnumpos; INT32 amxpos, amypos; - + amnumpos = K_GetKartObjectPosToMinimapPos(objx, objy); amxpos = amnumpos.x + ((hudx - (SHORT(icon->width))/2)<spectator || !stplyr->mo || (stplyr->mo->renderflags & RF_DONTDRAW)) + if (stplyr->spectator || !stplyr->mo || (stplyr->mo->renderflags & RF_DONTDRAW || stplyr->mo->state == &states[S_KART_DEAD])) return; { @@ -6170,7 +6733,7 @@ static void K_DrawGPRankDebugger(void) V_DrawThinString(0, 10, V_SNAPTOTOP|V_SNAPTOLEFT, va("PTS: %d / %d", grandprixinfo.rank.winPoints, grandprixinfo.rank.totalPoints)); V_DrawThinString(0, 20, V_SNAPTOTOP|V_SNAPTOLEFT, - va("LAPS: %d / %d", grandprixinfo.rank.laps, grandprixinfo.rank.totalLaps)); + va("LAPS: %d / %d", grandprixinfo.rank.exp, grandprixinfo.rank.totalExp)); V_DrawThinString(0, 30, V_SNAPTOTOP|V_SNAPTOLEFT, va("CONTINUES: %d", grandprixinfo.rank.continuesUsed)); V_DrawThinString(0, 40, V_SNAPTOTOP|V_SNAPTOLEFT, @@ -6204,21 +6767,21 @@ typedef enum typedef struct { - std::string text; + srb2::String text; sfxenum_t sound; } message_t; struct messagestate_t { - std::deque messages; - std::string objective = ""; + std::deque messages; + srb2::String objective = ""; tic_t timer = 0; boolean persist = false; messagemode_t mode = MM_IN; const tic_t speedyswitch = 2*TICRATE; const tic_t lazyswitch = 4*TICRATE; - void add(std::string msg) + void add(srb2::String msg) { messages.push_back(msg); } @@ -6256,7 +6819,7 @@ struct messagestate_t switch (mode) { case MM_IN: - if (timer > messages[0].length()) + if (timer > messages[0].size()) switch_mode(MM_HOLD); break; case MM_HOLD: @@ -6266,7 +6829,7 @@ struct messagestate_t switch_mode(MM_OUT); break; case MM_OUT: - if (timer > messages[0].length()) + if (timer > messages[0].size()) next(); break; } @@ -6318,7 +6881,7 @@ void K_ClearPersistentMessages() } // Return value can be used for "paired" splitscreen messages, true = was displayed -void K_AddMessageForPlayer(const player_t *player, const char *msg, boolean interrupt, boolean persist) +void K_AddMessageForPlayer(player_t *player, const char *msg, boolean interrupt, boolean persist) { if (!player) return; @@ -6334,7 +6897,7 @@ void K_AddMessageForPlayer(const player_t *player, const char *msg, boolean inte if (interrupt) state->clear(); - std::string parsedmsg = srb2::Draw::TextElement().parse(msg).string(); + std::string parsedmsg = srb2::Draw::TextElement().as(player - players).parse(msg).string(); if (persist) state->objective = parsedmsg; @@ -6394,7 +6957,7 @@ static void K_DrawMessageFeed(void) text.font(Draw::Font::kMenu); - UINT8 x = 160; + UINT8 x = BASEVIDWIDTH/2; UINT8 y = 10; SINT8 shift = 0; if (r_splitscreen >= 2) @@ -6418,6 +6981,7 @@ static void K_DrawMessageFeed(void) if (i >= 1) y += BASEVIDHEIGHT / 2; } + UINT16 sw = text.width(); K_DrawSticker(x - sw/2, y, sw, 0, true); @@ -6430,6 +6994,7 @@ void K_drawKartHUD(void) boolean islonesome = false; UINT8 viewnum = R_GetViewNumber(); boolean freecam = camera[viewnum].freecam; //disable some hud elements w/ freecam + INT32 flags = V_HUDTRANS|V_SLIDEIN|V_SNAPTOTOP|V_SNAPTORIGHT; // Define the X and Y for each drawn object // This is handled by console/menu values @@ -6454,6 +7019,40 @@ void K_drawKartHUD(void) K_drawEmeraldWin(false); } + // In case of font debugging break glass +#if 0 + using srb2::Draw; + + if (1) + { + CV_StealthSetValue(cv_descriptiveinput, 0); + Draw::TextElement text = Draw::TextElement().parse("Hamburger Hamburger\n\nHamburger Hamburger\n\nHamburger \xEB\xEF\xA0\xEB\xEF\xA1\xEB\xEF\xA2\xEB\xEF\xA3\xEB\xEF\xA4\xEB\xEF\xA5\xEB\xEF\xA6\xEB\xEF\xA7\xEB\xEF\xA8\xEB\xEF\xA9\xEB\xEF\xAA\xEB\xEF\xAB\xEB\xEF\xAC Hamburger"); + + UINT8 fakeoff = (stplyr - players)*40; + Draw(5, 5+fakeoff).align((srb2::Draw::Align)0).font(Draw::Font::kMenu).text(text); + Draw(40, 80+fakeoff).align((srb2::Draw::Align)0).font(Draw::Font::kThin).text(text); + } + + if (0) + { + Draw::TextElement text = Draw::TextElement().parse("\xEELEFTSPACE\xEE\n\xEESPC\xEE \xEETAB\xEE\nA \xEF\xA0 A\nB \xEF\xA1 B\nX \xEF\xA2 X\nY \xEF\xA3 Y\nLB \xEF\xA4 LB\nRB \xEF\xA5 RB\nLT \xEF\xA6 LT\nRT \xEF\xA7 RT\nST \xEF\xA8 ST\nBK \xEF\xA9 BK\nLS \xEF\xAA LS\nRS \xEF\xAB RS\n"); + + UINT8 offset = 0; + Draw(160+offset, 5).align((srb2::Draw::Align)1).font(Draw::Font::kThin).text(text); + Draw(55+offset, 5).align((srb2::Draw::Align)1).font(Draw::Font::kMenu).text(text); + } + + if (0) + { + Draw::TextElement text = Draw::TextElement().parse("\xEELEFTSPACE\xEE\n\xEESPC\xEE \xEETAB\xEE\nA \xEB\xEF\xA0 A\nB \xEB\xEF\xA1 B\nX \xEB\xEF\xA2 X\nY \xEB\xEF\xA3 Y\nLB \xEB\xEF\xA4 LB\nRB \xEB\xEF\xA5 RB\nLT \xEB\xEF\xA6 LT\nRT \xEB\xEF\xA7 RT\nST \xEB\xEF\xA8 ST\nBK \xEB\xEF\xA9 BK\nLS \xEB\xEF\xAA LS\nRS \xEB\xEF\xAB RS\n"); + + UINT8 offset = 0; + Draw(160+offset, 5).align((srb2::Draw::Align)1).font(Draw::Font::kThin).text(text); + Draw(55+offset, 5).align((srb2::Draw::Align)1).font(Draw::Font::kMenu).text(text); + } +#endif + + if (!demo.attract) { // Draw the CHECK indicator before the other items, so it's overlapped by everything else @@ -6478,6 +7077,12 @@ void K_drawKartHUD(void) K_drawKartMinimap(); } + if (!modeattacking && mapheaderinfo[gamemap-1]->menuttl[0] && (gametype == GT_TUTORIAL || gametype == GT_SPECIAL)) + { + using srb2::Draw; + Draw(BASEVIDWIDTH - 4, 5).flags(flags|V_FORCEUPPERCASE).align(Draw::Align::kRight).font(Draw::Font::kMedium).text(mapheaderinfo[gamemap-1]->menuttl); + } + if (demo.attract) ; else if (gametype == GT_TUTORIAL) @@ -6490,7 +7095,6 @@ void K_drawKartHUD(void) if (LUA_HudEnabled(hud_time)) { bool ta = modeattacking && !demo.playback; - INT32 flags = V_HUDTRANS|V_SLIDEIN|V_SNAPTOTOP|V_SNAPTORIGHT; tic_t realtime = stplyr->realtime; @@ -6509,7 +7113,7 @@ void K_drawKartHUD(void) } } - if (modeattacking || (gametyperules & GTR_TIMELIMIT)) + if (modeattacking || (gametyperules & GTR_TIMELIMIT) || cv_drawtimer.value) K_drawKartTimestamp(realtime, TIME_X, TIME_Y + (ta ? 2 : 0), flags, 0); if (modeattacking) @@ -6517,10 +7121,11 @@ void K_drawKartHUD(void) if (ta) { using srb2::Draw; + Draw::TextElement text = Draw::TextElement().parse(" Restart"); Draw(BASEVIDWIDTH - 19, 2) .flags(flags | V_YELLOWMAP) .align(Draw::Align::kRight) - .text("\xBE Restart"); + .text(text.string()); } else { @@ -6558,7 +7163,7 @@ void K_drawKartHUD(void) if (r_splitscreen == 1) { - if (LUA_HudEnabled(hud_time)) + if (LUA_HudEnabled(hud_time) && ((gametyperules & GTR_TIMELIMIT) || cv_drawtimer.value)) { K_drawKart2PTimestamp(); } @@ -6570,7 +7175,7 @@ void K_drawKartHUD(void) } else if (viewnum == r_splitscreen) { - if (LUA_HudEnabled(hud_time)) + if (LUA_HudEnabled(hud_time) && ((gametyperules & GTR_TIMELIMIT) || cv_drawtimer.value)) { K_drawKart4PTimestamp(); } @@ -6651,7 +7256,14 @@ void K_drawKartHUD(void) K_drawKartEmeralds(); } else if (!islonesome && !K_Cooperative()) + { K_DrawKartPositionNum(stplyr->position); + } + } + + if (G_GametypeHasTeams() == true) + { + K_drawKartTeamScores(); } if (LUA_HudEnabled(hud_gametypeinfo)) @@ -6698,10 +7310,30 @@ void K_drawKartHUD(void) { K_drawKartItem(); } + + if (stplyr->backupitemtype) + K_drawBackupItem(); } } } + // TODO better voice chat speaking indicator integration for spectators + { + char speakingstring[2048]; + memset(speakingstring, 0, sizeof(speakingstring)); + + for (int i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] && players[i].spectator && S_IsPlayerVoiceActive(i)) + { + strcat(speakingstring, player_names[i]); + strcat(speakingstring, " "); + } + } + + V_DrawThinString(0, 0, V_SNAPTOTOP|V_SNAPTOLEFT, speakingstring); + } + // Draw the countdowns after everything else. if (stplyr->lives <= 0 && stplyr->playerstate == PST_DEAD) { @@ -6813,6 +7445,48 @@ void K_drawKartHUD(void) } } + if (netgame && cv_voice_servermute.value == 0) + { + if (players[consoleplayer].pflags2 & (PF2_SELFMUTE | PF2_SERVERMUTE | PF2_SELFDEAFEN | PF2_SERVERDEAFEN)) + { + patch_t* micmuted = kp_voice_localmuted; + V_DrawFixedPatch(-1 * FRACUNIT, (BASEVIDHEIGHT - 21) << FRACBITS, FRACUNIT, V_SNAPTOBOTTOM|V_SNAPTOLEFT, micmuted, NULL); + } + else if (S_IsPlayerVoiceActive(consoleplayer)) + { + patch_t* micactivebase = kp_voice_localactive[(leveltime / 2) % 16]; + patch_t* micactivetop = kp_voice_localactiveoverlay[(leveltime / 2) % 16]; + + UINT8* micactivecolormap = NULL; + if (g_local_voice_last_peak < 0.7) + { + micactivecolormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_GREEN, GTC_CACHE); + } + else if (g_local_voice_last_peak < 0.95) + { + micactivecolormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_YELLOW, GTC_CACHE); + } + else + { + micactivecolormap = R_GetTranslationColormap(TC_DEFAULT, SKINCOLOR_RED, GTC_CACHE); + } + V_DrawFixedPatch(-15 * FRACUNIT, (BASEVIDHEIGHT - 34) << FRACBITS, FRACUNIT, V_SNAPTOBOTTOM|V_SNAPTOLEFT, micactivebase, micactivecolormap); + V_DrawFixedPatch(-15 * FRACUNIT, (BASEVIDHEIGHT - 34) << FRACBITS, FRACUNIT, V_SNAPTOBOTTOM|V_SNAPTOLEFT, micactivetop, micactivecolormap); + } + else + { + patch_t* micopen = kp_voice_localopen; + V_DrawFixedPatch(-1 * FRACUNIT, (BASEVIDHEIGHT - 21) << FRACBITS, FRACUNIT, V_SNAPTOBOTTOM|V_SNAPTOLEFT, micopen, NULL); + } + + // Deafen indicator + if (players[consoleplayer].pflags2 & (PF2_SELFDEAFEN | PF2_SERVERDEAFEN)) + { + patch_t* deafened = kp_voice_localdeafened; + V_DrawFixedPatch(16 * FRACUNIT, (BASEVIDHEIGHT - 15) << FRACBITS, FRACUNIT, V_SNAPTOBOTTOM|V_SNAPTOLEFT, deafened, NULL); + } + } + debug: K_DrawWaypointDebugger(); K_DrawBotDebugger(); @@ -6864,3 +7538,17 @@ void K_DrawMarginSticker(INT32 x, INT32 y, INT32 width, INT32 flags, boolean isS if (!leftedge) V_DrawFixedPatch((x + width)*FRACUNIT, y*FRACUNIT, FRACUNIT, flags|V_FLIP, stickerEnd, NULL); } + +// common fonts: 0 = thin, 8 = menu. sorry we have to launder a C++ enum in here +INT32 K_DrawGameControl(UINT16 x, UINT16 y, UINT8 player, const char *str, UINT8 alignment, UINT8 font, UINT32 flags) +{ + using srb2::Draw; + + Draw::TextElement text = Draw::TextElement().as(player).parse(str).font((Draw::Font)font); + + INT32 width = text.width(); + + Draw(x, y).align((srb2::Draw::Align)alignment).flags(flags).text(text); + + return width; +} diff --git a/src/k_hud.h b/src/k_hud.h index 9112f00da..cd93a192b 100644 --- a/src/k_hud.h +++ b/src/k_hud.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -63,6 +63,7 @@ void K_drawButton(fixed_t x, fixed_t y, INT32 flags, patch_t *button[2], boolean void K_drawButtonAnim(INT32 x, INT32 y, INT32 flags, patch_t *button[2], tic_t animtic); void K_DrawSticker(INT32 x, INT32 y, INT32 width, INT32 flags, boolean isSmall); void K_DrawMarginSticker(INT32 x, INT32 y, INT32 width, INT32 flags, boolean isSmall, boolean leftedge); +INT32 K_GetTransFlagFromFixed(fixed_t value); void K_DrawKartPositionNumXY( UINT8 num, @@ -94,22 +95,48 @@ extern patch_t *kp_button_c[2][2]; extern patch_t *kp_button_x[2][2]; extern patch_t *kp_button_y[2][2]; extern patch_t *kp_button_z[2][2]; -extern patch_t *kp_button_start[2]; -extern patch_t *kp_button_l[2]; -extern patch_t *kp_button_r[2]; -extern patch_t *kp_button_up[2]; -extern patch_t *kp_button_down[2]; -extern patch_t *kp_button_right[2]; -extern patch_t *kp_button_left[2]; -extern patch_t *kp_button_dpad[2]; +extern patch_t *kp_button_start[2][2]; +extern patch_t *kp_button_l[2][2]; +extern patch_t *kp_button_r[2][2]; +extern patch_t *kp_button_up[2][2]; +extern patch_t *kp_button_down[2][2]; +extern patch_t *kp_button_right[2][2]; +extern patch_t *kp_button_left[2][2]; +extern patch_t *kp_button_lua1[2][2]; +extern patch_t *kp_button_lua2[2][2]; +extern patch_t *kp_button_lua3[2][2]; + +extern patch_t *gen_button_a[2][2]; +extern patch_t *gen_button_b[2][2]; +extern patch_t *gen_button_x[2][2]; +extern patch_t *gen_button_y[2][2]; +extern patch_t *gen_button_lb[2][2]; +extern patch_t *gen_button_rb[2][2]; +extern patch_t *gen_button_lt[2][2]; +extern patch_t *gen_button_rt[2][2]; +extern patch_t *gen_button_start[2][2]; +extern patch_t *gen_button_back[2][2]; +extern patch_t *gen_button_ls[2][2]; +extern patch_t *gen_button_rs[2][2]; +extern patch_t *gen_button_dpad[2][2]; + +extern patch_t *gen_button_keyleft[2]; +extern patch_t *gen_button_keyright[2]; +extern patch_t *gen_button_keycenter[2]; + +extern patch_t *gen_button_keyleft[2]; +extern patch_t *gen_button_keyright[2]; +extern patch_t *gen_button_keycenter[2]; extern patch_t *kp_eggnum[6]; extern patch_t *kp_facenum[MAXPLAYERS+1]; +extern patch_t *kp_pickmeup[2]; + extern patch_t *kp_unknownminimap; void K_AddMessage(const char *msg, boolean interrupt, boolean persist); -void K_AddMessageForPlayer(const player_t *player, const char *msg, boolean interrupt, boolean persist); +void K_AddMessageForPlayer(player_t *player, const char *msg, boolean interrupt, boolean persist); void K_ClearPersistentMessages(void); void K_ClearPersistentMessageForPlayer(player_t *player); void K_TickMessages(void); @@ -133,6 +160,8 @@ INT32 K_GetMinimapTransFlags(const boolean usingProgressBar); INT32 K_GetMinimapSplitFlags(const boolean usingProgressBar); position_t K_GetKartObjectPosToMinimapPos(fixed_t objx, fixed_t objy); +INT32 K_DrawGameControl(UINT16 x, UINT16 y, UINT8 player, const char *str, UINT8 alignment, UINT8 font, UINT32 flags); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_hud_track.cpp b/src/k_hud_track.cpp index f4bacdd09..49abd8a5b 100644 --- a/src/k_hud_track.cpp +++ b/src/k_hud_track.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -278,6 +278,26 @@ private: }}, }; + case MT_JAWZ: + case MT_JAWZ_SHIELD: + case MT_ORBINAUT: + case MT_ORBINAUT_SHIELD: + case MT_DROPTARGET: + case MT_DROPTARGET_SHIELD: + case MT_LANDMINE: + case MT_BANANA: + case MT_BANANA_SHIELD: + case MT_GACHABOM: + case MT_EGGMANITEM: + case MT_EGGMANITEM_SHIELD: + case MT_BUBBLESHIELDTRAP: + return { + { // Near + {2, TICRATE/2, {kp_pickmeup}, 0}, // 1P + {{2, TICRATE/2, {kp_pickmeup}, 0}}, // 4P + }, + }; + default: return { { // Near @@ -378,6 +398,24 @@ bool is_object_tracking_target(const mobj_t* mobj) return !(mobj->renderflags & (RF_TRANSMASK | RF_DONTDRAW)) && // the spraycan wasn't collected yet P_CheckSight(stplyr->mo, const_cast(mobj)); + case MT_JAWZ: + case MT_JAWZ_SHIELD: + case MT_ORBINAUT: + case MT_ORBINAUT_SHIELD: + case MT_DROPTARGET: + case MT_DROPTARGET_SHIELD: + case MT_LANDMINE: + case MT_BANANA: + case MT_BANANA_SHIELD: + case MT_GACHABOM: + case MT_BUBBLESHIELDTRAP: + case MT_EGGMANITEM: + case MT_EGGMANITEM_SHIELD: + return (mobj->target && !P_MobjWasRemoved(mobj->target) && ( + (mobj->target->player && stplyr == mobj->target->player) + || (mobj->target->player && G_SameTeam(stplyr, mobj->target->player)) + ) && P_CheckSight(stplyr->mo, const_cast(mobj))); + default: return false; } @@ -433,13 +471,18 @@ std::optional object_tooltip(const mobj_t* mobj) case MT_GARDENTOP: return conditional( mobj->tracer == stplyr->mo && Obj_GardenTopPlayerNeedsHelp(mobj), - [&] { return TextElement("Try \xA7!").font(splitfont); } + [&] { return TextElement().parse("Try !").font(splitfont); } ); case MT_PLAYER: return conditional( mobj->player == stplyr && stplyr->icecube.frozen, - [&] { return Tooltip(TextElement("\xA7")).offset3d(0, 0, 64 * mobj->scale * P_MobjFlip(mobj)); } + [&] { return Tooltip(TextElement( + (leveltime/(TICRATE/2)%2) ? + TextElement().parse("").font(splitfont) : + TextElement().parse("").font(splitfont) + )).offset3d(0, 0, 64 * mobj->scale * P_MobjFlip(mobj)); } + // I will be trying to figure out why the return value didn't accept a straightforward call to parse() for the rest of my life (apprx. 15 seconds) ); default: @@ -858,6 +901,35 @@ void K_drawTargetHUD(const vector3_t* origin, player_t* player) if (tracking) { + fixed_t itemOffset = 36*mobj->scale; + switch (mobj->type) + { + case MT_JAWZ: + case MT_JAWZ_SHIELD: + case MT_ORBINAUT: + case MT_ORBINAUT_SHIELD: + case MT_DROPTARGET: + case MT_DROPTARGET_SHIELD: + case MT_LANDMINE: + case MT_BANANA: + case MT_BANANA_SHIELD: + case MT_GACHABOM: + case MT_BUBBLESHIELDTRAP: + case MT_EGGMANITEM: + case MT_EGGMANITEM_SHIELD: + if (stplyr->mo->eflags & MFE_VERTICALFLIP) + { + pos.z -= itemOffset; + } + else + { + pos.z += itemOffset; + } + break; + default: + break; + } + K_ObjectTracking(&tr.result, &pos, false); targetList.push_back(tr); } diff --git a/src/k_kart.c b/src/k_kart.c index 264846a0a..94f5e9cef 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2018 by ZarroTsu. // // This program is free software distributed under the @@ -317,7 +317,7 @@ void K_TimerInit(void) if (G_TimeAttackStart()) { - starttime = 15*TICRATE; // Longest permitted start. No half-laps in reverse. + starttime = TIMEATTACK_START; // Longest permitted start. No half-laps in reverse. // (Changed on finish line cross later, don't worry.) } @@ -468,9 +468,15 @@ boolean K_IsPlayerLosing(player_t *player) // Some behavior should change if the player approaches the frontrunner unusually fast. boolean K_IsPlayerScamming(player_t *player) { + if (!M_NotFreePlay()) + return false; + + if (!(gametyperules & GTR_CIRCUIT)) + return false; + // "Why 8?" Consistency // "Why 2000?" Vibes - return (K_GetItemRouletteDistance(player, 8) < 2000); + return (K_GetItemRouletteDistance(player, 8) < SCAMDIST); } fixed_t K_GetKartGameSpeedScalar(SINT8 value) @@ -722,7 +728,7 @@ static fixed_t K_PlayerWeight(mobj_t *mobj, mobj_t *against) } else if (against && !P_MobjWasRemoved(against) && against->player && ((!P_PlayerInPain(against->player) && P_PlayerInPain(mobj->player)) // You're hurt - || (against->player->itemtype == KITEM_BUBBLESHIELD && mobj->player->itemtype != KITEM_BUBBLESHIELD))) // They have a Bubble Shield + || (against->player->curshield == KSHIELD_BUBBLE && mobj->player->curshield != KSHIELD_BUBBLE))) // They have a Bubble Shield { weight = 0; // This player does not cause any bump action } @@ -746,7 +752,7 @@ static fixed_t K_PlayerWeight(mobj_t *mobj, mobj_t *against) } else { - if (mobj->player->itemtype == KITEM_BUBBLESHIELD) + if (mobj->player->curshield == KSHIELD_BUBBLE) weight += 9*FRACUNIT; } @@ -839,8 +845,8 @@ static void K_SpawnBumpForObjs(mobj_t *mobj1, mobj_t *mobj2) P_SetScale(fx, (fx->destscale = avgScale)); - if ((mobj1->player && mobj1->player->itemtype == KITEM_BUBBLESHIELD) - || (mobj2->player && mobj2->player->itemtype == KITEM_BUBBLESHIELD)) + if ((mobj1->player && mobj1->player->curshield == KSHIELD_BUBBLE) + || (mobj2->player && mobj2->player->curshield == KSHIELD_BUBBLE)) { S_StartSound(mobj1, sfx_s3k44); } @@ -1503,6 +1509,9 @@ static boolean K_TryDraft(player_t *player, mobj_t *dest, fixed_t minDist, fixed return false; } + if (dest->player && G_SameTeam(player, dest->player)) + draftdistance = FixedMul(draftdistance, K_TeamComebackMultiplier(player)); + // Not close enough to draft. if (dist > draftdistance && draftdistance > 0) { @@ -3023,6 +3032,21 @@ fixed_t K_PlayerTripwireSpeedThreshold(const player_t *player) { fixed_t required_speed = 2 * K_GetKartSpeed(player, false, false); // 200% + if (specialstageinfo.valid) + required_speed = 3 * K_GetKartSpeed(player, false, false) / 2; // 150% + + UINT32 distance = K_GetItemRouletteDistance(player, 8); + + if (gametype == GT_RACE && M_NotFreePlay() && !modeattacking) + { + if (distance < SCAMDIST) // Players near 1st need more speed! + { + fixed_t percentscam = FixedDiv(FRACUNIT*(SCAMDIST - distance), FRACUNIT*SCAMDIST); + required_speed += FixedMul(required_speed, percentscam); + } + } + + if (player->offroad && K_ApplyOffroad(player)) { // Increase to 300% if you're lawnmowering. @@ -3047,8 +3071,7 @@ tripwirepass_t K_TripwirePassConditions(const player_t *player) { if ( player->invincibilitytimer || - player->sneakertimer || - player->panelsneakertimer + player->sneakertimer ) return TRIPWIRE_BLASTER; @@ -3462,10 +3485,6 @@ static fixed_t K_RingDurationBoost(const player_t *player) return ret; } -// v2 almost broke sliptiding when it fixed turning bugs! -// This value is fine-tuned to feel like v1 again without reverting any of those changes. -#define SLIPTIDEHANDLING 7*FRACUNIT/8 - // sets boostpower, speedboost, accelboost, and handleboost to whatever we need it to be static void K_GetKartBoostPower(player_t *player) { @@ -3507,7 +3526,7 @@ static void K_GetKartBoostPower(player_t *player) UINT8 i; for (i = 0; i < player->numsneakers; i++) { - ADDBOOST(FRACUNIT*85/100, 8*FRACUNIT, SLIPTIDEHANDLING+SLIPTIDEHANDLING/3); // + 50% top speed, + 800% acceleration, +50% handling + ADDBOOST(FRACUNIT*85/100, 8*FRACUNIT, HANDLESCALING+HANDLESCALING/3); // + 50% top speed, + 800% acceleration, +50% handling } } @@ -3516,7 +3535,7 @@ static void K_GetKartBoostPower(player_t *player) UINT8 i; for (i = 0; i < player->numpanelsneakers; i++) { - ADDBOOST(FRACUNIT/2, 8*FRACUNIT, SLIPTIDEHANDLING); // + 50% top speed, + 800% acceleration, +50% handling + ADDBOOST(FRACUNIT/2, 8*FRACUNIT, HANDLESCALING); // + 50% top speed, + 800% acceleration, +50% handling } } @@ -3524,12 +3543,12 @@ static void K_GetKartBoostPower(player_t *player) { // S-Monitor: no extra % fixed_t extra = FRACUNIT / 1400 * (player->invincibilitytimer - K_PowerUpRemaining(player, POWERUP_SMONITOR)); - ADDBOOST(3*FRACUNIT/8 + extra, 3*FRACUNIT, SLIPTIDEHANDLING/2); // + 37.5 + ?% top speed, + 300% acceleration, +25% handling + ADDBOOST(3*FRACUNIT/8 + extra, 3*FRACUNIT, HANDLESCALING/2); // + 37.5 + ?% top speed, + 300% acceleration, +25% handling } if (player->growshrinktimer > 0) // Grow { - ADDBOOST(0, 0, SLIPTIDEHANDLING/2); // + 0% top speed, + 0% acceleration, +25% handling + ADDBOOST(0, 0, HANDLESCALING/2); // + 0% top speed, + 0% acceleration, +25% handling } if (player->flamedash) // Flame Shield dash @@ -3538,7 +3557,7 @@ static void K_GetKartBoostPower(player_t *player) ADDBOOST( dash, // + infinite top speed 3*FRACUNIT, // + 300% acceleration - FixedMul(FixedDiv(dash, FRACUNIT/2), SLIPTIDEHANDLING/2) // + infinite handling + FixedMul(FixedDiv(dash, FRACUNIT/2), HANDLESCALING/2) // + infinite handling ); } @@ -3548,13 +3567,13 @@ static void K_GetKartBoostPower(player_t *player) ADDBOOST( dash, // + infinite top speed 3*FRACUNIT, // + 300% acceleration - FixedMul(FixedDiv(dash, FRACUNIT/2), SLIPTIDEHANDLING/2) // + infinite handling + FixedMul(FixedDiv(dash, FRACUNIT/2), HANDLESCALING/2) // + infinite handling ); } if (player->wavedashboost) { - // NB: This is intentionally under the 25% handleboost threshold required to initiate a sliptide + // NB: This is intentionally under the handleboost threshold required to initiate a sliptide ADDBOOST( Easing_InCubic( player->wavedashpower, @@ -3566,7 +3585,7 @@ static void K_GetKartBoostPower(player_t *player) 0, 4*FRACUNIT ), - 2*SLIPTIDEHANDLING/5 + 2*HANDLESCALING/5 ); // + 80% top speed (peak), +400% acceleration (peak), +20% handling } @@ -3576,14 +3595,14 @@ static void K_GetKartBoostPower(player_t *player) Easing_InCubic( player->overdrivepower, 0, - 5*FRACUNIT/10 + 75*FRACUNIT/100 ), Easing_InSine( player->overdrivepower, 0, 3*FRACUNIT ), - 1*SLIPTIDEHANDLING/5 + 1*HANDLESCALING/5 ); // + 80% top speed (peak), +400% acceleration (peak), +20% handling } @@ -3602,12 +3621,12 @@ static void K_GetKartBoostPower(player_t *player) if (player->startboost) // Startup Boost { - ADDBOOST(FRACUNIT, 4*FRACUNIT, SLIPTIDEHANDLING); // + 100% top speed, + 400% acceleration, +50% handling + ADDBOOST(FRACUNIT, 4*FRACUNIT, HANDLESCALING); // + 100% top speed, + 400% acceleration, +50% handling } if (player->dropdashboost) // Drop dash { - ADDBOOST(FRACUNIT/3, 4*FRACUNIT, SLIPTIDEHANDLING); // + 33% top speed, + 400% acceleration, +50% handling + ADDBOOST(FRACUNIT/3, 4*FRACUNIT, HANDLESCALING); // + 33% top speed, + 400% acceleration, +50% handling } if (player->driftboost) // Drift Boost @@ -3645,21 +3664,21 @@ static void K_GetKartBoostPower(player_t *player) if (player->gateBoost) // SPB Juicebox boost { - ADDBOOST(3*FRACUNIT/4, 4*FRACUNIT, SLIPTIDEHANDLING/2); // + 75% top speed, + 400% acceleration, +25% handling + ADDBOOST(3*FRACUNIT/4, 4*FRACUNIT, HANDLESCALING/2); // + 75% top speed, + 400% acceleration, +25% handling } if (player->ringboost) // Ring Boost { fixed_t ringboost_base = FRACUNIT/4; if (player->overdrive) - ringboost_base += FRACUNIT/2; + ringboost_base += FRACUNIT/4; // This one's a little special: we add extra top speed per tic of ringboost stored up, to allow for Ring Box to really rocket away. // (We compensate when decrementing ringboost to avoid runaway exponential scaling hell.) fixed_t rb = FixedDiv(player->ringboost * FRACUNIT, max(FRACUNIT, K_RingDurationBoost(player))); ADDBOOST( ringboost_base + FixedMul(FRACUNIT / 1750, rb), 4*FRACUNIT, - Easing_InCubic(min(FRACUNIT, rb / (TICRATE*12)), 0, 2*SLIPTIDEHANDLING/5) + Easing_InCubic(min(FRACUNIT, rb / (TICRATE*12)), 0, 2*HANDLESCALING/5) ); // + 20% + ???% top speed, + 400% acceleration, +???% handling } @@ -3673,11 +3692,69 @@ static void K_GetKartBoostPower(player_t *player) ADDBOOST(player->vortexBoost/6, FRACUNIT/10, 0); // + ???% top speed, + 10% acceleration, +0% handling } + if (player->drift != 0) // Neutral drifts are marginally faster + { + // Trying to emulate the old leniency timer being stat-based. + // I dunno if this is overkill because turning is already stat-based. + // Should this be a pure constant instead? + const fixed_t max_steer_threshold = (FRACUNIT * KART_FULLTURN * 5) / 6; + + // Even when not inputting a turn, drift prediction is hard. + // Turn solver will sometimes need to slightly turn to stay "aligned". + // Award full boost even if turn solver creates a fractional miniturn. + const INT16 inner_deadzone = KART_FULLTURN / 100; + + INT16 steer_threshold = FixedMul((FRACUNIT * player->kartweight) / 9, max_steer_threshold)>>FRACBITS; + + INT16 steering = abs(player->steering); + steering = max(steering - inner_deadzone, 0); + + fixed_t frac = 0; + if (steering < steer_threshold) + { + frac = FixedDiv(FRACUNIT*(steer_threshold - steering), FRACUNIT*(steer_threshold)); + } + + // Weaken the effect with drifts that were just started. + frac = (frac * abs(player->drift)) / 5; + + fixed_t multiplier = 0; + if (frac > 0) + { + if (frac > FRACUNIT) + { + // Clamp between reasonable bounds. + frac = FRACUNIT; + } + + // Get multiplier from easing function, to + // heavily reward being near exactly 0. + multiplier = Easing_InExpo(frac, 0, FRACUNIT); + if (multiplier > 0) + { + ADDBOOST( + FixedMul(multiplier, 4*FRACUNIT/10), // + 40% top speed + FixedMul(multiplier, FRACUNIT/3), // + 33% acceleration + 0 // 0 handling + ); + numboosts--; // No afterimage! + } + } + + // Debug print for neutral drift + // CONS_Printf("Drift debug: %d/%d = %f (speed +%.0f%%, accel +%.0f%%)\n", + // steering, + // steer_threshold, + // FIXED_TO_FLOAT(frac), + // FIXED_TO_FLOAT(FixedMul(multiplier, 4*FRACUNIT/10)*100), + // FIXED_TO_FLOAT(FixedMul(multiplier, FRACUNIT/3)*100)); + } + if (player->trickcharge) { // NB: This is an acceleration-only boost. // If this is applied earlier in the chain, it will diminish real speed boosts. - ADDBOOST(0, FRACUNIT, 2*SLIPTIDEHANDLING/10); // 0% speed 100% accel 20% handle + ADDBOOST(0, FRACUNIT, 2*HANDLESCALING/10); // 0% speed 100% accel 20% handle } // This should always remain the last boost stack before tethering @@ -4055,6 +4132,12 @@ angle_t K_MomentumAngleReal(const mobj_t *mo) // Scale amp rewards for crab bucketing. Play ambitiously! boolean K_PvPAmpReward(UINT32 award, player_t *attacker, player_t *defender) { + if (G_SameTeam(attacker, defender) == true) + { + // Do not reward amps for friendly fire. + return 0; + } + UINT32 epsilon = FixedMul(2048/4, mapobjectscale); // How close is close enough that full reward seems fair, even if you're technically ahead? UINT32 range = FixedMul(2048, mapobjectscale); UINT32 atkdist = attacker->distancetofinish + epsilon; @@ -4079,14 +4162,28 @@ void K_SpawnAmps(player_t *player, UINT8 amps, mobj_t *impact) if (gametyperules & GTR_SPHERES) return; + if (amps == 0) + return; + + UINT32 itemdistance = max(0, min( FRACUNIT, K_GetItemRouletteDistance(player, D_NumPlayersInRace()))); // cap this to FRACUNIT, so it doesn't wrap when turning it into fixed_t + fixed_t itemdistmult = FRACUNIT + max( 0, min(FixedMul(FixedDiv(itemdistance<kartspeed) - (9-player->kartweight)) / 10); + // Debug print for scaledamps calculation + // CONS_Printf("K_SpawnAmps: player=%s, amps=%d, kartspeed=%d, kartweight=%d, itemdistance=%d, itemdistmult=%0.2f, statscaledamps=%d, distscaledamps=%d\n", + // player_names[player-players], amps, player->kartspeed, player->kartweight, + // itemdistance, FixedToFloat(itemdistmult), + // min(amps, amps * (10 + (9-player->kartspeed) - (9-player->kartweight)) / 10), + // FixedMul(scaledamps<>FRACBITS); + scaledamps = FixedMul(scaledamps<>FRACBITS; /* if (player->position <= 1) scaledamps /= 2; */ - for (int i = 0; i < (scaledamps/2); i++) + UINT16 finalreward = max(1, scaledamps/2); + + for (int i = 0; i < finalreward; i++) { mobj_t *pickup = P_SpawnMobj(impact->x, impact->y, impact->z, MT_AMPS); pickup->momx = P_RandomRange(PR_ITEM_DEBRIS, -40*mapobjectscale, 40*mapobjectscale); @@ -4133,7 +4230,7 @@ void K_AwardPlayerAmps(player_t *player, UINT8 amps) if (!player->overdrive && player->mo && !P_MobjWasRemoved(player->mo) && player->overdriveready == 0) { S_StartSound(player->mo, sfx_gshac); - player->amps *= 2; + player->amps += 10; } K_Overdrive(player); } @@ -4170,7 +4267,16 @@ void K_AwardPlayerRings(player_t *player, UINT16 rings, boolean overload) void K_CheckpointCrossAward(player_t *player) { - player->exp += K_GetExpAdjustment(player); + if (gametype != GT_RACE) + return; + + player->gradingfactor += K_GetGradingMultAdjustment(player); + player->gradingpointnum++; + player->exp = K_GetEXP(player); + //CONS_Printf("player: %s factor: %.2f exp: %d\n", player_names[player-players], FIXED_TO_FLOAT(player->gradingfactor), player->exp); + if (!player->cangrabitems) + player->cangrabitems = 1; + K_AwardPlayerRings(player, (player->bot ? 20 : 10), true); // Update Duel scoring. @@ -4398,6 +4504,12 @@ void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UIN return; } + if (G_SameTeam(player, victim) == true) + { + // No farming points off of teammates, either. + return; + } + if (player->exiting) { // The round has already ended, don't mess with points @@ -4428,7 +4540,7 @@ void K_BattleAwardHit(player_t *player, player_t *victim, mobj_t *inflictor, UIN } // Check this before adding to player score - if ((gametyperules & GTR_BUMPERS) && finishOff && g_pointlimit <= player->roundscore) + if ((gametyperules & GTR_BUMPERS) && finishOff && g_pointlimit <= G_TeamOrIndividualScore(player)) { K_EndBattleRound(player); @@ -5180,6 +5292,8 @@ void K_TumbleInterrupt(player_t *player) player->pflags &= ~PF_TUMBLELASTBOUNCE; //players->tumbleHeight = 20; + player->transfer = 0; + player->mo->rollangle = 0; player->spinouttype = KSPIN_WIPEOUT; player->spinouttimer = player->wipeoutslow = TICRATE+2; @@ -5401,8 +5515,8 @@ void K_TakeBumpersFromPlayer(player_t *player, player_t *victim, UINT8 amount) void K_GivePointsToPlayer(player_t *player, player_t *victim, UINT8 amount) { + K_SpawnBattlePoints(player, victim, amount); // first just in case player score ends the game P_AddPlayerScore(player, amount); - K_SpawnBattlePoints(player, victim, amount); } #define MINEQUAKEDIST 4096 @@ -6352,6 +6466,33 @@ void K_SpawnWipeoutTrail(mobj_t *mo) K_FlipFromObject(dust, mo); } +void K_SpawnFireworkTrail(mobj_t *mo) +{ + mobj_t *dust; + + I_Assert(mo != NULL); + I_Assert(!P_MobjWasRemoved(mo)); + + dust = P_SpawnMobjFromMobj(mo, 0, 0, 0, MT_WIPEOUTTRAIL); + + P_SetTarget(&dust->target, mo); + dust->angle = K_MomentumAngle(mo); + + P_SetMobjState(dust, S_FIREWORKTRAIL1); + + if (mo->player) + dust->color = mo->player->skincolor; + else + dust->color = mo->color; + dust->colorized = true; + + P_InstaScale(dust, mo->scale/2); + dust->destscale = 2*mo->scale; + dust->scalespeed = mo->scale/2; + + K_FlipFromObject(dust, mo); +} + void K_SpawnDraftDust(mobj_t *mo) { UINT8 i; @@ -6675,6 +6816,10 @@ mobj_t *K_ThrowKartItemEx(player_t *player, boolean missile, mobjtype_t mapthing mo->reactiontime = TICRATE/2; P_SetMobjState(mo, mo->info->painstate); } + else if (mapthing == MT_LANDMINE && mo) + { + mo->reactiontime = TICRATE/2; + } } else { @@ -7028,14 +7173,21 @@ void K_DoSneaker(player_t *player, INT32 type) { fixed_t intendedboost = FRACUNIT/2; + + // If you've already got an item sneaker type boost, panel sneakers will instead turn into item sneaker boosts + if (player->numsneakers && type == 0) + { + type = 1; + } + switch (type) { case 0: // Panel sneaker intendedboost = FRACUNIT/2; break; case 1: // Single item sneaker - case 2: // ROcket sneaker - intendedboost = FRACUNIT; + case 2: // Rocket sneaker + intendedboost = 85*FRACUNIT/100; break; } @@ -7109,22 +7261,19 @@ void K_DoSneaker(player_t *player, INT32 type) } } - - - switch (type) { case 0: player->panelsneakertimer = sneakertime; - player->overshield += 1; // TEMP prototype + player->overshield += 1; break; case 1: player->sneakertimer = sneakertime; - player->overshield += TICRATE/2; // TEMP prototype + player->overshield += TICRATE/2; break; case 2: player->sneakertimer = 3*sneakertime/4; - player->overshield += TICRATE/2; // TEMP prototype + player->overshield += TICRATE/2; break; } @@ -7404,7 +7553,7 @@ void K_UpdateHnextList(player_t *player, boolean clean) // For getting hit! void K_PopPlayerShield(player_t *player) { - INT32 shield = K_GetShieldFromItem(player->itemtype); + INT32 shield = player->curshield; // Doesn't apply if player is invalid. if (player->mo == NULL || P_MobjWasRemoved(player->mo)) @@ -8296,6 +8445,31 @@ static void K_MoveHeldObjects(player_t *player) } } +// If we can move our backup item into main slots, do so. +static void K_TryMoveBackupItem(player_t *player) +{ + if (player->itemtype && player->itemtype == player->backupitemtype) + { + player->itemamount += player->backupitemamount; + + player->backupitemtype = 0; + player->backupitemamount = 0; + + S_StartSound(player->mo, sfx_mbs54); + } + + if (player->itemtype == KITEM_NONE && player->backupitemtype && P_CanPickupItem(player, PICKUP_PAPERITEM)) + { + player->itemtype = player->backupitemtype; + player->itemamount = player->backupitemamount; + + player->backupitemtype = 0; + player->backupitemamount = 0; + + S_StartSound(player->mo, sfx_mbs54); + } +} + mobj_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range) { fixed_t best = INT32_MAX; @@ -8346,7 +8520,7 @@ mobj_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range) continue; } - if (G_GametypeHasTeams() && source != NULL && source->ctfteam == player->ctfteam) + if (G_SameTeam(source, player) == true) { // Don't home in on teammates. continue; @@ -8766,7 +8940,7 @@ static inline BlockItReturn_t PIT_AttractingRings(mobj_t *thing) return BMIT_CONTINUE; // Too far away } - if (RINGTOTAL(attractmo->player) >= 20 || (attractmo->player->pflags & PF_RINGLOCK)) + if (RINGTOTAL(attractmo->player) >= 20 || !P_CanPickupItem(attractmo->player, PICKUP_RINGORSPHERE)) { // Already reached max -- just joustle rings around. @@ -8793,6 +8967,19 @@ static inline BlockItReturn_t PIT_AttractingRings(mobj_t *thing) return BMIT_CONTINUE; // find other rings } +boolean K_LegacyRingboost(player_t *player) +{ + if (netgame) + return false; + if (modeattacking == ATTACKING_SPB) + return false; + if (!modeattacking) + return false; + if (!(skins[player->skin].flags & SF_HIVOLT)) + return false; + return true; +} + /** Looks for rings near a player in the blockmap. * * \param pmo Player object looking for rings to attract @@ -8833,6 +9020,13 @@ static void K_UpdateTripwire(player_t *player) mobj_t *front = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_TRIPWIREBOOST); mobj_t *back = P_SpawnMobjFromMobj(player->mo, 0, 0, 0, MT_TRIPWIREBOOST); + if (P_IsDisplayPlayer(player)) + { + S_StartSound(player->mo, sfx_s3k40); + S_StopSoundByID(player->mo, sfx_gshaf); + } + + P_SetTarget(&front->target, player->mo); P_SetTarget(&back->target, player->mo); @@ -9098,6 +9292,58 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->itemtype == KITEM_NONE) player->itemflags &= ~IF_HOLDREADY; + K_TryMoveBackupItem(player); + + if (onground || player->transfer < 10*player->mo->scale) + { + player->transfer = 0; + player->transfersound = false; + } + + if (player->transfer) + { + boolean eligible = (abs(player->mo->momz) < (2*abs(player->transfer)/4)) || (player->mo->momz > 0) != (player->transfer > 0); + if ((player->cmd.buttons & BT_ACCELERATE) && eligible) + { + fixed_t fuckfactor = FRACUNIT; + fixed_t transfergravity = 10*FRACUNIT/100; + + fixed_t transferclamp = min(abs(player->transfer), (player->mo->scale*100)); + if (player->transfer < 0) + transferclamp *= -1; + + if ((player->mo->momz > 0) == (transferclamp > 0)) + { + fuckfactor = FRACUNIT/2; + } + else if (!player->transfersound) + { + S_StartSound(player->mo, sfx_ggfall); + player->transfersound = true; + } + + fixed_t sx, sy; + sx = P_RandomRange(PR_DECORATION, -48, 48)*FRACUNIT; + sy = P_RandomRange(PR_DECORATION, -48, 48)*FRACUNIT; + + mobj_t *spdl = P_SpawnMobjFromMobj(player->mo, sx, sy, 0, MT_DOWNLINE); + spdl->colorized = true; + spdl->color = player->skincolor; + K_MatchGenericExtraFlags(spdl, player->mo); + P_SetTarget(&spdl->owner, player->mo); + spdl->renderflags |= RF_REDUCEVFX; + P_InstaScale(spdl, 4*player->mo->scale/2); + + if (abs(player->mo->momz) < (3*transferclamp/2)) + player->mo->momz -= FixedMul(transferclamp, FixedMul(fuckfactor, transfergravity)); + } + else + { + if (leveltime % 2) + K_SpawnFireworkTrail(player->mo); + } + } + // DKR style camera for boosting if (player->karthud[khud_boostcam] != 0 || player->karthud[khud_destboostcam] != 0) { @@ -9155,6 +9401,9 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->flashing = K_GetKartFlashing(player); } + if (player->spinouttimer && player->respawn.state != RESPAWNST_NONE) + player->spinouttimer = 0; + if (player->spinouttimer) { if (((P_IsObjectOnGround(player->mo) @@ -9269,6 +9518,33 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->ringdelay) player->ringdelay--; + if ((player->stunned > 0) + && (player->respawn.state == RESPAWNST_NONE) + && !P_PlayerInPain(player) + && P_IsObjectOnGround(player->mo) + ) + { + // MEGA FUCKING HACK BECAUSE P_SAVEG MOBJS ARE FULL + // Would updating player_saveflags to 32 bits have any negative consequences? + // For now, player->stunned 16th bit is a flag to determine whether the flybots were spawned + + // timer counts down at triple speed while spindashing + player->stunned = (player->stunned & 0x8000) | max(0, (player->stunned & 0x7FFF) - (player->spindash ? 3 : 1)); + + // when timer reaches 0, reset the flag and stun combo counter + if ((player->stunned & 0x7FFF) == 0) + { + player->stunned = 0; + player->stunnedCombo = 0; + } + // otherwise if the flybots aren't spawned, spawn them now! + else if ((player->stunned & 0x8000) == 0) + { + player->stunned |= 0x8000; + Obj_SpawnFlybotsForPlayer(player); + } + } + if (player->trickpanel == TRICKSTATE_READY) { if (!player->throwdir && !cmd->turning) @@ -9291,13 +9567,22 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) // extreme ringboost duration. Less aggressive for accel types, so they // retain more speed for small payouts. + // 2.4: Even if it IS paying out, if the duration gets extreme, + // start applying decay anyway! + UINT8 roller = TICRATE*2; roller += 4*(8-player->kartspeed); - if (player->superring == 0) + // UINT16 oldringboost = player->ringboost; + + if (player->superring == 0 || player->stunned) player->ringboost -= max((player->ringboost / roller), 1); - else + else if (K_LegacyRingboost(player)) player->ringboost--; + else + player->ringboost -= min(K_GetFullKartRingPower(player, false) - 1, max(player->ringboost / 2 / roller, 1)); + + // CONS_Printf("%d - %d\n", player->ringboost, oldringboost - player->ringboost); } if (player->sneakertimer) @@ -9323,6 +9608,9 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->trickboost) player->trickboost--; + if (K_PlayerUsesBotMovement(players) && player->botvars.bumpslow && player->incontrol) + player->botvars.bumpslow--; + if (player->flamedash) { player->flamedash--; @@ -9463,6 +9751,8 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) player->invincibilitytimer--; } + if (player->cangrabitems && player->cangrabitems <= EARLY_ITEM_FLICKER) + player->cangrabitems++; if (!player->invincibilitytimer) player->invincibilityextensions = 0; @@ -9512,7 +9802,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (!player->bigwaypointgap) K_DoIngameRespawn(player); else if (player->bigwaypointgap == AUTORESPAWN_THRESHOLD) - K_AddMessageForPlayer(player, "Press \xAE to respawn", true, false); + K_AddMessageForPlayer(player, "Press to respawn", true, false); } if (player->tripwireUnstuck && !player->mo->hitlag) @@ -9578,18 +9868,38 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) if (player->superring) { player->nextringaward++; - UINT8 ringrate = 3 - min(2, player->superring / 20); // Used to consume fat stacks of cash faster. + + UINT8 fastringscaler = (K_GetKartGameSpeedScalar(gamespeed) > FRACUNIT) ? 20 : 20; // If G3 / TA gets out of control, can speed up all ring box payout + + UINT32 existing = (player->lastringboost / K_GetFullKartRingPower(player, true)); // How many rings (effectively) do we have boost credit for right now? + + if (K_LegacyRingboost(player)) + existing = 0; + + UINT8 ringrate = 3 - min(2, (player->superring + existing) / fastringscaler); // Used to consume fat stacks of cash faster. + + if (player->stunned) + ringrate = 6; + if (player->nextringaward >= ringrate) { - mobj_t *ring = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_RING); - ring->extravalue1 = 1; // Ring collect animation timer - ring->angle = player->mo->angle; // animation angle - P_SetTarget(&ring->target, player->mo); // toucher for thinker - player->pickuprings++; - if (player->superring == 1) - ring->cvmem = 1; // play caching when collected - player->nextringaward = 0; - player->superring--; + if (player->instaWhipCharge) + { + // Store award rings to do diabolical horseshit with later. + player->nextringaward = ringrate; + } + else + { + mobj_t *ring = P_SpawnMobj(player->mo->x, player->mo->y, player->mo->z, MT_RING); + ring->extravalue1 = 1; // Ring collect animation timer + ring->angle = player->mo->angle; // animation angle + P_SetTarget(&ring->target, player->mo); // toucher for thinker + player->pickuprings++; + if (player->superring == 1) + ring->cvmem = 1; // play caching when collected + player->nextringaward = 0; + player->superring--; + } } } else @@ -9918,7 +10228,7 @@ void K_KartPlayerThink(player_t *player, ticcmd_t *cmd) } extern consvar_t cv_fuzz; - if (cv_fuzz.value && P_CanPickupItem(player, 1)) + if (cv_fuzz.value && P_CanPickupItem(player, PICKUP_ITEMBOX)) { K_StartItemRoulette(player, P_RandomRange(PR_FUZZ, 0, 1)); } @@ -10100,9 +10410,7 @@ void K_KartResetPlayerColor(player_t *player) if (player->mo->health <= 0 || player->playerstate == PST_DEAD || (player->respawn.state == RESPAWNST_MOVE)) // Override everything { - player->mo->colorized = (player->dye != 0); - player->mo->color = player->dye ? player->dye : player->skincolor; - goto finalise; + goto base; } if (player->eggmanexplode) // You're gonna diiiiie @@ -10191,7 +10499,6 @@ void K_KartResetPlayerColor(player_t *player) fullbright = true; player->mo->color = player->skincolor; goto finalise; - } else if (player->overdrive) { @@ -10215,8 +10522,18 @@ void K_KartResetPlayerColor(player_t *player) goto finalise; } - player->mo->colorized = (player->dye != 0); - player->mo->color = player->dye ? player->dye : player->skincolor; +base: + + if (player->dye) + { + player->mo->colorized = true; + player->mo->color = player->dye; + } + else + { + player->mo->colorized = false; + player->mo->color = player->skincolor; + } finalise: @@ -10349,7 +10666,7 @@ void K_KartPlayerAfterThink(player_t *player) player->jawztargetdelay = 0; } - if (player->itemtype == KITEM_LIGHTNINGSHIELD || ((gametyperules & GTR_POWERSTONES) && K_IsPlayerWanted(player))) + if (player->curshield == KSHIELD_LIGHTNING || ((gametyperules & GTR_POWERSTONES) && K_IsPlayerWanted(player))) { K_LookForRings(player->mo); } @@ -10899,6 +11216,11 @@ INT32 K_GetKartRingPower(const player_t *player, boolean boosted) return max(ringPower / FRACUNIT, 1); } +INT32 K_GetFullKartRingPower(const player_t *player, boolean boosted) +{ + return 3 + K_GetKartRingPower(player, boosted); +} + // Returns false if this player being placed here causes them to collide with any other player // Used in g_game.c for match etc. respawning // This does not check along the z because the z is not correctly set for the spawnee at this point @@ -11159,6 +11481,14 @@ INT16 K_GetKartTurnValue(const player_t *player, INT16 turnvalue) } else { + if (player->driftcharge > 0 && (turnvalue > 0) == (player->drift > 0)) // If drifting and turning inward, then... + { + // Apply a progressive handling boost, stronger for higher weight, + // as you charge driftsparks. Reduces reliance on brakedrifting, especially G2! + fixed_t eggfactor = Easing_InCubic(player->kartweight * FRACUNIT / 9, 0, FRACUNIT/4); + turnfixed = FixedMul(turnfixed, FRACUNIT + (player->driftcharge * eggfactor / K_GetKartDriftSparkValue(player))); + } + // If we're drifting we have a completely different turning value fixed_t countersteer = FixedDiv(turnfixed, KART_FULLTURN * FRACUNIT); return K_GetKartDriftValue(player, countersteer); @@ -11171,18 +11501,18 @@ INT16 K_GetKartTurnValue(const player_t *player, INT16 turnvalue) // If you're sliptiding, don't interact with handling boosts. // You need turning power proportional to your speed, no matter what! fixed_t topspeed = K_GetKartSpeed(player, false, false); - if (K_Sliptiding(player)) + if (K_Sliptiding(player) || player->flamedash) { fixed_t sliptide_handle; if (G_CompatLevel(0x000A)) { // Compat level for 2.0 staff ghosts - sliptide_handle = 5 * SLIPTIDEHANDLING / 4; + sliptide_handle = 5 * HANDLESCALING / 4; } else { - sliptide_handle = 3 * SLIPTIDEHANDLING / 4; + sliptide_handle = 3 * HANDLESCALING / 4; } finalhandleboost = FixedMul(sliptide_handle, FixedDiv(player->speed, topspeed)); @@ -11491,6 +11821,8 @@ static void K_KartDrift(player_t *player, boolean onground) player->pflags &= ~(PF_BRAKEDRIFT|PF_GETSPARKS); // And take away wavedash properties: advanced cornering demands advanced finesse player->wavedash = 0; + player->wavedashleft = 0; + player->wavedashright = 0; player->wavedashboost = 0; player->trickcharge = 0; } @@ -11568,6 +11900,14 @@ static void K_KartDrift(player_t *player, boolean onground) K_SpawnDriftSparks(player); } + /* + // Magic numbers ahoy! Meant to allow purple drifts to progress past color transition. + if ((player->driftcharge + driftadditive) > (dsthree+(32*3)) && K_TimeAttackRules() && leveltime < starttime) + { + driftadditive = max(0, (dsthree+(32*3)) - player->driftcharge); + } + */ + if ((player->driftcharge < dsone && player->driftcharge+driftadditive >= dsone) || (player->driftcharge < dstwo && player->driftcharge+driftadditive >= dstwo) || (player->driftcharge < dsthree && player->driftcharge+driftadditive >= dsthree)) @@ -11593,7 +11933,7 @@ static void K_KartDrift(player_t *player, boolean onground) boolean extendedSliptide = false; // We don't meet sliptide conditions! - if ((player->handleboost < (SLIPTIDEHANDLING/2)) + if ((player->handleboost < SLIPTIDEHANDLING) || (!player->steering) || (!player->aizdriftstrat) || (player->steering > 0) != (player->aizdriftstrat > 0)) @@ -11643,7 +11983,12 @@ static void K_KartDrift(player_t *player, boolean onground) // This makes wavedash charge noticeably slower on even modest delay, despite the magnitude of the turn seeming the same. // So we only require 90% of a turn to get full charge strength. - player->wavedash += addCharge; + if (player->steering > 0) + player->wavedashleft += addCharge; + else + player->wavedashright += addCharge; + + player->wavedash = max(player->wavedashleft, player->wavedashright) + min(player->wavedashleft, player->wavedashright)/4; if (player->wavedash >= MIN_WAVEDASH_CHARGE && (player->wavedash - addCharge) < MIN_WAVEDASH_CHARGE) S_StartSound(player->mo, sfx_waved5); @@ -11724,6 +12069,8 @@ static void K_KartDrift(player_t *player, boolean onground) S_StopSoundByID(player->mo, sfx_waved2); S_StopSoundByID(player->mo, sfx_waved4); player->wavedash = 0; + player->wavedashleft = 0; + player->wavedashright = 0; player->wavedashdelay = 0; } } @@ -11764,13 +12111,18 @@ static void K_KartDrift(player_t *player, boolean onground) else player->pflags &= ~PF_BRAKEDRIFT; } + // // K_KartUpdatePosition // void K_KartUpdatePosition(player_t *player) { - fixed_t position = 1; - fixed_t oldposition = player->position; + UINT8 position = 1; + UINT8 oldposition = player->position; + + UINT8 team_position = 1; + UINT32 team_importance = 0; + fixed_t i; INT32 realplayers = 0; @@ -11779,6 +12131,8 @@ void K_KartUpdatePosition(player_t *player) // Ensure these are reset for spectators player->position = 0; player->positiondelay = 0; + player->teamposition = 0; + player->teamimportance = 0; return; } @@ -11808,23 +12162,40 @@ void K_KartUpdatePosition(player_t *player) realplayers++; + const boolean same_team = G_SameTeam(player, &players[i]); + +#define increment_position(condition) \ + if (condition) \ + { \ + position++; \ + if (!same_team) \ + { \ + team_position++; \ + } \ + } \ + else \ + { \ + if (!same_team) \ + { \ + team_importance++; \ + } \ + } + if (gametyperules & GTR_CIRCUIT) { if (player->exiting) // End of match standings { // Only time matters - if (players[i].realtime < player->realtime) - position++; + increment_position(players[i].realtime < player->realtime) } else { // I'm a lap behind this player OR // My distance to the finish line is higher, so I'm behind - if ((players[i].laps > player->laps) - || (players[i].distancetofinish < player->distancetofinish)) - { - position++; - } + increment_position( + (players[i].laps > player->laps) + || (players[i].distancetofinish < player->distancetofinish) + ) } } else @@ -11832,8 +12203,7 @@ void K_KartUpdatePosition(player_t *player) if (player->exiting) // End of match standings { // Only score matters - if (players[i].roundscore > player->roundscore) - position++; + increment_position(players[i].roundscore > player->roundscore) } else { @@ -11843,26 +12213,25 @@ void K_KartUpdatePosition(player_t *player) // First compare all points if (players[i].roundscore > player->roundscore) { - position++; + increment_position(true) } else if (players[i].roundscore == player->roundscore) { // Emeralds are a tie breaker if (yourEmeralds > myEmeralds) { - position++; + increment_position(true) } else if (yourEmeralds == myEmeralds) { // Bumpers are the second tier tie breaker - if (K_Bumpers(&players[i]) > K_Bumpers(player)) - { - position++; - } + increment_position(K_Bumpers(&players[i]) > K_Bumpers(player)) } } } } + +#undef increment_position } } @@ -11890,7 +12259,9 @@ void K_KartUpdatePosition(player_t *player) /* except in FREE PLAY */ if (player->curshield == KSHIELD_TOP && (gametyperules & GTR_CIRCUIT) && - realplayers > 1) + realplayers > 1 && + !specialstageinfo.valid + && !K_Cooperative()) { /* grace period so you don't fall off INSTANTLY */ if (K_GetItemRouletteDistance(player, 8) < 2000 && player->topinfirst < 2*TICRATE) // "Why 8?" Literally no reason, but since we intend for constant-ish distance we choose a fake fixed playercount. @@ -11916,6 +12287,15 @@ void K_KartUpdatePosition(player_t *player) } player->position = position; + player->teamposition = team_position; + + // "Team importance" is used for scoring + // in gametypes without scoring / point limit. + player->teamimportance = (team_importance * 2); + if (position == 1) + { + player->teamimportance++; + } } void K_UpdateAllPlayerPositions(void) @@ -11956,6 +12336,26 @@ void K_UpdateAllPlayerPositions(void) K_KartUpdatePosition(&players[i]); } } + + // Team Race: Live update score. + if (G_GametypeHasTeams() == true && (gametyperules & GTR_POINTLIMIT) == 0) + { + for (i = 0; i < TEAM__MAX; i++) + { + g_teamscores[i] = 0; + } + + for (i = 0; i < MAXPLAYERS; i++) + { + const player_t *player = &players[i]; + if (playeringame[i] == false || player->spectator == true || player->team == TEAM_UNASSIGNED) + { + continue; + } + + g_teamscores[player->team] += player->teamimportance; + } + } } // @@ -12460,6 +12860,10 @@ static void K_KartSpindash(player_t *player) if (player->fastfall == 0) { + if (player->pflags2 & PF2_STRICTFASTFALL) + if (!(player->cmd.buttons & BT_SPINDASH)) + return; + // Factors 3D momentum. player->fastfallBase = FixedHypot(player->speed, player->mo->momz); } @@ -12662,6 +13066,7 @@ boolean K_FastFallBounce(player_t *player) } player->mo->momz = bounce * P_MobjFlip(player->mo); + // CONS_Printf("%s FastFallBounce %d\n", player_names[player-players], player->mo->momz); return true; } @@ -12978,6 +13383,7 @@ void K_MoveKartPlayer(player_t *player, boolean onground) player->ringboxdelay--; if (player->ringboxdelay == 0) { + player->lastringboost = player->ringboost; UINT32 award = 5*player->ringboxaward + 10; if (!K_ThunderDome()) award = 3 * award / 2; @@ -12987,18 +13393,12 @@ void K_MoveKartPlayer(player_t *player, boolean onground) // SPB Attack is hard. award = award / 2; } - else if (modeattacking) + else if (K_LegacyRingboost(player)) { - // At high distance values, the power of Ring Box is mainly an extra source of speed, to be - // stacked with power items (or itself!) during the payout period. - // Low-dist Ring Box follows some special rules, to somewhat normalize the reward between stat - // blocks that respond to rings differently; here, variance in payout period counts for a lot! - + // An ancient power is revealed once more... UINT8 accel = 10-player->kartspeed; UINT8 weight = player->kartweight; - // Fixed point math can suck a dick. - if (accel > weight) { accel *= 10; @@ -13006,21 +13406,73 @@ void K_MoveKartPlayer(player_t *player, boolean onground) } else { + accel *= 3; weight *= 10; } award = (110 + accel + weight) * award / 120; } + else if (modeattacking) + { + // TA has: + // - no one to tether from + // - no player damage + // - no player bumps + // ...which nullifies a lot of designed advantages for accel types and high-weight racers. + // + // In addition, it's at Gear 3 Thunderdome speed, which can make it hard for heavies to + // take strong lines without brakedrifting. + // + // To try and help close this gap, we fudge Ring Box payouts to allow weaker characters + // better access to things that make them go fast, without changing core handling. + + UINT8 accel = 10-player->kartspeed; + UINT8 weight = player->kartweight; + + // Relative stat power for bonus TA Ring Box awards. + // AP 1, WP 2 = weight is worth twice what accel is. + // 0 = stat not considered at all! + UINT8 accelPower = 0; + UINT8 weightPower = 4; + + UINT8 total = accelPower*accel + weightPower*weight; + UINT8 maxtotal = accelPower*9 + weightPower*9; + + // Scale from base payout at 9/1 to max payout at 1/9. + award = Easing_InCubic(FRACUNIT*total/maxtotal, 13*award/10, 18*award/10); + + // And, because we don't have to give a damn about sandbagging, up the stakes the longer we progress! + if (gametyperules & GTR_CIRCUIT) + { + UINT8 maxgrade = 10; + UINT8 margin = min(player->gradingpointnum, maxgrade); + + award = Easing_Linear(FRACUNIT * margin / maxgrade, award, 2*award); + } + } else { UINT32 behind = K_GetItemRouletteDistance(player, player->itemRoulette.playing); - behind = FixedMul(behind, max(player->exp, FRACUNIT/2)); + behind = FixedMul(behind, max(player->gradingfactor, FRACUNIT/2)); UINT32 behindMulti = behind / 500; behindMulti = min(behindMulti, 60); award = award * (behindMulti + 10) / 10; } + // Felt kinda arbitrary, replaced with G3+ fast payout. Sealed away for later...? + + /* + // Stacked Ring Box is good. REALLY good. "Uncapped speed that feeds into itself" good. + // Keep highly unusual values under control, using the following core rule: + // If we already have more boost than we're about to be awarded, STOP!!! + UINT32 existing = (player->ringboost / K_GetFullKartRingPower(player, true)); // How many rings (effectively) do we have boost credit for right now? + UINT32 reduction = 8*existing/10; // Take an arbitrary percentage of those rings, and... + fixed_t reductionfactor = FixedDiv(FRACUNIT*reduction, FRACUNIT*award); // ...get a ratio to compare our potential award against it. 0 = no existing boost, 1+ = existing boost comparable to our award. + reductionfactor = min(reductionfactor, FRACUNIT); // Cap for easing function, and... + award = Easing_Linear(reductionfactor, award, award/4); // ...ease between unmodified and minimum award. + */ + K_AwardPlayerRings(player, award, true); player->ringboxaward = 0; } @@ -13239,11 +13691,23 @@ void K_MoveKartPlayer(player_t *player, boolean onground) P_SetOrigin(ring, ring->x, ring->y, ring->z); ring->extravalue1 = 1; - player->rings--; + UINT8 dumprate = 3; + + // Allow players to spend out of pending payout to "dump" rings faster. + if (player->superring) + { + player->superring--; + dumprate = 2; + } + else + { + player->rings--; + } + if (player->autoring && !(cmd->buttons & BT_ATTACK)) player->ringdelay = tiereddelay; else - player->ringdelay = 3; + player->ringdelay = dumprate; if (player->rings == 0) K_Overdrive(player); @@ -13536,7 +14000,14 @@ void K_MoveKartPlayer(player_t *player, boolean onground) if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) { player->itemamount--; - K_ThrowLandMine(player); + if (player->throwdir > 0) + { + K_ThrowKartItem(player, true, MT_LANDMINE, -1, 0, 0); + } + else + { + K_ThrowLandMine(player); + } K_PlayAttackTaunt(player->mo); player->botvars.itemconfirm = 0; } @@ -13709,6 +14180,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) P_SetTarget(&shield->target, player->mo); S_StartSound(player->mo, sfx_s3k41); player->curshield = KSHIELD_LIGHTNING; + + Obj_SpawnLightningShieldVisuals(shield); } if (ATTACK_IS_DOWN && !HOLDING_ITEM && NO_HYUDORO) @@ -13786,6 +14259,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) P_SetTarget(&shield->target, player->mo); S_StartSound(player->mo, sfx_s3k3f); player->curshield = KSHIELD_BUBBLE; + + Obj_SpawnBubbleShieldVisuals(shield); } if (!HOLDING_ITEM && NO_HYUDORO) @@ -13839,6 +14314,8 @@ void K_MoveKartPlayer(player_t *player, boolean onground) P_SetTarget(&shield->target, player->mo); S_StartSound(player->mo, sfx_s3k3e); player->curshield = KSHIELD_FLAME; + + Obj_SpawnFlameShieldVisuals(shield); } if (!HOLDING_ITEM && NO_HYUDORO) @@ -14900,8 +15377,17 @@ void K_EggmanTransfer(player_t *source, player_t *victim) if (victim->eggmanexplode) return; + boolean prank = false; + + if (victim->itemRoulette.eggman) + { + K_StopRoulette(&source->itemRoulette); + prank = true; // Give the transferring player the victim's eggbox roulette?! + } + K_AddHitLag(victim->mo, 5, false); K_DropItems(victim); + victim->eggmanexplode = 6*TICRATE; victim->eggmanblame = (source - players); K_StopRoulette(&victim->itemRoulette); @@ -14910,9 +15396,20 @@ void K_EggmanTransfer(player_t *source, player_t *victim) S_StartSound(NULL, sfx_itrole); K_AddHitLag(source->mo, 5, false); - source->eggmanexplode = 0; - source->eggmanblame = -1; - K_StopRoulette(&source->itemRoulette); + + if (prank) + { + source->eggmanexplode = 0; + source->eggmanblame = (victim - players); + K_StartEggmanRoulette(source); + S_StartSound(source->mo, sfx_s223); + } + else + { + source->eggmanexplode = 0; + source->eggmanblame = -1; + K_StopRoulette(&source->itemRoulette); + } source->eggmanTransferDelay = 25; victim->eggmanTransferDelay = 15; @@ -14996,7 +15493,7 @@ UINT32 K_PointLimitForGametype(void) // counted. for (i = 0; i < MAXPLAYERS; ++i) { - if (D_IsPlayerHumanAndGaming(i)) + if (playeringame[i] == true && players[i].spectator == false) { ptsCap += 3; } @@ -15006,6 +15503,43 @@ UINT32 K_PointLimitForGametype(void) { ptsCap = 16; } + + if (G_GametypeHasTeams() == true) + { + // Scale up the point limit based on + // the team sizes. Based upon the smallest + // team, because it would make an uneven + // fucked up 1v15 possible to win, even + // if it was still unbalanced. + const UINT32 old_ptsCap = ptsCap; + UINT8 smallest_team = MAXPLAYERS; + + for (i = TEAM_UNASSIGNED+1; i < TEAM__MAX; i++) + { + UINT8 countteam = G_CountTeam(i); + smallest_team = min( smallest_team, countteam ); + } + + if (smallest_team > 1) + { + UINT8 pts_accumulator = ptsCap / 2; + for (i = 0; i < smallest_team - 1; i++) + { + if (pts_accumulator == 0) + { + break; + } + + ptsCap += pts_accumulator; + pts_accumulator /= 2; + } + } + + CONS_Debug( + DBG_TEAMS, "Team Battle: points cap increased from %u to %u. (team size is %u)\n", + old_ptsCap, ptsCap, smallest_team + ); + } } return ptsCap; @@ -15103,25 +15637,34 @@ boolean K_PlayerCanUseItem(player_t *player) return (player->mo->health > 0 && !player->spectator && !P_PlayerInPain(player) && !mapreset && leveltime > introtime); } -fixed_t K_GetExpAdjustment(player_t *player) +fixed_t K_GetGradingMultAdjustment(player_t *player) { - fixed_t exp_power = 3*FRACUNIT/100; // adjust to change overall xp volatility - fixed_t exp_stablerate = 3*FRACUNIT/10; // how low is your placement before losing XP? 4*FRACUNIT/10 = top 40% of race will gain + fixed_t power = 3*FRACUNIT/100; // adjust to change overall xp volatility + fixed_t stablerate = 3*FRACUNIT/10; // how low is your placement before losing XP? 4*FRACUNIT/10 = top 40% of race will gain fixed_t result = 0; - INT32 live_players = 0; + if (g_teamplay) + power = 3 * power / 4; + + INT32 live_players = 0; // players we are competing against for (INT32 i = 0; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator || player == players+i) continue; + if (G_SameTeam(player, &players[i]) == true) + { + // You don't win/lose against your teammates. + continue; + } + live_players++; } if (live_players < 8) { - exp_power += (8 - live_players) * exp_power/4; + power += (8 - live_players) * power/4; } // Increase XP for each player you're beating... @@ -15130,21 +15673,217 @@ fixed_t K_GetExpAdjustment(player_t *player) if (!playeringame[i] || players[i].spectator || player == players+i) continue; + if (G_SameTeam(player, &players[i]) == true) + { + // You don't win/lose against your teammates. + continue; + } + if (player->position < players[i].position) - result += exp_power; + result += power; } // ...then take all of the XP you could possibly have earned, // and lose it proportional to the stable rate. If you're below // the stable threshold, this results in you losing XP. - result -= FixedMul(exp_power, FixedMul(live_players*FRACUNIT, FRACUNIT - exp_stablerate)); + result -= FixedMul(power, FixedMul(live_players*FRACUNIT, FRACUNIT - stablerate)); return result; } +UINT16 K_GetEXP(player_t *player) +{ + UINT32 numgradingpoints = K_GetNumGradingPoints(); + // target is where you should be if you're doing good and at a 1.0 mult + fixed_t clampedmult = max(FRACUNIT/2, min(FRACUNIT*5/4, player->gradingfactor)); // clamp between 0.5 and 1.25 + fixed_t targetexp = (TARGETEXP*player->gradingpointnum/max(1,numgradingpoints))<>FRACBITS; + return exp; +} + UINT32 K_GetNumGradingPoints(void) { + if (K_Cooperative()) + return 0; + return numlaps * (1 + Obj_GetCheckpointCount()); } +void K_BotHitPenalty(player_t *player) +{ + if (K_PlayerUsesBotMovement(player)) + { + player->botvars.rubberband = max(player->botvars.rubberband/2, FRACUNIT/2); + player->botvars.bumpslow = TICRATE*2; + } +} + +static boolean K_PickUp(player_t *player, mobj_t *picked) +{ + SINT8 type = -1; + SINT8 amount = 1; + + switch (picked->type) + { + case MT_ORBINAUT: + case MT_ORBINAUT_SHIELD: + type = KITEM_ORBINAUT; + break; + case MT_JAWZ: + case MT_JAWZ_SHIELD: + type = KITEM_JAWZ; + break; + case MT_BALLHOG: + case MT_BALLHOGBOOM: + type = KITEM_BALLHOG; + break; + case MT_LANDMINE: + type = KITEM_LANDMINE; + break; + case MT_EGGMANITEM: + case MT_EGGMANITEM_SHIELD: + type = KITEM_EGGMAN; + break; + case MT_BANANA: + case MT_BANANA_SHIELD: + type = KITEM_BANANA; + break; + case MT_DROPTARGET: + case MT_DROPTARGET_SHIELD: + type = KITEM_DROPTARGET; + break; + case MT_GACHABOM: + type = KITEM_GACHABOM; + break; + case MT_BUBBLESHIELDTRAP: + type = KITEM_BUBBLESHIELD; + break; + case MT_SINK: + type = KITEM_KITCHENSINK; + break; + default: + type = KITEM_SAD; + break; + } + + if (type == KITEM_SAD) + return false; + + // CONS_Printf("it %d ia %d t %d a %d\n", player->itemtype, player->itemamount, type, amount); + + if (player->itemtype == type && player->itemamount && !(player->itemflags & IF_ITEMOUT)) + { + // We have this item in main slot but not deployed, just add it + player->itemamount += amount; + } + else if (player->backupitemamount && player->backupitemtype) + { + // We already have a backup item, stack it if it can be stacked or discard it + if (player->backupitemtype == type) + { + player->backupitemamount += amount; + } + else + { + K_DropPaperItem(player, player->backupitemtype, player->backupitemamount); + player->backupitemtype = type; + player->backupitemamount = amount; + S_StartSound(player->mo, sfx_kc65); + } + } + else + { + // We have no backup item, load one up + player->backupitemtype = type; + player->backupitemamount = amount; + } + + S_StartSound(player->mo, sfx_aple); + K_TryMoveBackupItem(player); + + mobj_t *gotit = P_SpawnMobjFromMobj(player->mo, 0, 0, player->mo->height/2, MT_GOTIT); + P_SetTarget(&gotit->target, player->mo); + + return true; +} + +// ACHTUNG this destroys items when returning true, make sure to bail out +boolean K_TryPickMeUp(mobj_t *m1, mobj_t *m2) +{ + if (!m1 || P_MobjWasRemoved(m1)) + return false; + + if (!m2 || P_MobjWasRemoved(m2)) + return false; + + if (m1->type != MT_PLAYER && m2->type != MT_PLAYER) + return false; + + if (m1->type == MT_PLAYER && m2->type == MT_PLAYER) + return false; + + // CONS_Printf("player check passed\n"); + + mobj_t *victim = m1; + mobj_t *inflictor = m2; + + // Convenience for collision functions where arg order is freaky + if (m2->type == MT_PLAYER) + { + victim = m2; + inflictor = m1; + } + + if (!victim->player) + return false; + + boolean allied = (inflictor->target == victim); + + if (!allied && inflictor->target && !P_MobjWasRemoved(inflictor->target)) + if (inflictor->target->player && G_SameTeam(inflictor->target->player, victim->player)) + allied = true; + + if (!allied) + return false; + + // CONS_Printf("target check passed\n"); + + if (!K_PickUp(victim->player, inflictor)) + return false; + + K_AddHitLag(victim, 3, false); + + P_RemoveMobj(inflictor); + return true; +} + +fixed_t K_TeamComebackMultiplier(player_t *player) +{ + INT32 myteam = player->team; + INT32 theirteam = (myteam == TEAM_ORANGE) ? TEAM_BLUE : TEAM_ORANGE; + + if (g_teamscores[myteam] >= g_teamscores[theirteam]) + return FRACUNIT; + + UINT32 ourdistance = 0; + UINT32 theirdistance = 0; + + for (UINT8 i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator) + continue; + + if (players[i].team == myteam) + ourdistance += K_GetItemRouletteDistance(&players[i], players[i].itemRoulette.playing); + else + theirdistance += K_GetItemRouletteDistance(&players[i], players[i].itemRoulette.playing); + } + + fixed_t multiplier = FixedDiv(ourdistance, theirdistance); + multiplier = min(multiplier, 3*FRACUNIT); + multiplier = max(multiplier, FRACUNIT); + + return multiplier; +} + //} diff --git a/src/k_kart.h b/src/k_kart.h index faabd9704..1b817eb53 100644 --- a/src/k_kart.h +++ b/src/k_kart.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2018 by ZarroTsu. // // This program is free software distributed under the @@ -50,6 +50,8 @@ Make sure this matches the actual number of states #define MINCOMBOFLOAT (mapobjectscale*1) #define MAXCOMBOTIME (TICRATE*4) +#define TIMEATTACK_START (TICRATE*10) + #define OVERDRIVE_STARTUP (0) #define AMPLEVEL (15) @@ -58,6 +60,10 @@ Make sure this matches the actual number of states #define RR_PROJECTILE_FUSE (8*TICRATE) +#define SCAMDIST (2000) + +#define EARLY_ITEM_FLICKER (NUMTRANSMAPS) + // 2023-08-26 +ang20 to Sal's OG values to make them friendlier - Tyron #define STUMBLE_STEEP_VAL (ANG60 + ANG20) #define STUMBLE_STEEP_VAL_AIR (ANG30 + ANG10 + ANG20) @@ -82,6 +88,13 @@ Make sure this matches the actual number of states #define MAXTOPACCEL (12*FRACUNIT) #define TOPACCELREGEN (FRACUNIT/16) +// Handling boosts and sliptide conditions got weird. +// You must be under a handling boost of at least SLIPTIDEHANDLING to sliptide. +// HANDLESCALING is used to adjust all handling boosts simultaneously (weight factors in the future?) +// If you need to touch this in an involved way later, please just make sliptide eligibility a flag LMAO +#define HANDLESCALING (7*FRACUNIT/8) +#define SLIPTIDEHANDLING (HANDLESCALING/2) + // Mispredicted turns can generate phantom sliptide inputs for a few tics. // Delay the wavedash visuals until we're reasonably sure that it's a deliberate turn. #define HIDEWAVEDASHCHARGE (60) @@ -179,6 +192,7 @@ UINT16 K_DriftSparkColor(player_t *player, INT32 charge); void K_SpawnBoostTrail(player_t *player); void K_SpawnSparkleTrail(mobj_t *mo); void K_SpawnWipeoutTrail(mobj_t *mo); +void K_SpawnFireworkTrail(mobj_t *mo); void K_SpawnDraftDust(mobj_t *mo); void K_SpawnMagicianParticles(mobj_t *mo, int spread); void K_DriftDustHandling(mobj_t *spawner); @@ -196,6 +210,7 @@ void K_RepairOrbitChain(mobj_t *orbit); void K_CalculateBananaSlope(mobj_t *mobj, fixed_t x, fixed_t y, fixed_t z, fixed_t radius, fixed_t height, boolean flip, boolean player); mobj_t *K_FindJawzTarget(mobj_t *actor, player_t *source, angle_t range); INT32 K_GetKartRingPower(const player_t *player, boolean boosted); +INT32 K_GetFullKartRingPower(const player_t *player, boolean boosted); boolean K_CheckPlayersRespawnColliding(INT32 playernum, fixed_t x, fixed_t y); INT16 K_UpdateSteeringValue(INT16 inputSteering, INT16 destSteering); INT16 K_GetKartTurnValue(const player_t *player, INT16 turnvalue); @@ -300,10 +315,20 @@ boolean K_ThunderDome(void); boolean K_PlayerCanUseItem(player_t *player); -fixed_t K_GetExpAdjustment(player_t *player); +fixed_t K_GetGradingMultAdjustment(player_t *player); + +UINT16 K_GetEXP(player_t *player); UINT32 K_GetNumGradingPoints(void); +boolean K_LegacyRingboost(player_t *player); + +void K_BotHitPenalty(player_t *player); + +boolean K_TryPickMeUp(mobj_t *m1, mobj_t *m2); + +fixed_t K_TeamComebackMultiplier(player_t *player); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_mapuser.c b/src/k_mapuser.c index 22319fd9f..740798bd3 100644 --- a/src/k_mapuser.c +++ b/src/k_mapuser.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_mapuser.h b/src/k_mapuser.h index 6d784d751..40a7e5d85 100644 --- a/src/k_mapuser.h +++ b/src/k_mapuser.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_menu.h b/src/k_menu.h index 116caa793..c9564c484 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2016 by Kay "Kaito" Sinclaire. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. @@ -346,6 +346,7 @@ typedef enum mopt_profiles = 0, mopt_video, mopt_sound, + mopt_voice, mopt_hud, mopt_gameplay, mopt_server, @@ -468,6 +469,9 @@ extern menu_t OPTIONS_VideoAdvancedDef; extern menuitem_t OPTIONS_Sound[]; extern menu_t OPTIONS_SoundDef; +extern menuitem_t OPTIONS_Voice[]; +extern menu_t OPTIONS_VoiceDef; + extern menuitem_t OPTIONS_HUD[]; extern menu_t OPTIONS_HUDDef; @@ -477,15 +481,17 @@ extern menu_t OPTIONS_HUDOnlineDef; typedef enum { gopt_spacer0 = 0, - gopt_gamespeed, + gopt_teamplay, gopt_frantic, + gopt_spacer1, + gopt_gamespeed, gopt_encore, gopt_exitcountdown, - gopt_spacer1, + gopt_spacer2, gopt_timelimit, gopt_pointlimit, gopt_startingbumpers, - gopt_spacer2, + gopt_spacer3, gopt_itemtoggles } gopt_e; @@ -1124,6 +1130,8 @@ extern consvar_t cv_dummyprofileplayername; extern consvar_t cv_dummyprofilekickstart; extern consvar_t cv_dummyprofileautoroulette; extern consvar_t cv_dummyprofilelitesteer; +extern consvar_t cv_dummyprofilestrictfastfall; +extern consvar_t cv_dummyprofiledescriptiveinput; extern consvar_t cv_dummyprofileautoring; extern consvar_t cv_dummyprofilerumble; extern consvar_t cv_dummyprofilefov; @@ -1478,6 +1486,7 @@ extern struct statisticsmenu_s { INT32 gotmedals; INT32 nummedals; INT32 numextramedals; + INT32 numcanbonus; UINT32 statgridplayed[9][9]; INT32 maxscroll; UINT16 *maplist; diff --git a/src/k_menudraw.c b/src/k_menudraw.c index dce2b8c7b..61853ece5 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2016 by Kay "Kaito" Sinclaire. // Copyright (C) 2020 by Sonic Team Junior. // @@ -755,6 +755,8 @@ static void M_DrawMenuTyping(void) } +// Largely replaced by boxed drawing mode in K_DrawGameControl and rich text +/* static void M_DrawMediocreKeyboardKey(const char *text, INT32 *workx, INT32 worky, boolean push, boolean rightaligned) { INT32 buttonwidth = V_StringWidth(text, 0) + 2; @@ -779,6 +781,7 @@ static void M_DrawMediocreKeyboardKey(const char *text, INT32 *workx, INT32 work 0, text ); } +*/ // Draw the message popup submenu void M_DrawMenuMessage(void) @@ -806,10 +809,6 @@ void M_DrawMenuMessage(void) INT32 workx = x + menumessage.x; INT32 worky = y + menumessage.y; - boolean standardbuttons = ( - cv_currprofile.value != -1 || G_GetNumAvailableGamepads() - ); - boolean push; if (menumessage.closing) @@ -830,26 +829,11 @@ void M_DrawMenuMessage(void) workx -= 2; - if (standardbuttons) - { - workx -= SHORT(kp_button_x[1][0]->width); - K_drawButton( - workx * FRACUNIT, worky * FRACUNIT, - 0, kp_button_x[1], - push - ); - - workx -= SHORT(kp_button_b[1][0]->width); - K_drawButton( - workx * FRACUNIT, worky * FRACUNIT, - 0, kp_button_b[1], - push - ); - } - else - { - M_DrawMediocreKeyboardKey("ESC", &workx, worky, push, true); - } + workx -= K_DrawGameControl( + workx+2, worky+2, + 0, " ", + 2, 8, 0 + ); if (menumessage.confirmstr) { @@ -869,19 +853,11 @@ void M_DrawMenuMessage(void) workx -= 2; } - if (standardbuttons) - { - workx -= SHORT(kp_button_a[1][0]->width); - K_drawButton( - workx * FRACUNIT, worky * FRACUNIT, - 0, kp_button_a[1], - push - ); - } - else - { - M_DrawMediocreKeyboardKey("ENTER", &workx, worky, push, true); - } + workx -= K_DrawGameControl( + workx+2, worky+2, + 0, " ", + 2, 8, 0 + ); } x -= 4; @@ -1264,7 +1240,7 @@ static INT32 M_DrawRejoinIP(INT32 x, INT32 y, INT32 tx) V_DrawMenuString(x - 10 - (skullAnimCounter/5), y, f, "\x1C"); // left arrow V_DrawMenuString(x + w + 2+ (skullAnimCounter/5), y, f, "\x1D"); // right arrow V_DrawThinString(x, y, f, text); - V_DrawRightAlignedThinString(BASEVIDWIDTH + 4 + tx, y, V_ORANGEMAP, "\xAC Rejoin"); + K_DrawGameControl(BASEVIDWIDTH + 4 + tx, y, 0, " Rejoin", 2, 0, V_ORANGEMAP); return shift; } @@ -1928,7 +1904,7 @@ static boolean M_DrawFollowerSprite(INT16 x, INT16 y, INT32 num, boolean charfli static void M_DrawCharSelectSprite(UINT8 num, INT16 x, INT16 y, boolean charflip) { setup_player_t *p = &setup_player[num]; - UINT8 color; + UINT16 color; UINT8 *colormap; if (p->skin < 0) @@ -2122,6 +2098,7 @@ static void M_DrawCharSelectPreview(UINT8 num) if (p->showextra == true) { INT32 randomskin = 0; + INT32 doping = 0; switch (p->mdepth) { case CSSTEP_ALTS: // Select clone @@ -2132,6 +2109,7 @@ static void M_DrawCharSelectPreview(UINT8 num) V_DrawThinString(x-3, y+12, 0, skins[setup_chargrid[p->gridx][p->gridy].skinlist[p->clonenum]].name); randomskin = (skins[setup_chargrid[p->gridx][p->gridy].skinlist[p->clonenum]].flags & SF_IRONMAN); + doping = (skins[setup_chargrid[p->gridx][p->gridy].skinlist[p->clonenum]].flags & SF_HIVOLT); } else { @@ -2140,7 +2118,8 @@ static void M_DrawCharSelectPreview(UINT8 num) /* FALLTHRU */ case CSSTEP_CHARS: // Character Select grid V_DrawThinString(x-3, y+2, 0, va("Class %c (s %c - w %c)", - ('A' + R_GetEngineClass(p->gridx+1, p->gridy+1, randomskin)), + (doping + ? 'R' : ('A' + R_GetEngineClass(p->gridx+1, p->gridy+1, randomskin))), (randomskin ? '?' : ('1'+p->gridx)), (randomskin @@ -2462,19 +2441,16 @@ void M_DrawCharacterSelect(void) } { - const int kLeft = 76; const int kTop = 6; - const int kButtonWidth = 16; - INT32 x = basex + kLeft; if (!optionsmenu.profile) // Does nothing on this screen { - K_drawButton((x += 22) * FRACUNIT, (kTop - 3) * FRACUNIT, 0, kp_button_r, M_MenuButtonPressed(pid, MBT_R)); - V_DrawThinString((x += kButtonWidth), kTop, 0, "Info"); + K_DrawGameControl(BASEVIDWIDTH/2, kTop, pid, " Info Default", 1, 0, 0); + } + else + { + K_DrawGameControl(BASEVIDWIDTH/2+62, kTop, pid, " Accept Back Default", 1, 0, 0); } - - K_drawButton((x += 58) * FRACUNIT, (kTop - 1) * FRACUNIT, 0, kp_button_c[1], M_MenuButtonPressed(pid, MBT_C)); - V_DrawThinString((x += kButtonWidth), kTop, 0, "Default"); } // We have to loop twice -- first time to draw the drop shadows, a second time to draw the icons. @@ -2584,6 +2560,11 @@ void M_DrawCharacterSelect(void) // Draw the priority player over the other ones M_DrawCharSelectCursor(priority); } + + if (setup_numplayers > 1) + { + V_DrawCenteredThinString(BASEVIDWIDTH/2, BASEVIDHEIGHT-12, V_30TRANS, "\x85""Double-input problems?\x80 Close Steam, DS4Windows, and other controller wrappers!"); + } } // DIFFICULTY SELECT @@ -2744,21 +2725,16 @@ void M_DrawRaceDifficulty(void) V_DrawMappedPatch(cx, cy, 0, W_CachePatchName("OFF_TOGG", PU_CACHE), NULL); } - patch_t **bt = NULL; switch (it->mvar2) { case MBT_Y: - bt = kp_button_y[1]; + K_DrawGameControl(cx + 24, cy + 22, 0, activated ? "" : "", 0, 8, 0); break; case MBT_Z: - bt = kp_button_z[1]; + K_DrawGameControl(cx + 24, cy + 22, 0, activated ? "" : "", 0, 8, 0); break; } - if (bt) - { - K_drawButton((cx + 24) * FRACUNIT, (cy + 22) * FRACUNIT, 0, bt, activated); - } break; } } @@ -3298,11 +3274,179 @@ void M_DrawCupSelect(void) } INT16 ty = M_EaseWithTransition(Easing_Linear, 5 * 24); - V_DrawFill(0, 146 + ty, BASEVIDWIDTH, 54, 31); - M_DrawCupPreview(146 + ty, &templevelsearch); + y = 146 + ty; + V_DrawFill(0, y, BASEVIDWIDTH, 54, 31); + M_DrawCupPreview(y, &templevelsearch); M_DrawCupTitle(120 - ty, &templevelsearch); + if (templevelsearch.grandprix == false && templevelsearch.cup != NULL) + { + if (templevelsearch.cup != &dummy_lostandfound) + { + templevelsearch.checklocked = false; + } + + // The following makes a HUGE assumption that we're + // never going to have more than ~9 Race Courses + // (with Medals) in Lost & Found. Which is almost + // certainly true for Krew, but is very likely to + // be violated by the long tail of modding. To those + // finding this eventually: I'M SORRY ~toast 221024 + + struct work_array_t { + emblem_t *medal; + UINT16 col; + UINT16 dotcol; + } work_array[CUPCACHE_MAX]; + + boolean incj = false; + + i = j = 0; + + INT16 map = M_GetFirstLevelInList(&i, &templevelsearch); + emblem_t *emblem = NULL; + + while (map < nummapheaders && j < CUPCACHE_MAX) + { + if (map < basenummapheaders) + { + emblem = NULL; + incj = false; + + work_array[j].medal = NULL; + work_array[j].col = work_array[j].dotcol = MCAN_INVALID; + + if (templevelsearch.timeattack) + { + emblem = M_GetLevelEmblems(map+1); + + while (emblem) + { + if (emblem->type == ET_TIME) + { + incj = true; + + if (gamedata->collected[emblem-emblemlocations]) + { + if (!work_array[j].medal + || ( + (work_array[j].medal->type == ET_TIME) + && (work_array[j].medal->tag < emblem->tag) + ) + ) + { + work_array[j].medal = emblem; + } + } + } + else if ((emblem->type == ET_MAP) + && (emblem->flags & ME_SPBATTACK)) + { + incj = true; + + if (gamedata->collected[emblem-emblemlocations]) + { + work_array[j].dotcol = M_GetEmblemColor(emblem); + } + } + + emblem = M_GetLevelEmblems(-1); + } + } + else if (mapheaderinfo[map]->typeoflevel & TOL_RACE) + { + incj = true; + + if (mapheaderinfo[map]->records.spraycan == MCAN_BONUS) + { + work_array[j].col = MCAN_BONUS; + } + else if (mapheaderinfo[map]->records.spraycan < gamedata->numspraycans) + { + work_array[j].col = gamedata->spraycans[mapheaderinfo[map]->records.spraycan].col; + } + + if (mapheaderinfo[map]->records.mapvisited & MV_MYSTICMELODY) + { + work_array[j].dotcol = SKINCOLOR_TURQUOISE; + } + } + + if (incj) + j++; + } + + map = M_GetNextLevelInList(map, &i, &templevelsearch); + } + + if (j) + { + x = (BASEVIDWIDTH - (j*10))/2 + 1; + y += 2; + + V_DrawFill(x - 4, y, (j*10) + 6, 3, 31); + for (i = 1; i <= 6; i++) + { + V_DrawFill(x + i - 4, y+2+i, (j*10) + 6 - (i*2), 1, 31); + } + + y--; + + for (i = 0; i < j; i++) + { + if (templevelsearch.timeattack) + { + if (work_array[i].medal) + { + // Primary Medal + + V_DrawMappedPatch(x, y, 0, W_CachePatchName(M_GetEmblemPatch(work_array[i].medal, false), PU_CACHE), + R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(work_array[i].medal), GTC_MENUCACHE)); + } + else + { + // Need it! + + V_DrawScaledPatch(x, y, 0, W_CachePatchName("NEEDIT", PU_CACHE)); + } + } + else + { + if (work_array[i].col == MCAN_BONUS) + { + // Bonus in place of Spray Can + + V_DrawScaledPatch(x, y, 0, W_CachePatchName("GOTBON", PU_CACHE)); + } + else if (work_array[i].col < numskincolors) + { + // Spray Can + + V_DrawMappedPatch(x, y, 0, W_CachePatchName("GOTCAN", PU_CACHE), + R_GetTranslationColormap(TC_DEFAULT, work_array[i].col, GTC_MENUCACHE)); + } + else + { + // Need it! + + V_DrawFill(x+3, y+3, 2, 2, 6); + } + } + + if (work_array[i].dotcol < numskincolors) + { + // Bonus (Secondary Medal or Ancient Shrine) + + V_DrawMappedPatch(x+4, y+7, 0, W_CachePatchName("COLORSP1", PU_CACHE), + R_GetTranslationColormap(TC_DEFAULT, work_array[i].dotcol, GTC_MENUCACHE)); + } + + x += 10; + } + } + } + if (cupgrid.numpages > 1) { x = 3 - (skullAnimCounter/5); @@ -3420,6 +3564,8 @@ static void M_DrawHighLowLevelTitle(INT16 x, INT16 y, INT16 map) V_DrawLSTitleLowString(x2, y+28, 0, word2); } +static INT32 M_DrawMapMedals(INT32 mapnum, INT32 x, INT32 y, boolean allowtime, boolean allowencore, boolean allowspb, boolean allowbonus, boolean draw); + static void M_DrawLevelSelectBlock(INT16 x, INT16 y, UINT16 map, boolean redblink, boolean greyscale) { UINT8 *colormap = NULL; @@ -3453,6 +3599,49 @@ static void M_DrawLevelSelectBlock(INT16 x, INT16 y, UINT16 map, boolean redblin ) ); } + else if (!levellist.netgame) + { + x += 80+2; + y += 50-1; + + const boolean allowencore = ( + !levellist.levelsearch.timeattack + && M_SecretUnlocked(SECRET_ENCORE, true) + ); + const boolean allowspb = ( + levellist.levelsearch.timeattack + && M_SecretUnlocked(SECRET_SPBATTACK, true) + ); + + INT32 width = x - M_DrawMapMedals(map, x, y, + levellist.levelsearch.timeattack, + allowencore, + allowspb, + !levellist.levelsearch.timeattack, + false + ); + + if (width > 2) + { + width -= 2; // minor poke + + V_DrawFill(x + 7 - width, y - 2, width, 9, 31); + + UINT8 i; + for (i = 1; i < 7; i++) + { + V_DrawFill(x + 7 - width - i, y + i - 2, 1, 9 - i, 31); + } + + M_DrawMapMedals(map, x, y, + levellist.levelsearch.timeattack, + allowencore, + allowspb, + !levellist.levelsearch.timeattack, + true + ); + } + } } void M_DrawLevelSelect(void) @@ -3662,7 +3851,7 @@ void M_DrawTimeAttack(void) if (M_EncoreAttackTogglePermitted()) { - K_drawButtonAnim(buttonx + 35, buttony - 3, 0, kp_button_r, timeattackmenu.ticker); + K_DrawGameControl(buttonx + 35, buttony - 3, 0, "", 0, 8, 0); } if ((timeattackmenu.spbflicker == 0 || timeattackmenu.ticker % 2) == (cv_dummyspbattack.value == 1)) @@ -4205,6 +4394,8 @@ void M_DrawMPServerBrowser(void) servpats[i] = W_CachePatchName(va("M_SERV%c", i + '1'), PU_CACHE); gearpats[i] = W_CachePatchName(va("M_SGEAR%c", i + '1'), PU_CACHE); } + patch_t *voicepat; + voicepat = W_CachePatchName("VOCRMU", PU_CACHE); fixed_t text1loop = SHORT(text1->height)*FRACUNIT; fixed_t text2loop = SHORT(text2->width)*FRACUNIT; @@ -4306,6 +4497,12 @@ void M_DrawMPServerBrowser(void) V_DrawFixedPatch((startx + 251)*FRACUNIT, (starty + ypos + 9)*FRACUNIT, FRACUNIT, transflag, gearpats[speed], NULL); } } + + // voice chat enabled + if (serverlist[i].info.kartvars & SV_VOICEENABLED) + { + V_DrawFixedPatch((startx - 3) * FRACUNIT, (starty + 2) * FRACUNIT, FRACUNIT, 0, voicepat, NULL); + } } ypos += SERVERSPACE; } @@ -5008,6 +5205,8 @@ static void M_DrawBindMediumString(INT32 y, INT32 flags, const char *string) ); } +// largely replaced by K_DrawGameControl +/* static INT32 M_DrawProfileLegend(INT32 x, INT32 y, const char *legend, const char *mediocre_key) { INT32 w = V_ThinStringWidth(legend, 0); @@ -5017,6 +5216,7 @@ static INT32 M_DrawProfileLegend(INT32 x, INT32 y, const char *legend, const cha M_DrawMediocreKeyboardKey(mediocre_key, &x, y, false, true); return x; } +*/ // the control stuff. // Dear god. @@ -5106,12 +5306,12 @@ void M_DrawProfileControls(void) V_DrawMenuString(x, y+2, (i == itemOn ? highlightflags : 0), currentMenu->menuitems[i].text); if (currentMenu->menuitems[i].status & IT_CVAR) // not the proper way to check but this menu only has normal onoff cvars. - { + { // (bitch you thought - Tyron 2024-09-22) INT32 w; consvar_t *cv = currentMenu->menuitems[i].itemaction.cvar; w = V_MenuStringWidth(cv->string, 0); - V_DrawMenuString(x + 12, y + 13, ((cv->flags & CV_CHEAT) && !CV_IsSetToDefault(cv) ? warningflags : highlightflags), cv->string); + V_DrawMenuString(x + 12, y + 13, (!CV_IsSetToDefault(cv) ? warningflags : highlightflags), cv->string); if (i == itemOn) { V_DrawMenuString(x - (skullAnimCounter/5), y+12, highlightflags, "\x1C"); // left arrow @@ -5300,13 +5500,50 @@ void M_DrawProfileControls(void) if (currentMenu->menuitems[itemOn].tooltip != NULL) { INT32 ypos = BASEVIDHEIGHT + hintofs - 9 - 12; - V_DrawThinString(12, ypos, V_YELLOWMAP, currentMenu->menuitems[itemOn].tooltip); - boolean standardbuttons = gamedata->gonerlevel > GDGONER_PROFILE; + if (!strcmp(currentMenu->menuitems[itemOn].tooltip, "DESCRIPTIVEINPUT-SENTINEL")) + { + char* help = va("Modern: Standard console controller/keyboard prompts."); + switch (cv_dummyprofiledescriptiveinput.value) + { + case 0: + help = va("\"Emulator\": Display the default (Saturn) controls."); + break; + case 2: + help = va("Modern Flip: Swap A+X/B+Y. Use if Modern is wrong."); + break; + case 3: + help = va("6Bt. (Auto): Tries to guess your 6-button pad's layout."); + break; + case 4: + help = va("6Bt. (A): Saturn buttons, Retro-Bit Wired DInput layout."); + break; + case 5: + help = va("6Bt. (B): Saturn buttons, Retro-Bit Wireless DInput layout."); + break; + case 6: + help = va("6Bt. (C): Saturn buttons, Retro-Bit XInput layout."); + break; + case 7: + help = va("6Bt. (D): Saturn buttons, arcade/8BitDo layout. (C/Z = RT/RB)"); + break; + case 8: + help = va("6Bt. (E): Saturn buttons, Hori/M30X layout. (LB/LT = LS/RS)"); + break; + } + + V_DrawThinString(12, ypos, V_YELLOWMAP, help); + } + else + { + V_DrawThinString(12, ypos, V_YELLOWMAP, currentMenu->menuitems[itemOn].tooltip); + } + + UINT16 oldsetting = cv_descriptiveinput->value; + CV_StealthSetValue(cv_descriptiveinput, cv_dummyprofiledescriptiveinput.value); INT32 xpos = BASEVIDWIDTH - 12; - xpos = standardbuttons ? - M_DrawProfileLegend(xpos, ypos, "\xB2 / \xBC Clear", NULL) : - M_DrawProfileLegend(xpos, ypos, "Clear", "BKSP"); + xpos = K_DrawGameControl(xpos, ypos, 0, " / Clear", 2, 0, 0); + CV_StealthSetValue(cv_descriptiveinput, oldsetting); } // Overlay for control binding @@ -6093,6 +6330,19 @@ void M_DrawPause(void) Y_RoundQueueDrawer(&standings, offset/2, false, false); } + else if (gametype == GT_TUTORIAL) + { + K_DrawGameControl(4, 184 - 60 + offset/2, 0, " Steering", 0, 0, 0); + K_DrawGameControl(4, 184 - 45 + offset/2, 0, " Accelerate", 0, 0, 0); + K_DrawGameControl(4, 184 - 30 + offset/2, 0, " Look Back", 0, 0, 0); + K_DrawGameControl(4, 184 - 15 + offset/2, 0, " Spindash", 0, 0, 0); + K_DrawGameControl(4, 184 - 0 + offset/2, 0, " Item/Rings", 0, 0, 0); + + K_DrawGameControl(90, 184 - 45 + offset/2, 0, " Brake", 0, 0, 0); + K_DrawGameControl(90, 184 - 30 + offset/2, 0, " Respawn", 0, 0, 0); + K_DrawGameControl(90, 184 - 15 + offset/2, 0, " Dialogue / Action", 0, 0, 0); + K_DrawGameControl(90, 184 - 0 + offset/2, 0, " Drift", 0, 0, 0); + } else { V_DrawMenuString(4, 188 + offset/2, V_YELLOWMAP, M_GetGameplayMode()); @@ -6190,12 +6440,12 @@ void M_DrawKickHandler(void) //V_DrawFill(32 + (playerkickmenu.player & 8), 32 + (playerkickmenu.player & 7)*8, 8, 8, playeringame[playerkickmenu.player] ? 0 : 16); V_DrawFixedPatch(0, 0, FRACUNIT, 0, W_CachePatchName("MENUHINT", PU_CACHE), NULL); - V_DrawCenteredThinString( - BASEVIDWIDTH/2, 12, - 0, + K_DrawGameControl( + BASEVIDWIDTH/2, 12, 0, (playerkickmenu.adminpowered) - ? "You are using ""\x85""Admin Tools""\x80"", ""\x83""(A)""\x80"" to kick and ""\x84""(C)""\x80"" to ban" - : K_GetMidVoteLabel(menucallvote) + ? "You are using Admin Tools. Kick Ban" + : K_GetMidVoteLabel(menucallvote), + 1, 0, 0 ); } @@ -6875,6 +7125,14 @@ void M_DrawCharacterIconAndEngine(INT32 x, INT32 y, UINT8 skin, UINT8 *colormap, } } + else if (skins[baseskin].flags & SF_HIVOLT) + { + UINT32 fucktimer = (gamedata->totalmenutime/2)%8; + UINT8 sq[] = {0, 1, 2, 2, 2, 1, 0, 0}; + UINT8 wq[] = {0, 0, 0, 1, 2, 2, 2, 1}; + s = sq[fucktimer]; + w = wq[fucktimer]; + } else { // The following is a partial duplication of R_GetEngineClass @@ -7262,9 +7520,10 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) y = BASEVIDHEIGHT-16; V_DrawGamemodeString(x, y - 33, 0, R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_PLAGUE, GTC_MENUCACHE), M_UseAlternateTitleScreen() ? "On" : "Off"); - K_drawButtonAnim(x, y, 0, kp_button_a[1], challengesmenu.ticker); - x += SHORT(kp_button_a[1][0]->width); - V_DrawThinString(x, y + 1, highlightflags, "Toggle"); + K_DrawGameControl(x, y, 0, " Toggle", 0, 0, 0); + // K_drawButtonAnim(x, y, 0, kp_button_a[1], challengesmenu.ticker); + // x += SHORT(kp_button_a[1][0]->width); + // V_DrawThinString(x, y + 1, highlightflags, "Toggle"); break; @@ -7327,9 +7586,13 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) pushed = strcmp(song, mapheaderinfo[map]->encoremusname[musicid]) == 0; } - K_drawButton(x&FRACUNIT, y*FRACUNIT, 0, kp_button_l, pushed); - x += SHORT(kp_button_l[0]->width); - V_DrawThinString(x, y + 1, (pushed ? V_GRAYMAP : highlightflags), "E Side"); + if (!pushed) + K_DrawGameControl(x, y, 0, " E Side", 0, 0, 0); + else + K_DrawGameControl(x, y, 0, " E Side", 0, 0, 0); + // K_drawButton(x&FRACUNIT, y*FRACUNIT, 0, kp_button_l, pushed); + // x += SHORT(kp_button_l[0]->width); + // V_DrawThinString(x, y + 1, (pushed ? V_GRAYMAP : highlightflags), "E Side"); x = 8; y -= 10; @@ -7348,9 +7611,13 @@ static void M_DrawChallengePreview(INT32 x, INT32 y) pushed = strcmp(song, mapheaderinfo[map]->musname[musicid]) == 0; } - K_drawButton(x*FRACUNIT, y*FRACUNIT, 0, kp_button_a[1], pushed); - x += SHORT(kp_button_a[1][0]->width); - V_DrawThinString(x, y + 1, (pushed ? V_GRAYMAP : highlightflags), "Play CD"); + if (!pushed) + K_DrawGameControl(x, y, 0, " Play CD", 0, 0, 0); + else + K_DrawGameControl(x, y, 0, " Play CD", 0, 0, 0); + // K_drawButton(x*FRACUNIT, y*FRACUNIT, 0, kp_button_a[1], pushed); + // x += SHORT(kp_button_a[1][0]->width); + // V_DrawThinString(x, y + 1, (pushed ? V_GRAYMAP : highlightflags), "Play CD"); } } default: @@ -7427,11 +7694,10 @@ static void M_DrawChallengeKeys(INT32 tilex, INT32 tiley) const boolean keybuttonpress = (menumessage.active == false && M_MenuExtraHeld(pid) == true); // Button prompt - K_drawButton( - 24 << FRACBITS, - 16 << FRACBITS, - 0, kp_button_c[1], - keybuttonpress + K_DrawGameControl( + 24, 16, + 0, keybuttonpress ? "" : "", + 0, 0, 0 ); // Metyr of rounds played that contribute to Chao Key generation @@ -7986,15 +8252,14 @@ challengedesc: #define STATSSTEP 10 -static void M_DrawMapMedals(INT32 mapnum, INT32 x, INT32 y, boolean allowtime, boolean allowencore, boolean allowspb) +static INT32 M_DrawMapMedals(INT32 mapnum, INT32 x, INT32 y, boolean allowtime, boolean allowencore, boolean allowspb, boolean allowbonus, boolean draw) { UINT8 lasttype = UINT8_MAX, curtype; // M_GetLevelEmblems is ONE-indexed, urgh emblem_t *emblem = M_GetLevelEmblems(mapnum+1); - const boolean hasmedals = (emblem != NULL); - boolean collected = false; + boolean collected = false, hasmedals = false; while (emblem) { @@ -8041,11 +8306,19 @@ static void M_DrawMapMedals(INT32 mapnum, INT32 x, INT32 y, boolean allowtime, b } // Shift over if emblem is of a different discipline - if (lasttype != UINT8_MAX && lasttype != curtype) - x -= 4; - lasttype = curtype; + if (lasttype != curtype) + { + if (lasttype != UINT8_MAX) + x -= 4; + else + hasmedals = true; - if (collected) + lasttype = curtype; + } + + if (!draw) + ; + else if (collected) V_DrawMappedPatch(x, y, 0, W_CachePatchName(M_GetEmblemPatch(emblem, false), PU_CACHE), R_GetTranslationColormap(TC_DEFAULT, M_GetEmblemColor(emblem), GTC_MENUCACHE)); else @@ -8055,14 +8328,24 @@ static void M_DrawMapMedals(INT32 mapnum, INT32 x, INT32 y, boolean allowtime, b x -= 8; } + if (!allowbonus) + return x; + if (hasmedals) x -= 4; - if (mapheaderinfo[mapnum]->cache_spraycan < gamedata->numspraycans) + if (mapheaderinfo[mapnum]->records.spraycan == MCAN_BONUS) { - UINT16 col = gamedata->spraycans[mapheaderinfo[mapnum]->cache_spraycan].col; + if (draw) + V_DrawScaledPatch(x, y, 0, W_CachePatchName("GOTBON", PU_CACHE)); - if (col < numskincolors) + x -= 8; + } + else if (mapheaderinfo[mapnum]->records.spraycan < gamedata->numspraycans) + { + UINT16 col = gamedata->spraycans[mapheaderinfo[mapnum]->records.spraycan].col; + + if (draw && col < numskincolors) { V_DrawMappedPatch(x, y, 0, W_CachePatchName("GOTCAN", PU_CACHE), R_GetTranslationColormap(TC_DEFAULT, col, GTC_MENUCACHE)); @@ -8073,9 +8356,13 @@ static void M_DrawMapMedals(INT32 mapnum, INT32 x, INT32 y, boolean allowtime, b if (mapheaderinfo[mapnum]->records.mapvisited & MV_MYSTICMELODY) { - V_DrawScaledPatch(x, y, 0, W_CachePatchName("GOTMEL", PU_CACHE)); + if (draw) + V_DrawScaledPatch(x, y, 0, W_CachePatchName("GOTMEL", PU_CACHE)); + x -= 8; } + + return x; } static void M_DrawStatsMaps(void) @@ -8104,11 +8391,18 @@ static void M_DrawStatsMaps(void) if (gamedata->numspraycans) { medalspos = 30 + V_ThinStringWidth(medalcountstr, 0); - medalcountstr = va("x %d/%d", gamedata->gotspraycans, gamedata->numspraycans); + medalcountstr = va("x %d/%d", gamedata->gotspraycans + statisticsmenu.numcanbonus, gamedata->numspraycans); V_DrawThinString(20 + medalspos, 60, 0, medalcountstr); V_DrawMappedPatch(10 + medalspos, 60, 0, W_CachePatchName("GOTCAN", PU_CACHE), R_GetTranslationColormap(TC_DEFAULT, gamedata->spraycans[0].col, GTC_MENUCACHE)); } + else if (statisticsmenu.numcanbonus) + { + medalspos = 30 + V_ThinStringWidth(medalcountstr, 0); + medalcountstr = va("x %d", statisticsmenu.numcanbonus); + V_DrawThinString(20 + medalspos, 60, 0, medalcountstr); + V_DrawScaledPatch(10 + medalspos, 60, 0, W_CachePatchName("GOTBON", PU_CACHE)); + } medalspos = BASEVIDWIDTH - 20; @@ -8242,7 +8536,7 @@ static void M_DrawStatsMaps(void) ); } - M_DrawMapMedals(mnum, medalspos - 8, y, allowtime, allowencore, allowspb); + M_DrawMapMedals(mnum, medalspos - 8, y, allowtime, allowencore, allowspb, true, true); if (mapheaderinfo[mnum]->menuttl[0]) { @@ -9092,8 +9386,6 @@ void M_DrawDiscordRequests(void) patch_t *hand = NULL; const char *wantText = "...would like to join!"; - const char *acceptText = "Accept" ; - const char *declineText = "Decline"; INT32 x = 100; INT32 y = 133; @@ -9135,29 +9427,31 @@ void M_DrawDiscordRequests(void) K_DrawSticker(x, y + 12, V_ThinStringWidth(wantText, 0), 0, true); V_DrawThinString(x, y + 10, 0, wantText); - INT32 confirmButtonWidth = SHORT(kp_button_a[1][0]->width); - INT32 declineButtonWidth = SHORT(kp_button_b[1][0]->width); - INT32 altDeclineButtonWidth = SHORT(kp_button_x[1][0]->width); - INT32 acceptTextWidth = V_ThinStringWidth(acceptText, 0); - INT32 declineTextWidth = V_ThinStringWidth(declineText, 0); - INT32 stickerWidth = (confirmButtonWidth + declineButtonWidth + altDeclineButtonWidth + acceptTextWidth + declineTextWidth); - + /* K_DrawSticker(x, y + 26, stickerWidth, 0, true); - K_drawButtonAnim(x, y + 22, V_SNAPTORIGHT, kp_button_a[1], discordrequestmenu.ticker); + K_DrawGameControl(x, y+22, 0, "", 0, 0, V_SNAPTORIGHT); + // K_drawButtonAnim(x, y + 22, V_SNAPTORIGHT, kp_button_a[1], discordrequestmenu.ticker); + */ - INT32 xoffs = confirmButtonWidth; + UINT32 bigwidth = K_DrawGameControl(x, y+22, 0, " Accept Decline", 0, 0, V_SNAPTORIGHT); + K_DrawSticker(x, y + 26, bigwidth, 0, true); + K_DrawGameControl(x, y+22, 0, " Accept Decline", 0, 0, V_SNAPTORIGHT); + /* V_DrawThinString((x + xoffs), y + 24, 0, acceptText); xoffs += acceptTextWidth; K_drawButtonAnim((x + xoffs), y + 22, V_SNAPTORIGHT, kp_button_b[1], discordrequestmenu.ticker); xoffs += declineButtonWidth; + xoffs += K_DrawGameControl(x + xoffs, y+22, 0, "", 0, 0, V_SNAPTORIGHT); K_drawButtonAnim((x + xoffs), y + 22, V_SNAPTORIGHT, kp_button_x[1], discordrequestmenu.ticker); xoffs += altDeclineButtonWidth; V_DrawThinString((x + xoffs), y + 24, 0, declineText); + */ + y -= 18; while (curRequest->next != NULL) diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 16d255838..0e3b1385d 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2016 by Kay "Kaito" Sinclaire. // Copyright (C) 2020 by Sonic Team Junior. // diff --git a/src/k_objects.h b/src/k_objects.h index cbc2f320e..cbdb189a7 100644 --- a/src/k_objects.h +++ b/src/k_objects.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -435,6 +435,11 @@ boolean Obj_DestroyKart(mobj_t *kart); void Obj_DestroyedKartParticleThink(mobj_t *part); void Obj_DestroyedKartParticleLanding(mobj_t *part); +/* Flybot767 (stun) */ +void Obj_SpawnFlybotsForPlayer(player_t *player); +void Obj_FlybotThink(mobj_t *flybot); +void Obj_FlybotDeath(mobj_t *flybot); + /* Pulley */ void Obj_PulleyThink(mobj_t *root); void Obj_PulleyHookTouch(mobj_t *special, mobj_t *toucher); @@ -444,6 +449,18 @@ UINT8 K_HogChargeToHogCount(INT32 charge, UINT8 cap); void K_UpdateBallhogReticules(player_t *player, UINT8 num_hogs, boolean on_release); void K_DoBallhogAttack(player_t *player, UINT8 num_hogs); +/* Bubble Shield */ +void Obj_SpawnBubbleShieldVisuals(mobj_t *source); +boolean Obj_TickBubbleShieldVisual(mobj_t *mobj); + +/* Lightning Shield */ +void Obj_SpawnLightningShieldVisuals(mobj_t *source); +boolean Obj_TickLightningShieldVisual(mobj_t *mobj); + +/* Flame Shield */ +void Obj_SpawnFlameShieldVisuals(mobj_t *source); +boolean Obj_TickFlameShieldVisual(mobj_t *mobj); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/k_pathfind.c b/src/k_pathfind.c index 837248b92..52110a8dd 100644 --- a/src/k_pathfind.c +++ b/src/k_pathfind.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sean "Sryder" Ryder -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sean "Sryder" Ryder +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_pathfind.h b/src/k_pathfind.h index 70c66a02c..102973299 100644 --- a/src/k_pathfind.h +++ b/src/k_pathfind.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sean "Sryder" Ryder -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sean "Sryder" Ryder +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_podium.cpp b/src/k_podium.cpp index e5ca26d8f..f2a6b5d89 100644 --- a/src/k_podium.cpp +++ b/src/k_podium.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -12,6 +12,7 @@ #include "k_podium.h" +#include "core/string.h" #include "doomdef.h" #include "d_main.h" #include "d_netcmd.h" @@ -51,8 +52,6 @@ #include "k_hud.h" -#include - typedef enum { PODIUM_ST_CONGRATS_SLIDEIN, @@ -139,14 +138,14 @@ void podiumData_s::Init(void) constexpr INT32 numRaces = 5; for (INT32 i = 0; i < rank.numPlayers; i++) { - rank.totalPoints += numRaces * K_CalculateGPRankPoints(i + 1, rank.totalPlayers); + rank.totalPoints += numRaces * K_CalculateGPRankPoints(MAXEXP, i+1, rank.totalPlayers); } rank.totalRings = numRaces * rank.numPlayers * 20; // Randomized winnings INT32 rgs = 0; - INT32 laps = 0; - INT32 tlaps = 0; + INT32 exp = 0; + INT32 texp = 0; INT32 prs = 0; INT32 tprs = 0; @@ -157,7 +156,7 @@ void podiumData_s::Init(void) gpRank_level_t *const lvl = &rank.levels[i]; UINT8 specialWinner = 0; UINT16 pprs = 0; - UINT16 plaps = 0; + UINT16 pexp = 0; lvl->id = M_RandomRange(4, nummapheaders); @@ -180,8 +179,8 @@ void podiumData_s::Init(void) } default: { - lvl->totalLapPoints = M_RandomRange(2, 5) * 2; - tlaps += lvl->totalLapPoints; + lvl->totalExp = TARGETEXP; + texp += lvl->totalExp * rank.numPlayers; break; } } @@ -199,8 +198,8 @@ void podiumData_s::Init(void) dta->rings = M_RandomRange(0, 20); rgs += dta->rings; - dta->lapPoints = M_RandomRange(0, lvl->totalLapPoints); - plaps = std::max(plaps, dta->lapPoints); + dta->exp = M_RandomRange(MINEXP, MAXEXP); + pexp += dta->exp; } if (lvl->event == GPEVENT_BONUS) @@ -224,13 +223,13 @@ void podiumData_s::Init(void) } } - laps += plaps; + exp += pexp; prs += pprs; } rank.rings = rgs; - rank.laps = laps; - rank.totalLaps = tlaps; + rank.exp = exp; + rank.totalExp = texp; rank.prisons = prs; rank.totalPrisons = tprs; } @@ -511,22 +510,22 @@ void podiumData_s::Draw(void) .font(srb2::Draw::Font::kZVote) .text(va("%c%d", (rank.scorePosition > 0 ? '+' : ' '), rank.scorePosition)); - drawer_winner - .xy(64, 19) - .patch("K_POINT4"); + // drawer_winner + // .xy(64, 19) + // .patch("K_POINT4"); - drawer_winner - .xy(88, 21) - .align(srb2::Draw::Align::kLeft) - .font(srb2::Draw::Font::kPing) - .colormap(TC_RAINBOW, SKINCOLOR_GOLD) - .text(va("%d", rank.winPoints)); + // drawer_winner + // .xy(88, 21) + // .align(srb2::Draw::Align::kLeft) + // .font(srb2::Draw::Font::kPing) + // .colormap(TC_RAINBOW, SKINCOLOR_GOLD) + // .text(va("%d", rank.winPoints)); - drawer_winner - .xy(75, 31) - .align(srb2::Draw::Align::kCenter) - .font(srb2::Draw::Font::kZVote) - .text(va("%c%d", (rank.scoreGPPoints > 0 ? '+' : ' '), rank.scoreGPPoints)); + // drawer_winner + // .xy(75, 31) + // .align(srb2::Draw::Align::kCenter) + // .font(srb2::Draw::Font::kZVote) + // .text(va("%c%d", (rank.scoreGPPoints > 0 ? '+' : ' '), rank.scoreGPPoints)); srb2::Draw drawer_trophy = drawer.xy(272, 10); @@ -652,7 +651,7 @@ void podiumData_s::Draw(void) } { - std::string emeraldName; + srb2::String emeraldName; if (emeraldNum > 7) { emeraldName = (useWhiteFrame ? "K_SUPER2" : "K_SUPER1"); @@ -684,15 +683,27 @@ void podiumData_s::Draw(void) } default: { - drawer_gametype - .xy(0, 1) - .patch("K_SPTLAP"); drawer_gametype - .xy(22, 1) + .xy(0, 1) + .colorize(static_cast(SKINCOLOR_MUSTARD)) + .patch("K_SPTEXP"); + // Colorize the crystal, just like we do for hud + fixed_t factor = FixedDiv(dta->exp*FRACUNIT, lvl->totalExp*FRACUNIT); + skincolornum_t overlaycolor = factor < FRACUNIT ? SKINCOLOR_RUBY : SKINCOLOR_ULTRAMARINE; + if (factor >= FRACUNIT) {factor += factor-FRACUNIT;} // exaggerate the positive side, since reverse engineering the factor like this results in half the translucency range + auto transflag = K_GetTransFlagFromFixed(factor); + drawer_gametype + .xy(0, 1) + .colorize(static_cast(overlaycolor)) + .flags(transflag) + .patch("K_SPTEXP"); + + drawer_gametype + .xy(23, 1) .align(srb2::Draw::Align::kCenter) .font(srb2::Draw::Font::kPing) - .text(va("%d/%d", dta->lapPoints, lvl->totalLapPoints)); + .text(va("%d", dta->exp)); break; } } @@ -752,7 +763,7 @@ void podiumData_s::Draw(void) .x(-144.0); srb2::Draw drawer_totals_right = drawer_totals - .x(78.0); + .x(72.0); if (state == PODIUM_ST_TOTALS_SLIDEIN) { @@ -808,35 +819,46 @@ void podiumData_s::Draw(void) .text(va("%c%d", (rank.scoreRings > 0 ? '+' : ' '), rank.scoreRings)); drawer_totals_right - .xy(10.0, 46.0) + .xy(16.0, 49.0) .patch("CAPS_ZB"); drawer_totals_right - .xy(44.0, 24.0) + .xy(50.0, 24.0) .align(srb2::Draw::Align::kCenter) .font(srb2::Draw::Font::kThinTimer) .text(va("%d / %d", rank.prisons, rank.totalPrisons)); drawer_totals_right - .xy(44.0, 38.0) + .xy(50.0, 38.0) .align(srb2::Draw::Align::kCenter) .font(srb2::Draw::Font::kZVote) .text(va("%c%d", (rank.scorePrisons > 0 ? '+' : ' '), rank.scorePrisons)); drawer_totals_right - .patch("RANKLAPS"); + .colorize(static_cast(SKINCOLOR_MUSTARD)) + .patch("K_STEXP"); + // Colorize the crystal for the totals, just like we do for in race hud + fixed_t factor = FixedDiv((rank.exp+(35*rank.numPlayers-1))*FRACUNIT, rank.totalExp*FRACUNIT); // bump the calc a bit, because its probably not possible for every human to get 125 on every race + skincolornum_t overlaycolor = factor < FRACUNIT ? SKINCOLOR_RUBY : SKINCOLOR_ULTRAMARINE; + if (factor >= FRACUNIT) {factor += factor-FRACUNIT;} // exaggerate the positive side, since reverse engineering the factor like this results in half the translucency range + auto transflag = K_GetTransFlagFromFixed(factor); + + drawer_totals_right + .colorize(static_cast(overlaycolor)) + .flags(transflag) + .patch("K_STEXP"); drawer_totals_right - .xy(44.0, 0.0) + .xy(50.0, 0.0) .align(srb2::Draw::Align::kCenter) .font(srb2::Draw::Font::kThinTimer) - .text(va("%d / %d", rank.laps, rank.totalLaps)); + .text(va("%d / %d", rank.exp, rank.totalExp)); drawer_totals_right - .xy(44.0, 14.0) + .xy(50.0, 14.0) .align(srb2::Draw::Align::kCenter) .font(srb2::Draw::Font::kZVote) - .text(va("%c%d", (rank.scoreLaps > 0 ? '+' : ' '), rank.scoreLaps)); + .text(va("%c%d", (rank.scoreExp > 0 ? '+' : ' '), rank.scoreExp)); } if ((state == PODIUM_ST_GRADE_APPEAR && delay == 0) diff --git a/src/k_podium.h b/src/k_podium.h index 0dba7fdc6..0b25a21db 100644 --- a/src/k_podium.h +++ b/src/k_podium.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_powerup.cpp b/src/k_powerup.cpp index 49be3e51f..2efd94b84 100644 --- a/src/k_powerup.cpp +++ b/src/k_powerup.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_powerup.h b/src/k_powerup.h index 7ec95eecc..dd0ddf12f 100644 --- a/src/k_powerup.h +++ b/src/k_powerup.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_profiles.cpp b/src/k_profiles.cpp index f054e84d3..06febe5c7 100644 --- a/src/k_profiles.cpp +++ b/src/k_profiles.cpp @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'". -// Copyright (C) 2024 by AJ "Tyron" Martinez. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by "Lat'". +// Copyright (C) 2025 by AJ "Tyron" Martinez. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -12,10 +12,13 @@ /// \brief implements methods for profiles etc. #include +#include #include #include +#include "core/string.h" +#include "core/vector.hpp" #include "io/streams.hpp" #include "doomtype.h" #include "d_main.h" // pandf @@ -82,6 +85,8 @@ profile_t* PR_MakeProfile( newprofile->kickstartaccel = false; newprofile->autoroulette = false; newprofile->litesteer = false; + newprofile->strictfastfall = false; + newprofile->descriptiveinput = 1; newprofile->autoring = false; newprofile->rumble = true; newprofile->fov = atoi(cv_dummyprofilefov.defaultvalue); @@ -104,6 +109,8 @@ profile_t* PR_MakeProfileFromPlayer(const char *prname, const char *pname, const newprofile->kickstartaccel = cv_kickstartaccel[pnum].value; newprofile->autoroulette = cv_autoroulette[pnum].value; newprofile->litesteer = cv_litesteer[pnum].value; + newprofile->strictfastfall = cv_strictfastfall[pnum].value; + newprofile->descriptiveinput = cv_descriptiveinput[pnum].value; newprofile->autoring = cv_autoring[pnum].value; newprofile->rumble = cv_rumble[pnum].value; newprofile->fov = cv_fov[pnum].value / FRACUNIT; @@ -251,7 +258,6 @@ void PR_InitNewProfile(void) void PR_SaveProfiles(void) { namespace fs = std::filesystem; - using json = nlohmann::json; using namespace srb2; namespace io = srb2::io; @@ -269,13 +275,13 @@ void PR_SaveProfiles(void) profile_t* cprof = profilesList[i]; jsonprof.version = PROFILEVER; - jsonprof.profilename = std::string(cprof->profilename); + jsonprof.profilename = String(cprof->profilename); std::copy(std::begin(cprof->public_key), std::end(cprof->public_key), std::begin(jsonprof.publickey)); std::copy(std::begin(cprof->secret_key), std::end(cprof->secret_key), std::begin(jsonprof.secretkey)); - jsonprof.playername = std::string(cprof->playername); - jsonprof.skinname = std::string(cprof->skinname); - jsonprof.colorname = std::string(skincolors[cprof->color].name); - jsonprof.followername = std::string(cprof->follower); + jsonprof.playername = String(cprof->playername); + jsonprof.skinname = String(cprof->skinname); + jsonprof.colorname = String(skincolors[cprof->color].name); + jsonprof.followername = String(cprof->follower); if (cprof->followercolor == FOLLOWERCOLOR_MATCH) { jsonprof.followercolorname = "Match"; @@ -290,42 +296,48 @@ void PR_SaveProfiles(void) } else if (cprof->followercolor >= numskincolors) { - jsonprof.followercolorname = std::string(); + jsonprof.followercolorname = String(); } else { - jsonprof.followercolorname = std::string(skincolors[cprof->followercolor].name); + jsonprof.followercolorname = String(skincolors[cprof->followercolor].name); } jsonprof.records.wins = cprof->wins; jsonprof.records.rounds = cprof->rounds; jsonprof.preferences.kickstartaccel = cprof->kickstartaccel; jsonprof.preferences.autoroulette = cprof->autoroulette; jsonprof.preferences.litesteer = cprof->litesteer; + jsonprof.preferences.strictfastfall = cprof->strictfastfall; + jsonprof.preferences.descriptiveinput = cprof->descriptiveinput; jsonprof.preferences.autoring = cprof->autoring; jsonprof.preferences.rumble = cprof->rumble; jsonprof.preferences.fov = cprof->fov; for (size_t j = 0; j < num_gamecontrols; j++) { + srb2::Vector mappings; for (size_t k = 0; k < MAXINPUTMAPPING; k++) { - jsonprof.controls[j][k] = cprof->controls[j][k]; + mappings.push_back(cprof->controls[j][k]); } + jsonprof.controls.emplace_back(std::move(mappings)); } ng.profiles.emplace_back(std::move(jsonprof)); } - std::vector ubjson = json::to_ubjson(ng); + JsonValue ngv; + to_json(ngv, ng); + Vector ubjson = ngv.to_ubjson(); - std::string realpath = fmt::format("{}/{}", srb2home, PROFILESFILE); - std::string bakpath = fmt::format("{}.bak", realpath); + String realpath = srb2::format("{}/{}", srb2home, PROFILESFILE); + String bakpath = srb2::format("{}.bak", realpath); - if (fs::exists(realpath)) + if (fs::exists(fs::path(static_cast(realpath)))) { try { - fs::rename(realpath, bakpath); + fs::rename(fs::path(static_cast(realpath)), fs::path(static_cast(bakpath))); } catch (const fs::filesystem_error& ex) { @@ -344,7 +356,7 @@ void PR_SaveProfiles(void) io::write(static_cast(0), file); // reserved2 io::write(static_cast(0), file); // reserved3 io::write(static_cast(0), file); // reserved4 - io::write_exact(file, tcb::as_bytes(tcb::make_span(ubjson))); + io::write_exact(file, ubjson); file.close(); } catch (const std::exception& ex) @@ -362,7 +374,6 @@ void PR_LoadProfiles(void) namespace fs = std::filesystem; using namespace srb2; namespace io = srb2::io; - using json = nlohmann::json; profile_t *dprofile = PR_MakeProfile( PROFILEDEFAULTNAME, @@ -373,7 +384,7 @@ void PR_LoadProfiles(void) true ); - std::string datapath {fmt::format("{}/{}", srb2home, PROFILESFILE)}; + String datapath { srb2::format("{}/{}", srb2home, PROFILESFILE) }; io::BufferedInputStream bis; try @@ -408,11 +419,10 @@ void PR_LoadProfiles(void) throw std::domain_error("Header is incompatible"); } - std::vector remainder = io::read_to_vec(bis); + Vector remainder = io::read_to_vec(bis); // safety: std::byte repr is always uint8_t 1-byte aligned - tcb::span remainder_as_u8 = tcb::span((uint8_t*)remainder.data(), remainder.size()); - json parsed = json::from_ubjson(remainder_as_u8); - js = parsed.template get(); + JsonValue parsed = JsonValue::from_ubjson(remainder); + from_json(parsed, js); } catch (const std::exception& ex) { @@ -486,6 +496,8 @@ void PR_LoadProfiles(void) newprof->kickstartaccel = jsprof.preferences.kickstartaccel; newprof->autoroulette = jsprof.preferences.autoroulette; newprof->litesteer = jsprof.preferences.litesteer; + newprof->strictfastfall = jsprof.preferences.strictfastfall; + newprof->descriptiveinput = jsprof.preferences.descriptiveinput; newprof->autoring = jsprof.preferences.autoring; newprof->rumble = jsprof.preferences.rumble; newprof->fov = jsprof.preferences.fov; @@ -494,9 +506,24 @@ void PR_LoadProfiles(void) { for (size_t j = 0; j < num_gamecontrols; j++) { + if (jsprof.controls.size() <= j) + { + for (size_t k = 0; k < MAXINPUTMAPPING; k++) + { + newprof->controls[j][k] = gamecontroldefault[j][k]; + } + continue; + } + + auto& mappings = jsprof.controls.at(j); for (size_t k = 0; k < MAXINPUTMAPPING; k++) { - newprof->controls[j][k] = jsprof.controls.at(j).at(k); + if (mappings.size() <= k) + { + newprof->controls[j][k] = 0; + continue; + } + newprof->controls[j][k] = mappings.at(k); } } } @@ -541,6 +568,12 @@ void PR_LoadProfiles(void) converted = true; } + if (jsprof.version < 4) + { + newprof->descriptiveinput = 1; + converted = true; + } + if (converted) { CONS_Printf("Profile '%s' was converted from version %d to version %d\n", @@ -568,6 +601,8 @@ static void PR_ApplyProfile_Settings(profile_t *p, UINT8 playernum) CV_StealthSetValue(&cv_kickstartaccel[playernum], p->kickstartaccel); CV_StealthSetValue(&cv_autoroulette[playernum], p->autoroulette); CV_StealthSetValue(&cv_litesteer[playernum], p->litesteer); + CV_StealthSetValue(&cv_strictfastfall[playernum], p->strictfastfall); + CV_StealthSetValue(&cv_descriptiveinput[playernum], p->descriptiveinput); CV_StealthSetValue(&cv_autoring[playernum], p->autoring); CV_StealthSetValue(&cv_rumble[playernum], p->rumble); CV_StealthSetValue(&cv_fov[playernum], p->fov); diff --git a/src/k_profiles.h b/src/k_profiles.h index 988101f98..9572bd973 100644 --- a/src/k_profiles.h +++ b/src/k_profiles.h @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'". -// Copyright (C) 2024 by AJ "Tyron" Martinez. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by "Lat'". +// Copyright (C) 2025 by AJ "Tyron" Martinez. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -25,10 +25,10 @@ #include #include -#include -#include -#include +#include "core/json.hpp" +#include "core/string.h" +#include "core/vector.hpp" namespace srb2 { @@ -38,7 +38,7 @@ struct ProfileRecordsJson uint32_t wins; uint32_t rounds; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ProfileRecordsJson, wins, rounds) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ProfileRecordsJson, wins, rounds) }; struct ProfilePreferencesJson @@ -46,16 +46,19 @@ struct ProfilePreferencesJson bool kickstartaccel; bool autoroulette; bool litesteer; + bool strictfastfall; + uint8_t descriptiveinput; bool autoring; bool rumble; uint8_t fov; - tm test; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( ProfilePreferencesJson, kickstartaccel, autoroulette, litesteer, + strictfastfall, + descriptiveinput, autoring, rumble, fov @@ -65,19 +68,19 @@ struct ProfilePreferencesJson struct ProfileJson { uint32_t version; - std::string profilename; - std::string playername; + String profilename; + String playername; std::array publickey = {{}}; std::array secretkey = {{}}; - std::string skinname; - std::string colorname; - std::string followername; - std::string followercolorname; + String skinname; + String colorname; + String followername; + String followercolorname; ProfileRecordsJson records; ProfilePreferencesJson preferences; - std::array, gamecontrols_e::num_gamecontrols> controls = {{{{}}}}; + Vector> controls = {}; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( ProfileJson, version, profilename, @@ -96,9 +99,9 @@ struct ProfileJson struct ProfilesJson { - std::vector profiles; + Vector profiles; - NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ProfilesJson, profiles) + SRB2_JSON_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ProfilesJson, profiles) }; } // namespace srb2 @@ -116,7 +119,8 @@ extern "C" { // 2 - litesteer is off by default, old profiles litesteer // 3 - auto roulette is switched off again // option is reset to default -#define PROFILEVER 3 +// 4 - Descriptive Input - set everyone to Modern! +#define PROFILEVER 4 #define MAXPROFILES 16 #define PROFILESFILE "ringprofiles.prf" #define PROFILE_GUEST 0 @@ -162,6 +166,8 @@ struct profile_t boolean kickstartaccel; // cv_kickstartaccel boolean autoroulette; // cv_autoroulette boolean litesteer; // cv_litesteer + boolean strictfastfall; // cv_strictfastfall + UINT8 descriptiveinput; // cv_descriptiveinput boolean autoring; // cv_autoring boolean rumble; // cv_rumble UINT8 fov; // cv_fov diff --git a/src/k_pwrlv.c b/src/k_pwrlv.c index ca2c09a0a..6291d3a21 100644 --- a/src/k_pwrlv.c +++ b/src/k_pwrlv.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -245,6 +245,12 @@ void K_UpdatePowerLevels(player_t *player, UINT8 lap, boolean forfeit) continue; } + if (G_SameTeam(player, &players[i]) == true) + { + // You don't win/lose against your teammates. + continue; + } + CONS_Debug(DBG_PWRLV, "%s VS %s:\n", player_names[playerNum], player_names[i]); theirPower = clientpowerlevels[i][powerType]; diff --git a/src/k_pwrlv.h b/src/k_pwrlv.h index 9494ed0f4..f0a4d5b01 100644 --- a/src/k_pwrlv.h +++ b/src/k_pwrlv.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_race.c b/src/k_race.c index 430204adb..b41e55021 100644 --- a/src/k_race.c +++ b/src/k_race.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_race.h b/src/k_race.h index 88c9d1266..542acacb0 100644 --- a/src/k_race.h +++ b/src/k_race.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_rank.cpp b/src/k_rank.cpp index 4844440b9..c2e222158 100644 --- a/src/k_rank.cpp +++ b/src/k_rank.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -289,7 +289,7 @@ static UINT32 RankCapsules_CountFromMap(const INT32 cupLevelNum) void gpRank_t::Init(void) { UINT8 numHumans = 0; - UINT32 laps = 0; + UINT32 exp = 0; INT32 i; memset(this, 0, sizeof(gpRank_t)); @@ -322,7 +322,7 @@ void gpRank_t::Init(void) // (Should this account for all coop players?) for (i = 0; i < numHumans; i++) { - totalPoints += grandprixinfo.cup->numlevels * K_CalculateGPRankPoints(i + 1, totalPlayers); + totalPoints += grandprixinfo.cup->numlevels * K_CalculateGPRankPoints(MAXEXP, i+1, totalPlayers); } totalRings = grandprixinfo.cup->numlevels * numHumans * 20; @@ -332,14 +332,13 @@ void gpRank_t::Init(void) const INT32 cupLevelNum = grandprixinfo.cup->cachedlevels[i]; if (cupLevelNum < nummapheaders && mapheaderinfo[cupLevelNum] != NULL) { - laps += K_RaceLapCount(cupLevelNum); + exp += TARGETEXP; } } - // +1, since 1st place laps are worth 2 pts. - for (i = 0; i < numHumans+1; i++) + for (i = 0; i < numHumans; i++) { - totalLaps += laps; + totalExp += exp; } // Search through all of the cup's bonus levels @@ -373,9 +372,8 @@ void gpRank_t::Rejigger(UINT16 removedmap, UINT16 removedgt, UINT16 addedmap, UI { for (i = 0; i < numPlayers; i++) { - deltaPoints += K_CalculateGPRankPoints(i + 1, totalPlayers); + deltaPoints += K_CalculateGPRankPoints(MAXEXP, i + 1, totalPlayers); } - if (addedgt == GT_RACE) totalPoints += deltaPoints; else if (totalPoints > deltaPoints) @@ -384,7 +382,7 @@ void gpRank_t::Rejigger(UINT16 removedmap, UINT16 removedgt, UINT16 addedmap, UI totalPoints = 0; } - INT32 deltaLaps = 0; + INT32 deltaExp = 0; INT32 deltaPrisons = 0; INT32 deltaRings = 0; @@ -393,12 +391,7 @@ void gpRank_t::Rejigger(UINT16 removedmap, UINT16 removedgt, UINT16 addedmap, UI { if (removedgt == GT_RACE) { - // AGH CAN'T USE, gametype already possibly not GT_RACE... - //deltaLaps -= K_RaceLapCount(removedmap); - if (cv_numlaps.value == -1) - deltaLaps -= mapheaderinfo[removedmap]->numlaps; - else - deltaLaps -= cv_numlaps.value; + deltaExp -= TARGETEXP; } if ((gametypes[removedgt]->rules & GTR_SPHERES) == 0) { @@ -415,7 +408,7 @@ void gpRank_t::Rejigger(UINT16 removedmap, UINT16 removedgt, UINT16 addedmap, UI { if (addedgt == GT_RACE) { - deltaLaps += K_RaceLapCount(addedmap); + deltaExp += TARGETEXP; } if ((gametypes[addedgt]->rules & GTR_SPHERES) == 0) { @@ -427,26 +420,13 @@ void gpRank_t::Rejigger(UINT16 removedmap, UINT16 removedgt, UINT16 addedmap, UI } } - if (deltaLaps) + if (deltaExp) { - INT32 workingTotalLaps = totalLaps; - - // +1, since 1st place laps are worth 2 pts. - for (i = 0; i < numPlayers+1; i++) - { - workingTotalLaps += deltaLaps; - } - - if (workingTotalLaps > 0) - totalLaps = workingTotalLaps; + deltaExp += totalExp; + if (deltaExp > 0) + totalExp = deltaExp; else - totalLaps = 0; - - deltaLaps += laps; - if (deltaLaps > 0) - laps = deltaLaps; - else - laps = 0; + totalExp = 0; } if (deltaPrisons) @@ -512,7 +492,7 @@ void gpRank_t::Update(void) lvl->time = UINT32_MAX; - lvl->totalLapPoints = 500; + lvl->totalExp = TARGETEXP; lvl->totalPrisons = maptargets; UINT8 i; @@ -554,7 +534,7 @@ void gpRank_t::Update(void) dta->position = player->tally.position; dta->rings = player->tally.rings; - dta->lapPoints = player->tally.laps; + dta->exp = player->tally.exp; dta->prisons = player->tally.prisons; dta->gotSpecialPrize = !!!(player->pflags & PF_NOCONTEST); dta->grade = static_cast(player->tally.rank); @@ -595,16 +575,16 @@ gp_rank_e K_CalculateGPGrade(gpRank_t *rankData) rankData->scorePosition = 0; rankData->scoreGPPoints = 0; - rankData->scoreLaps = 0; + rankData->scoreExp = 0; rankData->scorePrisons = 0; rankData->scoreRings = 0; rankData->scoreContinues = 0; rankData->scoreTotal = 0; - const INT32 lapsWeight = (rankData->totalLaps > 0) ? RANK_WEIGHT_LAPS : 0; + const INT32 expWeight = (rankData->totalExp > 0) ? RANK_WEIGHT_EXP : 0; const INT32 prisonsWeight = (rankData->totalPrisons > 0) ? RANK_WEIGHT_PRISONS : 0; - const INT32 total = RANK_WEIGHT_POSITION + RANK_WEIGHT_SCORE + lapsWeight + prisonsWeight + RANK_WEIGHT_RINGS; + const INT32 total = RANK_WEIGHT_POSITION + expWeight + prisonsWeight + RANK_WEIGHT_RINGS; const INT32 continuesPenalty = total / RANK_CONTINUE_PENALTY_DIV; if (rankData->position > 0) @@ -619,9 +599,9 @@ gp_rank_e K_CalculateGPGrade(gpRank_t *rankData) rankData->scoreGPPoints += (rankData->winPoints * RANK_WEIGHT_SCORE) / rankData->totalPoints; } - if (rankData->totalLaps > 0) + if (rankData->totalExp > 0) { - rankData->scoreLaps += (rankData->laps * lapsWeight) / rankData->totalLaps; + rankData->scoreExp += (std::min(rankData->exp, rankData->totalExp) * expWeight) / rankData->totalExp; } if (rankData->totalPrisons > 0) @@ -638,8 +618,8 @@ gp_rank_e K_CalculateGPGrade(gpRank_t *rankData) rankData->scoreTotal = rankData->scorePosition + - rankData->scoreGPPoints + - rankData->scoreLaps + + // rankData->scoreGPPoints + + rankData->scoreExp + rankData->scorePrisons + rankData->scoreRings + rankData->scoreContinues; diff --git a/src/k_rank.h b/src/k_rank.h index 4d6974006..4f36d12df 100644 --- a/src/k_rank.h +++ b/src/k_rank.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -21,7 +21,7 @@ struct gpRank_level_perplayer_t { UINT8 position; UINT8 rings; - UINT16 lapPoints; + UINT16 exp; UINT16 prisons; boolean gotSpecialPrize; gp_rank_e grade; @@ -32,7 +32,7 @@ struct gpRank_level_t UINT16 id; INT32 event; UINT32 time; - UINT16 totalLapPoints; + UINT16 totalExp; UINT16 totalPrisons; gpRank_level_perplayer_t perPlayer[MAXSPLITSCREENPLAYERS]; }; @@ -49,8 +49,8 @@ struct gpRank_t UINT32 winPoints; UINT32 totalPoints; - UINT32 laps; - UINT32 totalLaps; + UINT32 exp; + UINT32 totalExp; UINT32 continuesUsed; @@ -64,7 +64,7 @@ struct gpRank_t INT32 scorePosition; INT32 scoreGPPoints; - INT32 scoreLaps; + INT32 scoreExp; INT32 scorePrisons; INT32 scoreRings; INT32 scoreContinues; @@ -91,7 +91,7 @@ extern "C" { #define RANK_WEIGHT_POSITION (150) #define RANK_WEIGHT_SCORE (100) -#define RANK_WEIGHT_LAPS (100) +#define RANK_WEIGHT_EXP (100) #define RANK_WEIGHT_PRISONS (100) #define RANK_WEIGHT_RINGS (50) diff --git a/src/k_respawn.c b/src/k_respawn.c index e3854ea9c..d08c8d6c2 100644 --- a/src/k_respawn.c +++ b/src/k_respawn.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -184,7 +184,7 @@ void K_DoIngameRespawn(player_t *player) player->gateBoost = 0; player->trickcharge = 0; player->infinitether = 0; - player->wavedash = player->wavedashboost = player->wavedashdelay = 0; + player->wavedash = player -> wavedashleft = player->wavedashright = player->wavedashboost = player->wavedashdelay = 0; K_TumbleInterrupt(player); P_ResetPlayer(player); @@ -446,6 +446,9 @@ static void K_MovePlayerToRespawnPoint(player_t *player) // Reduce by the amount we needed to get to this waypoint stepamt -= dist; + fixed_t oldx = player->mo->x; + fixed_t oldy = player->mo->y = dest.y; + // We've reached the destination point, P_UnsetThingPosition(player->mo); player->mo->x = dest.x; @@ -453,6 +456,9 @@ static void K_MovePlayerToRespawnPoint(player_t *player) player->mo->z = dest.z; P_SetThingPosition(player->mo); + // Did we cross a checkpoint during our last step? + Obj_CrossCheckpoints(player, oldx, oldy); + // We are no longer traveling from death location to 1st waypoint, so use standard timings if (player->respawn.fast) player->respawn.fast = false; @@ -724,7 +730,7 @@ static void K_DropDashWait(player_t *player) player->respawn.timer--; if (player->pflags & PF_FAULT) - return; + return; if (leveltime % 8 == 0) { @@ -871,6 +877,12 @@ static void K_HandleDropDash(player_t *player) { player->mo->colorized = false; } + // if player got trapped inside a bubble but lost its bubble object in a unintended way, remove no gravity flag + if (((P_MobjWasRemoved(player->mo->tracer) || player->mo->tracer == NULL || (!P_MobjWasRemoved(player->mo->tracer) && player->mo->tracer && player->mo->tracer->type != MT_BUBBLESHIELDTRAP)) && player->carry == CR_TRAPBUBBLE) && (player->mo->flags & MF_NOGRAVITY)) + { + player->mo->flags &= ~MF_NOGRAVITY; + player->carry = CR_NONE; + } } else { diff --git a/src/k_respawn.h b/src/k_respawn.h index a6c0ea09b..fdf4f48e5 100644 --- a/src/k_respawn.h +++ b/src/k_respawn.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_roulette.c b/src/k_roulette.c index 671bca774..bba953a64 100644 --- a/src/k_roulette.c +++ b/src/k_roulette.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -486,8 +486,8 @@ static UINT32 K_ScaleItemDistance(const player_t *player, UINT32 distance, UINT8 FRACUNIT + (K_ItemOddsScale(numPlayers) / 2) ); - // Distance is reduced based on the player's exp - // distance = FixedMul(distance, player->exp); + // Distance is reduced based on the player's gradingfactor + // distance = FixedMul(distance, player->gradingfactor); return distance; } @@ -927,7 +927,7 @@ static void K_AddItemToReel(const player_t *player, itemroulette_t *const roulet // If we're in ring debt, pad out the reel with // a BUNCH of Super Rings. if (K_ItemEnabled(KITEM_SUPERRING) == true - && player->rings <= 0 + && player->rings <= -10 && player->position == 1 && (gametyperules & GTR_SPHERES) == 0) { @@ -1078,7 +1078,7 @@ static boolean K_IsItemUselessAlone(kartitems_t item) case KRITEM_TRIPLEORBINAUT: case KRITEM_QUADORBINAUT: case KITEM_BALLHOG: - case KITEM_BUBBLESHIELD: + case KITEM_BUBBLESHIELD: // shhhhhh return true; default: return false; @@ -1103,6 +1103,18 @@ ATTRUNUSED static boolean K_IsItemSpeed(kartitems_t item) } } +static fixed_t K_RequiredXPForItem(kartitems_t item) +{ + switch (item) + { + case KITEM_GARDENTOP: + case KITEM_SHRINK: + return FRACUNIT; // "Base" item odds + default: + return 0; + } +} + // Which items are disallowed for this player's specific placement? static boolean K_ShouldPlayerAllowItem(kartitems_t item, const player_t *player) { @@ -1118,6 +1130,11 @@ static boolean K_ShouldPlayerAllowItem(kartitems_t item, const player_t *player) // A little inelegant: filter the most chaotic items from courses with early sets and tight layouts. if (K_IsItemPower(item) && (leveltime < ((15*TICRATE) + starttime))) return false; + + // GIGA power items reserved only for players who were doing great and died. + if (player->gradingfactor < K_RequiredXPForItem(item)) + return false; + return !K_IsItemFirstOnly(item); } } @@ -1307,7 +1324,7 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet // every item in the game! // Create the same item reel given the same inputs. - // P_SetRandSeed(PR_ITEM_ROULETTE, ITEM_REEL_SEED); + P_SetRandSeed(PR_ITEM_ROULETTE, ITEM_REEL_SEED); for (i = 1; i < NUMKARTRESULTS; i++) { @@ -1380,8 +1397,10 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet roulette->preexpdist = K_GetItemRouletteDistance(player, roulette->playing); roulette->dist = roulette->preexpdist; - if (gametyperules & GTR_CIRCUIT) - roulette->dist = FixedMul(roulette->preexpdist, max(player->exp, FRACUNIT/2)); + if ((gametyperules & GTR_CIRCUIT) && !K_Cooperative()) + { + roulette->dist = FixedMul(roulette->preexpdist, max(player->gradingfactor, FRACUNIT/2)); + } // =============================================================================== // Dynamic Roulette. Oh boy! @@ -1553,7 +1572,8 @@ void K_FillItemRouletteData(const player_t *player, itemroulette_t *const roulet if ((gametyperules & GTR_CIRCUIT) && specialstageinfo.valid == false && (spb_odds > 0) & (spbplace == -1) - && (roulette->preexpdist >= powers[KITEM_SPB])) // SPECIAL CASE: Check raw distance instead of EXP-influenced target distance. + && (roulette->preexpdist >= powers[KITEM_SPB]) // SPECIAL CASE: Check raw distance instead of EXP-influenced target distance. + && !K_GetItemCooldown(KITEM_SPB)) { // When reenabling the SPB, we also adjust its delta to ensure that it has good odds of showing up. // Players who are _seriously_ struggling are more likely to see Invinc or Rockets, since those items @@ -2012,7 +2032,7 @@ void K_KartItemRoulette(player_t *const player, ticcmd_t *const cmd) else S_StartSound(NULL, sfx_itrol1 + roulette->sound); - if (roulette->index == 0) + if (roulette->index == 0 && roulette->itemListLen > 1) { S_StartSound(NULL, sfx_kc50); S_StartSound(NULL, sfx_kc50); diff --git a/src/k_roulette.h b/src/k_roulette.h index 03a4adb22..6f3054c5e 100644 --- a/src/k_roulette.h +++ b/src/k_roulette.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_serverstats.c b/src/k_serverstats.c index b5107f1c4..4556b84a9 100644 --- a/src/k_serverstats.c +++ b/src/k_serverstats.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by AJ "Tyron" Martinez. -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by AJ "Tyron" Martinez. +// Copyright (C) 2025 by Kart Krew // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/k_serverstats.h b/src/k_serverstats.h index d174beaed..3273351dc 100644 --- a/src/k_serverstats.h +++ b/src/k_serverstats.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by AJ "Tyron" Martinez. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by AJ "Tyron" Martinez. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_specialstage.c b/src/k_specialstage.c index 6c0ab53e6..93b10a9ba 100644 --- a/src/k_specialstage.c +++ b/src/k_specialstage.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_specialstage.h b/src/k_specialstage.h index b155c1bed..4770c9af8 100644 --- a/src/k_specialstage.h +++ b/src/k_specialstage.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_tally.cpp b/src/k_tally.cpp index 3b49c352f..07fc6859f 100644 --- a/src/k_tally.cpp +++ b/src/k_tally.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -95,11 +95,11 @@ void level_tally_t::DetermineBonuses(void) } } - if (totalLaps > 0) + if (totalExp > 0) { // Give circuit gamemodes a consolation bonus - // for getting good placements on each lap. - temp_bonuses.push_back(TALLY_BONUS_LAP); + // for getting good placements on each grading point. + temp_bonuses.push_back(TALLY_BONUS_EXP); } if (totalPrisons > 0) @@ -206,7 +206,7 @@ INT32 level_tally_t::CalculateGrade(void) bonusWeights[i] = ((pointLimit != 0) ? 100 : 0); break; } - case TALLY_BONUS_LAP: + case TALLY_BONUS_EXP: case TALLY_BONUS_PRISON: case TALLY_BONUS_POWERSTONES: { @@ -243,13 +243,13 @@ INT32 level_tally_t::CalculateGrade(void) ours += (rings * bonusWeights[i]) / 20; break; } - case TALLY_BONUS_LAP: + case TALLY_BONUS_EXP: { // Use a special curve for this. - // The difference between 0 and 1 lap points is an important difference in skill, - // while the difference between 5 and 6 is not very notable. - const fixed_t frac = std::min(FRACUNIT, (laps * FRACUNIT) / std::max(1, static_cast(totalLaps))); - ours += Easing_OutSine(frac, 0, bonusWeights[i]); + // Low Exp amounts are guaranteed, higher than half is where skill expression starts + // Magic numbers here are to reduce the range from 50-125 to 0-75 and compare with a max of 58, 85% of which is 49.3, which should put an even 100 or higher exp at A rank + const fixed_t frac = std::min(FRACUNIT, ((exp-50) * FRACUNIT) / std::max(1, static_cast(totalExp-42))); + ours += Easing_Linear(frac, 0, bonusWeights[i]); break; } case TALLY_BONUS_PRISON: @@ -313,7 +313,7 @@ void level_tally_t::Init(player_t *player) position = numPlayers = 0; rings = 0; - laps = totalLaps = 0; + exp = totalExp = 0; points = pointLimit = 0; powerStones = 0; releasedFastForward = false; @@ -346,8 +346,11 @@ void level_tally_t::Init(player_t *player) if ((gametypes[gt]->rules & GTR_CIRCUIT) == GTR_CIRCUIT) { - laps = std::clamp(FixedMul(std::max(stplyr->exp, FRACUNIT/2), (500/K_GetNumGradingPoints())*player->gradingpointnum), 0, 999); - totalLaps = 500; + if (player->exp) + { + exp = player->exp; + totalExp = TARGETEXP; + } } if (battleprisons) @@ -662,8 +665,8 @@ boolean level_tally_t::IncrementLine(void) amount = 1; freq = 1; break; - case TALLY_BONUS_LAP: - dest = laps; + case TALLY_BONUS_EXP: + dest = exp; amount = 20; freq = 1; break; @@ -1188,7 +1191,7 @@ void level_tally_t::Draw(void) case TALLY_BONUS_RING: bonus_code = "RB"; break; - case TALLY_BONUS_LAP: + case TALLY_BONUS_EXP: bonus_code = "LA"; break; case TALLY_BONUS_PRISON: @@ -1243,15 +1246,29 @@ void level_tally_t::Draw(void) work_tics % 10 )); - if (modeattacking && !demo.playback && (state == TALLY_ST_DONE || state == TALLY_ST_TEXT_PAUSE) - && !K_IsPlayerLosing(&players[consoleplayer]) && players[consoleplayer].realtime < oldbest) + if (K_LegacyRingboost(&players[consoleplayer])) { drawer_text .x(197.0 * frac) .y(13.0 * frac) .align(srb2::Draw::Align::kCenter) .font(srb2::Draw::Font::kMenu) - .text((leveltime/2 % 2) ? "NEW RECORD!" : "\x82NEW RECORD!"); + .flags(V_TRANSLUCENT) + .text("\"CLASS R\""); + } + else + { + if (modeattacking && !demo.playback && (state == TALLY_ST_DONE || state == TALLY_ST_TEXT_PAUSE) + && !K_IsPlayerLosing(&players[consoleplayer]) && players[consoleplayer].realtime < oldbest) + { + + drawer_text + .x(197.0 * frac) + .y(13.0 * frac) + .align(srb2::Draw::Align::kCenter) + .font(srb2::Draw::Font::kMenu) + .text((leveltime/2 % 2) ? "NEW RECORD!" : "\x82NEW RECORD!"); + } } break; } @@ -1319,12 +1336,12 @@ void level_tally_t::Draw(void) .text(va("%d / 20", displayBonus[i])); break; } - case TALLY_BONUS_LAP: + case TALLY_BONUS_EXP: { drawer_text .x(197.0 * frac) .align(srb2::Draw::Align::kCenter) - .text(va("%d / %d", displayBonus[i], totalLaps)); + .text(va("%d / %d", displayBonus[i], totalExp)); break; } case TALLY_BONUS_PRISON: diff --git a/src/k_tally.h b/src/k_tally.h index 9079103a6..335a74979 100644 --- a/src/k_tally.h +++ b/src/k_tally.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -33,7 +33,7 @@ typedef enum { TALLY_BONUS_NA, TALLY_BONUS_RING, - TALLY_BONUS_LAP, + TALLY_BONUS_EXP, TALLY_BONUS_PRISON, TALLY_BONUS_SCORE, TALLY_BONUS_POWERSTONES, @@ -77,7 +77,7 @@ struct level_tally_t // Possible grade metrics UINT8 position, numPlayers; UINT8 rings; - UINT16 laps, totalLaps; + UINT16 exp, totalExp; UINT16 prisons, totalPrisons; INT32 points, pointLimit; UINT8 powerStones; diff --git a/src/k_terrain.c b/src/k_terrain.c index 872d94067..9cc2ac9e2 100644 --- a/src/k_terrain.c +++ b/src/k_terrain.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // Copyright (C) 2021 by ZDoom + GZDoom teams, and contributors // // This program is free software distributed under the diff --git a/src/k_terrain.h b/src/k_terrain.h index 10c862261..d91f20f3d 100644 --- a/src/k_terrain.h +++ b/src/k_terrain.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // Copyright (C) 2021 by ZDoom + GZDoom teams, and contributors // // This program is free software distributed under the diff --git a/src/k_vote.c b/src/k_vote.c index 8c17cfedb..419d0feec 100644 --- a/src/k_vote.c +++ b/src/k_vote.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -1224,6 +1224,23 @@ void Y_VoteDrawer(void) ); } + // TODO better voice chat speaking indicator integration + { + char speakingstring[2048]; + memset(speakingstring, 0, sizeof(speakingstring)); + + for (int i = 0; i < MAXPLAYERS; i++) + { + if (S_IsPlayerVoiceActive(i)) + { + strcat(speakingstring, player_names[i]); + strcat(speakingstring, " "); + } + } + + V_DrawThinString(0, 0, 0, speakingstring); + } + M_DrawMenuForeground(); } @@ -2353,7 +2370,7 @@ static void Y_UnloadVoteData(void) vote.loaded = false; - if (rendermode != render_soft) + if (rendermode == render_opengl) { return; } diff --git a/src/k_vote.h b/src/k_vote.h index 0f35ffb37..2edbf2b7a 100644 --- a/src/k_vote.h +++ b/src/k_vote.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_waypoint.cpp b/src/k_waypoint.cpp index 88d54ae0c..a43fb1b1c 100644 --- a/src/k_waypoint.cpp +++ b/src/k_waypoint.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sean "Sryder" Ryder -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sean "Sryder" Ryder +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -24,10 +24,12 @@ #include "cxxutil.hpp" #include -#include #include +#include "core/string.h" +#include "core/vector.hpp" + // The number of sparkles per waypoint connection in the waypoint visualisation static const UINT32 SPARKLES_PER_CONNECTION = 16U; @@ -2377,8 +2379,8 @@ static BlockItReturn_t K_TrackWaypointNearOffroad(line_t *line) struct complexity_sneaker_s { fixed_t bbox[4]; - //std::vector sectors; - //std::vector things; + //srb2::Vector sectors; + //srb2::Vector things; complexity_sneaker_s(sector_t *sec) { @@ -2631,7 +2633,7 @@ static INT32 K_CalculateTrackComplexity(void) delta = FixedMul(delta, FixedMul(FixedMul(dist_factor, radius_factor), wall_factor)); - std::string msg = fmt::format( + srb2::String msg = srb2::format( "TURN [{}]: r: {:.2f}, d: {:.2f}, w: {:.2f}, r*d*w: {:.2f}, DELTA: {}\n", turn_id, FixedToFloat(radius_factor), @@ -2644,7 +2646,7 @@ static INT32 K_CalculateTrackComplexity(void) trackcomplexity += (delta / FRACUNIT); } - std::vector sneaker_panels; + srb2::Vector sneaker_panels; for (size_t i = 0; i < numsectors; i++) { diff --git a/src/k_waypoint.h b/src/k_waypoint.h index ca4aa351a..41ae1cdf8 100644 --- a/src/k_waypoint.h +++ b/src/k_waypoint.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sean "Sryder" Ryder -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sean "Sryder" Ryder +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/k_zvote.c b/src/k_zvote.c index cfd2dbc40..be005c110 100644 --- a/src/k_zvote.c +++ b/src/k_zvote.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -58,6 +58,25 @@ static void K_MidVoteKick(void) SendKick(g_midVote.victim - players, KICK_MSG_VOTE_KICK); } +/*-------------------------------------------------- + static void K_MidVoteMute(void) + + MVT_MUTE's success function. +--------------------------------------------------*/ +static void K_MidVoteMute(void) +{ + UINT8 buf[2]; + + if (g_midVote.victim == NULL) + { + return; + } + + buf[0] = g_midVote.victim - players; + buf[1] = 1; + SendNetXCmd(XD_SERVERMUTEPLAYER, &buf, 2); +} + /*-------------------------------------------------- static void K_MidVoteRockTheVote(void) @@ -99,6 +118,13 @@ static midVoteTypeDef_t g_midVoteTypeDefs[MVT__MAX] = K_MidVoteKick }, + { // MVT_MUTE + "MUTE", + "Mute Player?", + CVAR_INIT ("zvote_mute_allowed", "Yes", CV_SAVE|CV_NETVAR, CV_YesNo, NULL), + K_MidVoteMute + }, + { // MVT_RTV "RTV", "Skip Level?", @@ -127,6 +153,10 @@ boolean K_MidVoteTypeUsesVictim(midVoteType_e voteType) { return true; } + case MVT_MUTE: + { + return true; + } default: { return false; @@ -1103,12 +1133,19 @@ void K_DrawMidVote(void) V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN, exc, NULL ); + K_DrawGameControl( + x/FRACUNIT - 4, y/FRACUNIT + exc->height - 8, + id, pressed ? "" : "", + 0, 8, V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN + ); + /* K_drawButton( x - (4 * FRACUNIT), y + ((exc->height - kp_button_z[1][0]->height) * FRACUNIT), V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN, kp_button_z[1], pressed ); + */ } else { @@ -1179,6 +1216,7 @@ void K_DrawMidVote(void) switch (g_midVote.type) { case MVT_KICK: + case MVT_MUTE: { // Draw victim name if (g_midVote.victim != NULL) @@ -1219,12 +1257,19 @@ void K_DrawMidVote(void) if (drawButton == true) { + K_DrawGameControl( + x/FRACUNIT-20, y/FRACUNIT + 2, id, + pressed ? "" : "", + 0, 8, V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN + ); + /* K_drawButton( x - (20 * FRACUNIT), y - (2 * FRACUNIT), V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_SPLITSCREEN, kp_button_z[0], pressed ); + */ } // Vote count diff --git a/src/k_zvote.h b/src/k_zvote.h index 3aac7f1e2..7ead33772 100644 --- a/src/k_zvote.h +++ b/src/k_zvote.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -27,6 +27,7 @@ extern "C" { typedef enum { MVT_KICK, // Kick another player in the server + MVT_MUTE, // Mute another player in the server (Voice Chat) MVT_RTV, // Exit level early MVT_RUNITBACK, // Restart level fresh MVT__MAX, // Total number of vote types diff --git a/src/keys.h b/src/keys.h index 4bb63be4e..d016d0cc3 100644 --- a/src/keys.h +++ b/src/keys.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/libdivide.h b/src/libdivide.h index 96dd27211..c10b16669 100644 --- a/src/libdivide.h +++ b/src/libdivide.h @@ -11,37 +11,56 @@ #ifndef LIBDIVIDE_H #define LIBDIVIDE_H -#define LIBDIVIDE_VERSION "5.0" +// *** Version numbers are auto generated - do not edit *** +#define LIBDIVIDE_VERSION "5.2.0" #define LIBDIVIDE_VERSION_MAJOR 5 -#define LIBDIVIDE_VERSION_MINOR 0 +#define LIBDIVIDE_VERSION_MINOR 2 +#define LIBDIVIDE_VERSION_PATCH 0 #include + #if !defined(__AVR__) #include #include #endif +#if defined(_MSC_VER) && (defined(__cplusplus) && (__cplusplus >= 202002L)) || \ + (defined(_MSVC_LANG) && (_MSVC_LANG >= 202002L)) +#include +#include +#define LIBDIVIDE_VC_CXX20 +#endif + #if defined(LIBDIVIDE_SSE2) #include #endif + #if defined(LIBDIVIDE_AVX2) || defined(LIBDIVIDE_AVX512) #include #endif + #if defined(LIBDIVIDE_NEON) #include #endif +// Clang-cl prior to Visual Studio 2022 doesn't include __umulh/__mulh intrinsics +#if defined(_MSC_VER) && (!defined(__clang__) || _MSC_VER > 1930) && \ + (defined(_M_X64) || defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC)) +#define LIBDIVIDE_MULH_INTRINSICS +#endif + #if defined(_MSC_VER) +#if defined(LIBDIVIDE_MULH_INTRINSICS) || !defined(__clang__) #include +#endif +#ifndef __clang__ #pragma warning(push) -// disable warning C4146: unary minus operator applied -// to unsigned type, result still unsigned +// 4146: unary minus operator applied to unsigned type, result still unsigned #pragma warning(disable : 4146) -// disable warning C4204: nonstandard extension used : non-constant aggregate -// initializer -// -// It's valid C99 + +// 4204: nonstandard extension used : non-constant aggregate initializer #pragma warning(disable : 4204) +#endif #define LIBDIVIDE_VC #endif @@ -83,8 +102,12 @@ #endif #endif #ifndef LIBDIVIDE_INLINE +#ifdef _MSC_VER +#define LIBDIVIDE_INLINE __forceinline +#else #define LIBDIVIDE_INLINE inline #endif +#endif #if defined(__AVR__) #define LIBDIVIDE_ERROR(msg) @@ -110,9 +133,81 @@ #endif #ifdef __cplusplus + +// For constexpr zero initialization, c++11 might handle things ok, +// but just limit to at least c++14 to ensure we don't break anyone's code: + +// Use https://en.cppreference.com/w/cpp/feature_test#cpp_constexpr +#if defined(__cpp_constexpr) && (__cpp_constexpr >= 201304L) +#define LIBDIVIDE_CONSTEXPR constexpr LIBDIVIDE_INLINE + +// Supposedly, MSVC might not implement feature test macros right: +// https://stackoverflow.com/questions/49316752/feature-test-macros-not-working-properly-in-visual-c +// so check that _MSVC_LANG corresponds to at least c++14, and _MSC_VER corresponds to at least VS +// 2017 15.0 (for extended constexpr support: +// https://learn.microsoft.com/en-us/cpp/overview/visual-cpp-language-conformance?view=msvc-170) +#elif (defined(_MSC_VER) && _MSC_VER >= 1910) && (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +#define LIBDIVIDE_CONSTEXPR constexpr LIBDIVIDE_INLINE + +#else +#define LIBDIVIDE_CONSTEXPR LIBDIVIDE_INLINE +#endif + namespace libdivide { #endif +#if defined(_MSC_VER) && !defined(__clang__) +#if defined(LIBDIVIDE_VC_CXX20) +static LIBDIVIDE_CONSTEXPR int __builtin_clz(unsigned x) { + if (std::is_constant_evaluated()) { + for (int i = 0; i < sizeof(x) * CHAR_BIT; ++i) { + if (x >> (sizeof(x) * CHAR_BIT - 1 - i)) return i; + } + return sizeof(x) * CHAR_BIT; + } +#else +static LIBDIVIDE_INLINE int __builtin_clz(unsigned x) { +#endif +#if defined(_M_ARM) || defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) + return (int)_CountLeadingZeros(x); +#elif defined(__AVX2__) || defined(__LZCNT__) + return (int)_lzcnt_u32(x); +#else + unsigned long r; + _BitScanReverse(&r, x); + return (int)(r ^ 31); +#endif +} + +#if defined(LIBDIVIDE_VC_CXX20) +static LIBDIVIDE_CONSTEXPR int __builtin_clzll(unsigned long long x) { + if (std::is_constant_evaluated()) { + for (int i = 0; i < sizeof(x) * CHAR_BIT; ++i) { + if (x >> (sizeof(x) * CHAR_BIT - 1 - i)) return i; + } + return sizeof(x) * CHAR_BIT; + } +#else +static LIBDIVIDE_INLINE int __builtin_clzll(unsigned long long x) { +#endif +#if defined(_M_ARM) || defined(_M_ARM64) || defined(_M_HYBRID_X86_ARM64) || defined(_M_ARM64EC) + return (int)_CountLeadingZeros64(x); +#elif defined(_WIN64) +#if defined(__AVX2__) || defined(__LZCNT__) + return (int)_lzcnt_u64(x); +#else + unsigned long r; + _BitScanReverse64(&r, x); + return (int)(r ^ 63); +#endif +#else + int l = __builtin_clz((unsigned)x) + 32; + int h = __builtin_clz((unsigned)(x >> 32)); + return !!((unsigned)(x >> 32)) ? h : l; +#endif +} +#endif // defined(_MSC_VER) && !defined(__clang__) + // pack divider structs to prevent compilers from padding. // This reduces memory usage by up to 43% when using a large // array of libdivide dividers and improves performance @@ -235,18 +330,28 @@ static LIBDIVIDE_INLINE struct libdivide_u32_branchfree_t libdivide_u32_branchfr static LIBDIVIDE_INLINE struct libdivide_s64_branchfree_t libdivide_s64_branchfree_gen(int64_t d); static LIBDIVIDE_INLINE struct libdivide_u64_branchfree_t libdivide_u64_branchfree_gen(uint64_t d); -static LIBDIVIDE_INLINE int16_t libdivide_s16_do_raw(int16_t numer, int16_t magic, uint8_t more); +static LIBDIVIDE_INLINE int16_t libdivide_s16_do_raw( + int16_t numer, int16_t magic, uint8_t more); static LIBDIVIDE_INLINE int16_t libdivide_s16_do( int16_t numer, const struct libdivide_s16_t *denom); -static LIBDIVIDE_INLINE uint16_t libdivide_u16_do_raw(uint16_t numer, uint16_t magic, uint8_t more); +static LIBDIVIDE_INLINE uint16_t libdivide_u16_do_raw( + uint16_t numer, uint16_t magic, uint8_t more); static LIBDIVIDE_INLINE uint16_t libdivide_u16_do( uint16_t numer, const struct libdivide_u16_t *denom); +static LIBDIVIDE_INLINE int32_t libdivide_s32_do_raw( + int32_t numer, int32_t magic, uint8_t more); static LIBDIVIDE_INLINE int32_t libdivide_s32_do( int32_t numer, const struct libdivide_s32_t *denom); +static LIBDIVIDE_INLINE uint32_t libdivide_u32_do_raw( + uint32_t numer, uint32_t magic, uint8_t more); static LIBDIVIDE_INLINE uint32_t libdivide_u32_do( uint32_t numer, const struct libdivide_u32_t *denom); +static LIBDIVIDE_INLINE int64_t libdivide_s64_do_raw( + int64_t numer, int64_t magic, uint8_t more); static LIBDIVIDE_INLINE int64_t libdivide_s64_do( int64_t numer, const struct libdivide_s64_t *denom); +static LIBDIVIDE_INLINE uint64_t libdivide_u64_do_raw( + uint64_t numer, uint64_t magic, uint8_t more); static LIBDIVIDE_INLINE uint64_t libdivide_u64_do( uint64_t numer, const struct libdivide_u64_t *denom); @@ -312,7 +417,7 @@ static LIBDIVIDE_INLINE int32_t libdivide_mullhi_s32(int32_t x, int32_t y) { } static LIBDIVIDE_INLINE uint64_t libdivide_mullhi_u64(uint64_t x, uint64_t y) { -#if defined(LIBDIVIDE_VC) && defined(LIBDIVIDE_X86_64) +#if defined(LIBDIVIDE_MULH_INTRINSICS) return __umulh(x, y); #elif defined(HAS_INT128_T) __uint128_t xl = x, yl = y; @@ -338,7 +443,7 @@ static LIBDIVIDE_INLINE uint64_t libdivide_mullhi_u64(uint64_t x, uint64_t y) { } static LIBDIVIDE_INLINE int64_t libdivide_mullhi_s64(int64_t x, int64_t y) { -#if defined(LIBDIVIDE_VC) && defined(LIBDIVIDE_X86_64) +#if defined(LIBDIVIDE_MULH_INTRINSICS) return __mulh(x, y); #elif defined(HAS_INT128_T) __int128_t xl = x, yl = y; @@ -364,15 +469,9 @@ static LIBDIVIDE_INLINE int16_t libdivide_count_leading_zeros16(uint16_t val) { // Fast way to count leading zeros // On the AVR 8-bit architecture __builtin_clz() works on a int16_t. return __builtin_clz(val); -#elif defined(__GNUC__) || __has_builtin(__builtin_clz) +#elif defined(__GNUC__) || __has_builtin(__builtin_clz) || defined(_MSC_VER) // Fast way to count leading zeros - return __builtin_clz(val) - 16; -#elif defined(LIBDIVIDE_VC) - unsigned long result; - if (_BitScanReverse(&result, (unsigned long)val)) { - return (int16_t)(15 - result); - } - return 0; + return (int16_t)(__builtin_clz(val) - 16); #else if (val == 0) return 16; int16_t result = 4; @@ -393,15 +492,9 @@ static LIBDIVIDE_INLINE int32_t libdivide_count_leading_zeros32(uint32_t val) { #if defined(__AVR__) // Fast way to count leading zeros return __builtin_clzl(val); -#elif defined(__GNUC__) || __has_builtin(__builtin_clz) +#elif defined(__GNUC__) || __has_builtin(__builtin_clz) || defined(_MSC_VER) // Fast way to count leading zeros return __builtin_clz(val); -#elif defined(LIBDIVIDE_VC) - unsigned long result; - if (_BitScanReverse(&result, val)) { - return 31 - result; - } - return 0; #else if (val == 0) return 32; int32_t result = 8; @@ -419,15 +512,9 @@ static LIBDIVIDE_INLINE int32_t libdivide_count_leading_zeros32(uint32_t val) { } static LIBDIVIDE_INLINE int32_t libdivide_count_leading_zeros64(uint64_t val) { -#if defined(__GNUC__) || __has_builtin(__builtin_clzll) +#if defined(__GNUC__) || __has_builtin(__builtin_clzll) || defined(_MSC_VER) // Fast way to count leading zeros return __builtin_clzll(val); -#elif defined(LIBDIVIDE_VC) && defined(_WIN64) - unsigned long result; - if (_BitScanReverse64(&result, val)) { - return 63 - result; - } - return 0; #else uint32_t hi = val >> 32; uint32_t lo = val & 0xFFFFFFFF; @@ -474,7 +561,7 @@ static LIBDIVIDE_INLINE uint64_t libdivide_128_div_64_to_64( // it's not LIBDIVIDE_INLINEd. #if defined(LIBDIVIDE_X86_64) && defined(LIBDIVIDE_GCC_STYLE_ASM) uint64_t result; - __asm__("divq %[v]" : "=a"(result), "=d"(*r) : [v] "r"(den), "a"(numlo), "d"(numhi)); + __asm__("div %[v]" : "=a"(result), "=d"(*r) : [v] "r"(den), "a"(numlo), "d"(numhi)); return result; #else // We work in base 2**32. @@ -510,7 +597,7 @@ static LIBDIVIDE_INLINE uint64_t libdivide_128_div_64_to_64( // Check for overflow and divide by 0. if (numhi >= den) { - if (r != NULL) *r = ~0ull; + if (r) *r = ~0ull; return ~0ull; } @@ -524,7 +611,7 @@ static LIBDIVIDE_INLINE uint64_t libdivide_128_div_64_to_64( shift = libdivide_count_leading_zeros64(den); den <<= shift; numhi <<= shift; - numhi |= (numlo >> (-shift & 63)) & (-(int64_t)shift >> 63); + numhi |= (numlo >> (-shift & 63)) & (uint64_t)(-(int64_t)shift >> 63); numlo <<= shift; // Extract the low digits of the numerator and both digits of the denominator. @@ -556,7 +643,7 @@ static LIBDIVIDE_INLINE uint64_t libdivide_128_div_64_to_64( q0 = (uint32_t)qhat; // Return remainder if requested. - if (r != NULL) *r = (rem * b + num0 - q0 * den) >> shift; + if (r) *r = (rem * b + num0 - q0 * den) >> shift; return ((uint64_t)q1 << 32) | q0; #endif } @@ -733,11 +820,11 @@ static LIBDIVIDE_INLINE struct libdivide_u16_t libdivide_internal_u16_gen( return result; } -struct libdivide_u16_t libdivide_u16_gen(uint16_t d) { +static LIBDIVIDE_INLINE struct libdivide_u16_t libdivide_u16_gen(uint16_t d) { return libdivide_internal_u16_gen(d, 0); } -struct libdivide_u16_branchfree_t libdivide_u16_branchfree_gen(uint16_t d) { +static LIBDIVIDE_INLINE struct libdivide_u16_branchfree_t libdivide_u16_branchfree_gen(uint16_t d) { if (d == 1) { LIBDIVIDE_ERROR("branchfree divider must be != 1"); } @@ -750,11 +837,11 @@ struct libdivide_u16_branchfree_t libdivide_u16_branchfree_gen(uint16_t d) { // The original libdivide_u16_do takes a const pointer. However, this cannot be used // with a compile time constant libdivide_u16_t: it will generate a warning about // taking the address of a temporary. Hence this overload. -uint16_t libdivide_u16_do_raw(uint16_t numer, uint16_t magic, uint8_t more) { +static LIBDIVIDE_INLINE uint16_t libdivide_u16_do_raw(uint16_t numer, uint16_t magic, uint8_t more) { if (!magic) { return numer >> more; } else { - uint16_t q = libdivide_mullhi_u16(magic, numer); + uint16_t q = libdivide_mullhi_u16(numer, magic); if (more & LIBDIVIDE_ADD_MARKER) { uint16_t t = ((numer - q) >> 1) + q; return t >> (more & LIBDIVIDE_16_SHIFT_MASK); @@ -766,18 +853,18 @@ uint16_t libdivide_u16_do_raw(uint16_t numer, uint16_t magic, uint8_t more) { } } -uint16_t libdivide_u16_do(uint16_t numer, const struct libdivide_u16_t *denom) { +static LIBDIVIDE_INLINE uint16_t libdivide_u16_do(uint16_t numer, const struct libdivide_u16_t *denom) { return libdivide_u16_do_raw(numer, denom->magic, denom->more); } -uint16_t libdivide_u16_branchfree_do( +static LIBDIVIDE_INLINE uint16_t libdivide_u16_branchfree_do( uint16_t numer, const struct libdivide_u16_branchfree_t *denom) { - uint16_t q = libdivide_mullhi_u16(denom->magic, numer); + uint16_t q = libdivide_mullhi_u16(numer, denom->magic); uint16_t t = ((numer - q) >> 1) + q; return t >> denom->more; } -uint16_t libdivide_u16_recover(const struct libdivide_u16_t *denom) { +static LIBDIVIDE_INLINE uint16_t libdivide_u16_recover(const struct libdivide_u16_t *denom) { uint8_t more = denom->more; uint8_t shift = more & LIBDIVIDE_16_SHIFT_MASK; @@ -815,7 +902,7 @@ uint16_t libdivide_u16_recover(const struct libdivide_u16_t *denom) { } } -uint16_t libdivide_u16_branchfree_recover(const struct libdivide_u16_branchfree_t *denom) { +static LIBDIVIDE_INLINE uint16_t libdivide_u16_branchfree_recover(const struct libdivide_u16_branchfree_t *denom) { uint8_t more = denom->more; uint8_t shift = more & LIBDIVIDE_16_SHIFT_MASK; @@ -897,11 +984,11 @@ static LIBDIVIDE_INLINE struct libdivide_u32_t libdivide_internal_u32_gen( return result; } -struct libdivide_u32_t libdivide_u32_gen(uint32_t d) { +static LIBDIVIDE_INLINE struct libdivide_u32_t libdivide_u32_gen(uint32_t d) { return libdivide_internal_u32_gen(d, 0); } -struct libdivide_u32_branchfree_t libdivide_u32_branchfree_gen(uint32_t d) { +static LIBDIVIDE_INLINE struct libdivide_u32_branchfree_t libdivide_u32_branchfree_gen(uint32_t d) { if (d == 1) { LIBDIVIDE_ERROR("branchfree divider must be != 1"); } @@ -911,12 +998,11 @@ struct libdivide_u32_branchfree_t libdivide_u32_branchfree_gen(uint32_t d) { return ret; } -uint32_t libdivide_u32_do(uint32_t numer, const struct libdivide_u32_t *denom) { - uint8_t more = denom->more; - if (!denom->magic) { +static LIBDIVIDE_INLINE uint32_t libdivide_u32_do_raw(uint32_t numer, uint32_t magic, uint8_t more) { + if (!magic) { return numer >> more; } else { - uint32_t q = libdivide_mullhi_u32(denom->magic, numer); + uint32_t q = libdivide_mullhi_u32(numer, magic); if (more & LIBDIVIDE_ADD_MARKER) { uint32_t t = ((numer - q) >> 1) + q; return t >> (more & LIBDIVIDE_32_SHIFT_MASK); @@ -928,14 +1014,18 @@ uint32_t libdivide_u32_do(uint32_t numer, const struct libdivide_u32_t *denom) { } } -uint32_t libdivide_u32_branchfree_do( +static LIBDIVIDE_INLINE uint32_t libdivide_u32_do(uint32_t numer, const struct libdivide_u32_t *denom) { + return libdivide_u32_do_raw(numer, denom->magic, denom->more); +} + +static LIBDIVIDE_INLINE uint32_t libdivide_u32_branchfree_do( uint32_t numer, const struct libdivide_u32_branchfree_t *denom) { - uint32_t q = libdivide_mullhi_u32(denom->magic, numer); + uint32_t q = libdivide_mullhi_u32(numer, denom->magic); uint32_t t = ((numer - q) >> 1) + q; return t >> denom->more; } -uint32_t libdivide_u32_recover(const struct libdivide_u32_t *denom) { +static LIBDIVIDE_INLINE uint32_t libdivide_u32_recover(const struct libdivide_u32_t *denom) { uint8_t more = denom->more; uint8_t shift = more & LIBDIVIDE_32_SHIFT_MASK; @@ -973,7 +1063,7 @@ uint32_t libdivide_u32_recover(const struct libdivide_u32_t *denom) { } } -uint32_t libdivide_u32_branchfree_recover(const struct libdivide_u32_branchfree_t *denom) { +static LIBDIVIDE_INLINE uint32_t libdivide_u32_branchfree_recover(const struct libdivide_u32_branchfree_t *denom) { uint8_t more = denom->more; uint8_t shift = more & LIBDIVIDE_32_SHIFT_MASK; @@ -1002,7 +1092,7 @@ uint32_t libdivide_u32_branchfree_recover(const struct libdivide_u32_branchfree_ } } -/////////// UINT64 +////////// UINT64 static LIBDIVIDE_INLINE struct libdivide_u64_t libdivide_internal_u64_gen( uint64_t d, int branchfree) { @@ -1057,11 +1147,11 @@ static LIBDIVIDE_INLINE struct libdivide_u64_t libdivide_internal_u64_gen( return result; } -struct libdivide_u64_t libdivide_u64_gen(uint64_t d) { +static LIBDIVIDE_INLINE struct libdivide_u64_t libdivide_u64_gen(uint64_t d) { return libdivide_internal_u64_gen(d, 0); } -struct libdivide_u64_branchfree_t libdivide_u64_branchfree_gen(uint64_t d) { +static LIBDIVIDE_INLINE struct libdivide_u64_branchfree_t libdivide_u64_branchfree_gen(uint64_t d) { if (d == 1) { LIBDIVIDE_ERROR("branchfree divider must be != 1"); } @@ -1071,12 +1161,11 @@ struct libdivide_u64_branchfree_t libdivide_u64_branchfree_gen(uint64_t d) { return ret; } -uint64_t libdivide_u64_do(uint64_t numer, const struct libdivide_u64_t *denom) { - uint8_t more = denom->more; - if (!denom->magic) { +static LIBDIVIDE_INLINE uint64_t libdivide_u64_do_raw(uint64_t numer, uint64_t magic, uint8_t more) { + if (!magic) { return numer >> more; } else { - uint64_t q = libdivide_mullhi_u64(denom->magic, numer); + uint64_t q = libdivide_mullhi_u64(numer, magic); if (more & LIBDIVIDE_ADD_MARKER) { uint64_t t = ((numer - q) >> 1) + q; return t >> (more & LIBDIVIDE_64_SHIFT_MASK); @@ -1088,14 +1177,18 @@ uint64_t libdivide_u64_do(uint64_t numer, const struct libdivide_u64_t *denom) { } } -uint64_t libdivide_u64_branchfree_do( +static LIBDIVIDE_INLINE uint64_t libdivide_u64_do(uint64_t numer, const struct libdivide_u64_t *denom) { + return libdivide_u64_do_raw(numer, denom->magic, denom->more); +} + +static LIBDIVIDE_INLINE uint64_t libdivide_u64_branchfree_do( uint64_t numer, const struct libdivide_u64_branchfree_t *denom) { - uint64_t q = libdivide_mullhi_u64(denom->magic, numer); + uint64_t q = libdivide_mullhi_u64(numer, denom->magic); uint64_t t = ((numer - q) >> 1) + q; return t >> denom->more; } -uint64_t libdivide_u64_recover(const struct libdivide_u64_t *denom) { +static LIBDIVIDE_INLINE uint64_t libdivide_u64_recover(const struct libdivide_u64_t *denom) { uint8_t more = denom->more; uint8_t shift = more & LIBDIVIDE_64_SHIFT_MASK; @@ -1139,7 +1232,7 @@ uint64_t libdivide_u64_recover(const struct libdivide_u64_t *denom) { } } -uint64_t libdivide_u64_branchfree_recover(const struct libdivide_u64_branchfree_t *denom) { +static LIBDIVIDE_INLINE uint64_t libdivide_u64_branchfree_recover(const struct libdivide_u64_branchfree_t *denom) { uint8_t more = denom->more; uint8_t shift = more & LIBDIVIDE_64_SHIFT_MASK; @@ -1174,7 +1267,7 @@ uint64_t libdivide_u64_branchfree_recover(const struct libdivide_u64_branchfree_ } } -/////////// SINT16 +////////// SINT16 static LIBDIVIDE_INLINE struct libdivide_s16_t libdivide_internal_s16_gen( int16_t d, int branchfree) { @@ -1242,11 +1335,11 @@ static LIBDIVIDE_INLINE struct libdivide_s16_t libdivide_internal_s16_gen( return result; } -struct libdivide_s16_t libdivide_s16_gen(int16_t d) { +static LIBDIVIDE_INLINE struct libdivide_s16_t libdivide_s16_gen(int16_t d) { return libdivide_internal_s16_gen(d, 0); } -struct libdivide_s16_branchfree_t libdivide_s16_branchfree_gen(int16_t d) { +static LIBDIVIDE_INLINE struct libdivide_s16_branchfree_t libdivide_s16_branchfree_gen(int16_t d) { struct libdivide_s16_t tmp = libdivide_internal_s16_gen(d, 1); struct libdivide_s16_branchfree_t result = {tmp.magic, tmp.more}; return result; @@ -1255,7 +1348,7 @@ struct libdivide_s16_branchfree_t libdivide_s16_branchfree_gen(int16_t d) { // The original libdivide_s16_do takes a const pointer. However, this cannot be used // with a compile time constant libdivide_s16_t: it will generate a warning about // taking the address of a temporary. Hence this overload. -int16_t libdivide_s16_do_raw(int16_t numer, int16_t magic, uint8_t more) { +static LIBDIVIDE_INLINE int16_t libdivide_s16_do_raw(int16_t numer, int16_t magic, uint8_t more) { uint8_t shift = more & LIBDIVIDE_16_SHIFT_MASK; if (!magic) { @@ -1267,7 +1360,7 @@ int16_t libdivide_s16_do_raw(int16_t numer, int16_t magic, uint8_t more) { q = (q ^ sign) - sign; return q; } else { - uint16_t uq = (uint16_t)libdivide_mullhi_s16(magic, numer); + uint16_t uq = (uint16_t)libdivide_mullhi_s16(numer, magic); if (more & LIBDIVIDE_ADD_MARKER) { // must be arithmetic shift and then sign extend int16_t sign = (int8_t)more >> 7; @@ -1282,17 +1375,17 @@ int16_t libdivide_s16_do_raw(int16_t numer, int16_t magic, uint8_t more) { } } -int16_t libdivide_s16_do(int16_t numer, const struct libdivide_s16_t *denom) { +static LIBDIVIDE_INLINE int16_t libdivide_s16_do(int16_t numer, const struct libdivide_s16_t *denom) { return libdivide_s16_do_raw(numer, denom->magic, denom->more); } -int16_t libdivide_s16_branchfree_do(int16_t numer, const struct libdivide_s16_branchfree_t *denom) { +static LIBDIVIDE_INLINE int16_t libdivide_s16_branchfree_do(int16_t numer, const struct libdivide_s16_branchfree_t *denom) { uint8_t more = denom->more; uint8_t shift = more & LIBDIVIDE_16_SHIFT_MASK; // must be arithmetic shift and then sign extend int16_t sign = (int8_t)more >> 7; int16_t magic = denom->magic; - int16_t q = libdivide_mullhi_s16(magic, numer); + int16_t q = libdivide_mullhi_s16(numer, magic); q += numer; // If q is non-negative, we have nothing to do @@ -1310,7 +1403,7 @@ int16_t libdivide_s16_branchfree_do(int16_t numer, const struct libdivide_s16_br return q; } -int16_t libdivide_s16_recover(const struct libdivide_s16_t *denom) { +static LIBDIVIDE_INLINE int16_t libdivide_s16_recover(const struct libdivide_s16_t *denom) { uint8_t more = denom->more; uint8_t shift = more & LIBDIVIDE_16_SHIFT_MASK; if (!denom->magic) { @@ -1345,11 +1438,12 @@ int16_t libdivide_s16_recover(const struct libdivide_s16_t *denom) { } } -int16_t libdivide_s16_branchfree_recover(const struct libdivide_s16_branchfree_t *denom) { - return libdivide_s16_recover((const struct libdivide_s16_t *)denom); +static LIBDIVIDE_INLINE int16_t libdivide_s16_branchfree_recover(const struct libdivide_s16_branchfree_t *denom) { + const struct libdivide_s16_t den = {denom->magic, denom->more}; + return libdivide_s16_recover(&den); } -/////////// SINT32 +////////// SINT32 static LIBDIVIDE_INLINE struct libdivide_s32_t libdivide_internal_s32_gen( int32_t d, int branchfree) { @@ -1417,21 +1511,20 @@ static LIBDIVIDE_INLINE struct libdivide_s32_t libdivide_internal_s32_gen( return result; } -struct libdivide_s32_t libdivide_s32_gen(int32_t d) { +static LIBDIVIDE_INLINE struct libdivide_s32_t libdivide_s32_gen(int32_t d) { return libdivide_internal_s32_gen(d, 0); } -struct libdivide_s32_branchfree_t libdivide_s32_branchfree_gen(int32_t d) { +static LIBDIVIDE_INLINE struct libdivide_s32_branchfree_t libdivide_s32_branchfree_gen(int32_t d) { struct libdivide_s32_t tmp = libdivide_internal_s32_gen(d, 1); struct libdivide_s32_branchfree_t result = {tmp.magic, tmp.more}; return result; } -int32_t libdivide_s32_do(int32_t numer, const struct libdivide_s32_t *denom) { - uint8_t more = denom->more; +static LIBDIVIDE_INLINE int32_t libdivide_s32_do_raw(int32_t numer, int32_t magic, uint8_t more) { uint8_t shift = more & LIBDIVIDE_32_SHIFT_MASK; - if (!denom->magic) { + if (!magic) { uint32_t sign = (int8_t)more >> 7; uint32_t mask = ((uint32_t)1 << shift) - 1; uint32_t uq = numer + ((numer >> 31) & mask); @@ -1440,7 +1533,7 @@ int32_t libdivide_s32_do(int32_t numer, const struct libdivide_s32_t *denom) { q = (q ^ sign) - sign; return q; } else { - uint32_t uq = (uint32_t)libdivide_mullhi_s32(denom->magic, numer); + uint32_t uq = (uint32_t)libdivide_mullhi_s32(numer, magic); if (more & LIBDIVIDE_ADD_MARKER) { // must be arithmetic shift and then sign extend int32_t sign = (int8_t)more >> 7; @@ -1455,13 +1548,17 @@ int32_t libdivide_s32_do(int32_t numer, const struct libdivide_s32_t *denom) { } } -int32_t libdivide_s32_branchfree_do(int32_t numer, const struct libdivide_s32_branchfree_t *denom) { +static LIBDIVIDE_INLINE int32_t libdivide_s32_do(int32_t numer, const struct libdivide_s32_t *denom) { + return libdivide_s32_do_raw(numer, denom->magic, denom->more); +} + +static LIBDIVIDE_INLINE int32_t libdivide_s32_branchfree_do(int32_t numer, const struct libdivide_s32_branchfree_t *denom) { uint8_t more = denom->more; uint8_t shift = more & LIBDIVIDE_32_SHIFT_MASK; // must be arithmetic shift and then sign extend int32_t sign = (int8_t)more >> 7; int32_t magic = denom->magic; - int32_t q = libdivide_mullhi_s32(magic, numer); + int32_t q = libdivide_mullhi_s32(numer, magic); q += numer; // If q is non-negative, we have nothing to do @@ -1479,7 +1576,7 @@ int32_t libdivide_s32_branchfree_do(int32_t numer, const struct libdivide_s32_br return q; } -int32_t libdivide_s32_recover(const struct libdivide_s32_t *denom) { +static LIBDIVIDE_INLINE int32_t libdivide_s32_recover(const struct libdivide_s32_t *denom) { uint8_t more = denom->more; uint8_t shift = more & LIBDIVIDE_32_SHIFT_MASK; if (!denom->magic) { @@ -1514,11 +1611,12 @@ int32_t libdivide_s32_recover(const struct libdivide_s32_t *denom) { } } -int32_t libdivide_s32_branchfree_recover(const struct libdivide_s32_branchfree_t *denom) { - return libdivide_s32_recover((const struct libdivide_s32_t *)denom); +static LIBDIVIDE_INLINE int32_t libdivide_s32_branchfree_recover(const struct libdivide_s32_branchfree_t *denom) { + const struct libdivide_s32_t den = {denom->magic, denom->more}; + return libdivide_s32_recover(&den); } -///////////// SINT64 +////////// SINT64 static LIBDIVIDE_INLINE struct libdivide_s64_t libdivide_internal_s64_gen( int64_t d, int branchfree) { @@ -1586,21 +1684,20 @@ static LIBDIVIDE_INLINE struct libdivide_s64_t libdivide_internal_s64_gen( return result; } -struct libdivide_s64_t libdivide_s64_gen(int64_t d) { +static LIBDIVIDE_INLINE struct libdivide_s64_t libdivide_s64_gen(int64_t d) { return libdivide_internal_s64_gen(d, 0); } -struct libdivide_s64_branchfree_t libdivide_s64_branchfree_gen(int64_t d) { +static LIBDIVIDE_INLINE struct libdivide_s64_branchfree_t libdivide_s64_branchfree_gen(int64_t d) { struct libdivide_s64_t tmp = libdivide_internal_s64_gen(d, 1); struct libdivide_s64_branchfree_t ret = {tmp.magic, tmp.more}; return ret; } -int64_t libdivide_s64_do(int64_t numer, const struct libdivide_s64_t *denom) { - uint8_t more = denom->more; +static LIBDIVIDE_INLINE int64_t libdivide_s64_do_raw(int64_t numer, int64_t magic, uint8_t more) { uint8_t shift = more & LIBDIVIDE_64_SHIFT_MASK; - if (!denom->magic) { // shift path + if (!magic) { // shift path uint64_t mask = ((uint64_t)1 << shift) - 1; uint64_t uq = numer + ((numer >> 63) & mask); int64_t q = (int64_t)uq; @@ -1610,7 +1707,7 @@ int64_t libdivide_s64_do(int64_t numer, const struct libdivide_s64_t *denom) { q = (q ^ sign) - sign; return q; } else { - uint64_t uq = (uint64_t)libdivide_mullhi_s64(denom->magic, numer); + uint64_t uq = (uint64_t)libdivide_mullhi_s64(numer, magic); if (more & LIBDIVIDE_ADD_MARKER) { // must be arithmetic shift and then sign extend int64_t sign = (int8_t)more >> 7; @@ -1625,13 +1722,17 @@ int64_t libdivide_s64_do(int64_t numer, const struct libdivide_s64_t *denom) { } } -int64_t libdivide_s64_branchfree_do(int64_t numer, const struct libdivide_s64_branchfree_t *denom) { +static LIBDIVIDE_INLINE int64_t libdivide_s64_do(int64_t numer, const struct libdivide_s64_t *denom) { + return libdivide_s64_do_raw(numer, denom->magic, denom->more); +} + +static LIBDIVIDE_INLINE int64_t libdivide_s64_branchfree_do(int64_t numer, const struct libdivide_s64_branchfree_t *denom) { uint8_t more = denom->more; uint8_t shift = more & LIBDIVIDE_64_SHIFT_MASK; // must be arithmetic shift and then sign extend int64_t sign = (int8_t)more >> 7; int64_t magic = denom->magic; - int64_t q = libdivide_mullhi_s64(magic, numer); + int64_t q = libdivide_mullhi_s64(numer, magic); q += numer; // If q is non-negative, we have nothing to do. @@ -1649,7 +1750,7 @@ int64_t libdivide_s64_branchfree_do(int64_t numer, const struct libdivide_s64_br return q; } -int64_t libdivide_s64_recover(const struct libdivide_s64_t *denom) { +static LIBDIVIDE_INLINE int64_t libdivide_s64_recover(const struct libdivide_s64_t *denom) { uint8_t more = denom->more; uint8_t shift = more & LIBDIVIDE_64_SHIFT_MASK; if (denom->magic == 0) { // shift path @@ -1675,8 +1776,9 @@ int64_t libdivide_s64_recover(const struct libdivide_s64_t *denom) { } } -int64_t libdivide_s64_branchfree_recover(const struct libdivide_s64_branchfree_t *denom) { - return libdivide_s64_recover((const struct libdivide_s64_t *)denom); +static LIBDIVIDE_INLINE int64_t libdivide_s64_branchfree_recover(const struct libdivide_s64_branchfree_t *denom) { + const struct libdivide_s64_t den = {denom->magic, denom->more}; + return libdivide_s64_recover(&den); } // Simplest possible vector type division: treat the vector type as an array @@ -2959,7 +3061,7 @@ __m128i libdivide_s64_branchfree_do_vec128( #endif -/////////// C++ stuff +////////// C++ stuff #ifdef __cplusplus @@ -3056,6 +3158,7 @@ struct NeonVecFor { #define DISPATCHER_GEN(T, ALGO) \ libdivide_##ALGO##_t denom; \ LIBDIVIDE_INLINE dispatcher() {} \ + explicit LIBDIVIDE_CONSTEXPR dispatcher(decltype(nullptr)) : denom{} {} \ LIBDIVIDE_INLINE dispatcher(T d) : denom(libdivide_##ALGO##_gen(d)) {} \ LIBDIVIDE_INLINE T divide(T n) const { return libdivide_##ALGO##_do(n, &denom); } \ LIBDIVIDE_INLINE T recover() const { return libdivide_##ALGO##_recover(&denom); } \ @@ -3147,6 +3250,9 @@ class divider { // later doesn't slow us down. divider() {} + // constexpr zero-initialization to allow for use w/ static constinit + explicit LIBDIVIDE_CONSTEXPR divider(decltype(nullptr)) : div(nullptr) {} + // Constructor that takes the divisor as a parameter LIBDIVIDE_INLINE divider(T d) : div(d) {} @@ -3158,7 +3264,7 @@ class divider { T recover() const { return div.recover(); } bool operator==(const divider &other) const { - return div.denom.magic == other.denom.magic && div.denom.more == other.denom.more; + return div.denom.magic == other.div.denom.magic && div.denom.more == other.div.denom.more; } bool operator!=(const divider &other) const { return !(*this == other); } @@ -3262,7 +3368,7 @@ using branchfree_divider = divider; #endif // __cplusplus -#if defined(_MSC_VER) +#if defined(_MSC_VER) && !defined(__clang__) #pragma warning(pop) #endif diff --git a/src/locale/en.po b/src/locale/en.po index d32761b4f..28f1f455e 100644 --- a/src/locale/en.po +++ b/src/locale/en.po @@ -1,6 +1,6 @@ # DR. ROBOTNIK'S RING RACERS #----------------------------------------------------------------------------- -# Copyright (C) 2024 by Kart Krew +# Copyright (C) 2025 by Kart Krew # Copyright (C) 2020 by Sonic Team Junior # # This program is free software distributed under the diff --git a/src/locale/srb2.pot b/src/locale/srb2.pot index 5e4d13b51..ac64aea12 100644 --- a/src/locale/srb2.pot +++ b/src/locale/srb2.pot @@ -1,6 +1,6 @@ # DR. ROBOTNIK'S RING RACERS #----------------------------------------------------------------------------- -# Copyright (C) 2024 by Kart Krew +# Copyright (C) 2025 by Kart Krew # Copyright (C) 2020 by Sonic Team Junior # # This program is free software distributed under the diff --git a/src/lua_baselib.c b/src/lua_baselib.c index 4ee70339b..873056643 100644 --- a/src/lua_baselib.c +++ b/src/lua_baselib.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by John "JTE" Muniz. // @@ -3407,7 +3407,7 @@ static int lib_kAddMessage(lua_State *L) INLEVEL if (msg == NULL) return luaL_error(L, "argument #1 not given (expected string)"); - K_AddMessage(msg, interrupt, persist); + K_AddMessage(msg, interrupt, persist); return 0; } diff --git a/src/lua_blockmaplib.c b/src/lua_blockmaplib.c index b0c8a9f74..0b0c050b4 100644 --- a/src/lua_blockmaplib.c +++ b/src/lua_blockmaplib.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Iestyn "Monster Iestyn" Jealous. // Copyright (C) 2020 by Sonic Team Junior. // diff --git a/src/lua_consolelib.c b/src/lua_consolelib.c index 274a215f6..f15876acf 100644 --- a/src/lua_consolelib.c +++ b/src/lua_consolelib.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by John "JTE" Muniz. // diff --git a/src/lua_followerlib.c b/src/lua_followerlib.c index 07fe8c998..a1c56d0b7 100644 --- a/src/lua_followerlib.c +++ b/src/lua_followerlib.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by John "JTE" Muniz. // diff --git a/src/lua_hook.h b/src/lua_hook.h index cb126c534..12c85be94 100644 --- a/src/lua_hook.h +++ b/src/lua_hook.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2022 by Sonic Team Junior. // Copyright (C) 2016 by John "JTE" Muniz. // @@ -143,7 +143,7 @@ int LUA_HookMapThingSpawn(mobj_t *, mapthing_t *); int LUA_HookFollowMobj(player_t *, mobj_t *); int LUA_HookPlayerCanDamage(player_t *, mobj_t *); void LUA_HookPlayerQuit(player_t *, kickreason_t); -int LUA_HookTeamSwitch(player_t *, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble); +//int LUA_HookTeamSwitch(player_t *, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble); int LUA_HookViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced); int LUA_HookSeenPlayer(player_t *player, player_t *seenfriend); diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c index 1ccedd091..676e40b77 100644 --- a/src/lua_hooklib.c +++ b/src/lua_hooklib.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by John "JTE" Muniz. // @@ -961,6 +961,7 @@ void LUA_HookPlayerQuit(player_t *plr, kickreason_t reason) } } +/* int LUA_HookTeamSwitch(player_t *player, int newteam, boolean fromspectators, boolean tryingautobalance, boolean tryingscramble) { Hook_State hook; @@ -975,6 +976,7 @@ int LUA_HookTeamSwitch(player_t *player, int newteam, boolean fromspectators, bo } return hook.status; } +*/ int LUA_HookViewpointSwitch(player_t *player, player_t *newdisplayplayer, boolean forced) { diff --git a/src/lua_hud.h b/src/lua_hud.h index 8525aabf4..7416ee324 100644 --- a/src/lua_hud.h +++ b/src/lua_hud.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2022 by Sonic Team Junior. // Copyright (C) 2016 by John "JTE" Muniz. // diff --git a/src/lua_hudlib.c b/src/lua_hudlib.c index b59e12cf3..6b80e61c0 100644 --- a/src/lua_hudlib.c +++ b/src/lua_hudlib.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by John "JTE" Muniz. // @@ -51,6 +51,7 @@ static const char *const hud_disable_options[] = { "minimap", "item", "position", + "names", "check", // "CHECK" f-zero indicator "minirankings", // Gametype rankings to the left "battlerankingsbumpers", // bumper drawer for battle. Useful if you want to make a custom battle gamemode without bumpers being involved. @@ -964,6 +965,21 @@ static int libd_stringWidth(lua_State *L) return 1; } +static int libd_parseText(lua_State *L) +{ + HUDONLY + + const char *rawText = luaL_checkstring(L, 1); + + if (!rawText) + return luaL_error(L, "no string provided to v.parseText"); + + char *newText = V_ParseText(rawText); + lua_pushstring(gL, newText); + Z_Free(newText); + return 1; +} + static int libd_getColormap(lua_State *L) { INT32 skinnum = TC_DEFAULT; @@ -1162,6 +1178,7 @@ static luaL_Reg lib_draw[] = { // misc {"stringWidth", libd_stringWidth}, {"titleCardStringWidth", libd_titleCardStringWidth}, + {"parseText", libd_parseText}, // m_random {"RandomFixed",libd_RandomFixed}, {"RandomByte",libd_RandomByte}, diff --git a/src/lua_hudlib_drawlist.c b/src/lua_hudlib_drawlist.c index e9120c692..ad5a62bbc 100644 --- a/src/lua_hudlib_drawlist.c +++ b/src/lua_hudlib_drawlist.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by John "JTE" Muniz. // diff --git a/src/lua_hudlib_drawlist.h b/src/lua_hudlib_drawlist.h index 1ad55ac2a..a090b5507 100644 --- a/src/lua_hudlib_drawlist.h +++ b/src/lua_hudlib_drawlist.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2022 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/lua_infolib.c b/src/lua_infolib.c index 6c3d64d91..8baf1008f 100644 --- a/src/lua_infolib.c +++ b/src/lua_infolib.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by John "JTE" Muniz. // @@ -730,8 +730,8 @@ static int lib_getState(lua_State *L) lua_remove(L, 1); i = luaL_checkinteger(L, 1); - if (i >= NUMSTATES) - return luaL_error(L, "states[] index %d out of range (0 - %d)", i, NUMSTATES-1); + if (i == 0 || i >= NUMSTATES) + return luaL_error(L, "states[] index %d out of range (1 - %d)", i, NUMSTATES-1); LUA_PushUserdata(L, &states[i], META_STATE); return 1; } @@ -743,8 +743,8 @@ static int lib_setState(lua_State *L) lua_remove(L, 1); // don't care about states[] userdata. { UINT32 i = luaL_checkinteger(L, 1); - if (i >= NUMSTATES) - return luaL_error(L, "states[] index %d out of range (0 - %d)", i, NUMSTATES-1); + if (i == 0 || i >= NUMSTATES) + return luaL_error(L, "states[] index %d out of range (1 - %d)", i, NUMSTATES-1); state = &states[i]; // get the state to assign to. } luaL_checktype(L, 2, LUA_TTABLE); // check that we've been passed a table. @@ -1092,8 +1092,8 @@ static int lib_getMobjInfo(lua_State *L) lua_remove(L, 1); i = luaL_checkinteger(L, 1); - if (i >= NUMMOBJTYPES) - return luaL_error(L, "mobjinfo[] index %d out of range (0 - %d)", i, NUMMOBJTYPES-1); + if (i == 0 || i >= NUMMOBJTYPES) + return luaL_error(L, "mobjinfo[] index %d out of range (1 - %d)", i, NUMMOBJTYPES-1); LUA_PushUserdata(L, &mobjinfo[i], META_MOBJINFO); return 1; } @@ -1105,8 +1105,8 @@ static int lib_setMobjInfo(lua_State *L) lua_remove(L, 1); // don't care about mobjinfo[] userdata. { UINT32 i = luaL_checkinteger(L, 1); - if (i >= NUMMOBJTYPES) - return luaL_error(L, "mobjinfo[] index %d out of range (0 - %d)", i, NUMMOBJTYPES-1); + if (i == 0 || i >= NUMMOBJTYPES) + return luaL_error(L, "mobjinfo[] index %d out of range (1 - %d)", i, NUMMOBJTYPES-1); info = &mobjinfo[i]; // get the mobjinfo to assign to. } luaL_checktype(L, 2, LUA_TTABLE); // check that we've been passed a table. @@ -1355,6 +1355,8 @@ static int mobjinfo_set(lua_State *L) info->flags = (INT32)luaL_checkinteger(L, 3); else if (fastcmp(field,"raisestate")) info->raisestate = luaL_checkinteger(L, 3); + else if (fastcmp(field,"string")) + return luaL_error(L, LUA_QL("mobjinfo_t") " field " LUA_QS " should not be set directly.", field); else { lua_getfield(L, LUA_REGISTRYINDEX, LREG_EXTVARS); I_Assert(lua_istable(L, -1)); diff --git a/src/lua_libs.h b/src/lua_libs.h index 290be4101..73d7b4f6e 100644 --- a/src/lua_libs.h +++ b/src/lua_libs.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2022 by Sonic Team Junior. // Copyright (C) 2016 by John "JTE" Muniz. // diff --git a/src/lua_maplib.c b/src/lua_maplib.c index 406c392fa..7329f355b 100644 --- a/src/lua_maplib.c +++ b/src/lua_maplib.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by John "JTE" Muniz. // @@ -2572,6 +2572,8 @@ static int mapheaderinfo_get(lua_State *L) lua_pushfixed(L, header->mobj_scale); else if (fastcmp(field, "gravity")) lua_pushfixed(L, header->gravity); + else if (fastcmp(field, "cameraheight")) + lua_pushfixed(L, header->cameraHeight); else { // Read custom vars now // (note: don't include the "LUA." in your lua scripts!) diff --git a/src/lua_mathlib.c b/src/lua_mathlib.c index d3a86da20..c6b2110b8 100644 --- a/src/lua_mathlib.c +++ b/src/lua_mathlib.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by John "JTE" Muniz. // diff --git a/src/lua_mobjlib.c b/src/lua_mobjlib.c index e8e9f89bd..158909815 100644 --- a/src/lua_mobjlib.c +++ b/src/lua_mobjlib.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by John "JTE" Muniz. // diff --git a/src/lua_playerlib.c b/src/lua_playerlib.c index 2eb7b4a1b..6b3c1032f 100644 --- a/src/lua_playerlib.c +++ b/src/lua_playerlib.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by John "JTE" Muniz. // @@ -226,6 +226,10 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->oldposition); else if (fastcmp(field,"positiondelay")) lua_pushinteger(L, plr->positiondelay); + else if (fastcmp(field,"teamposition")) + lua_pushinteger(L, plr->teamposition); + else if (fastcmp(field,"teamimportance")) + lua_pushinteger(L, plr->teamimportance); else if (fastcmp(field,"distancetofinish")) lua_pushinteger(L, plr->distancetofinish); else if (fastcmp(field,"distancetofinishprev")) @@ -256,12 +260,22 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->tumbleBounces); else if (fastcmp(field,"tumbleheight")) lua_pushinteger(L, plr->tumbleHeight); + else if (fastcmp(field,"stunned")) + lua_pushinteger(L, plr->stunned); + else if (fastcmp(field,"stunnedcombo")) + lua_pushinteger(L, plr->stunnedCombo); else if (fastcmp(field,"justdi")) lua_pushinteger(L, plr->justDI); else if (fastcmp(field,"flipdi")) lua_pushboolean(L, plr->flipDI); + else if (fastcmp(field,"cangrabitems")) + lua_pushinteger(L, plr->cangrabitems); else if (fastcmp(field,"analoginput")) lua_pushboolean(L, plr->analoginput); + else if (fastcmp(field,"transfer")) + lua_pushboolean(L, plr->transfer); + else if (fastcmp(field,"transfersound")) + lua_pushboolean(L, plr->transfersound); else if (fastcmp(field,"markedfordeath")) lua_pushboolean(L, plr->markedfordeath); else if (fastcmp(field,"incontrol")) @@ -280,6 +294,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->ringboxdelay); else if (fastcmp(field,"ringboxaward")) lua_pushinteger(L, plr->ringboxaward); + else if (fastcmp(field,"lastringboost")) + lua_pushinteger(L, plr->lastringboost); else if (fastcmp(field,"amps")) lua_pushinteger(L, plr->amps); else if (fastcmp(field,"amppickup")) @@ -302,7 +318,7 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->gateSound); else if (fastcmp(field,"startboost")) lua_pushinteger(L, plr->startboost); - else if (fastcmp(field,"aizdriftstraft")) + else if (fastcmp(field,"aizdriftstrat")) lua_pushinteger(L, plr->aizdriftstrat); else if (fastcmp(field,"aizdriftextend")) lua_pushinteger(L, plr->aizdriftextend); @@ -362,6 +378,10 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->eggmanTransferDelay); else if (fastcmp(field,"wavedash")) lua_pushinteger(L, plr->wavedash); + else if (fastcmp(field,"wavedashleft")) + lua_pushinteger(L, plr->wavedashleft); + else if (fastcmp(field,"wavedashright")) + lua_pushinteger(L, plr->wavedashright); else if (fastcmp(field,"wavedashdelay")) lua_pushinteger(L, plr->wavedashdelay); else if (fastcmp(field,"wavedashboost")) @@ -418,6 +438,10 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->itemtype); else if (fastcmp(field,"itemamount")) lua_pushinteger(L, plr->itemamount); + else if (fastcmp(field,"backupitemtype")) + lua_pushinteger(L, plr->backupitemtype); + else if (fastcmp(field,"backupitemamount")) + lua_pushinteger(L, plr->backupitemamount); else if (fastcmp(field,"throwdir")) lua_pushinteger(L, plr->throwdir); else if (fastcmp(field,"sadtimer")) @@ -557,6 +581,14 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->followercolor); else if (fastcmp(field,"follower")) LUA_PushUserdata(L, plr->follower, META_MOBJ); + else if (fastcmp(field,"prefskin")) + lua_pushinteger(L, plr->prefskin); + else if (fastcmp(field,"prefcolor")) + lua_pushinteger(L, plr->prefcolor); + else if (fastcmp(field,"preffollower")) + lua_pushinteger(L, plr->preffollower); + else if (fastcmp(field,"preffollowercolor")) + lua_pushinteger(L, plr->preffollowercolor); // // rideroids @@ -671,8 +703,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->laps); else if (fastcmp(field,"latestlap")) lua_pushinteger(L, plr->latestlap); - else if (fastcmp(field,"ctfteam")) - lua_pushinteger(L, plr->ctfteam); + else if (fastcmp(field,"team")) + lua_pushinteger(L, plr->team); else if (fastcmp(field,"checkskip")) lua_pushinteger(L, plr->checkskip); else if (fastcmp(field,"cheatchecknum")) @@ -710,6 +742,8 @@ static int player_get(lua_State *L) lua_pushinteger(L, plr->griefWarned); else if (fastcmp(field,"splitscreenindex")) lua_pushinteger(L, plr->splitscreenindex); + else if (fastcmp(field,"whip")) + LUA_PushUserdata(L, plr->whip, META_MOBJ); #ifdef HWRENDER else if (fastcmp(field,"fovadd")) lua_pushfixed(L, plr->fovadd); @@ -818,6 +852,10 @@ static int player_set(lua_State *L) plr->oldposition = luaL_checkinteger(L, 3); else if (fastcmp(field,"positiondelay")) plr->positiondelay = luaL_checkinteger(L, 3); + else if (fastcmp(field,"teamposition")) + plr->teamposition = luaL_checkinteger(L, 3); + else if (fastcmp(field,"teamimportance")) + plr->teamimportance = luaL_checkinteger(L, 3); else if (fastcmp(field,"distancetofinish")) return NOSET; else if (fastcmp(field,"distancetofinishprev")) @@ -846,10 +884,16 @@ static int player_set(lua_State *L) plr->tumbleBounces = luaL_checkinteger(L, 3); else if (fastcmp(field,"tumbleheight")) plr->tumbleHeight = luaL_checkinteger(L, 3); + else if (fastcmp(field,"stunned")) + plr->stunned = luaL_checkinteger(L, 3); + else if (fastcmp(field,"stunnedcombo")) + plr->stunnedCombo = luaL_checkinteger(L, 3); else if (fastcmp(field,"justdi")) plr->justDI = luaL_checkinteger(L, 3); else if (fastcmp(field,"flipdi")) plr->flipDI = luaL_checkboolean(L, 3); + else if (fastcmp(field,"cangrabitems")) + plr->cangrabitems = luaL_checkinteger(L, 3); else if (fastcmp(field,"incontrol")) plr->incontrol = luaL_checkinteger(L, 3); else if (fastcmp(field,"progressivethrust")) @@ -857,7 +901,11 @@ static int player_set(lua_State *L) else if (fastcmp(field,"ringvisualwarning")) plr->ringvisualwarning = luaL_checkboolean(L, 3); else if (fastcmp(field,"analoginput")) - plr->markedfordeath = luaL_checkboolean(L, 3); + plr->analoginput = luaL_checkboolean(L, 3); + else if (fastcmp(field,"transfer")) + plr->transfer = luaL_checkboolean(L, 3); + else if (fastcmp(field,"transfersound")) + plr->transfersound = luaL_checkboolean(L, 3); else if (fastcmp(field,"markedfordeath")) plr->markedfordeath = luaL_checkboolean(L, 3); else if (fastcmp(field,"dotrickfx")) @@ -870,6 +918,8 @@ static int player_set(lua_State *L) plr->ringboxdelay = luaL_checkinteger(L, 3); else if (fastcmp(field,"ringboxaward")) plr->ringboxaward = luaL_checkinteger(L, 3); + else if (fastcmp(field,"lastringboost")) + plr->lastringboost = luaL_checkinteger(L, 3); else if (fastcmp(field,"amps")) plr->amps = luaL_checkinteger(L, 3); else if (fastcmp(field,"amppickup")) @@ -892,7 +942,7 @@ static int player_set(lua_State *L) plr->gateSound = luaL_checkinteger(L, 3); else if (fastcmp(field,"startboost")) plr->startboost = luaL_checkinteger(L, 3); - else if (fastcmp(field,"aizdriftstraft")) + else if (fastcmp(field,"aizdriftstrat")) plr->aizdriftstrat = luaL_checkinteger(L, 3); else if (fastcmp(field,"aizdrifttilt")) plr->aizdrifttilt = luaL_checkinteger(L, 3); @@ -950,6 +1000,10 @@ static int player_set(lua_State *L) plr->eggmanTransferDelay = luaL_checkinteger(L, 3); else if (fastcmp(field,"wavedash")) plr->wavedash = luaL_checkinteger(L, 3); + else if (fastcmp(field,"wavedashleft")) + plr->wavedashleft = luaL_checkinteger(L, 3); + else if (fastcmp(field,"wavedashright")) + plr->wavedashright = luaL_checkinteger(L, 3); else if (fastcmp(field,"wavedashdelay")) plr->wavedashdelay = luaL_checkinteger(L, 3); else if (fastcmp(field,"wavedashboost")) @@ -1124,8 +1178,16 @@ static int player_set(lua_State *L) plr->followercolor = luaL_checkinteger(L, 3); else if (fastcmp(field,"followerready")) plr->followerready = luaL_checkboolean(L, 3); - else if (fastcmp(field,"follower")) // it's probably best we don't allow the follower mobj to change. - return NOSET; + else if (fastcmp(field,"follower")) + return NOSET; // it's probably best we don't allow the follower mobj to change. + else if (fastcmp(field,"prefskin")) + return NOSET; // don't allow changing user preferences + else if (fastcmp(field,"prefcolor")) + return NOSET; // don't allow changing user preferences + else if (fastcmp(field,"preffollower")) + return NOSET; // don't allow changing user preferences + else if (fastcmp(field,"preffollowercolor")) + return NOSET; // don't allow changing user preferences // time to add to the endless elseif list!!!! // rideroids @@ -1246,8 +1308,8 @@ static int player_set(lua_State *L) plr->laps = (UINT8)luaL_checkinteger(L, 3); else if (fastcmp(field,"latestlap")) plr->latestlap = (UINT8)luaL_checkinteger(L, 3); - else if (fastcmp(field,"ctfteam")) - plr->ctfteam = (INT32)luaL_checkinteger(L, 3); + else if (fastcmp(field,"team")) + G_AssignTeam(plr, (UINT8)luaL_checkinteger(L, 3)); else if (fastcmp(field,"checkskip")) plr->checkskip = (INT32)luaL_checkinteger(L, 3); else if (fastcmp(field,"cheatchecknum")) @@ -1293,6 +1355,13 @@ static int player_set(lua_State *L) plr->griefWarned = luaL_checkinteger(L, 3); else if (fastcmp(field,"splitscreenindex")) return NOSET; + else if (fastcmp(field,"whip")) + { + mobj_t *mo = NULL; + if (!lua_isnil(L, 3)) + mo = *((mobj_t **)luaL_checkudata(L, 3, META_MOBJ)); + P_SetTarget(&plr->whip, mo); + } #ifdef HWRENDER else if (fastcmp(field,"fovadd")) plr->fovadd = luaL_checkfixed(L, 3); diff --git a/src/lua_polyobjlib.c b/src/lua_polyobjlib.c index ad40b5bcd..31ed678e6 100644 --- a/src/lua_polyobjlib.c +++ b/src/lua_polyobjlib.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Iestyn "Monster Iestyn" Jealous. // Copyright (C) 2020 by Sonic Team Junior. // diff --git a/src/lua_profile.cpp b/src/lua_profile.cpp index ff28e294e..0cbd704fc 100644 --- a/src/lua_profile.cpp +++ b/src/lua_profile.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -10,10 +10,8 @@ #include #include -#include #include #include -#include #include @@ -21,6 +19,9 @@ extern "C" { #include "blua/lua.h" }; +#include "core/hash_map.hpp" +#include "core/string.h" +#include "core/vector.hpp" #include "v_draw.hpp" #include "command.h" @@ -58,7 +59,7 @@ struct lua_timer_t namespace { -std::unordered_map g_tic_timers; +srb2::HashMap g_tic_timers; }; // namespace @@ -156,7 +157,7 @@ void LUA_RenderTimers(void) return; } - std::vector view; + srb2::Vector view; view.reserve(g_tic_timers.size()); auto color_flag = [](double t) diff --git a/src/lua_profile.h b/src/lua_profile.h index 20c0bd7f2..e9ef5a151 100644 --- a/src/lua_profile.h +++ b/src/lua_profile.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/lua_script.c b/src/lua_script.c index 3a4f64f4a..1f01010cb 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by John "JTE" Muniz. // @@ -197,12 +197,6 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"paused")) { lua_pushboolean(L, paused); return 1; - } else if (fastcmp(word,"bluescore")) { - lua_pushinteger(L, bluescore); - return 1; - } else if (fastcmp(word,"redscore")) { - lua_pushinteger(L, redscore); - return 1; } else if (fastcmp(word,"timelimit")) { lua_pushinteger(L, timelimitintics); return 1; @@ -226,20 +220,6 @@ int LUA_PushGlobals(lua_State *L, const char *word) lua_pushstring(L, tutorialchallengemap); return 1; // end map vars - // begin CTF colors - } else if (fastcmp(word,"skincolor_redteam")) { - lua_pushinteger(L, skincolor_redteam); - return 1; - } else if (fastcmp(word,"skincolor_blueteam")) { - lua_pushinteger(L, skincolor_blueteam); - return 1; - } else if (fastcmp(word,"skincolor_redring")) { - lua_pushinteger(L, skincolor_redring); - return 1; - } else if (fastcmp(word,"skincolor_bluering")) { - lua_pushinteger(L, skincolor_bluering); - return 1; - // end CTF colors // begin timers } else if (fastcmp(word,"invulntics")) { lua_pushinteger(L, invulntics); @@ -351,6 +331,9 @@ int LUA_PushGlobals(lua_State *L, const char *word) } else if (fastcmp(word,"franticitems")) { lua_pushboolean(L, franticitems); return 1; + } else if (fastcmp(word,"teamplay")) { + lua_pushboolean(L, g_teamplay); + return 1; } else if (fastcmp(word,"wantedcalcdelay")) { lua_pushinteger(L, wantedcalcdelay); return 1; @@ -392,19 +375,7 @@ int LUA_PushGlobals(lua_State *L, const char *word) // See the above. int LUA_WriteGlobals(lua_State *L, const char *word) { - if (fastcmp(word, "redscore")) - redscore = (UINT32)luaL_checkinteger(L, 2); - else if (fastcmp(word, "bluescore")) - bluescore = (UINT32)luaL_checkinteger(L, 2); - else if (fastcmp(word, "skincolor_redteam")) - skincolor_redteam = (UINT16)luaL_checkinteger(L, 2); - else if (fastcmp(word, "skincolor_blueteam")) - skincolor_blueteam = (UINT16)luaL_checkinteger(L, 2); - else if (fastcmp(word, "skincolor_redring")) - skincolor_redring = (UINT16)luaL_checkinteger(L, 2); - else if (fastcmp(word, "skincolor_bluering")) - skincolor_bluering = (UINT16)luaL_checkinteger(L, 2); - else if (fastcmp(word, "gravity")) + if (fastcmp(word, "gravity")) gravity = (fixed_t)luaL_checkinteger(L, 2); else if (fastcmp(word, "stoppedclock")) stoppedclock = luaL_checkboolean(L, 2); diff --git a/src/lua_script.h b/src/lua_script.h index 33c9bfca5..28f0fecad 100644 --- a/src/lua_script.h +++ b/src/lua_script.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2022 by Sonic Team Junior. // Copyright (C) 2016 by John "JTE" Muniz. // diff --git a/src/lua_skinlib.c b/src/lua_skinlib.c index f02a07fab..534249ad9 100644 --- a/src/lua_skinlib.c +++ b/src/lua_skinlib.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by John "JTE" Muniz. // diff --git a/src/lua_taglib.c b/src/lua_taglib.c index 79ce038f4..f9a6cdf0e 100644 --- a/src/lua_taglib.c +++ b/src/lua_taglib.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by James Robert Roman. // Copyright (C) 2020 by Sonic Team Junior. // diff --git a/src/lua_thinkerlib.c b/src/lua_thinkerlib.c index 351e7c663..919f2f54d 100644 --- a/src/lua_thinkerlib.c +++ b/src/lua_thinkerlib.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by John "JTE" Muniz. // diff --git a/src/m_aatree.c b/src/m_aatree.c index 88f77d6e8..8c0f62c41 100644 --- a/src/m_aatree.c +++ b/src/m_aatree.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/m_aatree.h b/src/m_aatree.h index 942771bb2..821e983d4 100644 --- a/src/m_aatree.h +++ b/src/m_aatree.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/m_anigif.c b/src/m_anigif.c index 4a39a820a..4e8428b50 100644 --- a/src/m_anigif.c +++ b/src/m_anigif.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by Kay "Kaito" Sinclaire. // Copyright (C) 2013 by "Ninji". diff --git a/src/m_anigif.h b/src/m_anigif.h index 3c422658d..13dc16726 100644 --- a/src/m_anigif.h +++ b/src/m_anigif.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by Kay "Kaito" Sinclaire // diff --git a/src/m_argv.c b/src/m_argv.c index 544a9bcaa..a5f465102 100644 --- a/src/m_argv.c +++ b/src/m_argv.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/m_argv.h b/src/m_argv.h index d6654312a..bd5ec34fb 100644 --- a/src/m_argv.h +++ b/src/m_argv.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/m_avrecorder.cpp b/src/m_avrecorder.cpp index f4c916c82..efd874b4f 100644 --- a/src/m_avrecorder.cpp +++ b/src/m_avrecorder.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/m_avrecorder.h b/src/m_avrecorder.h index b33ea2cc6..01bf8ed1a 100644 --- a/src/m_avrecorder.h +++ b/src/m_avrecorder.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/m_avrecorder.hpp b/src/m_avrecorder.hpp index e98c8e98a..2acc9605c 100644 --- a/src/m_avrecorder.hpp +++ b/src/m_avrecorder.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/m_bbox.c b/src/m_bbox.c index e681c0b7f..6e7956c5d 100644 --- a/src/m_bbox.c +++ b/src/m_bbox.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/m_bbox.h b/src/m_bbox.h index bad97866a..d044a9ab5 100644 --- a/src/m_bbox.h +++ b/src/m_bbox.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/m_cheat.c b/src/m_cheat.c index 0635b1f6f..48b51f412 100644 --- a/src/m_cheat.c +++ b/src/m_cheat.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -290,6 +290,8 @@ struct debugFlagNames_s const debug_flag_names[] = {"PowerLevel", DBG_PWRLV}, // alt name {"Demo", DBG_DEMO}, {"Replay", DBG_DEMO}, // alt name + {"Teams", DBG_TEAMS}, + {"Teamplay", DBG_TEAMS}, // alt name {NULL, 0} }; @@ -370,7 +372,7 @@ void Command_Setlives_f(void) D_Cheat(consoleplayer, CHEAT_LIVES, atoi(COM_Argv(1))); } -void Command_Setscore_f(void) +void Command_Setroundscore_f(void) { REQUIRE_CHEATS; REQUIRE_INLEVEL; diff --git a/src/m_cheat.h b/src/m_cheat.h index 70207b8e5..8618fc1b1 100644 --- a/src/m_cheat.h +++ b/src/m_cheat.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -75,7 +75,7 @@ void Command_Savecheckpoint_f(void); void Command_Setrings_f(void); void Command_Setspheres_f(void); void Command_Setlives_f(void); -void Command_Setscore_f(void); +void Command_Setroundscore_f(void); void Command_Devmode_f(void); void Command_Scale_f(void); void Command_Gravflip_f(void); diff --git a/src/m_cond.c b/src/m_cond.c index 8a27efaa4..5659205ee 100644 --- a/src/m_cond.c +++ b/src/m_cond.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by Kay "Kaito" Sinclaire. // @@ -743,8 +743,7 @@ void M_ClearSecrets(void) continue; mapheaderinfo[i]->records.mapvisited = 0; - - mapheaderinfo[i]->cache_spraycan = UINT16_MAX; + mapheaderinfo[i]->records.spraycan = MCAN_INVALID; mapheaderinfo[i]->cache_maplock = MAXUNLOCKABLES; @@ -814,6 +813,30 @@ static void M_AssignSpraycans(void) conditionset_t *c; condition_t *cn; + UINT16 bonustocanmap = 0; + + // First, turn outstanding bonuses into existing uncollected Spray Cans. + while (gamedata->gotspraycans < gamedata->numspraycans) + { + while (bonustocanmap < basenummapheaders) + { + if (mapheaderinfo[bonustocanmap]->records.spraycan != MCAN_BONUS) + { + bonustocanmap++; + continue; + } + + break; + } + + if (bonustocanmap == basenummapheaders) + break; + + mapheaderinfo[bonustocanmap]->records.spraycan = gamedata->gotspraycans; + gamedata->spraycans[gamedata->gotspraycans].map = bonustocanmap; + gamedata->gotspraycans++; + } + const UINT16 prependoffset = MAXSKINCOLORS-1; // None of the following accounts for cans being removed, only added... @@ -829,7 +852,7 @@ static void M_AssignSpraycans(void) if (cn->type != UC_SPRAYCAN) continue; - // G_LoadGamedata, G_SaveGameData doesn't support custom skincolors right now. + // This will likely never support custom skincolors. if (cn->requirement >= SKINCOLOR_FIRSTFREESLOT) //numskincolors) continue; @@ -891,7 +914,24 @@ static void M_AssignSpraycans(void) for (i = 0; i < listlen; i++) { - gamedata->spraycans[gamedata->numspraycans].map = NEXTMAP_INVALID; + // Convert bonus pickups into Spray Cans if new ones have been added. + while (bonustocanmap < basenummapheaders) + { + if (mapheaderinfo[bonustocanmap]->records.spraycan != MCAN_BONUS) + { + bonustocanmap++; + continue; + } + + gamedata->gotspraycans++; + mapheaderinfo[bonustocanmap]->records.spraycan = gamedata->numspraycans; + break; + } + gamedata->spraycans[gamedata->numspraycans].map = ( + (bonustocanmap == basenummapheaders) + ? NEXTMAP_INVALID + : bonustocanmap + ); gamedata->spraycans[gamedata->numspraycans].col = tempcanlist[i]; skincolors[tempcanlist[i]].cache_spraycan = gamedata->numspraycans; @@ -1817,8 +1857,8 @@ boolean M_CheckCondition(condition_t *cn, player_t *player) && M_NotFreePlay() && (gamespeed != KARTSPEED_EASY) && (player->tally.active == true) - && (player->tally.totalLaps > 0) // Only true if not Time Attack - && (player->tally.laps >= player->tally.totalLaps)); + && (player->tally.totalExp > 0) // Only true if not Time Attack + && (player->tally.exp >= player->tally.totalExp)); case UCRP_FINISHALLPRISONS: return (battleprisons && !(player->pflags & PF_NOCONTEST) @@ -2442,10 +2482,12 @@ static const char *M_GetConditionString(condition_t *cn) case UC_EMBLEM: // Requires emblem x to be obtained { - INT32 checkLevel; + INT32 checkLevel = NEXTMAP_INVALID; i = cn->requirement-1; - checkLevel = M_EmblemMapNum(&emblemlocations[i]); + + if (i >= 0 && i < numemblems) + checkLevel = M_EmblemMapNum(&emblemlocations[i]); if (checkLevel >= nummapheaders || !mapheaderinfo[checkLevel] || emblemlocations[i].type == ET_NONE) return va("INVALID MEDAL MAP \"%d:%d\"", cn->requirement, checkLevel); @@ -3869,7 +3911,7 @@ UINT16 M_UnlockableMapNum(unlockable_t *unlock) UINT16 M_EmblemMapNum(emblem_t *emblem) { - if (emblem->levelCache == NEXTMAP_INVALID) + if (emblem->levelCache == NEXTMAP_INVALID && emblem->level) { UINT16 result = G_MapNumber(emblem->level); @@ -3877,6 +3919,8 @@ UINT16 M_EmblemMapNum(emblem_t *emblem) return result; emblem->levelCache = result; + Z_Free(emblem->level); + emblem->level = NULL; } return emblem->levelCache; diff --git a/src/m_cond.h b/src/m_cond.h index eccd88f5f..10b9e4883 100644 --- a/src/m_cond.h +++ b/src/m_cond.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by Kay "Kaito" Sinclaire. // diff --git a/src/m_dllist.h b/src/m_dllist.h index 6ae18a698..6a0a60bcd 100644 --- a/src/m_dllist.h +++ b/src/m_dllist.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2005 by James Haley. // diff --git a/src/m_easing.c b/src/m_easing.c index 9237ed8d1..ef48abe51 100644 --- a/src/m_easing.c +++ b/src/m_easing.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2021 by Jaime "Lactozilla" Passos. // Copyright (C) 2021 by Sonic Team Junior. // diff --git a/src/m_easing.h b/src/m_easing.h index 85d07de63..3685ed192 100644 --- a/src/m_easing.h +++ b/src/m_easing.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2021 by Sonic Team Junior. // Copyright (C) 2021 by Jaime "Lactozilla" Passos. // diff --git a/src/m_fixed.c b/src/m_fixed.c index 9c37b485a..e30f50115 100644 --- a/src/m_fixed.c +++ b/src/m_fixed.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/m_fixed.h b/src/m_fixed.h index a62dc14b0..13c46ac77 100644 --- a/src/m_fixed.h +++ b/src/m_fixed.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/m_memcpy.c b/src/m_memcpy.c index 67ef3541b..0f3bc237f 100644 --- a/src/m_memcpy.c +++ b/src/m_memcpy.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/m_misc.cpp b/src/m_misc.cpp index 9b488b93c..b7e0df826 100644 --- a/src/m_misc.cpp +++ b/src/m_misc.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -341,6 +341,12 @@ boolean FIL_ConvertTextFileToBinary(const char *textfilename, const char *binfil return success; } +boolean FIL_RenameFile(char const *old_name, char const *new_name) +{ + int result = rename(old_name, new_name); + return (result == 0); +} + /** Check if the filename exists * * \param name Filename to check. @@ -589,7 +595,7 @@ void Command_LoadConfig_f(void) CV_InitFilterVar(); // exec the config - COM_BufInsertText(va("exec \"%s\"\n", configfile)); + COM_BufInsertText(va("exec \"%s\" -immediate\n", configfile)); // don't filter anymore vars and don't let this convsvar be changed COM_BufInsertText(va("%s \"%d\"\n", cv_execversion.name, EXECVERSION)); @@ -650,7 +656,7 @@ void M_FirstLoadConfig(void) CV_InitFilterVar(); // load config, make sure those commands doesnt require the screen... - COM_BufInsertText(va("exec \"%s\"\n", configfile)); + COM_BufInsertText(va("exec \"%s\" -immediate\n", configfile)); // no COM_BufExecute() needed; that does it right away // don't filter anymore vars and don't let this convsvar be changed @@ -1456,7 +1462,7 @@ void M_LegacySaveFrame(void) #endif M_PNGFrame(apng_ptr, apng_info_ptr, (png_bytep)linear); #ifdef HWRENDER - if (rendermode != render_soft && linear) + if (rendermode == render_opengl && linear) free(linear); #endif diff --git a/src/m_misc.h b/src/m_misc.h index 4ef377074..9d54e4e68 100644 --- a/src/m_misc.h +++ b/src/m_misc.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -90,6 +90,8 @@ size_t FIL_ReadFileTag(char const *name, UINT8 **buffer, INT32 tag); boolean FIL_ConvertTextFileToBinary(const char *textfilename, const char *binfilename); +boolean FIL_RenameFile(const char *old_name, const char *new_name); + boolean FIL_FileExists(const char *name); boolean FIL_WriteFileOK(char const *name); boolean FIL_ReadFileOK(char const *name); diff --git a/src/m_perfstats.c b/src/m_perfstats.c index 2ef4480bc..4f3b11a0e 100644 --- a/src/m_perfstats.c +++ b/src/m_perfstats.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/m_perfstats.h b/src/m_perfstats.h index e38a6c19b..6bc1c1d21 100644 --- a/src/m_perfstats.h +++ b/src/m_perfstats.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/m_pw.cpp b/src/m_pw.cpp index 192a7947e..0808ff28c 100644 --- a/src/m_pw.cpp +++ b/src/m_pw.cpp @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,13 +14,13 @@ #include #include #include -#include #include #include -#include #include "modp_b64/modp_b64.h" +#include "core/string.h" +#include "core/vector.hpp" #include "cxxutil.hpp" #include "command.h" @@ -43,12 +43,13 @@ namespace constexpr const UINT8 kRRSalt[17] = "0L4rlK}{9ay6'VJS"; -std::array decode_hash(std::string encoded) +std::array decode_hash(srb2::String encoded) { std::array decoded; - if (modp::b64_decode(encoded).size() != decoded.size()) + std::string encoded_stl { static_cast(encoded) }; + if (modp::b64_decode(encoded_stl).size() != decoded.size()) throw std::invalid_argument("hash is incorrectly sized"); - std::copy(encoded.begin(), encoded.end(), decoded.begin()); + std::copy(encoded_stl.begin(), encoded_stl.end(), decoded.begin()); return decoded; } @@ -60,7 +61,7 @@ struct Pw const std::array hash_; }; -std::vector passwords; +srb2::Vector passwords; // m_cond.c template @@ -629,8 +630,8 @@ try_password_e M_TryPassword(const char *password, boolean conditions) using var = std::variant; // Normalize input casing - std::string key = password; - strlwr(key.data()); + srb2::String key = password; + strlwr((char*)key.data()); UINT8 key_hash[M_PW_HASH_SIZE]; M_HashPassword(key_hash, key.c_str(), kRRSalt); @@ -684,8 +685,8 @@ try_password_e M_TryPassword(const char *password, boolean conditions) boolean M_TryExactPassword(const char *password, const char *encodedhash) { // Normalize input casing - std::string key = password; - strlwr(key.data()); + srb2::String key = password; + strlwr((char*)key.data()); UINT8 key_hash[M_PW_HASH_SIZE]; M_HashPassword(key_hash, key.c_str(), kRRSalt); diff --git a/src/m_pw.h b/src/m_pw.h index 87efd98aa..05ed304db 100644 --- a/src/m_pw.h +++ b/src/m_pw.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/m_pw_hash.c b/src/m_pw_hash.c index ed9ca881b..c43397ff5 100644 --- a/src/m_pw_hash.c +++ b/src/m_pw_hash.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. -// Copyright (C) 2024 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/m_pw_hash.h b/src/m_pw_hash.h index 31bc81357..b89fe2873 100644 --- a/src/m_pw_hash.h +++ b/src/m_pw_hash.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/m_queue.c b/src/m_queue.c index dc9a4044d..183ab7599 100644 --- a/src/m_queue.c +++ b/src/m_queue.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2003 by James Haley. // diff --git a/src/m_queue.h b/src/m_queue.h index 595d503dd..bb7889f21 100644 --- a/src/m_queue.h +++ b/src/m_queue.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Kart Krew // Copyright (C) 2020 by Sonic Team Junior // Copyright (C) 2003 by James Haley // diff --git a/src/m_random.c b/src/m_random.c index 36d8582ab..8737c7cdc 100644 --- a/src/m_random.c +++ b/src/m_random.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by Kay "Kaito" Sinclaire. // Copyright (C) 2000 by DooM Legacy Team. @@ -145,7 +145,7 @@ ATTRINLINE static UINT32 FUNCINLINE __internal_prng__(pr_class_t pr_class) /** Provides a random number within a specified range. * - * \return A random, uniformly distributed number from [0,bound]. + * \return A random, uniformly distributed integer from [0,bound). */ ATTRINLINE static UINT32 FUNCINLINE __internal_prng_bound__(pr_class_t pr_class, UINT32 bound) { diff --git a/src/m_random.h b/src/m_random.h index 6f57efd8a..edc59ee66 100644 --- a/src/m_random.h +++ b/src/m_random.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2016 by Kay "Kaito" Sinclaire. // Copyright (C) 2000 by DooM Legacy Team. @@ -90,6 +90,7 @@ typedef enum PROLDDEMO, // The number of RNG classes in versions that didn't write down how many RNG classes they had in their replays. PR_ITEM_SPAWNER = PROLDDEMO, // Battle mode item spawners + PR_TEAMS, // Teamplay shuffling PRNUMSYNCED, diff --git a/src/m_swap.h b/src/m_swap.h index 23f886401..9e20408ef 100644 --- a/src/m_swap.h +++ b/src/m_swap.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -35,18 +35,39 @@ extern "C" { | \ (((UINT32)(x) & (UINT32)0xff000000UL) >> 24))) +#define SWAP_LONGLONG(x) ((INT64)(\ +(((UINT64)(x) & (UINT64)0x00000000000000ffULL) << 56) \ +| \ +(((UINT64)(x) & (UINT64)0x000000000000ff00ULL) << 40) \ +| \ +(((UINT64)(x) & (UINT64)0x0000000000ff0000ULL) << 24) \ +| \ +(((UINT64)(x) & (UINT64)0x00000000ff000000ULL) << 8) \ +| \ +(((UINT64)(x) & (UINT64)0x000000ff00000000ULL) >> 8) \ +| \ +(((UINT64)(x) & (UINT64)0x0000ff0000000000ULL) >> 24) \ +| \ +(((UINT64)(x) & (UINT64)0x00ff000000000000ULL) >> 40) \ +| \ +(((UINT64)(x) & (UINT64)0xff00000000000000ULL) >> 56))) + // Endianess handling. // WAD files are stored little endian. #ifdef SRB2_BIG_ENDIAN #define SHORT SWAP_SHORT #define LONG SWAP_LONG +#define LONGLON SWAP_LONGLONG #define MSBF_SHORT(x) ((INT16)(x)) #define MSBF_LONG(x) ((INT32)(x)) +#define MSBF_LONGLONG(x) ((INT64)(x)) #else #define SHORT(x) ((INT16)(x)) #define LONG(x) ((INT32)(x)) +#define LONGLONG(x) ((INT64)(x)) #define MSBF_SHORT SWAP_SHORT #define MSBF_LONG SWAP_LONG +#define MSBF_LONGLONG SWAP_LONGLONG #endif // Big to little endian diff --git a/src/math/fixed.hpp b/src/math/fixed.hpp index 01a6b8756..d92071bf0 100644 --- a/src/math/fixed.hpp +++ b/src/math/fixed.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/math/line_equation.hpp b/src/math/line_equation.hpp index beedd2cec..19523f106 100644 --- a/src/math/line_equation.hpp +++ b/src/math/line_equation.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/math/line_segment.hpp b/src/math/line_segment.hpp index 9e63902e8..4d0713ac3 100644 --- a/src/math/line_segment.hpp +++ b/src/math/line_segment.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/math/traits.hpp b/src/math/traits.hpp index 43ec8905d..cbbb7b406 100644 --- a/src/math/traits.hpp +++ b/src/math/traits.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/math/vec.hpp b/src/math/vec.hpp index 73023d484..c6513f402 100644 --- a/src/math/vec.hpp +++ b/src/math/vec.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/audio_encoder.hpp b/src/media/audio_encoder.hpp index bd5e68de6..e6aa583e1 100644 --- a/src/media/audio_encoder.hpp +++ b/src/media/audio_encoder.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/avrecorder.cpp b/src/media/avrecorder.cpp index cfbd8eab1..be28b2175 100644 --- a/src/media/avrecorder.cpp +++ b/src/media/avrecorder.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/avrecorder.hpp b/src/media/avrecorder.hpp index e2560179d..bcf05ad7d 100644 --- a/src/media/avrecorder.hpp +++ b/src/media/avrecorder.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -17,11 +17,11 @@ #include #include #include -#include -#include #include +#include "../core/string.h" +#include "../core/vector.hpp" #include "../audio/sample.hpp" namespace srb2::media @@ -49,7 +49,7 @@ public: int frame_rate; }; - std::string file_name; + srb2::String file_name; std::optional max_size; // file size limit std::optional> max_duration; @@ -63,7 +63,7 @@ public: { using instance_t = std::unique_ptr; - std::vector screen; + srb2::Vector screen; uint32_t width, height; int pts; diff --git a/src/media/avrecorder_feedback.cpp b/src/media/avrecorder_feedback.cpp index d1a94d696..bf0d6c4b5 100644 --- a/src/media/avrecorder_feedback.cpp +++ b/src/media/avrecorder_feedback.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -10,10 +10,10 @@ #include #include -#include #include +#include "../core/string.h" #include "../cxxutil.hpp" #include "avrecorder_impl.hpp" @@ -39,7 +39,7 @@ void Impl::container_dtor_handler(const MediaContainer& container) const if (max_size_ && container.size() > *max_size_) { - const std::string line = fmt::format( + const srb2::String line = srb2::format( "Video size has exceeded limit {} > {} ({}%)." " This should not happen, please report this bug.\n", container.size(), @@ -100,7 +100,7 @@ void AVRecorder::draw_statistics() const { SRB2_ASSERT(impl_->video_encoder_ != nullptr); - auto draw = [](int x, std::string text, int32_t flags = 0) + auto draw = [](int x, const srb2::String text, int32_t flags = 0) { V_DrawThinString( x, @@ -144,7 +144,7 @@ void AVRecorder::draw_statistics() const return 0; }(); - draw(200, fmt::format("{:.0f}", fps), fps_color); - draw(230, fmt::format("{:.1f}s", impl_->container_->duration().count())); - draw(260, fmt::format("{:.1f} MB", size / kMb), mb_color); + draw(200, srb2::format("{:.0f}", fps), fps_color); + draw(230, srb2::format("{:.1f}s", impl_->container_->duration().count())); + draw(260, srb2::format("{:.1f} MB", size / kMb), mb_color); } diff --git a/src/media/avrecorder_impl.hpp b/src/media/avrecorder_impl.hpp index 12cdb5824..8a96503fb 100644 --- a/src/media/avrecorder_impl.hpp +++ b/src/media/avrecorder_impl.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -19,8 +19,8 @@ #include #include #include -#include +#include "../core/string.h" #include "../i_time.h" #include "avrecorder.hpp" #include "container.hpp" @@ -57,7 +57,7 @@ public: using frame_type = StagingVideoFrame::instance_t; }; - std::vector::frame_type> vec_; + srb2::Vector::frame_type> vec_; // This number only decrements once a frame has // actually been written to container. diff --git a/src/media/avrecorder_indexed.cpp b/src/media/avrecorder_indexed.cpp index 3c25a1073..0df1c2ea6 100644 --- a/src/media/avrecorder_indexed.cpp +++ b/src/media/avrecorder_indexed.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/avrecorder_queue.cpp b/src/media/avrecorder_queue.cpp index 545249635..34759a2d8 100644 --- a/src/media/avrecorder_queue.cpp +++ b/src/media/avrecorder_queue.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/cfile.cpp b/src/media/cfile.cpp index 272217e37..5837edfc4 100644 --- a/src/media/cfile.cpp +++ b/src/media/cfile.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -19,7 +19,7 @@ using namespace srb2::media; -CFile::CFile(const std::string file_name) : name_(file_name) +CFile::CFile(const srb2::String& file_name) : name_(file_name) { file_ = std::fopen(name(), "wb"); diff --git a/src/media/cfile.hpp b/src/media/cfile.hpp index 2da772c9f..3f6e73dbd 100644 --- a/src/media/cfile.hpp +++ b/src/media/cfile.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -12,7 +12,8 @@ #define __SRB2_MEDIA_CFILE_HPP__ #include -#include + +#include "../core/string.h" namespace srb2::media { @@ -20,7 +21,7 @@ namespace srb2::media class CFile { public: - CFile(const std::string file_name); + CFile(const srb2::String& file_name); ~CFile(); operator std::FILE*() const { return file_; } @@ -28,7 +29,7 @@ public: const char* name() const { return name_.c_str(); } private: - std::string name_; + srb2::String name_; std::FILE* file_; }; diff --git a/src/media/container.hpp b/src/media/container.hpp index c7a924924..836f2e47c 100644 --- a/src/media/container.hpp +++ b/src/media/container.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,8 +14,8 @@ #include #include #include -#include +#include "../core/string.h" #include "audio_encoder.hpp" #include "video_encoder.hpp" @@ -30,7 +30,7 @@ public: struct Config { - std::string file_name; + srb2::String file_name; dtor_cb_t destructor_callback; }; diff --git a/src/media/encoder.hpp b/src/media/encoder.hpp index 6d83145a8..5aed69302 100644 --- a/src/media/encoder.hpp +++ b/src/media/encoder.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/options.cpp b/src/media/options.cpp index 08d253f71..e2f7756a7 100644 --- a/src/media/options.cpp +++ b/src/media/options.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/options.hpp b/src/media/options.hpp index 7570dc694..e17d7ea44 100644 --- a/src/media/options.hpp +++ b/src/media/options.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -13,11 +13,11 @@ #include #include -#include #include -#include -#include +#include "../core/hash_map.hpp" +#include "../core/string.h" +#include "../core/vector.hpp" #include "../command.h" namespace srb2::media @@ -26,7 +26,7 @@ namespace srb2::media class Options { public: - using map_t = std::unordered_map; + using map_t = srb2::HashMap; template struct Range @@ -46,7 +46,7 @@ public: static consvar_t values(const char* default_value, const Range range, std::map list = {}); private: - static std::vector cvars_; + static srb2::Vector cvars_; const char* prefix_; map_t map_; @@ -54,6 +54,11 @@ private: const consvar_t& cvar(const char* option) const; }; +// clang-format off +extern template consvar_t Options::values(const char* default_value, const Range range, std::map list); +extern template consvar_t Options::values(const char* default_value, const Range range, std::map list); +// clang-format on + }; // namespace srb2::media #endif // __SRB2_MEDIA_OPTIONS_HPP__ diff --git a/src/media/options_values.cpp b/src/media/options_values.cpp index b27330c5a..66fb9e713 100644 --- a/src/media/options_values.cpp +++ b/src/media/options_values.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -12,6 +12,7 @@ #include +#include "../core/vector.hpp" #include "options.hpp" #include "vorbis.hpp" #include "vp8.hpp" @@ -23,7 +24,7 @@ using namespace srb2::media; // to be defined in the same translation unit as // Options::cvars_ to guarantee initialization order. -std::vector Options::cvars_; +srb2::Vector Options::cvars_; // clang-format off const Options VorbisEncoder::options_("vorbis", { diff --git a/src/media/video_encoder.hpp b/src/media/video_encoder.hpp index e8d404533..dbe79ac1b 100644 --- a/src/media/video_encoder.hpp +++ b/src/media/video_encoder.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/video_frame.hpp b/src/media/video_frame.hpp index d2d1bfbec..981a09b47 100644 --- a/src/media/video_frame.hpp +++ b/src/media/video_frame.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/vorbis.cpp b/src/media/vorbis.cpp index b535247a7..3c69b5068 100644 --- a/src/media/vorbis.cpp +++ b/src/media/vorbis.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/vorbis.hpp b/src/media/vorbis.hpp index 5a7384d79..c83382240 100644 --- a/src/media/vorbis.hpp +++ b/src/media/vorbis.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/vorbis_error.hpp b/src/media/vorbis_error.hpp index 4f089ae80..27940080c 100644 --- a/src/media/vorbis_error.hpp +++ b/src/media/vorbis_error.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -11,11 +11,11 @@ #ifndef __SRB2_MEDIA_VORBIS_ERROR_HPP__ #define __SRB2_MEDIA_VORBIS_ERROR_HPP__ -#include - #include #include +#include "../core/string.h" + class VorbisError { public: @@ -23,7 +23,7 @@ public: operator int() const { return error_; } - std::string name() const + srb2::String name() const { switch (error_) { @@ -43,12 +43,12 @@ private: }; template <> -struct fmt::formatter : formatter +struct fmt::formatter : formatter { template auto format(const VorbisError& error, FormatContext& ctx) const { - return formatter::format(error.name(), ctx); + return formatter::format(error.name(), ctx); } }; diff --git a/src/media/vp8.cpp b/src/media/vp8.cpp index b2e2a3ce7..8d57aef83 100644 --- a/src/media/vp8.cpp +++ b/src/media/vp8.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/vp8.hpp b/src/media/vp8.hpp index cf878cb3f..a6c8aad7a 100644 --- a/src/media/vp8.hpp +++ b/src/media/vp8.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/vpx_error.hpp b/src/media/vpx_error.hpp index 6ec7f5d9e..a5625f9da 100644 --- a/src/media/vpx_error.hpp +++ b/src/media/vpx_error.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -11,22 +11,22 @@ #ifndef __SRB2_MEDIA_VPX_ERROR_HPP__ #define __SRB2_MEDIA_VPX_ERROR_HPP__ -#include - #include #include +#include "../core/string.h" + class VpxError { public: VpxError(vpx_codec_ctx_t& ctx) : ctx_(&ctx) {} - std::string description() const + srb2::String description() const { const char* error = vpx_codec_error(ctx_); const char* detail = vpx_codec_error_detail(ctx_); - return detail ? fmt::format("{}: {}", error, detail) : error; + return detail ? srb2::format("{}: {}", error, detail) : error; } private: @@ -34,12 +34,12 @@ private: }; template <> -struct fmt::formatter : formatter +struct fmt::formatter : formatter { template auto format(const VpxError& error, FormatContext& ctx) const { - return formatter::format(error.description(), ctx); + return formatter::format(error.description(), ctx); } }; diff --git a/src/media/webm.hpp b/src/media/webm.hpp index 1a56f94c4..e81ddbf2c 100644 --- a/src/media/webm.hpp +++ b/src/media/webm.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/webm_container.cpp b/src/media/webm_container.cpp index 265d428f1..117a2827f 100644 --- a/src/media/webm_container.cpp +++ b/src/media/webm_container.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/webm_container.hpp b/src/media/webm_container.hpp index 4c4ff9828..ed974bb16 100644 --- a/src/media/webm_container.hpp +++ b/src/media/webm_container.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -13,11 +13,11 @@ #include #include -#include #include #include +#include "../core/hash_map.hpp" #include "container.hpp" #include "webm.hpp" #include "webm_writer.hpp" @@ -88,7 +88,7 @@ private: mutable std::recursive_mutex queue_mutex_; - std::unordered_map queue_; + srb2::HashMap queue_; webm::timestamp latest_timestamp_ = 0; std::size_t queue_size_ = 0; diff --git a/src/media/webm_encoder.hpp b/src/media/webm_encoder.hpp index 8de011811..372b40245 100644 --- a/src/media/webm_encoder.hpp +++ b/src/media/webm_encoder.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/webm_vorbis.hpp b/src/media/webm_vorbis.hpp index 86018c470..1b3577e48 100644 --- a/src/media/webm_vorbis.hpp +++ b/src/media/webm_vorbis.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/webm_vorbis_lace.cpp b/src/media/webm_vorbis_lace.cpp index cc1d26732..2c45d68ca 100644 --- a/src/media/webm_vorbis_lace.cpp +++ b/src/media/webm_vorbis_lace.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -61,7 +61,8 @@ std::vector WebmVorbisEncoder::make_vorbis_private_data() // The first byte is the number of packets. Once again, // the last packet is not counted. - v.emplace_back(std::byte {2}); + v.resize(1); + v[0] = std::byte {2}; // Then the laced sizes for each packet. lace(v, packets[0]); diff --git a/src/media/webm_vp8.hpp b/src/media/webm_vp8.hpp index 7dc3da423..700c83a72 100644 --- a/src/media/webm_vp8.hpp +++ b/src/media/webm_vp8.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/webm_writer.hpp b/src/media/webm_writer.hpp index 470bf213a..c4b2fd60f 100644 --- a/src/media/webm_writer.hpp +++ b/src/media/webm_writer.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -12,10 +12,10 @@ #define __SRB2_MEDIA_WEBM_WRITER_HPP__ #include -#include #include +#include "../core/string.h" #include "cfile.hpp" namespace srb2::media @@ -24,7 +24,7 @@ namespace srb2::media class WebmWriter : public CFile, public mkvmuxer::MkvWriter { public: - WebmWriter(const std::string file_name) : CFile(file_name), MkvWriter(static_cast(*this)) {} + WebmWriter(const srb2::String& file_name) : CFile(file_name), MkvWriter(static_cast(*this)) {} ~WebmWriter() { MkvWriter::Close(); } }; diff --git a/src/media/yuv420p.cpp b/src/media/yuv420p.cpp index 8cf01b212..d251389b8 100644 --- a/src/media/yuv420p.cpp +++ b/src/media/yuv420p.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/media/yuv420p.hpp b/src/media/yuv420p.hpp index 9e7ac942f..d430a27bd 100644 --- a/src/media/yuv420p.hpp +++ b/src/media/yuv420p.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -12,8 +12,8 @@ #define __SRB2_MEDIA_YUV420P_HPP__ #include -#include +#include "../core/vector.hpp" #include "video_frame.hpp" namespace srb2::media @@ -40,7 +40,7 @@ public: int width_ = 0; int height_ = 0; - std::vector vec_; + srb2::Vector vec_; }; YUV420pFrame(int pts, Buffer y, Buffer u, Buffer v, const BufferRGBA& rgba); diff --git a/src/menus/CMakeLists.txt b/src/menus/CMakeLists.txt index 80d3c261e..c82b41316 100644 --- a/src/menus/CMakeLists.txt +++ b/src/menus/CMakeLists.txt @@ -29,6 +29,7 @@ target_sources(SRB2SDL2 PRIVATE options-video-1.c options-video-advanced.c options-video-modes.c + options-voice.cpp play-1.c play-char-select.c play-local-1.c diff --git a/src/menus/class-egg-tv/EggTV.cpp b/src/menus/class-egg-tv/EggTV.cpp index 79ba9bbd8..1fc51337f 100644 --- a/src/menus/class-egg-tv/EggTV.cpp +++ b/src/menus/class-egg-tv/EggTV.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -11,11 +11,11 @@ #include #include #include -#include #include #include +#include "../../core/string.h" #include "../../cxxutil.hpp" #include "../../v_draw.hpp" #include "EggTV.hpp" @@ -85,11 +85,11 @@ void draw_face(const Draw& draw, const EggTVData::Replay::Standing& player, face } } -std::string player_time_string(const EggTVData::Replay::Standing& player) +srb2::String player_time_string(const EggTVData::Replay::Standing& player) { if (player.time) { - return fmt::format( + return srb2::format( R"({}'{}"{})", G_TicsToMinutes(*player.time, true), G_TicsToSeconds(*player.time), @@ -102,7 +102,7 @@ std::string player_time_string(const EggTVData::Replay::Standing& player) } } -std::string player_points_string(const EggTVData::Replay::Standing& player) +srb2::String player_points_string(const EggTVData::Replay::Standing& player) { return player.score ? fmt::format("{} PTS", *player.score) : "NO CONTEST"; } @@ -461,6 +461,8 @@ void EggTV::erase() } } + cache_ = cache_->folder().load(); + if (cache_->folder().empty()) { // Remove empty folder from list diff --git a/src/menus/class-egg-tv/EggTV.hpp b/src/menus/class-egg-tv/EggTV.hpp index 49aac3368..81f0d0906 100644 --- a/src/menus/class-egg-tv/EggTV.hpp +++ b/src/menus/class-egg-tv/EggTV.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -19,6 +19,7 @@ #include "EggTVData.hpp" #include "EggTVGraphics.hpp" +#include "../../core/vector.hpp" #include "../../doomdef.h" // TICRATE #include "../../i_time.h" #include "../../k_menu.h" @@ -197,7 +198,7 @@ private: { public: using limiter_t = std::function; - using anims_t = std::vector; + using anims_t = srb2::Vector; explicit Cursor(anims_t anims, limiter_t limiter) : limiter_(limiter), anims_(anims) {} diff --git a/src/menus/class-egg-tv/EggTVData.cpp b/src/menus/class-egg-tv/EggTVData.cpp index eb715736e..4fbe93424 100644 --- a/src/menus/class-egg-tv/EggTVData.cpp +++ b/src/menus/class-egg-tv/EggTVData.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,13 +14,14 @@ #include #include #include -#include #include #include #include // std::filesystem::path formatter -#include +#include "../../core/string.h" +#include "../../core/json.hpp" +#include "../../io/streams.hpp" #include "../../cxxutil.hpp" #include "EggTVData.hpp" @@ -34,15 +35,15 @@ using namespace srb2::menus::egg_tv; namespace fs = std::filesystem; -using nlohmann::json; +using srb2::JsonValue; template <> -struct fmt::formatter : formatter +struct fmt::formatter : formatter { template auto format(const fs::filesystem_error& ex, FormatContext& ctx) const { - return formatter::format( + return formatter::format( fmt::format("{}, path1={}, path2={}", ex.what(), ex.path1(), ex.path2()), ctx ); @@ -65,13 +66,13 @@ To time_point_conv(From time) return std::chrono::time_point_cast(To::clock::now() + (time - From::clock::now())); } -json& ensure_array(json& object, const char* key) +JsonValue& ensure_array(JsonValue& object, const char* key) { - json& array = object[key]; + JsonValue& array = object[key]; if (!array.is_array()) { - array = json::array(); + array = JsonValue::array(); } return array; @@ -91,18 +92,16 @@ EggTVData::EggTVData() : favorites_(ensure_array(favoritesFile_, "favorites")) } } -json EggTVData::cache_favorites() const +JsonValue EggTVData::cache_favorites() const { - json object; + JsonValue object; try { - std::ifstream f(favoritesPath_); - - if (f.is_open()) - { - f >> object; - } + srb2::io::FileStream stream { favoritesPath_.generic_string() }; + srb2::Vector f = srb2::io::read_to_vec(stream); + srb2::String json_string { (const char*)f.data(), f.size() }; + object = JsonValue::from_json_string(json_string); } catch (const std::exception& ex) { @@ -199,9 +198,9 @@ EggTVData::Folder::Folder(EggTVData& tv, const fs::directory_entry& entry) : } } -EggTVData::Replay::Title::operator const std::string() const +EggTVData::Replay::Title::operator const srb2::String() const { - return second().empty() ? first() : fmt::format("{} - {}", first(), second()); + return second().empty() ? first() : srb2::format("{} - {}", first(), second()); } EggTVData::Replay::Replay(Folder::Cache::ReplayRef& ref) : ref_(&ref) @@ -230,7 +229,7 @@ EggTVData::Replay::Replay(Folder::Cache::ReplayRef& ref) : ref_(&ref) const std::string_view str = info.title; const std::size_t mid = str.find(kDelimiter); - title_ = Title(str.substr(0, mid), mid == std::string::npos ? "" : str.substr(mid + kDelimiter.size())); + title_ = Title(str.substr(0, mid), mid == srb2::String::npos ? "" : str.substr(mid + kDelimiter.size())); //title_ = Title("WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW", "WWWWWWWWWWWWWWWWWWWWWWWWWWW"); } @@ -316,13 +315,13 @@ void EggTVData::Replay::toggle_favorite() const { const auto& it = ref_->iterator_to_favorite(); - if (it != ref_->favorites().end()) + if (it != ref_->favorites().as_array().end()) { - ref_->favorites().erase(it); + ref_->favorites().as_array().erase(it); } else { - ref_->favorites().emplace_back(ref_->favorites_path()); + ref_->favorites().as_array().emplace_back(ref_->favorites_path()); } ref_->cache().folder().tv().save_favorites(); @@ -382,7 +381,9 @@ void EggTVData::save_favorites() const { try { - std::ofstream(favoritesPath_) << favoritesFile_; + srb2::String json_string = favoritesFile_.to_json_string(); + srb2::io::FileStream fs { favoritesPath_.generic_string(), srb2::io::FileStreamMode::kWrite }; + srb2::io::write_exact(fs, tcb::as_bytes(tcb::span(json_string.data(), json_string.size()))); } catch (const std::exception& ex) { diff --git a/src/menus/class-egg-tv/EggTVData.hpp b/src/menus/class-egg-tv/EggTVData.hpp index fef4f9dfa..09ab45b93 100644 --- a/src/menus/class-egg-tv/EggTVData.hpp +++ b/src/menus/class-egg-tv/EggTVData.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -17,14 +17,13 @@ #include #include #include -#include #include #include #include #include -#include - +#include "../../core/string.h" +#include "../../core/json.hpp" #include "../../cxxutil.hpp" #include "../../d_main.h" // srb2home @@ -40,10 +39,10 @@ private: const std::filesystem::path root_ = std::filesystem::path{srb2home} / "media/replay/online"; const std::filesystem::path favoritesPath_ = root_ / "favorites.json"; - nlohmann::json favoritesFile_ = cache_favorites(); - nlohmann::json& favorites_; + JsonValue favoritesFile_ = cache_favorites(); + JsonValue& favorites_; - nlohmann::json cache_favorites() const; + JsonValue cache_favorites() const; void cache_folders(); void save_favorites() const; @@ -91,20 +90,21 @@ public: released_ = true; } - bool favorited() const { return iterator_to_favorite() != favorites().end(); } - nlohmann::json& favorites() const { return cache().folder().tv().favorites_; } + bool favorited() const { return iterator_to_favorite() != favorites().as_array().end(); } + JsonValue& favorites() const { return cache().folder().tv().favorites_; } - std::string favorites_path() const + srb2::String favorites_path() const { // path::generic_string converts to forward // slashes on Windows. This should suffice to make // the JSON file portable across installations. - return (std::filesystem::path{cache().folder().name()} / filename()).generic_string(); + return (std::filesystem::path{std::string_view(cache().folder().name())} / filename()).generic_string(); } - nlohmann::json::const_iterator iterator_to_favorite() const + JsonArray::const_iterator iterator_to_favorite() const { - return std::find(favorites().begin(), favorites().end(), favorites_path()); + srb2::String path = favorites_path(); + return std::find(favorites().as_array().begin(), favorites().as_array().end(), static_cast(path)); } private: @@ -134,13 +134,13 @@ public: int y = 0; bool empty() { return size() == 0; } - std::filesystem::path path() const { return tv_->root_ / name_; } + std::filesystem::path path() const { return tv_->root_ / std::string_view(name_); } EggTVData& tv() const { return *tv_; } std::size_t size() const { return size_; } const time_point_t& time() const { return time_; } - const std::string& name() const { return name_; } + const srb2::String& name() const { return name_; } std::unique_ptr load() { return std::make_unique(*this); }; @@ -150,7 +150,7 @@ public: std::size_t size_; time_point_t time_; EggTVData* tv_; - std::string name_; + srb2::String name_; }; class Replay @@ -165,18 +165,18 @@ public: { } - const std::string& first() const { return first_; } - const std::string& second() const { return second_; } + const srb2::String& first() const { return first_; } + const srb2::String& second() const { return second_; } - operator const std::string() const; + operator const srb2::String() const; private: - std::string first_, second_; + srb2::String first_, second_; }; struct Standing { - std::string name; + srb2::String name; std::optional skin; std::size_t color; std::optional time; @@ -247,7 +247,7 @@ public: void toggle_favorite() const; bool invalid() const { return invalid_; } - bool favorited() const { return ref_->iterator_to_favorite() != ref_->favorites().end(); } + bool favorited() const { return ref_->iterator_to_favorite() != ref_->favorites().as_array().end(); } std::filesystem::path path() const { return ref_->cache().folder().path() / ref_->filename(); } const time_point_t& date() const { return ref_->time(); } diff --git a/src/menus/class-egg-tv/EggTVGraphics.hpp b/src/menus/class-egg-tv/EggTVGraphics.hpp index 362914e7e..d1928a0ae 100644 --- a/src/menus/class-egg-tv/EggTVGraphics.hpp +++ b/src/menus/class-egg-tv/EggTVGraphics.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -13,8 +13,8 @@ #include #include -#include +#include "../../core/hash_map.hpp" #include "../../doomdef.h" // skincolornum_t #include "../../v_draw.hpp" @@ -90,7 +90,7 @@ public: patch select = "RHTVSQSL"; - std::unordered_map gametype = { + srb2::HashMap gametype = { {"Race", "RHGT1"}, {"Battle", "RHGT2"}, {"Prison Break", "RHGT3"}, diff --git a/src/menus/extras-1.c b/src/menus/extras-1.c index 1c3888ca7..b7c8dcd1a 100644 --- a/src/menus/extras-1.c +++ b/src/menus/extras-1.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'". -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by "Lat'". +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/extras-addons.c b/src/menus/extras-addons.c index 22c1c204a..0874b359d 100644 --- a/src/menus/extras-addons.c +++ b/src/menus/extras-addons.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/menus/extras-challenges.c b/src/menus/extras-challenges.c index 8308fae1c..905daeba9 100644 --- a/src/menus/extras-challenges.c +++ b/src/menus/extras-challenges.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/extras-egg-tv.cpp b/src/menus/extras-egg-tv.cpp index ca070cb6a..ae62e06d3 100644 --- a/src/menus/extras-egg-tv.cpp +++ b/src/menus/extras-egg-tv.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/extras-statistics.cpp b/src/menus/extras-statistics.cpp index 75c6821b1..e270b2db6 100644 --- a/src/menus/extras-statistics.cpp +++ b/src/menus/extras-statistics.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the @@ -11,6 +11,8 @@ /// \file menus/extras-challenges.c /// \brief Statistics menu +#include + #include "../k_menu.h" #include "../z_zone.h" #include "../m_cond.h" // Condition Sets @@ -260,7 +262,7 @@ static void M_StatisticsPageInit(void) M_StatisticsChars(); break; } - + case statisticspage_gp: { M_StatisticsGP(); @@ -288,10 +290,22 @@ void M_Statistics(INT32 choice) { (void)choice; + UINT16 i; + statisticsmenu.gotmedals = M_CountMedals(false, false); statisticsmenu.nummedals = M_CountMedals(true, false); statisticsmenu.numextramedals = M_CountMedals(true, true); + statisticsmenu.numcanbonus = 0; + for (i = 0; i < basenummapheaders; i++) + { + if (!mapheaderinfo[i]) + continue; + if (mapheaderinfo[i]->records.spraycan != MCAN_BONUS) + continue; + statisticsmenu.numcanbonus++; + } + M_StatisticsPageInit(); MISC_StatisticsDef.prevMenu = currentMenu; diff --git a/src/menus/extras-wrong.c b/src/menus/extras-wrong.c index c1229d339..22af1d3a5 100644 --- a/src/menus/extras-wrong.c +++ b/src/menus/extras-wrong.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/main-1.c b/src/menus/main-1.c index 7fc13d431..f055358dc 100644 --- a/src/menus/main-1.c +++ b/src/menus/main-1.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/menus/main-goner.cpp b/src/menus/main-goner.cpp index 92c5dc287..5539ed5a2 100644 --- a/src/menus/main-goner.cpp +++ b/src/menus/main-goner.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -29,6 +29,8 @@ #include +#include "../core/string.h" + static void M_GonerDrawer(void); static void M_GonerConclude(INT32 choice); static boolean M_GonerInputs(INT32 ch); @@ -46,7 +48,7 @@ menuitem_t MAIN_Goner[] = {.routine = M_VideoOptions}, 0, 0}, {IT_STRING | IT_CALL, "SOUND OPTIONS", - "CALIBRATE AURAL DATASTREAM.", NULL, + "CALIBRATE AURAL DATASTREAM.", NULL, {.routine = M_SoundOptions}, 0, 0}, {IT_STRING | IT_CALL, "PROFILE SETUP", @@ -95,7 +97,7 @@ class GonerSpeaker public: float offset = 0; - GonerSpeaker(std::string skinName, float offset) + GonerSpeaker(const srb2::String& skinName, float offset) { if (!skinName.empty()) { @@ -144,11 +146,11 @@ class GonerChatLine { public: gonerspeakers_t speaker; - std::string dialogue; + srb2::String dialogue; int value; // Mutlipurpose. void (*routine)(void); - GonerChatLine(gonerspeakers_t speaker, int delay, std::string dialogue) + GonerChatLine(gonerspeakers_t speaker, int delay, const srb2::String& dialogue) { char *newText = V_ScaledWordWrap( (BASEVIDWIDTH/2 + 6) << FRACBITS, @@ -158,7 +160,7 @@ public: ); this->speaker = speaker; - this->dialogue = std::string(newText); + this->dialogue = srb2::String(newText); this->value = delay; this->routine = nullptr; @@ -1142,7 +1144,7 @@ static void M_GonerDrawer(void) for (auto & element : LinesOutput) { - std::string text; + srb2::String text; INT32 flags; if (newy < 0) break; diff --git a/src/menus/main-profile-select.c b/src/menus/main-profile-select.c index 7702899fa..919d1421c 100644 --- a/src/menus/main-profile-select.c +++ b/src/menus/main-profile-select.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/options-1.c b/src/menus/options-1.c index 3b390abd6..56b09d721 100644 --- a/src/menus/options-1.c +++ b/src/menus/options-1.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'". -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by "Lat'". +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the @@ -31,6 +31,9 @@ menuitem_t OPTIONS_Main[] = {IT_STRING | IT_CALL, "Sound Options", "Adjust the volume.", NULL, {.routine = M_SoundOptions}, 0, 0}, + {IT_STRING | IT_SUBMENU, "Voice Options", "Adjust voice chat.", + NULL, {.submenu = &OPTIONS_VoiceDef}, 0, 0}, + {IT_STRING | IT_SUBMENU, "HUD Options", "Tweak the Heads-Up Display.", NULL, {.submenu = &OPTIONS_HUDDef}, 0, 0}, diff --git a/src/menus/options-data-1.c b/src/menus/options-data-1.c index 195fbc090..9406a3a04 100644 --- a/src/menus/options-data-1.c +++ b/src/menus/options-data-1.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/options-data-advanced-1.c b/src/menus/options-data-advanced-1.c index 366cbf0bf..63ffda0ec 100644 --- a/src/menus/options-data-advanced-1.c +++ b/src/menus/options-data-advanced-1.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/options-data-advanced-addon.c b/src/menus/options-data-advanced-addon.c index 77b10e003..9bf517abb 100644 --- a/src/menus/options-data-advanced-addon.c +++ b/src/menus/options-data-advanced-addon.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/options-data-erase-1.c b/src/menus/options-data-erase-1.c index 909364e20..88387b183 100644 --- a/src/menus/options-data-erase-1.c +++ b/src/menus/options-data-erase-1.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/menus/options-data-erase-profile.c b/src/menus/options-data-erase-profile.c index efde56525..c9c36681f 100644 --- a/src/menus/options-data-erase-profile.c +++ b/src/menus/options-data-erase-profile.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'" -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by "Lat'" +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/options-data-screenshots.c b/src/menus/options-data-screenshots.c index 0bf15fb7a..cf1533e85 100644 --- a/src/menus/options-data-screenshots.c +++ b/src/menus/options-data-screenshots.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/options-gameplay-1.c b/src/menus/options-gameplay-1.c index fc81174de..11398a6f4 100644 --- a/src/menus/options-gameplay-1.c +++ b/src/menus/options-gameplay-1.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,6 +14,14 @@ menuitem_t OPTIONS_Gameplay[] = { + {IT_HEADER, "Global...", NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Teamplay", "Split the game between two teams!", + NULL, {.cvar = &cv_teamplay}, 0, 0}, + + {IT_STRING | IT_CVAR, "Frantic Items", "Make item odds crazier with more powerful items!", + NULL, {.cvar = &cv_kartfrantic}, 0, 0}, {IT_HEADER, "Race...", NULL, NULL, {NULL}, 0, 0}, @@ -21,9 +29,6 @@ menuitem_t OPTIONS_Gameplay[] = {IT_STRING | IT_CVAR, "Game Speed", "Gear for the next map.", NULL, {.cvar = &cv_kartspeed}, 0, 0}, - {IT_STRING | IT_CVAR, "Frantic Items", "Make item odds crazier with more powerful items!", - NULL, {.cvar = &cv_kartfrantic}, 0, 0}, - {IT_STRING | IT_CVAR, "Encore Mode", "Play in Encore Mode next map.", NULL, {.cvar = &cv_kartencore}, 0, 0}, diff --git a/src/menus/options-gameplay-item-toggles.c b/src/menus/options-gameplay-item-toggles.c index 720fa4d08..d16eee0a9 100644 --- a/src/menus/options-gameplay-item-toggles.c +++ b/src/menus/options-gameplay-item-toggles.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/options-hud-1.c b/src/menus/options-hud-1.c index 35eb45895..b0ad986f2 100644 --- a/src/menus/options-hud-1.c +++ b/src/menus/options-hud-1.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/options-hud-online.c b/src/menus/options-hud-online.c index 5df03d8a4..3416d6bb0 100644 --- a/src/menus/options-hud-online.c +++ b/src/menus/options-hud-online.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/options-profiles-1.c b/src/menus/options-profiles-1.c index e7c31449c..8f61f01cd 100644 --- a/src/menus/options-profiles-1.c +++ b/src/menus/options-profiles-1.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'". -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by "Lat'". +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -102,6 +102,8 @@ void M_StartEditProfile(INT32 c) CV_StealthSetValue(&cv_dummyprofilekickstart, optionsmenu.profile->kickstartaccel); CV_StealthSetValue(&cv_dummyprofileautoroulette, optionsmenu.profile->autoroulette); CV_StealthSetValue(&cv_dummyprofilelitesteer, optionsmenu.profile->litesteer); + CV_StealthSetValue(&cv_dummyprofilestrictfastfall, optionsmenu.profile->strictfastfall); + CV_StealthSetValue(&cv_dummyprofiledescriptiveinput, optionsmenu.profile->descriptiveinput); CV_StealthSetValue(&cv_dummyprofileautoring, optionsmenu.profile->autoring); CV_StealthSetValue(&cv_dummyprofilerumble, optionsmenu.profile->rumble); CV_StealthSetValue(&cv_dummyprofilefov, optionsmenu.profile->fov); @@ -113,6 +115,8 @@ void M_StartEditProfile(INT32 c) CV_StealthSetValue(&cv_dummyprofilekickstart, 0); // off CV_StealthSetValue(&cv_dummyprofileautoroulette, 0); // off CV_StealthSetValue(&cv_dummyprofilelitesteer, 1); // on + CV_StealthSetValue(&cv_dummyprofilestrictfastfall, 0); // off + CV_StealthSetValue(&cv_dummyprofiledescriptiveinput, 1); // Modern CV_StealthSetValue(&cv_dummyprofileautoring, 0); // on CV_StealthSetValue(&cv_dummyprofilerumble, 1); // on CV_StealthSetValue(&cv_dummyprofilefov, 90); diff --git a/src/menus/options-profiles-edit-1.c b/src/menus/options-profiles-edit-1.c index 8bdd552c2..fa5eb661c 100644 --- a/src/menus/options-profiles-edit-1.c +++ b/src/menus/options-profiles-edit-1.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'". -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by "Lat'". +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -98,6 +98,8 @@ static void M_ProfileEditApply(void) optionsmenu.profile->kickstartaccel = cv_dummyprofilekickstart.value; optionsmenu.profile->autoroulette = cv_dummyprofileautoroulette.value; optionsmenu.profile->litesteer = cv_dummyprofilelitesteer.value; + optionsmenu.profile->strictfastfall = cv_dummyprofilestrictfastfall.value; + optionsmenu.profile->descriptiveinput = cv_dummyprofiledescriptiveinput.value; optionsmenu.profile->autoring = cv_dummyprofileautoring.value; optionsmenu.profile->rumble = cv_dummyprofilerumble.value; optionsmenu.profile->fov = cv_dummyprofilefov.value; diff --git a/src/menus/options-profiles-edit-accessibility.cpp b/src/menus/options-profiles-edit-accessibility.cpp index 14dd0643d..d347b3ca4 100644 --- a/src/menus/options-profiles-edit-accessibility.cpp +++ b/src/menus/options-profiles-edit-accessibility.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -114,6 +114,9 @@ menuitem_t OPTIONS_ProfileAccessibility[] = { {IT_STRING | IT_CVAR, "Lite Steer", "Hold DOWN on d-pad/keyboard for shallow turns.", NULL, {.cvar = &cv_dummyprofilelitesteer}, 0, 0}, + {IT_STRING | IT_CVAR, "Strict Fastfall", "Fastfall only with the Spindash button.", + NULL, {.cvar = &cv_dummyprofilestrictfastfall}, 0, 0}, + {IT_STRING | IT_CVAR, "Field of View", "Higher FOV lets you see more.", NULL, {.cvar = &cv_dummyprofilefov}, 0, 0}, @@ -144,7 +147,7 @@ menu_t OPTIONS_ProfileAccessibilityDef = { &OPTIONS_EditProfileDef, 0, OPTIONS_ProfileAccessibility, - 145, 41, + 145, 31, SKINCOLOR_ULTRAMARINE, 0, MBF_DRAWBGWHILEPLAYING, "FILE", diff --git a/src/menus/options-profiles-edit-controls.c b/src/menus/options-profiles-edit-controls.c index 2c5dcec9e..628035f3a 100644 --- a/src/menus/options-profiles-edit-controls.c +++ b/src/menus/options-profiles-edit-controls.c @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by "Lat'". -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by "Lat'". +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -18,6 +18,12 @@ menuitem_t OPTIONS_ProfileControls[] = { + {IT_HEADER, "DEVICE SETTINGS", "", + NULL, {NULL}, 0, 0}, + + {IT_STRING2 | IT_CVAR, "Button Display", "DESCRIPTIVEINPUT-SENTINEL", + NULL, {.cvar = &cv_dummyprofiledescriptiveinput}, 0, 0}, + {IT_HEADER, "MAIN CONTROLS", "That's the stuff on the controller!!", NULL, {NULL}, 0, 0}, @@ -31,34 +37,34 @@ menuitem_t OPTIONS_ProfileControls[] = { "TLB_C", {.routine = M_ProfileSetControl}, gc_c, 0}, {IT_CONTROL, "Brake / Go back", "Brake / Go back", - "TLB_D", {.routine = M_ProfileSetControl}, gc_x, 0}, + "TLB_X", {.routine = M_ProfileSetControl}, gc_x, 0}, {IT_CONTROL, "Respawn", "Respawn", - "TLB_E", {.routine = M_ProfileSetControl}, gc_y, 0}, + "TLB_Y", {.routine = M_ProfileSetControl}, gc_y, 0}, {IT_CONTROL, "Action", "Multiplayer quick-chat / quick-vote", - "TLB_F", {.routine = M_ProfileSetControl}, gc_z, 0}, + "TLB_Z", {.routine = M_ProfileSetControl}, gc_z, 0}, {IT_CONTROL, "Use Item", "Use item", - "TLB_H", {.routine = M_ProfileSetControl}, gc_l, 0}, + "TLB_L1", {.routine = M_ProfileSetControl}, gc_l, 0}, {IT_CONTROL, "Drift", "Drift", - "TLB_I", {.routine = M_ProfileSetControl}, gc_r, 0}, + "TLB_R1", {.routine = M_ProfileSetControl}, gc_r, 0}, {IT_CONTROL, "Turn Left", "Turn left", - "TLB_M", {.routine = M_ProfileSetControl}, gc_left, 0}, + "TLB_ARL", {.routine = M_ProfileSetControl}, gc_left, 0}, {IT_CONTROL, "Turn Right", "Turn right", - "TLB_L", {.routine = M_ProfileSetControl}, gc_right, 0}, + "TLB_ARR", {.routine = M_ProfileSetControl}, gc_right, 0}, {IT_CONTROL, "Aim Forward", "Aim forwards", - "TLB_J", {.routine = M_ProfileSetControl}, gc_up, 0}, + "TLB_ARU", {.routine = M_ProfileSetControl}, gc_up, 0}, {IT_CONTROL, "Aim Backwards", "Aim backwards", - "TLB_K", {.routine = M_ProfileSetControl}, gc_down, 0}, + "TLB_ARD", {.routine = M_ProfileSetControl}, gc_down, 0}, {IT_CONTROL, "Open pause menu", "Open pause menu", - "TLB_G", {.routine = M_ProfileSetControl}, gc_start, 0}, + "TLB_S", {.routine = M_ProfileSetControl}, gc_start, 0}, {IT_HEADER, "OPTIONAL CONTROLS", "Take a screenshot, chat...", NULL, {NULL}, 0, 0}, @@ -81,14 +87,17 @@ menuitem_t OPTIONS_ProfileControls[] = { {IT_CONTROL, "OPEN TEAM CHAT", "Opens team-only full chat for online games.", NULL, {.routine = M_ProfileSetControl}, gc_teamtalk, 0}, - {IT_CONTROL, "LUA/A", "May be used by add-ons.", - NULL, {.routine = M_ProfileSetControl}, gc_luaa, 0}, + {IT_CONTROL, "PUSH-TO-TALK", "Activates voice chat transmission in Push-to-Talk (PTT) mode.", + NULL, {.routine = M_ProfileSetControl}, gc_voicepushtotalk, 0}, - {IT_CONTROL, "LUA/B", "May be used by add-ons.", - NULL, {.routine = M_ProfileSetControl}, gc_luab, 0}, + {IT_CONTROL, "LUA/1", "May be used by add-ons.", + NULL, {.routine = M_ProfileSetControl}, gc_lua1, 0}, - {IT_CONTROL, "LUA/C", "May be used by add-ons.", - NULL, {.routine = M_ProfileSetControl}, gc_luac, 0}, + {IT_CONTROL, "LUA/2", "May be used by add-ons.", + NULL, {.routine = M_ProfileSetControl}, gc_lua2, 0}, + + {IT_CONTROL, "LUA/3", "May be used by add-ons.", + NULL, {.routine = M_ProfileSetControl}, gc_lua3, 0}, {IT_CONTROL, "OPEN CONSOLE", "Opens the developer options console.", NULL, {.routine = M_ProfileSetControl}, gc_console, 0}, @@ -304,14 +313,14 @@ boolean M_ProfileControlsInputs(INT32 ch) S_StartSound(NULL, sfx_kc69); if (newbuttons & MBT_R) S_StartSound(NULL, sfx_s3ka2); - + if (newbuttons & MBT_A) S_StartSound(NULL, sfx_kc3c); if (newbuttons & MBT_B) S_StartSound(NULL, sfx_3db09); if (newbuttons & MBT_C) S_StartSound(NULL, sfx_s1be); - + if (newbuttons & MBT_X) S_StartSound(NULL, sfx_s1a4); if (newbuttons & MBT_Y) diff --git a/src/menus/options-server-1.c b/src/menus/options-server-1.c index e489045c8..877d0e223 100644 --- a/src/menus/options-server-1.c +++ b/src/menus/options-server-1.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -29,7 +29,7 @@ menuitem_t OPTIONS_Server[] = {IT_HEADER, "Players...", NULL, NULL, {NULL}, 0, 0}, - + {IT_STRING | IT_CVAR, "Maximum Players", "How many players can play at once.", NULL, {.cvar = &cv_maxplayers}, 0, 0}, @@ -40,7 +40,7 @@ menuitem_t OPTIONS_Server[] = NULL, {.cvar = &cv_kartbot}, 0, 0}, {IT_STRING | IT_CVAR, "Use PWR.LV", "Should players should be rated on their performance?", - NULL, {.cvar = &cv_kartusepwrlv}, 0, 0}, + NULL, {.cvar = &cv_kartusepwrlv}, 0, 0}, {IT_STRING | IT_CVAR, "Antigrief Timer (seconds)", "How long can players stop progressing before they're removed?", NULL, {.cvar = &cv_antigrief}, 0, 0}, @@ -78,6 +78,9 @@ menuitem_t OPTIONS_Server[] = {IT_STRING | IT_CVAR, "Mute Chat", "Prevent everyone but admins from sending chat messages.", NULL, {.cvar = &cv_mute}, 0, 0}, + {IT_STRING | IT_CVAR, "Mute Voice Chat", "Prevent everyone from sending voice chat.", + NULL, {.cvar = &cv_voice_servermute}, 0, 0}, + {IT_STRING | IT_CVAR, "Chat Spam Protection", "Prevent too many messages from a single player.", NULL, {.cvar = &cv_chatspamprotection}, 0, 0}, diff --git a/src/menus/options-server-advanced.c b/src/menus/options-server-advanced.c index fa22a0997..a2b15921c 100644 --- a/src/menus/options-server-advanced.c +++ b/src/menus/options-server-advanced.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -27,9 +27,6 @@ menuitem_t OPTIONS_ServerAdvanced[] = {IT_HEADER, "Network Connection", NULL, NULL, {NULL}, 0, 0}, - {IT_STRING | IT_CVAR, "Resynch. Attempts", "How many times to attempt sending data to desynchronized players.", - NULL, {.cvar = &cv_resynchattempts}, 0, 0}, - {IT_STRING | IT_CVAR, "Delay Limit (tics)", "Players above the delay limit will get kicked from the server.", NULL, {.cvar = &cv_maxping}, 0, 0}, diff --git a/src/menus/options-sound.cpp b/src/menus/options-sound.cpp index fd7b1b701..e77d7a6c0 100644 --- a/src/menus/options-sound.cpp +++ b/src/menus/options-sound.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -29,12 +29,6 @@ using srb2::Draw; namespace { -bool basic_options() -{ - // M_GameTrulyStarted - return gamedata && gamestartchallenge < MAXUNLOCKABLES && !netgame && gamedata->gonerlevel <= GDGONER_PROFILE; -} - int flip_delay = 0; struct Slider @@ -44,6 +38,7 @@ struct Slider kMasterVolume, kMusicVolume, kSfxVolume, + kVoiceVolume, kNumSliders }; @@ -68,10 +63,8 @@ struct Slider arrows.x(-10 - ofs).text("\x1C"); arrows.x(kWidth + 2 + ofs).text("\x1D"); - if (!basic_options()) - { - h.xy(kWidth + 9, -3).small_button(Draw::Button::z, false); - } + Draw::TextElement tx = Draw::TextElement().parse(""); + h.xy(kWidth + 9, -2).text(tx.string()); } h = h.y(1); @@ -128,6 +121,7 @@ std::array sliders{{ n = !n; CV_SetValue(&cv_gamedigimusic, n); CV_SetValue(&cv_gamesounds, n); + CV_SetValue(&cv_voice_chat, n); } return n; @@ -158,6 +152,18 @@ std::array sliders{{ }, cv_soundvolume, }, + { + [](bool toggle) -> bool + { + if (toggle) + { + CV_AddValue(&cv_voice_chat, 1); + } + + return !S_VoiceDisabled(); + }, + cv_voicevolume, + }, }}; void slider_routine(INT32 c) @@ -251,7 +257,7 @@ boolean input_routine(INT32) const menuitem_t& it = currentMenu->menuitems[itemOn]; - if (M_MenuButtonPressed(pid, MBT_Z) && (it.status & IT_TYPE) == IT_ARROWS && !basic_options()) + if (M_MenuButtonPressed(pid, MBT_Z) && (it.status & IT_TYPE) == IT_ARROWS) { sliders.at(it.mvar2).toggle_(true); return true; @@ -274,6 +280,9 @@ menuitem_t OPTIONS_Sound[] = {IT_STRING | IT_ARROWS | IT_CV_SLIDER, "Music Volume", "Loudness of music.", NULL, {.routine = slider_routine}, 0, Slider::kMusicVolume}, + {IT_STRING | IT_ARROWS | IT_CV_SLIDER, "Voice Volume", "Loudness of voice chat.", + NULL, {.routine = slider_routine}, 0, Slider::kVoiceVolume}, + {IT_SPACE | IT_NOTHING, NULL, NULL, NULL, {NULL}, 0, 0}, diff --git a/src/menus/options-video-1.c b/src/menus/options-video-1.c index e4965ca29..0896f78fe 100644 --- a/src/menus/options-video-1.c +++ b/src/menus/options-video-1.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/options-video-advanced.c b/src/menus/options-video-advanced.c index 5e29ed467..c2e40ae04 100644 --- a/src/menus/options-video-advanced.c +++ b/src/menus/options-video-advanced.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/options-video-modes.c b/src/menus/options-video-modes.c index 68506e018..33bfe5afc 100644 --- a/src/menus/options-video-modes.c +++ b/src/menus/options-video-modes.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/menus/options-voice.cpp b/src/menus/options-voice.cpp new file mode 100644 index 000000000..8f02fad5f --- /dev/null +++ b/src/menus/options-voice.cpp @@ -0,0 +1,91 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Kart Krew. +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file menus/options-voice.cpp +/// \brief Voice Options + +#include "../m_easing.h" +#include "../k_menu.h" +#include "../s_sound.h" // sounds consvars +#include "../v_video.h" + +menuitem_t OPTIONS_Voice[] = +{ + {IT_STRING | IT_CVAR, "Voice Chat", "Turn on or off all voice chat for yourself.", + NULL, {.cvar = &cv_voice_chat}, 0, 0}, + + {IT_STRING | IT_CVAR, "Voice Mode", "When to transmit your own voice.", + NULL, {.cvar = &cv_voice_mode}, 0, 0}, + + {IT_STRING | IT_CVAR, "Input Amplifier", "Amplify your voice, in decibels. Negative values are quieter.", + NULL, {.cvar = &cv_voice_inputamp}, 0, 0}, + + {IT_STRING | IT_CVAR, "Activation Threshold", "Voice higher than this threshold will transmit, in decibels.", + NULL, {.cvar = &cv_voice_activationthreshold}, 0, 0}, + + {IT_STRING | IT_CVAR, "Self Voice Mute", "Whether your voice is transmitted or not.", + NULL, {.cvar = &cv_voice_selfmute}, 0, 0}, + + {IT_STRING | IT_CVAR, "Voice Loopback", "Play your own recording voice back.", + NULL, {.cvar = &cv_voice_loopback}, 0, 0}, + + {IT_SPACE | IT_NOTHING, NULL, NULL, + NULL, {NULL}, 0, 0}, + + {IT_HEADER, "Server Voice Options...", NULL, + NULL, {NULL}, 0, 0}, + + {IT_STRING | IT_CVAR, "Server Voice Mute", "All voice chat will be disabled on your server.", + NULL, {.cvar = &cv_voice_servermute}, 0, 0}, + + {IT_STRING | IT_CVAR, "Proximity Effects", "Player voices will be adjusted relative to you.", + NULL, {.cvar = &cv_voice_proximity}, 0, 0}, +}; + +static void draw_routine() +{ + M_DrawGenericOptions(); + + int x = currentMenu->x - M_EaseWithTransition(Easing_Linear, 5 * 48); + int y = currentMenu->y - 12; + int range = 220; + float last_peak = g_local_voice_last_peak * range; + boolean detected = g_local_voice_detected; + INT32 color = detected ? 65 : 23; + + V_DrawFill(x, y, range + 2, 10, 31); + V_DrawFill(x + 1, y + 1, (int) last_peak, 8, color); +} + +static void tick_routine() +{ + M_OptionsTick(); +} + +static boolean input_routine(INT32) +{ + return false; +} + +menu_t OPTIONS_VoiceDef = { + sizeof (OPTIONS_Voice) / sizeof (menuitem_t), + &OPTIONS_MainDef, + 0, + OPTIONS_Voice, + 48, 80, + SKINCOLOR_ULTRAMARINE, 0, + MBF_DRAWBGWHILEPLAYING, + NULL, + 2, 5, + draw_routine, + M_DrawOptionsCogs, + tick_routine, + NULL, + NULL, + input_routine, +}; diff --git a/src/menus/play-1.c b/src/menus/play-1.c index 10b70a484..8bc18bda5 100644 --- a/src/menus/play-1.c +++ b/src/menus/play-1.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/play-char-select.c b/src/menus/play-char-select.c index ed8606ff0..010f13f7a 100644 --- a/src/menus/play-char-select.c +++ b/src/menus/play-char-select.c @@ -1,9 +1,9 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour. -// Copyright (C) 2024 by "Lat'". -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour. +// Copyright (C) 2025 by "Lat'". +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -288,7 +288,7 @@ static void M_SetupMidGameGridPos(setup_player_t *p, UINT8 num) void M_CharacterSelectInit(void) { - UINT8 i, j; + UINT16 i, j; setup_maxpage = 0; memset(setup_chargrid, -1, sizeof(setup_chargrid)); @@ -753,6 +753,20 @@ static void M_HandleBackToChars(setup_player_t *p) static boolean M_HandleBeginningColors(setup_player_t *p) { p->mdepth = CSSTEP_COLORS; + + if (Playing() && G_GametypeHasTeams()) + { + size_t pnum = (p - setup_player); + if (pnum <= splitscreen) + { + if (players[g_localplayers[pnum]].team != TEAM_UNASSIGNED) + { + p->color = g_teaminfo[players[g_localplayers[pnum]].team].color; + return false; + } + } + } + M_NewPlayerColors(p); if (p->colors.listLen != 1) return true; diff --git a/src/menus/play-local-1.c b/src/menus/play-local-1.c index b3844f785..433ed5ada 100644 --- a/src/menus/play-local-1.c +++ b/src/menus/play-local-1.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/play-local-race-1.c b/src/menus/play-local-race-1.c index 1de2de42e..d7013cafd 100644 --- a/src/menus/play-local-race-1.c +++ b/src/menus/play-local-race-1.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/play-local-race-difficulty.c b/src/menus/play-local-race-difficulty.c index 1f5636228..5793e76c4 100644 --- a/src/menus/play-local-race-difficulty.c +++ b/src/menus/play-local-race-difficulty.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/play-local-race-time-attack.c b/src/menus/play-local-race-time-attack.c index 880b764af..b682cfe07 100644 --- a/src/menus/play-local-race-time-attack.c +++ b/src/menus/play-local-race-time-attack.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/menus/play-online-1.c b/src/menus/play-online-1.c index 74f23b900..a9f99e1c0 100644 --- a/src/menus/play-online-1.c +++ b/src/menus/play-online-1.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/play-online-host.c b/src/menus/play-online-host.c index 56b7c8ab2..a52f04822 100644 --- a/src/menus/play-online-host.c +++ b/src/menus/play-online-host.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/play-online-join-ip.c b/src/menus/play-online-join-ip.c index aa7365b4f..294a613a1 100644 --- a/src/menus/play-online-join-ip.c +++ b/src/menus/play-online-join-ip.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/play-online-room-select.c b/src/menus/play-online-room-select.c index 56bdc1eaf..fa346c5e7 100644 --- a/src/menus/play-online-room-select.c +++ b/src/menus/play-online-room-select.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/play-online-server-browser.c b/src/menus/play-online-server-browser.c index 68880fa09..b28c0f66d 100644 --- a/src/menus/play-online-server-browser.c +++ b/src/menus/play-online-server-browser.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2016 by Kay "Kaito" Sinclaire. // Copyright (C) 2020 by Sonic Team Junior. // diff --git a/src/menus/transient/cup-select.c b/src/menus/transient/cup-select.c index 23a7e5943..8fa830094 100644 --- a/src/menus/transient/cup-select.c +++ b/src/menus/transient/cup-select.c @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour. -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/transient/discord-requests.c b/src/menus/transient/discord-requests.c index 5756dac91..200f0f8d3 100644 --- a/src/menus/transient/discord-requests.c +++ b/src/menus/transient/discord-requests.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/transient/explosions.c b/src/menus/transient/explosions.c index a75b105e5..dda138e25 100644 --- a/src/menus/transient/explosions.c +++ b/src/menus/transient/explosions.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/transient/gametype.c b/src/menus/transient/gametype.c index cad40fa3a..b52242b8b 100644 --- a/src/menus/transient/gametype.c +++ b/src/menus/transient/gametype.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/transient/level-select.c b/src/menus/transient/level-select.c index 486104117..a0c57ae8c 100644 --- a/src/menus/transient/level-select.c +++ b/src/menus/transient/level-select.c @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -676,6 +676,12 @@ void M_LevelSelectInit(INT32 choice) gt = menugametype; } + if (levellist.levelsearch.timeattack && gt == GT_RACE && skins[R_SkinAvailableEx(cv_skin[0].string, false)].flags & SF_HIVOLT) + { + M_StartMessage("A long-forgotten power...", "You are using a \x82prototype engine\x80.\nRecords will not be saved.", NULL, MM_NOTHING, NULL, NULL); + S_StartSound(NULL, sfx_s3k81); + } + if (!M_LevelListFromGametype(gt)) { S_StartSound(NULL, sfx_s3kb2); diff --git a/src/menus/transient/manual.c b/src/menus/transient/manual.c index 2e974069e..c237725f1 100644 --- a/src/menus/transient/manual.c +++ b/src/menus/transient/manual.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/transient/message-box.c b/src/menus/transient/message-box.c index 8f3bd8e23..6d2e12e19 100644 --- a/src/menus/transient/message-box.c +++ b/src/menus/transient/message-box.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/transient/pause-addonoptions.cpp b/src/menus/transient/pause-addonoptions.cpp index 42e238234..2f3f69526 100644 --- a/src/menus/transient/pause-addonoptions.cpp +++ b/src/menus/transient/pause-addonoptions.cpp @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by AJ "Tyron" Martinez -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by AJ "Tyron" Martinez +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -146,7 +146,7 @@ void list_commands() if (flags & COM_NOSHOWHELP) continue; - g_menu.push_back(menuitem_t {IT_STRING | IT_CALL, cmd->name, "Press \xAA to execute this command", nullptr, {.routine = call}, 0, 8}); + g_menu.push_back(menuitem_t {IT_STRING | IT_CALL, cmd->name, "No information available for commands. Press to execute.", nullptr, {.routine = call}, 0, 8}); } } @@ -264,7 +264,8 @@ void draw_menu() draw.x(BASEVIDWIDTH/2).font(Draw::Font::kGamemode).text(mode_strings[menu_mode()]); if (server || IsPlayerAdmin(consoleplayer)) - K_drawButton((draw.x() + 8) * FRACUNIT, (draw.y() + 8) * FRACUNIT, 0, kp_button_y[0], M_MenuButtonHeld(0, MBT_Y)); + K_DrawGameControl(draw.x() + 8, draw.y()-6, 0, M_MenuButtonHeld(0, MBT_Y) ? " Switch Page" : " Switch Page", 0, 8, 0); + // K_drawButton((draw.x() + 8) * FRACUNIT, (draw.y() + 8) * FRACUNIT, 0, kp_button_y[0], M_MenuButtonHeld(0, MBT_Y)); draw = draw.y(32 + kMargin); currentMenu->y = std::min(static_cast(draw.y()), (BASEVIDHEIGHT/2) - g_menu_offsets[itemOn]); diff --git a/src/menus/transient/pause-cheats.cpp b/src/menus/transient/pause-cheats.cpp index bbd9a4d8b..6f0e38886 100644 --- a/src/menus/transient/pause-cheats.cpp +++ b/src/menus/transient/pause-cheats.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -208,7 +208,8 @@ void draw_menu() draw = draw.y(27 + kMargin); draw.x(BASEVIDWIDTH/2).font(Draw::Font::kGamemode).text(mode_strings[menu_mode()]); - K_drawButton((draw.x() + 8) * FRACUNIT, (draw.y() + 8) * FRACUNIT, 0, kp_button_y[0], M_MenuButtonHeld(0, MBT_Y)); + K_DrawGameControl(draw.x() + 8, draw.y()-6, 0, M_MenuButtonHeld(0, MBT_Y) ? " Switch Page" : " Switch Page", 0, 8, 0); + // K_drawButton((draw.x() + 8) * FRACUNIT, (draw.y() + 8) * FRACUNIT, 0, kp_button_y[0], M_MenuButtonHeld(0, MBT_Y)); draw = draw.y(32 + kMargin); currentMenu->y = std::min(static_cast(draw.y()), (BASEVIDHEIGHT/2) - g_menu_offsets[itemOn]); diff --git a/src/menus/transient/pause-game.c b/src/menus/transient/pause-game.c index 7e283504e..00884cb4f 100644 --- a/src/menus/transient/pause-game.c +++ b/src/menus/transient/pause-game.c @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'". -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by "Lat'". +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -11,6 +11,7 @@ /// \file menus/transient/pause-game.c /// \brief In-game/pause menus +#include "../../byteptr.h" #include "../../d_netcmd.h" #include "../../i_time.h" #include "../../k_menu.h" @@ -352,7 +353,7 @@ void M_HandlePauseMenuGametype(INT32 choice) } else // ideally for "random" only, but no sane fallback for "same" and "next" { - COM_ImmedExecute(va("randommap -gt %s", gametypes[menugametype]->name)); + COM_ImmedExecute(va("map -random -gt %s", gametypes[menugametype]->name)); } } return; @@ -478,50 +479,33 @@ void M_HandleSpectateToggle(INT32 choice) return; } - boolean tospectator = false; + // Identify relevant spectator state of pausemenu.splitscreenfocusid. + // See also M_DrawPause. + + const UINT8 splitspecid = + g_localplayers[pausemenu.splitscreenfocusid]; + + const UINT8 joingame = ( + players[splitspecid].spectator == true + && ((players[splitspecid].pflags & PF_WANTSTOJOIN) == 0) + ) ? 1 : 0; + + if (joingame && !cv_allowteamchange.value) { - // Identify relevant spectator state of pausemenu.splitscreenfocusid. - // See also M_DrawPause. - - const UINT8 splitspecid = - g_localplayers[pausemenu.splitscreenfocusid]; - - tospectator = ( - players[splitspecid].spectator == false - || (players[splitspecid].pflags & PF_WANTSTOJOIN) - ); - } - - if (!tospectator && !cv_allowteamchange.value) - { - M_StartMessage("Team Change", M_GetText("The server is not allowing\nteam changes at this time.\n"), NULL, MM_NOTHING, NULL, NULL); + M_StartMessage("Joining Play", M_GetText("The server is not allowing\njoining play at this time.\n"), NULL, MM_NOTHING, NULL, NULL); return; } M_QuitPauseMenu(-1); - const char *destinationstate = tospectator ? "spectator" : "playing"; + // Send spectate + UINT8 buf[2]; + UINT8 *p = buf; - // These console command names... - if (pausemenu.splitscreenfocusid == 0) - { - COM_ImmedExecute( - va( - "changeteam %s", - destinationstate - ) - ); - } - else - { - COM_ImmedExecute( - va( - "changeteam%u %s", - pausemenu.splitscreenfocusid + 1, - destinationstate - ) - ); - } + WRITEUINT8(p, splitspecid); + WRITEUINT8(p, joingame); + + SendNetXCmd(XD_SPECTATE, &buf, p - buf); return; } diff --git a/src/menus/transient/pause-kick.c b/src/menus/transient/pause-kick.c index e61e5db24..3870ebaa3 100644 --- a/src/menus/transient/pause-kick.c +++ b/src/menus/transient/pause-kick.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/transient/pause-replay.c b/src/menus/transient/pause-replay.c index 41de599cc..864344d91 100644 --- a/src/menus/transient/pause-replay.c +++ b/src/menus/transient/pause-replay.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/transient/sound-test.c b/src/menus/transient/sound-test.c index ea1703106..4945e7082 100644 --- a/src/menus/transient/sound-test.c +++ b/src/menus/transient/sound-test.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/menus/transient/virtual-keyboard.c b/src/menus/transient/virtual-keyboard.c index 5e22f23a3..38a507f2b 100644 --- a/src/menus/transient/virtual-keyboard.c +++ b/src/menus/transient/virtual-keyboard.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/mobj.hpp b/src/mobj.hpp index 2c2e4e97f..5acdfed10 100644 --- a/src/mobj.hpp +++ b/src/mobj.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/mobj_list.hpp b/src/mobj_list.hpp index bf46e7e38..751397053 100644 --- a/src/mobj_list.hpp +++ b/src/mobj_list.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -37,7 +37,6 @@ struct MobjList { ptr->next(front()); front(ptr); - count_++; } void erase(T* node) @@ -46,7 +45,6 @@ struct MobjList { front(node->next()); node->next(nullptr); - count_--; return; } @@ -71,21 +69,25 @@ struct MobjList { prev->next(node->next()); node->next(nullptr); - count_--; break; } } } + void clear() + { + while (!empty()) + { + erase(front()); + } + } + auto begin() const { return view().begin(); } auto end() const { return view().end(); } - auto count() { return count_; } - private: void front(T* ptr) { Mobj::ManagedPtr {Head} = ptr; } auto view() const { return MobjListView(front(), [](T* node) { return node->next(); }); } - UINT32 count_ = 0; }; }; // namespace srb2 diff --git a/src/mobj_list_view.hpp b/src/mobj_list_view.hpp index 7568548ab..883b859ab 100644 --- a/src/mobj_list_view.hpp +++ b/src/mobj_list_view.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/mserv.c b/src/mserv.c index 0b1c1cefa..16f3a4e3d 100644 --- a/src/mserv.c +++ b/src/mserv.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/mserv.h b/src/mserv.h index fd780d8b6..3f71b11c1 100644 --- a/src/mserv.h +++ b/src/mserv.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // Copyright (C) 2020 by Sonic Team Junior // Copyright (C) 2000 by DooM Legacy Team // diff --git a/src/music.cpp b/src/music.cpp index e9aab9b60..b50dfaf66 100644 --- a/src/music.cpp +++ b/src/music.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -174,7 +174,7 @@ void Music_Init(void) { Tune& tune = g_tunes.insert("credits"); - tune.priority = 100; + tune.priority = 101; tune.song = "_creds"; tune.credit = true; } @@ -182,7 +182,7 @@ void Music_Init(void) { Tune& tune = g_tunes.insert("shore"); - tune.priority = 100; + tune.priority = 101; tune.loop = false; } diff --git a/src/music.h b/src/music.h index 4b005e282..630c7a98c 100644 --- a/src/music.h +++ b/src/music.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/music_detail.hpp b/src/music_detail.hpp index f903038e2..89add76c7 100644 --- a/src/music_detail.hpp +++ b/src/music_detail.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/music_manager.cpp b/src/music_manager.cpp index d65b7f9a9..86ca4bec9 100644 --- a/src/music_manager.cpp +++ b/src/music_manager.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -14,13 +14,13 @@ #include #include #include -#include #include #include "music_manager.hpp" #include "music_tune.hpp" +#include "core/string.h" #include "d_clisrv.h" #include "doomtype.h" #include "i_sound.h" @@ -49,8 +49,8 @@ void TuneManager::tick() Tune* tune = current_tune(); - std::string old_song = current_song_; - current_song_ = tune && tune->playing() && !tune->suspend ? tune->song : std::string{}; + srb2::String old_song = current_song_; + current_song_ = tune && tune->playing() && !tune->suspend ? tune->song : srb2::String{}; bool changed = current_song_ != old_song; @@ -167,7 +167,8 @@ void TuneManager::pause_unpause() const bool TuneManager::load() const { - lumpnum_t lumpnum = W_CheckNumForLongName(fmt::format("O_{}", current_song_).c_str()); + srb2::String lumpstring = srb2::format("O_{}", current_song_); + lumpnum_t lumpnum = W_CheckNumForLongName(lumpstring.c_str()); if (lumpnum == LUMPERROR) { diff --git a/src/music_manager.hpp b/src/music_manager.hpp index 682ebd27a..e3d377258 100644 --- a/src/music_manager.hpp +++ b/src/music_manager.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -11,11 +11,10 @@ #ifndef MUSIC_MANAGER_HPP #define MUSIC_MANAGER_HPP -#include #include -#include -#include +#include "core/hash_map.hpp" +#include "core/string.h" #include "cxxutil.hpp" #include "music_tune.hpp" @@ -25,7 +24,7 @@ namespace srb2::music class TuneManager { public: - const std::string& current_song() const { return current_song_; } + const srb2::String& current_song() const { return current_song_; } Tune* current_tune() const { @@ -99,8 +98,8 @@ public: } private: - std::unordered_map map_; - std::string current_song_; + srb2::HashMap map_; + srb2::String current_song_; tic_t time_sync_; tic_t time_local_; @@ -113,11 +112,20 @@ private: decltype(map_)::const_iterator current_iterator() const { - return std::max_element( - map_.begin(), - map_.end(), - [](const auto& a, const auto& b) { return a.second < b.second; } - ); + // Not using std::max_element due to buggy clang libc++ LegacyInputIterator assertion + auto first = map_.begin(); + auto last = map_.end(); + if (first == last) return first; + + auto max = first; + while (++first != last) + { + if (max->second < first->second) + { + max = first; + } + } + return max; } bool load() const; diff --git a/src/music_tune.hpp b/src/music_tune.hpp index fbf3d7207..557eb79a9 100644 --- a/src/music_tune.hpp +++ b/src/music_tune.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,8 +14,8 @@ #include #include #include -#include +#include "core/string.h" #include "doomdef.h" #include "doomtype.h" #include "k_boss.h" @@ -29,7 +29,7 @@ class Tune public: explicit Tune() {} - std::string song; // looks up the lump + srb2::String song; // looks up the lump // Higher priority tunes play first. int priority = 0; diff --git a/src/nds/i_video.c b/src/nds/i_video.c index 10ea55aef..7af7a2850 100644 --- a/src/nds/i_video.c +++ b/src/nds/i_video.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/nds/r_nds3d.c b/src/nds/r_nds3d.c index 006fa8dea..7994f2035 100644 --- a/src/nds/r_nds3d.c +++ b/src/nds/r_nds3d.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/objects/CMakeLists.txt b/src/objects/CMakeLists.txt index 593d5b8f6..d82a2d77c 100644 --- a/src/objects/CMakeLists.txt +++ b/src/objects/CMakeLists.txt @@ -60,6 +60,10 @@ target_sources(SRB2SDL2 PRIVATE pulley.cpp amps.c ballhog.cpp + bubble-shield.cpp + flybot767.c + lightning-shield.cpp + flame-shield.cpp ) add_subdirectory(versus) diff --git a/src/objects/adventure-air-booster.c b/src/objects/adventure-air-booster.c index 843d6c0ac..d971b0d82 100644 --- a/src/objects/adventure-air-booster.c +++ b/src/objects/adventure-air-booster.c @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'" -// Copyright (C) 2024 by Lachlan "Lach" Wright -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by "Lat'" +// Copyright (C) 2025 by Lachlan "Lach" Wright +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/amps.c b/src/objects/amps.c index 68022d10d..a1e74d744 100644 --- a/src/objects/amps.c +++ b/src/objects/amps.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by AJ "Tyron" Martinez. -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by AJ "Tyron" Martinez. +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -83,7 +83,7 @@ void Obj_AmpsThink (mobj_t *amps) amps->momz += FixedMul(FINECOSINE(vang>>ANGLETOFINESHIFT), speed); if (dist < (120 * amps->scale) && amps->extravalue2 && !player->amppickup) - { + { K_AwardPlayerAmps(player, 2); P_RemoveMobj(amps); } @@ -200,4 +200,4 @@ void Obj_AmpBurstThink (mobj_t *amps) { amps->renderflags &= ~RF_TRANSMASK; amps->renderflags |= (NUMTRANSMAPS - amps->fuse) << RF_TRANSSHIFT; -} \ No newline at end of file +} diff --git a/src/objects/ark-arrow.c b/src/objects/ark-arrow.c index a1657eadb..abb450038 100644 --- a/src/objects/ark-arrow.c +++ b/src/objects/ark-arrow.c @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Lachlan "Lach" Wright -// Copyright (C) 2024 by AJ "Tyron" Martinez -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Lachlan "Lach" Wright +// Copyright (C) 2025 by AJ "Tyron" Martinez +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/audience.c b/src/objects/audience.c index 61ec927af..534ab1b32 100644 --- a/src/objects/audience.c +++ b/src/objects/audience.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/ball-switch.cpp b/src/objects/ball-switch.cpp index 72cbae821..778c94dff 100644 --- a/src/objects/ball-switch.cpp +++ b/src/objects/ball-switch.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/ballhog.cpp b/src/objects/ballhog.cpp index d11a24bf3..1fe2ba218 100644 --- a/src/objects/ballhog.cpp +++ b/src/objects/ballhog.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -10,6 +10,8 @@ /// \file ballhog.cpp /// \brief Ballhog item code. +#include + #include "../doomdef.h" #include "../doomstat.h" #include "../info.h" diff --git a/src/objects/battle-ufo.cpp b/src/objects/battle-ufo.cpp index 07b43e15e..21045eef1 100644 --- a/src/objects/battle-ufo.cpp +++ b/src/objects/battle-ufo.cpp @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by SteelT. -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by SteelT. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/block.c b/src/objects/block.c index 9332274f9..7022f4cdb 100644 --- a/src/objects/block.c +++ b/src/objects/block.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by AJ "Tyron" Martinez. -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by AJ "Tyron" Martinez. +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/broly.cpp b/src/objects/broly.cpp index 74756c8a1..7768a7d11 100644 --- a/src/objects/broly.cpp +++ b/src/objects/broly.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/broly.hpp b/src/objects/broly.hpp index 9590c1781..985316679 100644 --- a/src/objects/broly.hpp +++ b/src/objects/broly.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/bubble-shield.cpp b/src/objects/bubble-shield.cpp new file mode 100644 index 000000000..89d60e129 --- /dev/null +++ b/src/objects/bubble-shield.cpp @@ -0,0 +1,136 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include + +#include "objects.hpp" + +#include "../m_easing.h" +#include "../m_fixed.h" +#include "../tables.h" + +using namespace srb2::objects; + +namespace +{ + +struct Bubble : Mobj +{ + static constexpr fixed_t kBaseScale = 5*FRACUNIT/4; + static constexpr fixed_t kMaxScale = 5*FRACUNIT; + static constexpr fixed_t kScaleRange = kMaxScale - kBaseScale; + + void target() = delete; + Mobj* follow() const { return Mobj::target(); } + void follow(Mobj* n) { Mobj::target(n); } + + player_t* player() const { return follow()->player; } + + bool valid() const { return Mobj::valid(follow()) && player(); } +}; + +struct Visual : Mobj +{ + void target() = delete; + Bubble* bubble() const { return Mobj::target(); } + void bubble(Bubble* n) { Mobj::target(n); } + + void extravalue1() = delete; + Fixed prev_scale() const { return Mobj::extravalue1; } + void prev_scale(Fixed n) { Mobj::extravalue1 = n; } + + bool valid() const { return Mobj::valid(bubble()) && bubble()->valid(); } + + static void spawn + ( Bubble* bubble, + statenum_t state, + int flicker, + int offset) + { + if (!bubble->valid()) + return; + + Visual* x = Mobj::spawn(bubble->pos(), MT_BUBBLESHIELD_VISUAL); + //x->scale(5 * x->scale() / 4); + x->state(state); + x->spriteyoffset(22*FRACUNIT); + x->bubble(bubble); + x->linkdraw(bubble->follow(), offset); + + if (flicker) + x->renderflags |= RF_DONTDRAW; + } + + bool tick() + { + if (!valid()) + { + remove(); + return false; + } + + move_origin(bubble()->pos()); + + renderflags = ((renderflags ^ RF_DONTDRAW) & RF_DONTDRAW); + + // ATTENTION: this object relies on the original MT_BUBBLESHIELD object for scale + fixed_t f = Fixed {bubble()->scale() / bubble()->follow()->scale() - Bubble::kBaseScale} / Fixed {Bubble::kScaleRange}; + + scale(Easing_Linear(f, + bubble()->follow()->scale() * Fixed {Bubble::kBaseScale}, + bubble()->follow()->scale() * 4)); + + if (sprite != SPR_BUBB && + sprite != SPR_BUBC && + bubble()->player()->bubblecool && + f == 0) // base size + renderflags |= RF_DONTDRAW; + + if (scale() > prev_scale()) + spritescale({ 3*FRACUNIT/2, 3*FRACUNIT/4 }); + else if (scale() < prev_scale()) + spritescale({ 3*FRACUNIT/4, 3*FRACUNIT/2 }); + else + { + if (f == FRACUNIT) // max size + { + if (leveltime & 1) + spritescale({ 3*FRACUNIT/4, 3*FRACUNIT/2 }); + else + { + spritescale({ FRACUNIT, FRACUNIT }); + renderflags |= RF_ADD; + } + } + else + spritescale({ FRACUNIT, FRACUNIT }); + } + + prev_scale(scale()); + + return true; + } +}; + +}; // namespace + +void Obj_SpawnBubbleShieldVisuals(mobj_t *bubble) +{ + Visual::spawn(static_cast(bubble), S_BUBA1, 1, 2); + Visual::spawn(static_cast(bubble), S_BUBB1, 0, 1); + Visual::spawn(static_cast(bubble), S_BUBC1, 1, -1); + Visual::spawn(static_cast(bubble), S_BUBD1, 0, -2); + Visual::spawn(static_cast(bubble), S_BUBE1, 1, -3); +} + +boolean Obj_TickBubbleShieldVisual(mobj_t *mobj) +{ + return static_cast(mobj)->tick(); +} diff --git a/src/objects/bungee.c b/src/objects/bungee.c index ccf6538b4..521f8d312 100644 --- a/src/objects/bungee.c +++ b/src/objects/bungee.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'" -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by "Lat'" +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/charge.c b/src/objects/charge.c index 973b65678..597395ca0 100644 --- a/src/objects/charge.c +++ b/src/objects/charge.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by AJ "Tyron" Martinez. -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by AJ "Tyron" Martinez. +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/checkpoint.cpp b/src/objects/checkpoint.cpp index c534b5bde..36e1eec84 100644 --- a/src/objects/checkpoint.cpp +++ b/src/objects/checkpoint.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,6 +14,8 @@ #include "../mobj_list.hpp" +#include "../core/hash_map.hpp" +#include "../core/vector.hpp" #include "../doomdef.h" #include "../doomtype.h" #include "../info.h" @@ -35,8 +37,6 @@ #include "../sounds.h" #include "../tables.h" -using std::vector; -using std::pair; using std::min; using std::max; using std::clamp; @@ -45,7 +45,6 @@ extern mobj_t* svg_checkpoints; #define checkpoint_id(o) ((o)->thing_args[0]) #define checkpoint_linetag(o) ((o)->thing_args[1]) -#define checkpoint_extralength(o) ((o)->thing_args[2]) #define checkpoint_other(o) ((o)->target) #define checkpoint_orb(o) ((o)->tracer) #define checkpoint_arm(o) ((o)->hnext) @@ -132,6 +131,7 @@ struct Checkpoint : mobj_t struct Arm : mobj_t {}; INT32 id() const { return checkpoint_id(this); } + INT32 linetag() const { return checkpoint_linetag(this); } Checkpoint* other() const { return static_cast(checkpoint_other(this)); } void other(Checkpoint* n) { P_SetTarget(&checkpoint_other(this), n); } @@ -224,7 +224,7 @@ struct Checkpoint : mobj_t speed(speed() - FixedDiv(speed() / 50, max(speed_multiplier(), 1))); } } - + if (!top_half_has_passed()) { sparkle_between(0); @@ -453,39 +453,16 @@ struct CheckpointManager auto begin() { return list_.begin(); } auto end() { return list_.end(); } - auto find_checkpoint(INT32 id) { - auto it = find_if(list_.begin(), list_.end(), [id](auto pair) { return pair.first->id() == id; }); - if (it != list_.end()) - { - return it->first; - } - return static_cast(nullptr); - } - - // auto find_pair(Checkpoint* chk) { - // pair> retpair; - // auto it = find_if(list_.begin(), list_.end(), [chk](auto pair) { return pair.first == chk; }); - // if (it != list_.end()) - // { - // retpair = *it; - // return retpair; - // } - // return static_cast>>(nullptr); - // } - - void remove_checkpoint(mobj_t* end) - { - auto chk = static_cast(end); - auto it = find_if(list_.begin(), list_.end(), [&](auto pair) { return pair.first == chk; }); - if (it != list_.end()) - { - list_.erase(it); - } - } - - void link_checkpoint(mobj_t* end) + auto find_checkpoint(INT32 id) + { + auto it = std::find_if(begin(), end(), [id](Checkpoint* chk) { return chk->id() == id; }); + return it != end() ? *it : nullptr; + } + + void remove_checkpoint(Checkpoint* end) { list_.erase(end); } + + void link_checkpoint(Checkpoint* chk) { - auto chk = static_cast(end); auto id = chk->id(); if (chk->spawnpoint && id == 0) { @@ -517,29 +494,46 @@ struct CheckpointManager } else // Checkpoint isn't in the list, find any associated tagged lines and make the pair { - vector checklines; - if (checkpoint_linetag(chk)) - { - INT32 li; - INT32 tag = checkpoint_linetag(chk); - TAG_ITER_LINES(tag, li) - { - line_t* line = lines + li; - checklines.push_back(line); - } - } - list_.emplace_back(chk, move(checklines)); + if (chk->linetag()) + lines_.try_emplace(chk->linetag(), tagged_lines(chk->linetag())); + list_.push_front(chk); + count_ += 1; // Mobjlist can't have a count on it, so we keep it here } chk->gingerbread(); } - void clear() { list_.clear(); } + void clear() + { + lines_.clear(); + list_.clear(); + count_ = 0; + } - auto count() { return list_.size(); } + auto count() { return count_; } + + const srb2::Vector* lines_for(const Checkpoint* chk) const + { + auto it = lines_.find(chk->linetag()); + return it != lines_.end() ? &it->second : nullptr; + } private: - vector>> list_; + INT32 count_; + srb2::MobjList list_; + srb2::HashMap> lines_; + + static srb2::Vector tagged_lines(INT32 tag) + { + srb2::Vector checklines; + INT32 li; + TAG_ITER_LINES(tag, li) + { + line_t* line = lines + li; + checklines.push_back(line); + } + return checklines; + } }; CheckpointManager g_checkpoints; @@ -548,13 +542,13 @@ CheckpointManager g_checkpoints; void Obj_LinkCheckpoint(mobj_t* end) { - g_checkpoints.link_checkpoint(end); + g_checkpoints.link_checkpoint(static_cast(end)); } void Obj_UnlinkCheckpoint(mobj_t* end) { auto chk = static_cast(end); - g_checkpoints.remove_checkpoint(end); + g_checkpoints.remove_checkpoint(chk); P_RemoveMobj(chk->orb()); P_RemoveMobj(chk->arm()); } @@ -571,48 +565,52 @@ void Obj_CheckpointThink(mobj_t* end) chk->animate(); } -void __attribute__((optimize("O0"))) Obj_CrossCheckpoints(player_t* player, fixed_t old_x, fixed_t old_y) +void Obj_CrossCheckpoints(player_t* player, fixed_t old_x, fixed_t old_y) { LineOnDemand ray(old_x, old_y, player->mo->x, player->mo->y, player->mo->radius); - auto it = find_if( + auto it = std::find_if( g_checkpoints.begin(), g_checkpoints.end(), - [&](auto chkpair) + [&](Checkpoint* chk) { - Checkpoint* chk = chkpair.first; if (!chk->valid()) { return false; } - LineOnDemand* gate; + const srb2::Vector* lines = g_checkpoints.lines_for(chk); + INT32 side; + INT32 oldside; - if (chkpair.second.empty()) + if (!lines || lines->empty()) { LineOnDemand dyngate = chk->crossing_line(); if (!ray.overlaps(dyngate)) return false; - gate = &dyngate; + + side = P_PointOnLineSide(player->mo->x, player->mo->y, &dyngate); + oldside = P_PointOnLineSide(old_x, old_y, &dyngate); } - else + else { - auto it = find_if( - chkpair.second.begin(), - chkpair.second.end(), + auto it = std::find_if( + lines->begin(), + lines->end(), [&](const line_t* line) { return ray.overlaps(*line); } ); - - if (it == chkpair.second.end()) + + if (it == lines->end()) { return false; } line_t* line = *it; - gate = static_cast(line); + side = P_PointOnLineSide(player->mo->x, player->mo->y, line); + oldside = P_PointOnLineSide(old_x, old_y, line); } // Check if the bounding boxes of the two lines @@ -621,14 +619,11 @@ void __attribute__((optimize("O0"))) Obj_CrossCheckpoints(player_t* player, fixe // but thankfully that doesn't seem to happen, under // normal circumstances. - INT32 side = P_PointOnLineSide(player->mo->x, player->mo->y, gate); - INT32 oldside = P_PointOnLineSide(old_x, old_y, gate); - if (side == oldside) { // Did not cross. return false; - + } return true; @@ -640,7 +635,7 @@ void __attribute__((optimize("O0"))) Obj_CrossCheckpoints(player_t* player, fixe return; } - Checkpoint* chk = it->first; + Checkpoint* chk = *it; if (player->checkpointId == chk->id()) { @@ -657,9 +652,8 @@ void __attribute__((optimize("O0"))) Obj_CrossCheckpoints(player_t* player, fixe if (gametyperules & GTR_CHECKPOINTS) { - for (auto chkpair : g_checkpoints) + for (Checkpoint* chk : g_checkpoints) { - Checkpoint* chk = chkpair.first; if (chk->valid()) { chk->untwirl(); @@ -672,20 +666,7 @@ void __attribute__((optimize("O0"))) Obj_CrossCheckpoints(player_t* player, fixe player->checkpointId = chk->id(); - if (D_NumPlayersInRace() > 1 && !K_IsPlayerLosing(player)) - { - if (player->position == 1) - { - player->lapPoints += 2; - } - else - { - player->lapPoints += 1; - } - } - K_CheckpointCrossAward(player); - player->gradingpointnum++; K_UpdatePowerLevels(player, player->laps, false); } @@ -741,13 +722,12 @@ void Obj_ClearCheckpoints() void Obj_DeactivateCheckpoints() { - for (auto chkpair : g_checkpoints) + for (Checkpoint* chk : g_checkpoints) { - Checkpoint* chk = chkpair.first; if (chk->valid()) { chk->untwirl(); chk->other()->untwirl(); } } -} \ No newline at end of file +} diff --git a/src/objects/cloud.c b/src/objects/cloud.c index ad1602cb2..045d360fc 100644 --- a/src/objects/cloud.c +++ b/src/objects/cloud.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'" -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by "Lat'" +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/crate.cpp b/src/objects/crate.cpp index 831a85488..bfea4d900 100644 --- a/src/objects/crate.cpp +++ b/src/objects/crate.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/dash-rings.c b/src/objects/dash-rings.c index 89fb81a4e..6803629d1 100644 --- a/src/objects/dash-rings.c +++ b/src/objects/dash-rings.c @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'" -// Copyright (C) 2024 by Lachlan "Lach" Wright -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by "Lat'" +// Copyright (C) 2025 by Lachlan "Lach" Wright +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -188,6 +188,7 @@ static void DashRingLaunch(player_t *player, mobj_t *ring) player->flashing = 0; player->fastfall = 0; K_TumbleInterrupt(player); + player->transfer = 0; switch (ring->extravalue1) { diff --git a/src/objects/destroyed-kart.cpp b/src/objects/destroyed-kart.cpp index b029d8304..c6a5a0946 100644 --- a/src/objects/destroyed-kart.cpp +++ b/src/objects/destroyed-kart.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -309,15 +309,34 @@ struct Kart : Mobj return true; } + if (health <= 1) { return false; } + Mobj* p = player(); + bool pValid = Mobj::valid(p) && p->player; + bool hasCustomHusk = pValid && skins[p->player->skin].sprites[SPR2_DKRT].numframes; + + if(hasCustomHusk) + { + skin = (void*)(&skins[p->player->skin]); + frame = 0; + } + Particle::spew(this); - scale(3 * scale() / 2); + scale(3*scale()/2); + + if(hasCustomHusk){ + flags |= MF_NOSQUISH; //K_Squish() automates spritexscale/spriteyscale & this flag prevents that at the cost of no squish visual when the kart husk hits the ground + fixed_t huskScale = FixedDiv(mapobjectscale, scale()); + spritexscale(FixedMul(spritexscale(), huskScale)); + spriteyscale(FixedMul(spriteyscale(), huskScale)); + } + health = 1; - state(S_KART_LEFTOVER_NOTIRES); + state(!hasCustomHusk ? S_KART_LEFTOVER_NOTIRES : S_KART_LEFTOVER_CUSTOM); cooldown(20); burning(burn_duration()); @@ -326,9 +345,9 @@ struct Kart : Mobj voice(sfx_die00); } - if (Mobj* p = player(); Mobj::valid(p)) + if(pValid) { - if (p->player && skins[p->player->skin].flags & SF_BADNIK) + if((skins[p->player->skin].flags & SF_BADNIK)) { P_SpawnBadnikExplosion(p); p->spritescale({2*FRACUNIT, 2*FRACUNIT}); @@ -446,7 +465,7 @@ private: P_PlayDeathSound(p); } - // First tick after hitlag: destroyed kart appears! + // First tick after hitlag: destroyed kart appears! State will change away from S_INVISIBLE inside destroy() where S_INVISIBLE was set in static spawn() if (state()->num() == S_INVISIBLE) { destroy(); diff --git a/src/objects/dlzothers.c b/src/objects/dlzothers.c index 1ba642cda..c7b9b043b 100644 --- a/src/objects/dlzothers.c +++ b/src/objects/dlzothers.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'" -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by "Lat'" +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/dlzrocket.c b/src/objects/dlzrocket.c index 36547c369..4d299fbd6 100644 --- a/src/objects/dlzrocket.c +++ b/src/objects/dlzrocket.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'" -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by "Lat'" +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/dlzseasaw.c b/src/objects/dlzseasaw.c index 4ebd37050..203c242a0 100644 --- a/src/objects/dlzseasaw.c +++ b/src/objects/dlzseasaw.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'" -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by "Lat'" +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/drop-target.c b/src/objects/drop-target.c index 795945949..a1470b338 100644 --- a/src/objects/drop-target.c +++ b/src/objects/drop-target.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/duel-bomb.c b/src/objects/duel-bomb.c index de57a1d07..64c63f96f 100644 --- a/src/objects/duel-bomb.c +++ b/src/objects/duel-bomb.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/eggball.c b/src/objects/eggball.c index d3de0d375..e5b01d62c 100644 --- a/src/objects/eggball.c +++ b/src/objects/eggball.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'" -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by "Lat'" +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/emerald.c b/src/objects/emerald.c index 44fdff118..2006a1406 100644 --- a/src/objects/emerald.c +++ b/src/objects/emerald.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/emz-faucet.cpp b/src/objects/emz-faucet.cpp index e8f7a068a..745a4b6eb 100644 --- a/src/objects/emz-faucet.cpp +++ b/src/objects/emz-faucet.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/flame-shield.cpp b/src/objects/flame-shield.cpp new file mode 100644 index 000000000..bed96c97a --- /dev/null +++ b/src/objects/flame-shield.cpp @@ -0,0 +1,82 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "objects.hpp" + +#include "../tables.h" + +using namespace srb2::objects; + +namespace +{ + +struct Shield : Mobj +{ + void target() = delete; + Mobj* follow() const { return Mobj::target(); } + void follow(Mobj* n) { Mobj::target(n); } + + player_t* player() const { return follow()->player; } + + bool valid() const { return Mobj::valid(follow()) && player(); } +}; + +struct Visual : Mobj +{ + void target() = delete; + Shield* shield() const { return Mobj::target(); } + void shield(Shield* n) { Mobj::target(n); } + + bool valid() const { return Mobj::valid(shield()) && shield()->valid(); } + + static void spawn + ( Shield* shield, + statenum_t state, + int offset) + { + if (!shield->valid()) + return; + + Visual* x = Mobj::spawn(shield->pos(), MT_FLAMESHIELD_VISUAL); + x->scale(5 * shield->follow()->scale() / 4); + x->state(state); + x->shield(shield); + x->linkdraw(shield->follow(), offset); + } + + bool tick() + { + if (!valid()) + { + remove(); + return false; + } + + move_origin(shield()->pos()); + + renderflags = (renderflags & ~RF_DONTDRAW) | + (shield()->state()->num() == S_INVISIBLE ? 0 : RF_DONTDRAW); + + return true; + } +}; + +}; // namespace + +void Obj_SpawnFlameShieldVisuals(mobj_t *shield) +{ + Visual::spawn(static_cast(shield), S_FLMA1, 1); + Visual::spawn(static_cast(shield), S_FLMB1, -1); +} + +boolean Obj_TickFlameShieldVisual(mobj_t *mobj) +{ + return static_cast(mobj)->tick(); +} diff --git a/src/objects/flybot767.c b/src/objects/flybot767.c new file mode 100644 index 000000000..790276387 --- /dev/null +++ b/src/objects/flybot767.c @@ -0,0 +1,142 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Lachlan "Lach" Wright +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file flybot767.c +/// \brief Flybot767 object code. + +#include "../p_local.h" +#include "../k_kart.h" +#include "../k_objects.h" +#include "../s_sound.h" +#include "../m_easing.h" + +#define FLYBOT_QUANTITY 2 +#define FLYBOT_VERTICAL_OFFSET (16 * FRACUNIT) +#define FLYBOT_BOB_AMPLITUDE (16 * FRACUNIT) +#define FLYBOT_BOB_FREQUENCY (ANG15) +#define FLYBOT_FADE_STARTTIME (2 * TICRATE) +#define FLYBOT_SCALE (17 * FRACUNIT / 20) +static const fixed_t PI = 355 * FRACUNIT / 113; + +static fixed_t SetFlybotZ(mobj_t *flybot) +{ + flybot->z = FixedMul(mapobjectscale, FLYBOT_VERTICAL_OFFSET) + FixedMul(mapobjectscale, P_ReturnThrustX(NULL, flybot->movedir, FLYBOT_BOB_AMPLITUDE)); + if (flybot->eflags & MFE_VERTICALFLIP) + { + flybot->z = -flybot->z - flybot->height; + } + else + { + flybot->z += flybot->target->height; + } + flybot->z += flybot->target->z; + return flybot->z; +} + +void Obj_SpawnFlybotsForPlayer(player_t *player) +{ + UINT8 i; + mobj_t *mo = player->mo; + fixed_t radius = mo->radius; + + for (i = 0; i < FLYBOT_QUANTITY; i++) + { + angle_t angle = mo->angle + ANGLE_90 + FixedAngle(i * 360 * FRACUNIT / FLYBOT_QUANTITY); + mobj_t *flybot = P_SpawnMobj( + mo->x + P_ReturnThrustX(NULL, angle, radius), + mo->y + P_ReturnThrustY(NULL, angle, radius), + mo->z, + MT_FLYBOT767 + ); + + P_InstaScale(flybot, flybot->old_scale = FixedMul(mapobjectscale, FLYBOT_SCALE)); + P_SetTarget(&flybot->target, mo); + flybot->eflags |= mo->eflags & MFE_VERTICALFLIP; + flybot->movedir = flybot->old_angle = flybot->angle = angle + ANGLE_90; + flybot->old_z = SetFlybotZ(flybot); + flybot->renderflags |= (i * RF_DONTDRAW); + } +} + +void Obj_FlybotThink(mobj_t *flybot) +{ + UINT16 stunned = UINT16_MAX; + angle_t deltaAngle, angle; + fixed_t radius, circumference; + fixed_t speed = FixedMul(mapobjectscale, flybot->info->speed); + mobj_t *mo = flybot->target; + + if (P_MobjWasRemoved(mo)) + { + P_KillMobj(flybot, NULL, NULL, 0); + return; + } + + if (mo->player) + { + if (((stunned = mo->player->stunned & 0x7FFF) == 0) || (mo->player->playerstate == PST_DEAD)) + { + P_KillMobj(flybot, NULL, NULL, 0); + return; + } + } + + flybot->frame = flybot->frame & ~FF_TRANSMASK; + if (stunned < FLYBOT_FADE_STARTTIME) + { + flybot->frame |= Easing_InCubic(FixedDiv(stunned, FLYBOT_FADE_STARTTIME), 7, 1) << FF_TRANSSHIFT; + } + + flybot->eflags = (flybot->eflags & ~MFE_VERTICALFLIP) | (mo->eflags & MFE_VERTICALFLIP); + flybot->movedir += FLYBOT_BOB_FREQUENCY; + flybot->renderflags ^= RF_DONTDRAW; + + radius = mo->radius; + circumference = 2 * FixedMul(PI, radius); + deltaAngle = FixedAngle(FixedMul(FixedDiv(speed, circumference), 360 * FRACUNIT)); + flybot->angle += deltaAngle; + angle = flybot->angle - ANGLE_90; + + P_MoveOrigin(flybot, + mo->x + P_ReturnThrustX(NULL, angle, radius), + mo->y + P_ReturnThrustY(NULL, angle, radius), + SetFlybotZ(flybot) + ); +} + +void Obj_FlybotDeath(mobj_t *flybot) +{ + UINT8 i; + angle_t angle = 0; + fixed_t hThrust = 4*mapobjectscale, vThrust = 4*mapobjectscale; + vector3_t mom = {0, 0, 0}; + mobj_t *mo = flybot->target; + + if (!P_MobjWasRemoved(mo)) + { + mom.x = mo->momx; + mom.y = mo->momy; + mom.z = mo->momz; + //S_StartSound(mo, flybot->info->deathsound); + } + + for (i = 0; i < 4; i++) + { + mo = P_SpawnMobjFromMobj(flybot, 0, 0, 0, MT_PARTICLE); + P_SetMobjState(mo, S_SPINDASHDUST); + mo->flags |= MF_NOSQUISH; + mo->renderflags |= RF_FULLBRIGHT; + mo->momx = mom.x; + mo->momy = mom.y; + mo->momz = mom.z + vThrust; + P_Thrust(mo, angle, hThrust); + vThrust *= -1; + angle += ANGLE_90; + } +} diff --git a/src/objects/frost-thrower.cpp b/src/objects/frost-thrower.cpp index 5c03d520f..c1566b2ce 100644 --- a/src/objects/frost-thrower.cpp +++ b/src/objects/frost-thrower.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/fuel.cpp b/src/objects/fuel.cpp index d49994ad5..cf45f1f1a 100644 --- a/src/objects/fuel.cpp +++ b/src/objects/fuel.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/gachabom-rebound.cpp b/src/objects/gachabom-rebound.cpp index f278aa58e..60c0d61f7 100644 --- a/src/objects/gachabom-rebound.cpp +++ b/src/objects/gachabom-rebound.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/gardentop.c b/src/objects/gardentop.c index cb7fea0ea..01f738218 100644 --- a/src/objects/gardentop.c +++ b/src/objects/gardentop.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/gpzseasaw.c b/src/objects/gpzseasaw.c index b36a821a1..dafc57610 100644 --- a/src/objects/gpzseasaw.c +++ b/src/objects/gpzseasaw.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'" -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by "Lat'" +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/hyudoro.c b/src/objects/hyudoro.c index 588ab5e04..c43317928 100644 --- a/src/objects/hyudoro.c +++ b/src/objects/hyudoro.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -599,7 +599,8 @@ hyudoro_patrol_hit_player P_SetTarget(&hyudoro_target(hyu), master); - K_SpawnAmps(master->player, K_PvPAmpReward(20, master->player, player), toucher); + if (master && !P_MobjWasRemoved(master)) + K_SpawnAmps(master->player, K_PvPAmpReward(20, master->player, player), toucher); if (center) P_RemoveMobj(center); @@ -629,7 +630,7 @@ award_immediately (mobj_t *hyu) return false; } - if (!P_CanPickupItem(player, 1)) + if (!P_CanPickupItem(player, PICKUP_ITEMBOX)) return false; // Prevent receiving any more items or even stacked diff --git a/src/objects/instawhip.c b/src/objects/instawhip.c index bc7657f11..4f07d3891 100644 --- a/src/objects/instawhip.c +++ b/src/objects/instawhip.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by AJ "Tyron" Martinez. -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by AJ "Tyron" Martinez. +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/item-debris.c b/src/objects/item-debris.c index e60ffc153..201c9eee7 100644 --- a/src/objects/item-debris.c +++ b/src/objects/item-debris.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/item-spot.c b/src/objects/item-spot.c index 759776d5f..c1441a44f 100644 --- a/src/objects/item-spot.c +++ b/src/objects/item-spot.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/ivoball.cpp b/src/objects/ivoball.cpp index 653a48987..2dd5a0581 100644 --- a/src/objects/ivoball.cpp +++ b/src/objects/ivoball.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/jawz.c b/src/objects/jawz.c index ee91462fa..d13cb0514 100644 --- a/src/objects/jawz.c +++ b/src/objects/jawz.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/lightning-shield.cpp b/src/objects/lightning-shield.cpp new file mode 100644 index 000000000..e08e4529c --- /dev/null +++ b/src/objects/lightning-shield.cpp @@ -0,0 +1,74 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "objects.hpp" + +#include "../tables.h" + +using namespace srb2::objects; + +namespace +{ + +struct Shield : Mobj +{ + void target() = delete; + Mobj* follow() const { return Mobj::target(); } + void follow(Mobj* n) { Mobj::target(n); } + + bool valid() const { return Mobj::valid(follow()); } +}; + +struct Visual : Mobj +{ + void target() = delete; + Shield* shield() const { return Mobj::target(); } + void shield(Shield* n) { Mobj::target(n); } + + bool valid() const { return Mobj::valid(shield()) && shield()->valid(); } + + static void spawn(Shield* shield) + { + if (!shield->valid()) + return; + + Visual* x = Mobj::spawn(shield->pos(), MT_LIGHTNINGSHIELD_VISUAL); + x->scale(5 * shield->follow()->scale() / 4); + x->shield(shield); + x->linkdraw(shield->follow()); + x->tick(); + } + + bool tick() + { + if (!valid()) + { + remove(); + return false; + } + + move_origin(shield()->pos()); + dispoffset = state()->num() == S_THNB1 ? -1 : 1; + + return true; + } +}; + +}; // namespace + +void Obj_SpawnLightningShieldVisuals(mobj_t *shield) +{ + Visual::spawn(static_cast(shield)); +} + +boolean Obj_TickLightningShieldVisual(mobj_t *mobj) +{ + return static_cast(mobj)->tick(); +} diff --git a/src/objects/loops.cpp b/src/objects/loops.cpp index f2e26a1ac..ddb401891 100644 --- a/src/objects/loops.cpp +++ b/src/objects/loops.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/manta-ring.c b/src/objects/manta-ring.c index d45bf9cb9..ccc6763c6 100644 --- a/src/objects/manta-ring.c +++ b/src/objects/manta-ring.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/mega-barrier.cpp b/src/objects/mega-barrier.cpp index 95f084e02..49c33daff 100644 --- a/src/objects/mega-barrier.cpp +++ b/src/objects/mega-barrier.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/monitor.c b/src/objects/monitor.c index 384410f13..f39c8d677 100644 --- a/src/objects/monitor.c +++ b/src/objects/monitor.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/objects.hpp b/src/objects/objects.hpp index 18b97184b..24af55e22 100644 --- a/src/objects/objects.hpp +++ b/src/objects/objects.hpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/orbinaut.c b/src/objects/orbinaut.c index 7c47cb30b..501bc0e2d 100644 --- a/src/objects/orbinaut.c +++ b/src/objects/orbinaut.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -190,6 +190,9 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) return true; } + if (K_TryPickMeUp(t1, t2)) + return true; + if (t1->type == MT_GARDENTOP) { tumbleitem = true; @@ -226,7 +229,8 @@ boolean Obj_OrbinautJawzCollide(mobj_t *t1, mobj_t *t2) { P_DamageMobj(t2, t1, t1->target, 1, DMG_WOMBO | (tumbleitem ? DMG_TUMBLE : DMG_WIPEOUT)); - K_KartBouncing(t2, t1); + if (tumbleitem || (gametyperules & GTR_SPHERES) || !t2->player->tripwireLeniency) + K_KartBouncing(t2, t1); } S_StartSound(t2, sfx_s3k7b); diff --git a/src/objects/powerup-aura.cpp b/src/objects/powerup-aura.cpp index ef716d62d..7666b4932 100644 --- a/src/objects/powerup-aura.cpp +++ b/src/objects/powerup-aura.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/powerup-spinner.cpp b/src/objects/powerup-spinner.cpp index 100c1bf27..da8d504e5 100644 --- a/src/objects/powerup-spinner.cpp +++ b/src/objects/powerup-spinner.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/pulley.cpp b/src/objects/pulley.cpp index 16fcb4b72..7e22f85f3 100644 --- a/src/objects/pulley.cpp +++ b/src/objects/pulley.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'" -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by "Lat'" +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/random-item.c b/src/objects/random-item.c index b6e79aad5..70f565046 100644 --- a/src/objects/random-item.c +++ b/src/objects/random-item.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -47,11 +47,11 @@ static player_t *GetItemBoxPlayer(mobj_t *mobj) continue; } - // Always use normal item box rules -- could pass in "2" for fakes but they blend in better like this - if (P_CanPickupItem(&players[i], 1)) + // Always use normal item box rules -- could pass in "PICKUP_EGGBOX" for fakes but they blend in better like this + if (P_CanPickupItem(&players[i], PICKUP_ITEMBOX)) { // Check for players who can take this pickup, but won't be allowed to (antifarming) - UINT8 mytype = (mobj->flags2 & MF2_BOSSDEAD) ? 2 : 1; + UINT8 mytype = (mobj->flags2 & MF2_BOSSDEAD) ? CHEESE_RINGBOX : CHEESE_ITEMBOX; if (P_IsPickupCheesy(&players[i], mytype)) continue; @@ -115,6 +115,38 @@ void Obj_RandomItemVisuals(mobj_t *mobj) if (mobj->type != MT_RANDOMITEM) return; + // Fade items in as we cross the first checkpoint, but don't touch their visibility otherwise! + if (!((mobj->flags & MF_NOCLIPTHING) || mobj->fuse)) + { + UINT8 maxgrab = 0; + + for (UINT8 i = 0; i <= r_splitscreen; i++) + { + maxgrab = max(maxgrab, players[displayplayers[i]].cangrabitems); + } + + if (maxgrab == 0) + mobj->renderflags |= RF_DONTDRAW; + else + mobj->renderflags &= ~RF_DONTDRAW; + + if (maxgrab > 0 && maxgrab <= EARLY_ITEM_FLICKER) + { + UINT8 maxtranslevel = NUMTRANSMAPS; + + UINT8 trans = maxgrab; + if (trans > maxtranslevel) + trans = maxtranslevel; + trans = NUMTRANSMAPS - trans; + + mobj->renderflags &= ~(RF_TRANSMASK); + + if (trans != 0) + mobj->renderflags |= (trans << RF_TRANSSHIFT); + } + } + + // Respawn flow, documented by a dumb asshole: // P_TouchSpecialThing -> P_ItemPop sets fuse, NOCLIPTHING and DONTDRAW. // P_FuseThink does visual flicker, and when fuse is 0, unsets NOCLIPTHING/DONTDRAW/etc... diff --git a/src/objects/rideroid.c b/src/objects/rideroid.c index fa40cc06e..3aead5faa 100644 --- a/src/objects/rideroid.c +++ b/src/objects/rideroid.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'" -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by "Lat'" +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/ring-shooter.c b/src/objects/ring-shooter.c index e43611552..83613d3c5 100644 --- a/src/objects/ring-shooter.c +++ b/src/objects/ring-shooter.c @@ -1,8 +1,8 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Lachlan "Lach" Wright -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Lachlan "Lach" Wright +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -430,10 +430,6 @@ void Obj_PlayerUsedRingShooter(mobj_t *base, player_t *player) return; } - // The original player should no longer have control over it, - // if they are using it via releasing. - RemoveRingShooterPointer(base); - // Respawn using the respawner's karted value. if (rs_base_karted(base) > 0) { diff --git a/src/objects/rocks.cpp b/src/objects/rocks.cpp index 7b88b5c74..8bf400838 100644 --- a/src/objects/rocks.cpp +++ b/src/objects/rocks.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/sealed-star.c b/src/objects/sealed-star.c index 994ba838d..ccf136800 100644 --- a/src/objects/sealed-star.c +++ b/src/objects/sealed-star.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/servant-hand.c b/src/objects/servant-hand.c index c140a0139..bb82e26d7 100644 --- a/src/objects/servant-hand.c +++ b/src/objects/servant-hand.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/shadow.cpp b/src/objects/shadow.cpp index 6cfc7a042..5327fa179 100644 --- a/src/objects/shadow.cpp +++ b/src/objects/shadow.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/shrink.c b/src/objects/shrink.c index d289dcf6c..269c46f40 100644 --- a/src/objects/shrink.c +++ b/src/objects/shrink.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/sneaker-panel.c b/src/objects/sneaker-panel.c index fba8f5025..7824bbedf 100644 --- a/src/objects/sneaker-panel.c +++ b/src/objects/sneaker-panel.c @@ -1,9 +1,9 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Lachlan "Lach" Wright -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Lachlan "Lach" Wright +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/spb.c b/src/objects/spb.c index e0375408a..24d98ed1d 100644 --- a/src/objects/spb.c +++ b/src/objects/spb.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/spear.cpp b/src/objects/spear.cpp index 6d187738f..c57e554b0 100644 --- a/src/objects/spear.cpp +++ b/src/objects/spear.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/super-flicky.cpp b/src/objects/super-flicky.cpp index aa5e659f4..28f956308 100644 --- a/src/objects/super-flicky.cpp +++ b/src/objects/super-flicky.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -727,6 +727,12 @@ void Controller::search() continue; } + // Do not target someone on the same team as our owner. + if (G_SameTeam(player, source()->player) == true) + { + continue; + } + // Target is already being hunted. if (player->flickyAttacker) { diff --git a/src/objects/talk-point.cpp b/src/objects/talk-point.cpp index 99befbd02..4d09ae6d7 100644 --- a/src/objects/talk-point.cpp +++ b/src/objects/talk-point.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/trick-balloon.c b/src/objects/trick-balloon.c index b322cf2c6..bd36ecf34 100644 --- a/src/objects/trick-balloon.c +++ b/src/objects/trick-balloon.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/ufo.c b/src/objects/ufo.c index 6ef391b18..f753874c3 100644 --- a/src/objects/ufo.c +++ b/src/objects/ufo.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/versus/arena.c b/src/objects/versus/arena.c index d02f606b4..e75a9fc30 100644 --- a/src/objects/versus/arena.c +++ b/src/objects/versus/arena.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/versus/blendeye.c b/src/objects/versus/blendeye.c index 2ae76977b..435f7f278 100644 --- a/src/objects/versus/blendeye.c +++ b/src/objects/versus/blendeye.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/waterfall-particle.c b/src/objects/waterfall-particle.c index 7928c2c9a..d52607bde 100644 --- a/src/objects/waterfall-particle.c +++ b/src/objects/waterfall-particle.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/wpzothers.c b/src/objects/wpzothers.c index f6ab5870f..e1f667202 100644 --- a/src/objects/wpzothers.c +++ b/src/objects/wpzothers.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'" -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by "Lat'" +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/objects/wpzturbine.c b/src/objects/wpzturbine.c index 440343341..f86f11da6 100644 --- a/src/objects/wpzturbine.c +++ b/src/objects/wpzturbine.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by "Lat'" -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by "Lat'" +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -213,6 +213,12 @@ void Obj_playerWPZTurbine(player_t *p) return; // wtf happened } + if (t->type != MT_WATERPALACETURBINE) + { + p->turbine = false; + return; + } + mt = t->spawnpoint; opt1 = (mt->thing_args[0] != 0); diff --git a/src/p_ceilng.c b/src/p_ceilng.c index 4da793777..e3bbe6acc 100644 --- a/src/p_ceilng.c +++ b/src/p_ceilng.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -12,6 +12,7 @@ /// \file p_ceilng.c /// \brief Ceiling aninmation (lowering, crushing, raising) +#include "d_think.h" #include "doomdef.h" #include "p_local.h" #include "r_fps.h" @@ -273,7 +274,9 @@ static ceiling_t *CreateCeilingThinker(sector_t *sec) return NULL; } - ceiling = Z_Calloc(sizeof (*ceiling), PU_LEVSPEC, NULL); + ceiling = Z_LevelPoolCalloc(sizeof(*ceiling)); + ceiling->thinker.alloctype = TAT_LEVELPOOL; + ceiling->thinker.size = sizeof(*ceiling); P_AddThinker(THINK_MAIN, &ceiling->thinker); sec->ceilingdata = ceiling; @@ -597,7 +600,9 @@ static ceiling_t *CreateCrushThinker(sector_t *sec) return NULL; } - ceiling = Z_Calloc(sizeof (*ceiling), PU_LEVSPEC, NULL); + ceiling = Z_LevelPoolCalloc(sizeof(*ceiling)); + ceiling->thinker.alloctype = TAT_LEVELPOOL; + ceiling->thinker.size = sizeof(*ceiling); P_AddThinker(THINK_MAIN, &ceiling->thinker); sec->ceilingdata = ceiling; diff --git a/src/p_deepcopy.cpp b/src/p_deepcopy.cpp new file mode 100644 index 000000000..dd4a64c96 --- /dev/null +++ b/src/p_deepcopy.cpp @@ -0,0 +1,282 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file p_deepcopy.cpp +/// \brief Methods for deep copying + +#include "p_deepcopy.h" + +#include "typedef.h" +#include "z_zone.h" +#include "taglist.h" +#include "r_defs.h" + +/*-------------------------------------------------- + static void P_DeepCopy(Type *target, Type *source, void (*callback)(Type *, Type *)) + + Make a deep copy of a struct, by using memcpy + for an initial shallow copy, and a callback + function to handle any memory addresses. + + Input Arguments:- + Type: The type of the structs. + target: The struct to copy into. + source: The struct to copy from. + callback: A callback function, intended to replace + pointers after the initial shallow copy. + + Return:- + N/A +--------------------------------------------------*/ + +template +static void P_DeepCopy(Type *target, Type *source, void (*callback)(Type *, Type *)) +{ + memcpy(target, source, sizeof(Type)); + + if (callback != nullptr) + { + callback(target, source); + } +} + +/*-------------------------------------------------- + static void P_DeepCopyArray(Type **target_array, Type **source_array, size_t source_len, void (*callback)(Type *, Type *)) + + Make a deep copy of an array of structs, by using + memcpy for an initial shallow copy, and a callback + function to handle any memory addresses for each + individual struct. + + Input Arguments:- + Type: The type of the structs. + target_array: The start of the array to copy into. + This will be allocated by this function, so + it should be freed beforehand. + source_array: The start of the array to copy from. + source_len: The length of the array to copy from. + callback: A callback function, intended to replace + pointers after the initial shallow copy. + + Return:- + N/A +--------------------------------------------------*/ + +template +static void P_DeepCopyArray(Type **target_array, Type **source_array, size_t source_len, void (*callback)(Type *, Type *)) +{ + const size_t source_total = source_len * sizeof(Type); + + *target_array = static_cast(Z_Calloc(source_total, PU_LEVEL, nullptr)); + memcpy(*target_array, *source_array, source_total); + + if (callback != nullptr) + { + for (size_t i = 0; i < source_len; i++) + { + Type *target = &(*target_array)[i]; + Type *source = &(*source_array)[i]; + + callback(target, source); + } + } +} + +/*-------------------------------------------------- + static void copy_taglist_tags(taglist_t *target, taglist_t *source) + + Make a deep copy of taglist's tags fields. + Used as a helper function for other callbacks, + does not work as a full deep copy of taglist_t + on its own. + + Input Arguments:- + target: The struct to copy into. + source: The struct to copy from. + + Return:- + N/A +--------------------------------------------------*/ + +static void copy_taglist_tags(taglist_t *target, taglist_t *source) +{ + if (source->count) + { + target->tags = static_cast( + memcpy( + Z_Malloc( + source->count * sizeof(mtag_t), + PU_LEVEL, + nullptr + ), + source->tags, + source->count * sizeof(mtag_t) + ) + ); + } +} + +/*-------------------------------------------------- + static void copy_stringarg(char **target, const char *source) + + Make a deep copy of a string argument. + + Input Arguments:- + target: Double pointer to the string to copy to. + source: The string to copy from. + + Return:- + N/A +--------------------------------------------------*/ + +static void copy_stringarg(char **target, const char *source) +{ + // stringarg memory is really freaking touchy, + // so I am being careful and being explicit + // on how it is copied over instead of just + // using strcpy or smth + + size_t len = 0; + if (source != nullptr) + { + len = strlen(source); + } + + if (len > 0) + { + *target = static_cast( + memcpy( + Z_Malloc( + len + 1, + PU_LEVEL, + nullptr + ), + source, + len + ) + ); + (*target)[len] = '\0'; + } +} + +/*-------------------------------------------------- + static void copy_sector_callback(sector_t *target, sector_t *source) + + Handles memory addresses after creating a shallow copy + of a sector_t, to turn it into a deep copy. + + Input Arguments:- + target: The struct to copy into. + source: The struct to copy from. + + Return:- + N/A +--------------------------------------------------*/ + +static void copy_sector_callback(sector_t *target, sector_t *source) +{ + // (Not a true deep copy until all of the memory addresses are accounted for.) + copy_taglist_tags(&target->tags, &source->tags); + + for (size_t i = 0; i < NUM_SCRIPT_STRINGARGS; i++) + { + copy_stringarg(&target->stringargs[i], source->stringargs[i]); + } +} + +/*-------------------------------------------------- + void P_DeepCopySector(sector_t *target, sector_t *source) + + See header file for description. +--------------------------------------------------*/ + +void P_DeepCopySector(sector_t *target, sector_t *source) +{ + P_DeepCopy(target, source, copy_sector_callback); +} + +/*-------------------------------------------------- + void P_DeepCopySectors(sector_t **target_array, sector_t **source_array, size_t source_len) + + See header file for description. +--------------------------------------------------*/ + +void P_DeepCopySectors(sector_t **target_array, sector_t **source_array, size_t source_len) +{ + P_DeepCopyArray(target_array, source_array, source_len, copy_sector_callback); +} + +/*-------------------------------------------------- + static void copy_line_callback(line_t *target, line_t *source) + + Handles memory addresses after creating a shallow copy + of a line_t, to turn it into a deep copy. + + Input Arguments:- + target: The struct to copy into. + source: The struct to copy from. + + Return:- + N/A +--------------------------------------------------*/ + +static void copy_line_callback(line_t *target, line_t *source) +{ + // (Not a true deep copy until all of the memory addresses are accounted for.) + copy_taglist_tags(&target->tags, &source->tags); + + for (size_t i = 0; i < NUM_SCRIPT_STRINGARGS; i++) + { + copy_stringarg(&target->stringargs[i], source->stringargs[i]); + } +} + +/*-------------------------------------------------- + void P_DeepCopyLine(line_t *target, line_t *source) + + See header file for description. +--------------------------------------------------*/ + +void P_DeepCopyLine(line_t *target, line_t *source) +{ + P_DeepCopy(target, source, copy_line_callback); +} + +/*-------------------------------------------------- + void P_DeepCopyLines(line_t **target_array, line_t **source_array, size_t source_len) + + See header file for description. +--------------------------------------------------*/ + +void P_DeepCopyLines(line_t **target_array, line_t **source_array, size_t source_len) +{ + P_DeepCopyArray(target_array, source_array, source_len, copy_line_callback); +} + +/*-------------------------------------------------- + void P_DeepCopySide(line_t *target, line_t *source) + + See header file for description. +--------------------------------------------------*/ + +void P_DeepCopySide(side_t *target, side_t *source) +{ + P_DeepCopy(target, source, nullptr); // (Not a true deep copy until all of the memory addresses are accounted for.) +} + +/*-------------------------------------------------- + void P_DeepCopySides(side_t **target_array, side_t **source_array, size_t source_len) + + See header file for description. +--------------------------------------------------*/ + +void P_DeepCopySides(side_t **target_array, side_t **source_array, size_t source_len) +{ + P_DeepCopyArray(target_array, source_array, source_len, nullptr); // (Not a true deep copy until all of the memory addresses are accounted for.) +} diff --git a/src/p_deepcopy.h b/src/p_deepcopy.h new file mode 100644 index 000000000..799d5f682 --- /dev/null +++ b/src/p_deepcopy.h @@ -0,0 +1,131 @@ +// DR. ROBOTNIK'S RING RACERS +//----------------------------------------------------------------------------- +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour +// Copyright (C) 2025 by Kart Krew +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- +/// \file p_deepcopy.h +/// \brief Methods for deep copying + +#ifndef __P_DEEPCOPY__ +#define __P_DEEPCOPY__ + +#include "doomdef.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/*-------------------------------------------------- + void P_DeepCopySector(sector_t *target, sector_t *source); + + Make a deep copy of a single sector_t. + + Input Arguments:- + target: The struct to copy into. + source: The struct to copy from. + + Return:- + N/A +--------------------------------------------------*/ + +void P_DeepCopySector(sector_t *target, sector_t *source); + + +/*-------------------------------------------------- + void P_DeepCopySectors(sector_t **target_array, sector_t **source_array, size_t source_len); + + Make a deep copy of an array of sector_t. + + Input Arguments:- + target_array: The start of the array to copy into. + This will be allocated by this function, so + it should be freed beforehand. + source_array: The start of the array to copy from. + source_len: The length of the array to copy from. + + Return:- + N/A +--------------------------------------------------*/ + +void P_DeepCopySectors(sector_t **target_array, sector_t **source_array, size_t source_len); + + +/*-------------------------------------------------- + void P_DeepCopyLine(line_t *target, line_t *source) + + Make a deep copy of a single line_t. + + Input Arguments:- + target: The struct to copy into. + source: The struct to copy from. + + Return:- + N/A +--------------------------------------------------*/ + +void P_DeepCopyLine(line_t *target, line_t *source); + + +/*-------------------------------------------------- + void P_DeepCopyLines(line_t **target_array, line_t **source_array, size_t source_len) + + Make a deep copy of an array of line_t. + + Input Arguments:- + target_array: The start of the array to copy into. + This will be allocated by this function, so + it should be freed beforehand. + source_array: The start of the array to copy from. + source_len: The length of the array to copy from. + + Return:- + N/A +--------------------------------------------------*/ + +void P_DeepCopyLines(line_t **target_array, line_t **source_array, size_t source_len); + + +/*-------------------------------------------------- + void P_DeepCopySide(line_t *target, line_t *source) + + Make a deep copy of a single side_t. + + Input Arguments:- + target: The struct to copy into. + source: The struct to copy from. + + Return:- + N/A +--------------------------------------------------*/ + +void P_DeepCopySide(side_t *target, side_t *source); + + +/*-------------------------------------------------- + void P_DeepCopySides(side_t **target_array, side_t **source_array, size_t source_len) + + Make a deep copy of an array of side_t. + + Input Arguments:- + target_array: The start of the array to copy into. + This will be allocated by this function, so + it should be freed beforehand. + source_array: The start of the array to copy from. + source_len: The length of the array to copy from. + + Return:- + N/A +--------------------------------------------------*/ + +void P_DeepCopySides(side_t **target_array, side_t **source_array, size_t source_len); + + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // __P_DEEPCOPY__ diff --git a/src/p_enemy.c b/src/p_enemy.c index 0cfc0d5e5..de957c0c4 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -3519,7 +3519,7 @@ void A_AttractChase(mobj_t *actor) angle_t offset = FixedAngle(18<target->player->ringboost += K_GetKartRingPower(actor->target->player, true) + 3; + actor->target->player->ringboost += K_GetFullKartRingPower(actor->target->player, true); S_ReducedVFXSoundAtVolume(actor->target, sfx_s1b5, actor->target->player->ringvolume, NULL); @@ -3575,7 +3575,7 @@ void A_AttractChase(mobj_t *actor) if (actor->extravalue1 >= 16) { if (!P_GivePlayerRings(actor->target->player, 1)) // returns 0 if addition failed - actor->target->player->ringboost += K_GetKartRingPower(actor->target->player, true) + 3; + actor->target->player->ringboost += K_GetFullKartRingPower(actor->target->player, true); if (actor->cvmem) // caching S_StartSound(actor->target, sfx_s1c5); @@ -3640,7 +3640,7 @@ void A_AttractChase(mobj_t *actor) if ( actor->tracer->player && actor->tracer->health && ((gametyperules & GTR_SPHERES) - || (actor->tracer->player->itemtype == KITEM_LIGHTNINGSHIELD + || (actor->tracer->player->curshield == KSHIELD_LIGHTNING && RINGTOTAL(actor->tracer->player) < 20 && !(actor->tracer->player->pflags & PF_RINGLOCK))) //&& P_CheckSight(actor, actor->tracer) @@ -3883,7 +3883,7 @@ void A_OverlayThink(mobj_t *actor) if (!actor->target) return; - if (!r_splitscreen && rendermode != render_soft) + if (!r_splitscreen && rendermode == render_opengl) { angle_t viewingangle; @@ -5128,10 +5128,7 @@ void A_OldRingExplode(mobj_t *actor) { if (changecolor) { - if (!(gametyperules & GTR_TEAMS)) - mo->color = actor->target->color; //copy color - else if (actor->target->player->ctfteam == 2) - mo->color = skincolor_bluering; + P_ColorTeamMissile(mo, actor->target->player); } } @@ -5144,10 +5141,7 @@ void A_OldRingExplode(mobj_t *actor) { if (changecolor) { - if (!(gametyperules & GTR_TEAMS)) - mo->color = actor->target->color; //copy color - else if (actor->target->player->ctfteam == 2) - mo->color = skincolor_bluering; + P_ColorTeamMissile(mo, actor->target->player); } mo = P_SpawnMobj(actor->x, actor->y, actor->z, locvar1); @@ -5159,10 +5153,7 @@ void A_OldRingExplode(mobj_t *actor) { if (changecolor) { - if (!(gametyperules & GTR_TEAMS)) - mo->color = actor->target->color; //copy color - else if (actor->target->player->ctfteam == 2) - mo->color = skincolor_bluering; + P_ColorTeamMissile(mo, actor->target->player); } } @@ -12212,6 +12203,7 @@ void A_BallhogExplode(mobj_t *actor) mo2 = P_SpawnMobj(actor->x, actor->y, actor->z, MT_BALLHOGBOOM); P_SetScale(mo2, actor->scale*2); mo2->destscale = mo2->scale; + P_SetTarget(&mo2->target, actor->target); S_StartSound(mo2, actor->info->deathsound); return; } diff --git a/src/p_floor.c b/src/p_floor.c index 9d0c6d816..a96a035a6 100644 --- a/src/p_floor.c +++ b/src/p_floor.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -1618,7 +1618,9 @@ static floormove_t *CreateFloorThinker(sector_t *sec) return NULL; } - dofloor = Z_Calloc(sizeof (*dofloor), PU_LEVSPEC, NULL); + dofloor = Z_LevelPoolCalloc(sizeof(*dofloor)); + dofloor->thinker.alloctype = TAT_LEVELPOOL; + dofloor->thinker.size = sizeof(*dofloor); P_AddThinker(THINK_MAIN, &dofloor->thinker); // make sure another floor thinker won't get started over this one @@ -1911,7 +1913,9 @@ static elevator_t *CreateElevatorThinker(sector_t *sec) return NULL; } - elevator = Z_Calloc(sizeof (*elevator), PU_LEVSPEC, NULL); + elevator = Z_LevelPoolCalloc(sizeof(*elevator)); + elevator->thinker.alloctype = TAT_LEVELPOOL; + elevator->thinker.size = sizeof(*elevator); P_AddThinker(THINK_MAIN, &elevator->thinker); // make sure other thinkers won't get started over this one @@ -2211,7 +2215,9 @@ void EV_BounceSector(sector_t *sec, fixed_t momz, line_t *sourceline) if (sec->ceilingdata) // One at a time, ma'am. return; - bouncer = Z_Calloc(sizeof (*bouncer), PU_LEVSPEC, NULL); + bouncer = Z_LevelPoolCalloc(sizeof(*bouncer)); + bouncer->thinker.alloctype = TAT_LEVELPOOL; + bouncer->thinker.size = sizeof(*bouncer); P_AddThinker(THINK_MAIN, &bouncer->thinker); sec->ceilingdata = bouncer; bouncer->thinker.function.acp1 = (actionf_p1)T_BounceCheese; @@ -2238,7 +2244,9 @@ void EV_DoContinuousFall(sector_t *sec, sector_t *backsector, fixed_t spd, boole backsector = sec; // create and initialize new thinker - faller = Z_Calloc(sizeof (*faller), PU_LEVSPEC, NULL); + faller = Z_LevelPoolCalloc(sizeof(*faller)); + faller->thinker.alloctype = TAT_LEVELPOOL; + faller->thinker.size = sizeof(*faller); P_AddThinker(THINK_MAIN, &faller->thinker); faller->thinker.function.acp1 = (actionf_p1)T_ContinuousFalling; @@ -2274,7 +2282,9 @@ INT32 EV_StartCrumble(sector_t *sec, ffloor_t *rover, boolean floating, return 0; // create and initialize new crumble thinker - crumble = Z_Calloc(sizeof (*crumble), PU_LEVSPEC, NULL); + crumble = Z_LevelPoolCalloc(sizeof(*crumble)); + crumble->thinker.alloctype = TAT_LEVELPOOL; + crumble->thinker.size = sizeof(*crumble); P_AddThinker(THINK_MAIN, &crumble->thinker); crumble->thinker.function.acp1 = (actionf_p1)T_StartCrumble; @@ -2346,7 +2356,9 @@ void EV_MarioBlock(ffloor_t *rover, sector_t *sector, mobj_t *puncher) { // create and initialize new elevator thinker - block = Z_Calloc(sizeof (*block), PU_LEVSPEC, NULL); + block = Z_LevelPoolCalloc(sizeof(*block)); + block->thinker.alloctype = TAT_LEVELPOOL; + block->thinker.size = sizeof(*block); P_AddThinker(THINK_MAIN, &block->thinker); roversec->floordata = block; roversec->ceilingdata = block; diff --git a/src/p_inter.c b/src/p_inter.c index 01928424d..2df953dec 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -49,11 +49,6 @@ #include "m_easing.h" #include "k_hud.h" // K_AddMessage - -// CTF player names -#define CTFTEAMCODE(pl) pl->ctfteam ? (pl->ctfteam == 1 ? "\x85" : "\x84") : "" -#define CTFTEAMENDCODE(pl) pl->ctfteam ? "\x80" : "" - void P_ForceFeed(const player_t *player, INT32 attack, INT32 fade, tic_t duration, INT32 period) { BasicFF_t Basicfeed; @@ -125,18 +120,32 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon) if (player->exiting || mapreset || (player->pflags & PF_ELIMINATED) || player->itemRoulette.reserved) return false; - // 0: Sphere/Ring - // 1: Random Item / Capsule - // 2: Eggbox - // 3: Paperitem + // See p_local.h for pickup types - if (weapon != 2 && player->instaWhipCharge) + if (weapon != PICKUP_EGGBOX && player->instaWhipCharge) return false; - if (weapon) + if (weapon == PICKUP_ITEMBOX && !player->cangrabitems) + return false; + + if (weapon == PICKUP_RINGORSPHERE) + { + // No picking up rings while SPB is targetting you + if (player->pflags & PF_RINGLOCK) + { + return false; + } + + // No picking up rings while stunned + if (player->stunned > 0) + { + return false; + } + } + else { // Item slot already taken up - if (weapon == 2) + if (weapon == PICKUP_EGGBOX) { // Invulnerable if (player->flashing > 0) @@ -158,11 +167,11 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon) // Item slot already taken up if (player->itemRoulette.active == true || player->ringboxdelay > 0 - || (weapon != 3 && player->itemamount) + || (weapon != PICKUP_PAPERITEM && player->itemamount) || (player->itemflags & IF_ITEMOUT)) return false; - if (weapon == 3 && K_GetShieldFromItem(player->itemtype) != KSHIELD_NONE) + if (weapon == PICKUP_PAPERITEM && K_GetShieldFromItem(player->itemtype) != KSHIELD_NONE) return false; // No stacking shields! } } @@ -173,7 +182,7 @@ boolean P_CanPickupItem(player_t *player, UINT8 weapon) // Allow players to pick up only one pickup from each set of pickups. // Anticheese pickup types are different than-P_CanPickupItem weapon, because that system is // already slightly scary without introducing special cases for different types of the same pickup. -// 1 = floating item, 2 = perma ring, 3 = capsule +// See p_local.h for cheese types. boolean P_IsPickupCheesy(player_t *player, UINT8 type) { extern consvar_t cv_debugcheese; @@ -275,8 +284,10 @@ static void P_ItemPop(mobj_t *actor) actor->extravalue1 = 0; // de-solidify - // (Nope! Handled in fusethink for item pickup leniency) - // actor->flags |= MF_NOCLIPTHING; + // Do not set item boxes intangible, those are handled in fusethink for item pickup leniency + // Sphere boxes still need to be set intangible here though + if (actor->type != MT_RANDOMITEM) + actor->flags |= MF_NOCLIPTHING; // RF_DONTDRAW will flicker as the object's fuse gets // closer to running out (see P_FuseThink) @@ -414,7 +425,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (special->scale < special->destscale/2) return; - if (!P_CanPickupItem(player, 3) || (player->itemamount && player->itemtype != special->threshold)) + if (!P_CanPickupItem(player, PICKUP_PAPERITEM) || (player->itemamount && player->itemtype != special->threshold)) return; player->itemtype = special->threshold; @@ -434,9 +445,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) special->flags &= ~MF_SPECIAL; return; case MT_RANDOMITEM: { - UINT8 cheesetype = (special->flags2 & MF2_BOSSDEAD) ? 2 : 1; // perma ring box + UINT8 cheesetype = (special->flags2 & MF2_BOSSDEAD) ? CHEESE_RINGBOX : CHEESE_ITEMBOX; // perma ring box - if (!P_CanPickupItem(player, 1)) + if (!P_CanPickupItem(player, PICKUP_ITEMBOX)) return; if (P_IsPickupCheesy(player, cheesetype)) return; @@ -451,7 +462,9 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (special->fuse) // This box is respawning, but was broken very recently (see P_FuseThink) { // What was this box broken as? - if (special->cvmem && !(special->flags2 & MF2_BOSSDEAD)) + if (cv_thunderdome.value) + K_StartItemRoulette(player, true); + else if (special->cvmem && !(special->flags2 & MF2_BOSSDEAD)) K_StartItemRoulette(player, false); else K_StartItemRoulette(player, true); @@ -474,7 +487,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) return; } case MT_SPHEREBOX: - if (!P_CanPickupItem(player, 0)) + if (!P_CanPickupItem(player, PICKUP_RINGORSPHERE)) return; special->momx = special->momy = special->momz = 0; @@ -494,15 +507,13 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) return; break; case KITEM_SUPERRING: - if (player->pflags & PF_RINGLOCK) // no cheaty rings - return; - if (player->instaWhipCharge) + if (!P_CanPickupItem(player, PICKUP_RINGORSPHERE)) // no cheaty rings return; break; default: - if (!P_CanPickupItem(player, 1)) + if (!P_CanPickupItem(player, PICKUP_ITEMCAPSULE)) return; - if (P_IsPickupCheesy(player, 3)) + if (P_IsPickupCheesy(player, CHEESE_ITEMCAPSULE)) return; break; } @@ -559,7 +570,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) return; } case MT_EMERALD: - if (!P_CanPickupItem(player, 0) || P_PlayerInPain(player)) + if (!P_CanPickupItem(player, PICKUP_RINGORSPHERE) || P_PlayerInPain(player)) return; if (special->threshold > 0) @@ -618,7 +629,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) return; case MT_CDUFO: // SRB2kart - if (special->fuse || !P_CanPickupItem(player, 1)) + if (special->fuse || !P_CanPickupItem(player, PICKUP_ITEMBOX)) return; K_StartItemRoulette(player, false); @@ -656,10 +667,15 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (!player->mo || player->spectator) return; + if (K_TryPickMeUp(special, toucher)) + return; + // attach to player! P_SetTarget(&special->tracer, toucher); toucher->flags |= MF_NOGRAVITY; toucher->momz = (8*toucher->scale) * P_MobjFlip(toucher); + toucher->player->carry = CR_TRAPBUBBLE; + P_SetTarget(&toucher->tracer, special); //use tracer to acces the object // Snap to the unfortunate player and quit moving laterally, or we can end up quite far away special->momx = 0; @@ -680,19 +696,11 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) if (special->extravalue1) return; - // No picking up rings while SPB is targetting you - if (player->pflags & PF_RINGLOCK) - return; - - // Prepping instawhip? Don't ruin it by collecting rings - if (player->instaWhipCharge) - return; - // Don't immediately pick up spilled rings if (special->threshold > 0 || P_PlayerInPain(player) || player->spindash) // player->spindash: Otherwise, players can pick up rings that are thrown out of them from invinc spindash penalty return; - if (!(P_CanPickupItem(player, 0))) + if (!(P_CanPickupItem(player, PICKUP_RINGORSPHERE))) return; // Reached the cap, don't waste 'em! @@ -714,7 +722,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) return; case MT_BLUESPHERE: - if (!(P_CanPickupItem(player, 0))) + if (!(P_CanPickupItem(player, PICKUP_RINGORSPHERE))) return; P_GivePlayerSpheres(player, 1); @@ -771,15 +779,27 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } // See also P_SprayCanInit - UINT16 can_id = mapheaderinfo[gamemap-1]->cache_spraycan; + UINT16 can_id = mapheaderinfo[gamemap-1]->records.spraycan; - if (can_id < gamedata->numspraycans) + if (can_id < gamedata->numspraycans || can_id == MCAN_BONUS) { // Assigned to this level, has been grabbed return; } - // Prevent footguns - these won't persist when custom levels are unloaded - else if (gamemap-1 < basenummapheaders) + + if ( + (gamemap-1 >= basenummapheaders) + || (gamedata->gotspraycans >= gamedata->numspraycans) + ) + { + // Custom course OR we ran out of assignables. + + if (special->threshold != 0) + return; + + can_id = MCAN_BONUS; + } + else { // Unassigned, get the next grabbable colour can_id = gamedata->gotspraycans; @@ -802,18 +822,7 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) skincolors[swapcol].cache_spraycan = can_id; } - } - - if (can_id >= gamedata->numspraycans) - { - // We've exhausted all the spraycans to grab. - return; - } - - if (gamedata->spraycans[can_id].map >= nummapheaders) - { gamedata->spraycans[can_id].map = gamemap-1; - mapheaderinfo[gamemap-1]->cache_spraycan = can_id; if (gamedata->gotspraycans == 0 && gametype == GT_TUTORIAL @@ -829,12 +838,14 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) } gamedata->gotspraycans++; - - if (!M_UpdateUnlockablesAndExtraEmblems(true, true)) - S_StartSound(NULL, sfx_ncitem); - gamedata->deferredsave = true; } + mapheaderinfo[gamemap-1]->records.spraycan = can_id; + + if (!M_UpdateUnlockablesAndExtraEmblems(true, true)) + S_StartSound(NULL, sfx_ncitem); + gamedata->deferredsave = true; + { mobj_t *canmo = NULL; mobj_t *next = NULL; @@ -1147,22 +1158,29 @@ static void P_AddBrokenPrison(mobj_t *target, mobj_t *inflictor, mobj_t *source) if (!battleprisons) return; + // Check to see if everyone's out. + { + UINT8 i = 0; + + for (; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator || players[i].exiting) + continue; + break; + } + + if (i == MAXPLAYERS) + { + // Nobody can claim credit for this just-too-late hit! + P_DoAllPlayersExit(0, false); // softlock prevention + return; + } + } + + // If you CAN recieve points, get them! if ((gametyperules & GTR_POINTLIMIT) && (source && source->player)) { - /*mobj_t * ring; - for (i = 0; i < 2; i++) - { - dir += (ANGLE_MAX/3); - ring = P_SpawnMobj(target->x, target->y, target->z, MT_RING); - ring->angle = dir; - P_InstaThrust(ring, dir, 16*ring->scale); - ring->momz = 8 * target->scale * P_MobjFlip(target); - P_SetTarget(&ring->tracer, source); - source->player->pickuprings++; - }*/ - - P_AddPlayerScore(source->player, 1); - K_SpawnBattlePoints(source->player, NULL, 1); + K_GivePointsToPlayer(source->player, NULL, 1); } targetdamaging_t targetdamaging = UFOD_GENERIC; @@ -1207,13 +1225,18 @@ static void P_AddBrokenPrison(mobj_t *target, mobj_t *inflictor, mobj_t *source) gamedata->prisoneggstothispickup--; } + // Standard progression. if (++numtargets >= maptargets) { + // Yipue! + P_DoAllPlayersExit(0, true); } else { S_StartSound(NULL, sfx_s221); + + // Time limit recovery if (timelimitintics) { UINT16 bonustime = 10*TICRATE; @@ -1260,7 +1283,7 @@ static void P_AddBrokenPrison(mobj_t *target, mobj_t *inflictor, mobj_t *source) secretextratime = TICRATE/2; } - + // Prison Egg challenge drops (CDs, etc) #ifdef DEVELOP extern consvar_t cv_debugprisoncd; #endif @@ -1510,13 +1533,15 @@ void P_CheckPointLimit(void) return; // pointlimit is nonzero, check if it's been reached by this player - if (G_GametypeHasTeams()) + if (G_GametypeHasTeams() == true) { - // Just check both teams - if (g_pointlimit <= redscore || g_pointlimit <= bluescore) + for (i = 0; i < TEAM__MAX; i++) { - if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0); + if (g_pointlimit <= g_teamscores[i]) + { + P_DoAllPlayersExit(0, false); + return; + } } } else @@ -1529,10 +1554,7 @@ void P_CheckPointLimit(void) if (g_pointlimit <= players[i].roundscore) { P_DoAllPlayersExit(0, false); - - /*if (server) - SendNetXCmd(XD_EXITLEVEL, NULL, 0);*/ - return; // good thing we're leaving the function immediately instead of letting the loop get mangled! + return; } } } @@ -2289,6 +2311,9 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget case MT_EMFAUCET_DRIP: Obj_EMZDripDeath(target); break; + case MT_FLYBOT767: + Obj_FlybotDeath(target); + break; default: break; } @@ -2506,12 +2531,11 @@ static boolean P_PlayerHitsPlayer(mobj_t *target, mobj_t *inflictor, mobj_t *sou if (source == target) return false; - if (G_GametypeHasTeams()) - { - // Don't hurt your team, either! - if (source->player->ctfteam == target->player->ctfteam) - return false; - } +#if 0 + // Don't hurt your team, either! + if (G_SameTeam(source->player, target->player) == true) + return false; +#endif } return true; @@ -2579,20 +2603,20 @@ static boolean P_KillPlayer(player_t *player, mobj_t *inflictor, mobj_t *source, player->roundconditions.checkthisframe = true; } - if (gametyperules & (GTR_BUMPERS|GTR_CHECKPOINTS)) + if ((player->pitblame > -1) && (player->pitblame < MAXPLAYERS) + && (playeringame[player->pitblame]) && (!players[player->pitblame].spectator) + && (players[player->pitblame].mo) && (!P_MobjWasRemoved(players[player->pitblame].mo))) { - if ((player->pitblame > -1) && (player->pitblame < MAXPLAYERS) - && (playeringame[player->pitblame]) && (!players[player->pitblame].spectator) - && (players[player->pitblame].mo) && (!P_MobjWasRemoved(players[player->pitblame].mo))) - { + if (gametyperules & (GTR_BUMPERS|GTR_CHECKPOINTS)) P_DamageMobj(player->mo, players[player->pitblame].mo, players[player->pitblame].mo, 1, DMG_KARMA); - player->pitblame = -1; - } - else if (player->mo->health > 1 || K_Cooperative()) - { + else + K_SpawnAmps(&players[player->pitblame], 20, player->mo); + player->pitblame = -1; + } + else if (player->mo->health > 1 || K_Cooperative()) + { + if (gametyperules & (GTR_BUMPERS|GTR_CHECKPOINTS)) player->mo->health--; - } - } if (modeattacking & ATTACKING_SPB) @@ -2990,6 +3014,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da UINT8 type = (damagetype & DMG_TYPEMASK); const boolean hardhit = (type == DMG_EXPLODE || type == DMG_KARMA || type == DMG_TUMBLE); // This damage type can do evil stuff like ALWAYS combo INT16 ringburst = 5; + UINT16 stunTics = 0; // Check if the player is allowed to be damaged! // If not, then spawn the instashield effect instead. @@ -3168,7 +3193,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da damage = 0; } - boolean hitFromInvinc = false; + boolean softenTumble = false; // Sting and stumble shouldn't be rewarding Battle hits. if (type == DMG_STING || type == DMG_STUMBLE) @@ -3181,7 +3206,28 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da if (source && source != player->mo && source->player) { - K_SpawnAmps(source->player, K_PvPAmpReward(20, source->player, player), target); + K_SpawnAmps(source->player, K_PvPAmpReward((type == DMG_WHUMBLE) ? 30 : 20, source->player, player), target); + K_BotHitPenalty(player); + + if (G_SameTeam(source->player, player)) + { + if (type != DMG_EXPLODE) + type = DMG_STUMBLE; + } + else + { + for (UINT8 i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator || !players[i].mo || P_MobjWasRemoved(players[i].mo)) + continue; + if (!G_SameTeam(source->player, &players[i])) + continue; + if (source->player == &players[i]) + continue; + K_SpawnAmps(&players[i], FixedInt(FixedMul(5, K_TeamComebackMultiplier(player))), target); + } + } + // Extend the invincibility if the hit was a direct hit. if (inflictor == source && source->player->invincibilitytimer && @@ -3189,7 +3235,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da { tic_t kinvextend; - hitFromInvinc = true; + softenTumble = true; if (gametyperules & GTR_CLOSERPLAYERS) kinvextend = 2*TICRATE; @@ -3309,6 +3355,27 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da K_PopPlayerShield(player); } + if (!(gametyperules & GTR_SPHERES) && player->tripwireLeniency) + { + switch (type) + { + case DMG_EXPLODE: + type = DMG_TUMBLE; + break; + case DMG_TUMBLE: + softenTumble = true; + break; + case DMG_NORMAL: + case DMG_WIPEOUT: + type = DMG_STUMBLE; + player->ringburst += 5; // THERE IS SIMPLY NO HOPE AT THIS POINT + K_PopPlayerShield(player); + break; + default: + break; + } + } + switch (type) { case DMG_STING: @@ -3322,7 +3389,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da ringburst = 0; break; case DMG_TUMBLE: - K_TumblePlayer(player, inflictor, source, hitFromInvinc); + K_TumblePlayer(player, inflictor, source, softenTumble); ringburst = 10; break; case DMG_EXPLODE: @@ -3366,7 +3433,7 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da if (gametyperules & GTR_BUMPERS) player->spheres = min(player->spheres + 10, 40); - if ((hardhit == true && !hitFromInvinc) || cv_kartdebughuddrop.value) + if ((hardhit == true && !softenTumble) || cv_kartdebughuddrop.value) { K_DropItems(player); } @@ -3380,6 +3447,27 @@ boolean P_DamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, INT32 da player->flipDI = true; } + // Apply stun! + // Feel free to move these calculations higher up if different damage sources should apply variable stun in future + #define MIN_STUNTICS (4 * TICRATE) + #define MAX_STUNTICS (10 * TICRATE) + stunTics = Easing_Linear((player->kartweight - 1) * FRACUNIT / 8, MAX_STUNTICS, MIN_STUNTICS); + stunTics >>= player->stunnedCombo; // consecutive hits add half as much stun as the previous hit + + // 1/3 base stun values in battle + if (gametyperules & GTR_SPHERES) + { + stunTics /= 3; + } + + if (player->stunnedCombo < UINT8_MAX) + { + player->stunnedCombo++; + } + player->stunned = (player->stunned & 0x8000) | min(0x7FFF, (player->stunned & 0x7FFF) + stunTics); + #undef MIN_STUNTICS + #undef MAX_STUNTICS + K_DefensiveOverdrive(target->player); } } diff --git a/src/p_lights.c b/src/p_lights.c index c46964c86..fe167838a 100644 --- a/src/p_lights.c +++ b/src/p_lights.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -75,7 +75,9 @@ fireflicker_t *P_SpawnAdjustableFireFlicker(sector_t *sector, INT16 lighta, INT1 fireflicker_t *flick; P_RemoveLighting(sector); // out with the old, in with the new - flick = Z_Calloc(sizeof (*flick), PU_LEVSPEC, NULL); + flick = Z_LevelPoolCalloc(sizeof(*flick)); + flick->thinker.alloctype = TAT_LEVELPOOL; + flick->thinker.size = sizeof(*flick); P_AddThinker(THINK_MAIN, &flick->thinker); @@ -150,7 +152,9 @@ void P_SpawnLightningFlash(sector_t *sector) sector->lightingdata = NULL; - flash = Z_Calloc(sizeof (*flash), PU_LEVSPEC, NULL); + flash = Z_LevelPoolCalloc(sizeof(*flash)); + flash->thinker.alloctype = TAT_LEVELPOOL; + flash->thinker.size = sizeof(*flash); P_AddThinker(THINK_MAIN, &flash->thinker); @@ -209,7 +213,9 @@ strobe_t *P_SpawnAdjustableStrobeFlash(sector_t *sector, INT16 lighta, INT16 lig strobe_t *flash; P_RemoveLighting(sector); // out with the old, in with the new - flash = Z_Calloc(sizeof (*flash), PU_LEVSPEC, NULL); + flash = Z_LevelPoolCalloc(sizeof(*flash)); + flash->thinker.alloctype = TAT_LEVELPOOL; + flash->thinker.size = sizeof(*flash); P_AddThinker(THINK_MAIN, &flash->thinker); @@ -279,7 +285,9 @@ glow_t *P_SpawnAdjustableGlowingLight(sector_t *sector, INT16 lighta, INT16 ligh glow_t *g; P_RemoveLighting(sector); // out with the old, in with the new - g = Z_Calloc(sizeof (*g), PU_LEVSPEC, NULL); + g = Z_LevelPoolCalloc(sizeof(*g)); + g->thinker.alloctype = TAT_LEVELPOOL; + g->thinker.size = sizeof(*g); P_AddThinker(THINK_MAIN, &g->thinker); @@ -333,7 +341,9 @@ void P_FadeLightBySector(sector_t *sector, INT32 destvalue, INT32 speed, boolean return; } - ll = Z_Calloc(sizeof (*ll), PU_LEVSPEC, NULL); + ll = Z_LevelPoolCalloc(sizeof(*ll)); + ll->thinker.alloctype = TAT_LEVELPOOL; + ll->thinker.size = sizeof(*ll); ll->thinker.function.acp1 = (actionf_p1)T_LightFade; sector->lightingdata = ll; // set it to the lightlevel_t diff --git a/src/p_link.cpp b/src/p_link.cpp index 6ad7f22fb..bbb2773de 100644 --- a/src/p_link.cpp +++ b/src/p_link.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/p_link.h b/src/p_link.h index 70671f555..09dff6875 100644 --- a/src/p_link.h +++ b/src/p_link.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/p_local.h b/src/p_local.h index 4d2bbdb07..505ccd8e0 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -78,7 +78,6 @@ typedef enum NUM_THINKERLISTS } thinklistnum_t; /**< Thinker lists. */ extern thinker_t thlist[]; -extern mobj_t *mobjcache; void P_InitThinkers(void); void P_InvalidateThinkersWithoutInit(void); @@ -258,6 +257,7 @@ mobjtype_t P_GetMobjtype(UINT16 mthingtype); void P_RespawnSpecials(void); fixed_t P_GetMobjDefaultScale(mobj_t *mobj); +mobj_t *P_AllocateMobj(void); mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type); void P_CalculatePrecipFloor(precipmobj_t *mobj); @@ -557,6 +557,17 @@ void P_CheckTimeLimit(void); void P_CheckPointLimit(void); boolean P_CheckRacers(void); +// Pickup types +#define PICKUP_RINGORSPHERE 0 +#define PICKUP_ITEMBOX 1 +#define PICKUP_EGGBOX 2 +#define PICKUP_PAPERITEM 3 +#define PICKUP_ITEMCAPSULE 4 + +#define CHEESE_ITEMBOX 1 +#define CHEESE_RINGBOX 2 +#define CHEESE_ITEMCAPSULE 3 + boolean P_CanPickupItem(player_t *player, UINT8 weapon); boolean P_IsPickupCheesy(player_t *player, UINT8 type); void P_UpdateLastPickup(player_t *player, UINT8 type); diff --git a/src/p_loop.c b/src/p_loop.c index 3db389e7c..9ddf22f7b 100644 --- a/src/p_loop.c +++ b/src/p_loop.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/p_map.c b/src/p_map.c index f8be8b6f9..bdec595d7 100644 --- a/src/p_map.c +++ b/src/p_map.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -35,6 +35,7 @@ #include "k_terrain.h" #include "k_objects.h" #include "k_boss.h" +#include "k_hitlag.h" // K_AddHitlag #include "r_splats.h" @@ -456,6 +457,8 @@ boolean P_DoSpring(mobj_t *spring, mobj_t *object) spring->reactiontime++; } + + object->player->transfer = 0; } P_SetMobjState(spring, raisestate); @@ -1081,7 +1084,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return Obj_OrbinautJawzCollide(thing, g_tm.thing) ? BMIT_CONTINUE : BMIT_ABORT; } - if (g_tm.thing->type == MT_BANANA || g_tm.thing->type == MT_BANANA_SHIELD || g_tm.thing->type == MT_BALLHOG) + if (g_tm.thing->type == MT_BANANA || g_tm.thing->type == MT_BANANA_SHIELD || g_tm.thing->type == MT_BALLHOG || g_tm.thing->type == MT_BALLHOGBOOM) { // see if it went over / under if (g_tm.thing->z > thing->z + thing->height) @@ -1091,7 +1094,7 @@ static BlockItReturn_t PIT_CheckThing(mobj_t *thing) return K_BananaBallhogCollide(g_tm.thing, thing) ? BMIT_CONTINUE : BMIT_ABORT; } - else if (thing->type == MT_BANANA || thing->type == MT_BANANA_SHIELD || thing->type == MT_BALLHOG) + else if (thing->type == MT_BANANA || thing->type == MT_BANANA_SHIELD || thing->type == MT_BALLHOG || thing->type == MT_BALLHOGBOOM) { // see if it went over / under if (g_tm.thing->z > thing->z + thing->height) @@ -2762,6 +2765,21 @@ fixed_t P_GetThingStepUp(mobj_t *thing, fixed_t destX, fixed_t destY) maxstep += maxstepmove; } + if (thing->standingslope && thing->standingslope->zdelta != 0 && (thing->momx || thing->momy)) + { + vector3_t slopemom = {0,0,0}; + slopemom.x = thing->momx; + slopemom.y = thing->momy; + P_QuantizeMomentumToSlope(&slopemom, thing->standingslope); + fixed_t momentumzdelta = FixedDiv(slopemom.z, FixedHypot(slopemom.x, slopemom.y)); // so this lets us know what the zdelta is for the vector the player is travelling along, in addition to the slope's zdelta in its xydirection + // if (thing->player) + // CONS_Printf("%s P_GetThingStepUp %d +", player_names[thing->player-players], maxstep); + maxstep += abs(momentumzdelta); + // if (thing->player) + // CONS_Printf(" %d = %d\n", momentumzdelta, maxstep); + + } + if (P_MobjTouchingSectorSpecialFlag(thing, SSF_DOUBLESTEPUP) || (R_PointInSubsector(destX, destY)->sector->specialflags & SSF_DOUBLESTEPUP)) { @@ -2802,7 +2820,10 @@ increment_move radius = max(radius, mapobjectscale); // And Big Large (tm) movements can skip over slopes. - radius = min(radius, 16*mapobjectscale); + radius = min(radius, 8*mapobjectscale); + + // if (thing->player) + // CONS_Printf("increment_move\n"); do { // Sal 12/19/2022 -- PIT_CheckThing code now runs @@ -2924,6 +2945,9 @@ increment_move // If the floor difference is MAXSTEPMOVE or less, and the sector isn't Section1:14, ALWAYS // step down! Formerly required a Section1:13 sector for the full MAXSTEPMOVE, but no more. + // if (thing->player && !(thingtop == thing->ceilingz && g_tm.ceilingz > thingtop && g_tm.ceilingz - thingtop <= maxstep)) + // CONS_Printf("%d == %d && %d < %d && %d - %d <= %d %s\n", thing->z, thing->floorz, g_tm.floorz, thing->z, thing->z, g_tm.floorz, maxstep, thing->z == thing->floorz && g_tm.floorz < thing->z && thing->z - g_tm.floorz <= maxstep ? "True" : "False"); + if (thingtop == thing->ceilingz && g_tm.ceilingz > thingtop && g_tm.ceilingz - thingtop <= maxstep) { if (flipped) @@ -3175,16 +3199,16 @@ boolean P_TryMove(mobj_t *thing, fixed_t x, fixed_t y, boolean allowdropoff, Try thing->player->pflags |= PF_FREEZEWAYPOINTS; } } + } - // Currently this just iterates all checkpoints. - // Pretty shitty way to do it, but only players can - // cross it, so it's good enough. Works as long as the - // move doesn't cross multiple -- it can only evaluate - // one. - if (thing->player) - { - Obj_CrossCheckpoints(thing->player, oldx, oldy); - } + // Currently this just iterates all checkpoints. + // Pretty shitty way to do it, but only players can + // cross it, so it's good enough. Works as long as the + // move doesn't cross multiple -- it can only evaluate + // one. + if (thing->player) + { + Obj_CrossCheckpoints(thing->player, oldx, oldy); } if (result != NULL) @@ -4094,13 +4118,29 @@ static void P_BouncePlayerMove(mobj_t *mo, TryMoveResult_t *result) if (mo->player) mo->player->bumpUnstuck += 5; + K_BotHitPenalty(mo->player); + // Combo avoidance! if (mo->player && P_PlayerInPain(mo->player) && gametyperules & GTR_BUMPERS && mo->health == 1) { - K_StumblePlayer(mo->player); - K_BumperInflate(mo->player); - mo->player->tumbleBounces = TUMBLEBOUNCES; - mo->hitlag = max(mo->hitlag, 6); + P_ResetPlayer(mo->player); + mo->player->spinouttimer = 0; + mo->player->wipeoutslow = 0; + mo->player->tumbleBounces = 0; + + K_AddHitLag(mo, 3, false); + + // "I dunno man, just fuckin' do it" - jart + S_StartSound(mo, sfx_mbs45); + S_StartSound(mo, sfx_mbs45); + S_StartSound(mo, sfx_mbs45); + S_StartSound(mo, sfx_mbs45); + S_StartSound(mo, sfx_mbv84); + + if (mo->eflags & MFE_VERTICALFLIP) + mo->momz -= 40*mo->scale; + else + mo->momz += 40*mo->scale; } mo->momx = tmxmove; @@ -4619,13 +4659,8 @@ boolean P_CheckSector(sector_t *sector, boolean crunch) Lots of new Boom functions that work faster and add functionality. */ -static msecnode_t *headsecnode = NULL; -static mprecipsecnode_t *headprecipsecnode = NULL; - void P_Initsecnode(void) { - headsecnode = NULL; - headprecipsecnode = NULL; } // P_GetSecnode() retrieves a node from the freelist. The calling routine @@ -4633,45 +4668,25 @@ void P_Initsecnode(void) static msecnode_t *P_GetSecnode(void) { - msecnode_t *node; - - if (headsecnode) - { - node = headsecnode; - headsecnode = headsecnode->m_thinglist_next; - } - else - node = Z_Calloc(sizeof (*node), PU_LEVEL, NULL); - return node; + return Z_LevelPoolCalloc(sizeof(msecnode_t)); } static mprecipsecnode_t *P_GetPrecipSecnode(void) { - mprecipsecnode_t *node; - - if (headprecipsecnode) - { - node = headprecipsecnode; - headprecipsecnode = headprecipsecnode->m_thinglist_next; - } - else - node = Z_Calloc(sizeof (*node), PU_LEVEL, NULL); - return node; + return Z_LevelPoolCalloc(sizeof(mprecipsecnode_t)); } // P_PutSecnode() returns a node to the freelist. static inline void P_PutSecnode(msecnode_t *node) { - node->m_thinglist_next = headsecnode; - headsecnode = node; + Z_LevelPoolFree(node, sizeof(msecnode_t)); } // Tails 08-25-2002 static inline void P_PutPrecipSecnode(mprecipsecnode_t *node) { - node->m_thinglist_next = headprecipsecnode; - headprecipsecnode = node; + Z_LevelPoolFree(node, sizeof(mprecipsecnode_t)); } // P_AddSecnode() searches the current list to see if this sector is diff --git a/src/p_mapthing.cpp b/src/p_mapthing.cpp index 5c7dc277f..13b9f04f6 100644 --- a/src/p_mapthing.cpp +++ b/src/p_mapthing.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/p_maputl.c b/src/p_maputl.c index bb33160c9..1388f4b6c 100644 --- a/src/p_maputl.c +++ b/src/p_maputl.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -29,6 +29,7 @@ // // P_ClosestPointOnLine // Finds the closest point on a given line to the supplied point +// Considers line length to be infinite, and can return results outside of the actual linedef bounds // void P_ClosestPointOnLine(fixed_t x, fixed_t y, const line_t *line, vertex_t *result) { @@ -68,6 +69,27 @@ void P_ClosestPointOnLine(fixed_t x, fixed_t y, const line_t *line, vertex_t *re return; } +// +// P_ClosestPointOnLineWithinLine +// Finds the closest point on a given line to the supplied point +// Like P_ClosestPointOnLine, except the result is constrained within the actual line +// +void P_ClosestPointOnLineWithinLine(fixed_t x, fixed_t y, line_t *line, vertex_t *result) +{ + P_ClosestPointOnLine(x, y, line, result); + + // Determine max and min bounds of the line + fixed_t maxx = max(line->v1->x, line->v2->x); + fixed_t maxy = max(line->v1->y, line->v2->y); + fixed_t minx = min(line->v1->x, line->v2->x); + fixed_t miny = min(line->v1->y, line->v2->y); + + // Constrain result to line by ensuring x and y don't go beyond the maximums + result->x = min(max(result->x, minx),maxx); + result->y = min(max(result->y, miny),maxy); + return; +} + /// Similar to FV3_ClosestPointOnLine() except it actually works. void P_ClosestPointOnLine3D(const vector3_t *p, const vector3_t *Line, vector3_t *result) { @@ -527,6 +549,107 @@ static boolean P_MidtextureIsSolid(line_t *linedef, mobj_t *mobj) return ((linedef->flags & ML_MIDSOLID) == ML_MIDSOLID); } +boolean P_FoFOpening(sector_t *sector, line_t *linedef, mobj_t *mobj, opening_t *open, fofopening_t *fofopen) +{ + fixed_t delta1, delta2; + fixed_t thingtop = mobj->z + mobj->height; + boolean ret = false; // DId we find any relevant FoFs? + // Check for frontsector's fake floors + for (ffloor_t *rover = sector->ffloors; rover; rover = rover->next) + { + fixed_t topheight, bottomheight, midheight; + + if (!(rover->fofflags & FOF_EXISTS)) + continue; + + if (P_CheckSolidFFloorSurface(mobj, rover)) + ; + else if (!((rover->fofflags & FOF_BLOCKPLAYER && mobj->player) + || (rover->fofflags & FOF_BLOCKOTHERS && !mobj->player))) + continue; + + ret = true; // Found a FoF that matters + + if (open->fofType != LO_FOF_ANY) + { + topheight = P_VeryTopOfFOF(rover); + bottomheight = P_VeryBottomOfFOF(rover); + } + else + { + topheight = P_GetFOFTopZ(mobj, sector, rover, g_tm.x, g_tm.y, linedef); + bottomheight = P_GetFOFBottomZ(mobj, sector, rover, g_tm.x, g_tm.y, linedef); + } + + switch (open->fofType) + { + case LO_FOF_FLOORS: + { + if (mobj->z >= topheight) + { + if ((rover->fofflags & FOF_INTANGIBLEFLATS) != FOF_REVERSEPLATFORM) + { + if (topheight > fofopen->floor) + { + fofopen->floor = topheight; + fofopen->floorrover = rover; + } + } + } + break; + } + case LO_FOF_CEILINGS: + { + if (thingtop <= bottomheight) + { + if ((rover->fofflags & FOF_INTANGIBLEFLATS) != FOF_PLATFORM) + { + if (bottomheight < fofopen->ceiling) + { + fofopen->ceiling = bottomheight; + fofopen->ceilingrover = rover; + } + } + } + break; + } + default: + { + midheight = bottomheight + (topheight - bottomheight) / 2; + delta1 = abs(mobj->z - midheight); + delta2 = abs(thingtop - midheight); + + if (delta1 > delta2) + { + // thing is below FOF + if ((rover->fofflags & FOF_INTANGIBLEFLATS) != FOF_PLATFORM) + { + if (bottomheight < fofopen->ceiling) + { + fofopen->ceiling = bottomheight; + fofopen->ceilingrover = rover; + } + } + } + else + { + // thing is above FOF + if ((rover->fofflags & FOF_INTANGIBLEFLATS) != FOF_REVERSEPLATFORM) + { + if (topheight > fofopen->floor) + { + fofopen->floor = topheight; + fofopen->floorrover = rover; + } + } + } + break; + } + } + } + return ret; +} + void P_LineOpening(line_t *linedef, mobj_t *mobj, opening_t *open) { enum { FRONT, BACK }; @@ -589,7 +712,17 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj, opening_t *open) height[FRONT] = P_GetCeilingZ(mobj, front, g_tm.x, g_tm.y, linedef); height[BACK] = P_GetCeilingZ(mobj, back, g_tm.x, g_tm.y, linedef); - hi = ( height[0] < height[1] ); + if (height[FRONT] == height[BACK] && (front->c_slope || back->c_slope)) + { + fixed_t savedradius = mobj->radius; // forgive me. Perhaps it would be better to refactor these functions to take a radius of fixed_t instead? thats all they grab from the mobj + mobj->radius = 1; // I need the same calculation, but at the center of the mobj + hi = ( P_GetCeilingZ(mobj, front, g_tm.x, g_tm.y, linedef) < P_GetCeilingZ(mobj, back, g_tm.x, g_tm.y, linedef) ); + hi = !hi; // actually lets do a funny and flip these + mobj->radius = savedradius; + } + else + hi = ( height[FRONT] < height[BACK] ); + lo = ! hi; open->ceiling = height[lo]; @@ -609,7 +742,17 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj, opening_t *open) height[FRONT] = P_GetFloorZ(mobj, front, g_tm.x, g_tm.y, linedef); height[BACK] = P_GetFloorZ(mobj, back, g_tm.x, g_tm.y, linedef); - hi = ( height[0] < height[1] ); + if (height[FRONT] == height[BACK] && (front->f_slope || back->f_slope)) + { + fixed_t savedradius = mobj->radius; // forgive me. Perhaps it would be better to refactor these functions to take a radius of fixed_t instead? thats all they grab from the mobj + mobj->radius = 1; // I need the same calculation, but at the center of the mobj + hi = ( P_GetFloorZ(mobj, front, g_tm.x, g_tm.y, linedef) < P_GetFloorZ(mobj, back, g_tm.x, g_tm.y, linedef) ); + hi = !hi; // actually lets do a funny and flip these + mobj->radius = savedradius; + } + else + hi = ( height[FRONT] < height[BACK] ); + lo = ! hi; open->floor = height[hi]; @@ -760,261 +903,104 @@ void P_LineOpening(line_t *linedef, mobj_t *mobj, opening_t *open) // Check for fake floors in the sector. if (front->ffloors || back->ffloors) { - ffloor_t *rover; - fixed_t delta1, delta2; - - /* yuck */ - struct - { - fixed_t ceiling; - fixed_t floor; - ffloor_t * ceilingrover; - ffloor_t * floorrover; - } fofopen[2] = { + boolean anyfrontfofs, anybackfofs; + fofopening_t fofopen[2] = { { INT32_MAX, INT32_MIN, NULL, NULL }, { INT32_MAX, INT32_MIN, NULL, NULL }, }; - // Check for frontsector's fake floors - for (rover = front->ffloors; rover; rover = rover->next) + anyfrontfofs = P_FoFOpening(front, linedef, mobj, open, &fofopen[FRONT]); + anybackfofs = P_FoFOpening(back, linedef, mobj, open, &fofopen[BACK]); + + if (anyfrontfofs || anybackfofs) // if all front and back fofs are irrelevant then skip { - fixed_t topheight, bottomheight, midheight; - - if (!(rover->fofflags & FOF_EXISTS)) - continue; - - if (P_CheckSolidFFloorSurface(mobj, rover)) - ; - else if (!((rover->fofflags & FOF_BLOCKPLAYER && mobj->player) - || (rover->fofflags & FOF_BLOCKOTHERS && !mobj->player))) - continue; - - if (open->fofType != LO_FOF_ANY) + if (fofopen[FRONT].ceiling == fofopen[BACK].ceiling && ((fofopen[FRONT].ceilingrover && *fofopen[FRONT].ceilingrover->b_slope) || (fofopen[BACK].ceilingrover && *fofopen[BACK].ceilingrover->b_slope))) { - topheight = P_VeryTopOfFOF(rover); - bottomheight = P_VeryBottomOfFOF(rover); + fixed_t savedradius = mobj->radius; // forgive me. Perhaps it would be better to refactor these functions to take a radius of fixed_t instead? thats all they grabn from the mobj + mobj->radius = 1; // I need the same calculation, but at the center of the mobj + fofopening_t temp[2] = { + { INT32_MAX, INT32_MIN, NULL, NULL }, + { INT32_MAX, INT32_MIN, NULL, NULL } + }; + P_FoFOpening(front, linedef, mobj, open, &temp[FRONT]); + P_FoFOpening(back, linedef, mobj, open, &temp[BACK]); + hi = ( temp[FRONT].ceiling < temp[BACK].ceiling ); + mobj->radius = savedradius; } else - { - topheight = P_GetFOFTopZ(mobj, front, rover, g_tm.x, g_tm.y, linedef); - bottomheight = P_GetFOFBottomZ(mobj, front, rover, g_tm.x, g_tm.y, linedef); - } + hi = ( fofopen[FRONT].ceiling < fofopen[BACK].ceiling ); - switch (open->fofType) - { - case LO_FOF_FLOORS: - { - if (mobj->z >= topheight) - { - if ((rover->fofflags & FOF_INTANGIBLEFLATS) != FOF_REVERSEPLATFORM) - { - if (topheight > fofopen[FRONT].floor) - { - fofopen[FRONT].floor = topheight; - fofopen[FRONT].floorrover = rover; - } - } - } - break; - } - case LO_FOF_CEILINGS: - { - if (thingtop <= bottomheight) - { - if ((rover->fofflags & FOF_INTANGIBLEFLATS) != FOF_PLATFORM) - { - if (bottomheight < fofopen[FRONT].ceiling) - { - fofopen[FRONT].ceiling = bottomheight; - fofopen[FRONT].ceilingrover = rover; - } - } - } - break; - } - default: - { - midheight = bottomheight + (topheight - bottomheight) / 2; - delta1 = abs(mobj->z - midheight); - delta2 = abs(thingtop - midheight); + lo = ! hi; - if (delta1 > delta2) - { - // thing is below FOF - if ((rover->fofflags & FOF_INTANGIBLEFLATS) != FOF_PLATFORM) - { - if (bottomheight < fofopen[FRONT].ceiling) - { - fofopen[FRONT].ceiling = bottomheight; - fofopen[FRONT].ceilingrover = rover; - } - } - } - else - { - // thing is above FOF - if ((rover->fofflags & FOF_INTANGIBLEFLATS) != FOF_REVERSEPLATFORM) - { - if (topheight > fofopen[FRONT].floor) - { - fofopen[FRONT].floor = topheight; - fofopen[FRONT].floorrover = rover; - } - } - } - break; + if (fofopen[lo].ceiling <= open->ceiling) + { + topedge[lo] = P_GetFFloorBottomZAt(fofopen[lo].ceilingrover, cross.x, cross.y); + + if (fofopen[hi].ceiling < open->ceiling) + { + topedge[hi] = P_GetFFloorBottomZAt(fofopen[hi].ceilingrover, cross.x, cross.y); + } + + open->ceiling = fofopen[lo].ceiling; + open->ceilingrover = fofopen[lo].ceilingrover; + open->ceilingslope = *fofopen[lo].ceilingrover->b_slope; + open->ceilingpic = *fofopen[lo].ceilingrover->bottompic; + open->ceilingstep = ( thingtop - topedge[lo] ); + open->ceilingdrop = ( topedge[hi] - topedge[lo] ); + + if (fofopen[hi].ceiling < open->highceiling) + { + open->highceiling = fofopen[hi].ceiling; } } - } - - // Check for backsectors fake floors - for (rover = back->ffloors; rover; rover = rover->next) - { - fixed_t topheight, bottomheight, midheight; - - if (!(rover->fofflags & FOF_EXISTS)) - continue; - - if (P_CheckSolidFFloorSurface(mobj, rover)) - ; - else if (!((rover->fofflags & FOF_BLOCKPLAYER && mobj->player) - || (rover->fofflags & FOF_BLOCKOTHERS && !mobj->player))) - continue; - - if (open->fofType != LO_FOF_ANY) + else if (fofopen[lo].ceiling < open->highceiling) { - topheight = P_VeryTopOfFOF(rover); - bottomheight = P_VeryBottomOfFOF(rover); + open->highceiling = fofopen[lo].ceiling; + } + + if (fofopen[FRONT].floor == fofopen[BACK].floor && ((fofopen[FRONT].floorrover && *fofopen[FRONT].floorrover->t_slope) || (fofopen[BACK].floorrover && *fofopen[BACK].floorrover->t_slope))) + { + fixed_t savedradius = mobj->radius; // forgive me. Perhaps it would be better to refactor these functions to take a radius of fixed_t instead? thats all they grabn from the mobj + mobj->radius = 1; // I need the same calculation, but at the center of the mobj + fofopening_t temp[2] = { + { INT32_MAX, INT32_MIN, NULL, NULL }, + { INT32_MAX, INT32_MIN, NULL, NULL } + }; + P_FoFOpening(front, linedef, mobj, open, &temp[FRONT]); + P_FoFOpening(back, linedef, mobj, open, &temp[BACK]); + hi = ( temp[FRONT].ceiling < temp[BACK].ceiling ); + mobj->radius = savedradius; } else - { - topheight = P_GetFOFTopZ(mobj, back, rover, g_tm.x, g_tm.y, linedef); - bottomheight = P_GetFOFBottomZ(mobj, back, rover, g_tm.x, g_tm.y, linedef); - } + hi = ( fofopen[FRONT].floor < fofopen[BACK].floor ); - switch (open->fofType) - { - case LO_FOF_FLOORS: - { - if (mobj->z >= topheight) - { - if ((rover->fofflags & FOF_INTANGIBLEFLATS) != FOF_REVERSEPLATFORM) - { - if (topheight > fofopen[BACK].floor) - { - fofopen[BACK].floor = topheight; - fofopen[BACK].floorrover = rover; - } - } - } - break; - } - case LO_FOF_CEILINGS: - { - if (thingtop <= bottomheight) - { - if ((rover->fofflags & FOF_INTANGIBLEFLATS) != FOF_PLATFORM) - { - if (bottomheight < fofopen[BACK].ceiling) - { - fofopen[BACK].ceiling = bottomheight; - fofopen[BACK].ceilingrover = rover; - } - } - } - break; - } - default: - { - midheight = bottomheight + (topheight - bottomheight) / 2; - delta1 = abs(mobj->z - midheight); - delta2 = abs(thingtop - midheight); + lo = ! hi; - if (delta1 > delta2) - { - // thing is below FOF - if ((rover->fofflags & FOF_INTANGIBLEFLATS) != FOF_PLATFORM) - { - if (bottomheight < fofopen[BACK].ceiling) - { - fofopen[BACK].ceiling = bottomheight; - fofopen[BACK].ceilingrover = rover; - } - } - } - else - { - // thing is above FOF - if ((rover->fofflags & FOF_INTANGIBLEFLATS) != FOF_REVERSEPLATFORM) - { - if (topheight > fofopen[BACK].floor) - { - fofopen[BACK].floor = topheight; - fofopen[BACK].floorrover = rover; - } - } - } - break; + if (fofopen[hi].floor >= open->floor) + { + botedge[hi] = P_GetFFloorTopZAt(fofopen[hi].floorrover, cross.x, cross.y); + + if (fofopen[lo].floor > open->floor) + { + botedge[lo] = P_GetFFloorTopZAt(fofopen[lo].floorrover, cross.x, cross.y); + } + + open->floor = fofopen[hi].floor; + open->floorrover = fofopen[hi].floorrover; + open->floorslope = *fofopen[hi].floorrover->t_slope; + open->floorpic = *fofopen[hi].floorrover->toppic; + open->floorstep = ( botedge[hi] - mobj->z ); + open->floordrop = ( botedge[hi] - botedge[lo] ); + + if (fofopen[lo].floor > open->lowfloor) + { + open->lowfloor = fofopen[lo].floor; } } - } - - hi = ( fofopen[0].ceiling < fofopen[1].ceiling ); - lo = ! hi; - - if (fofopen[lo].ceiling <= open->ceiling) - { - topedge[lo] = P_GetFFloorBottomZAt(fofopen[lo].ceilingrover, cross.x, cross.y); - - if (fofopen[hi].ceiling < open->ceiling) + else if (fofopen[hi].floor > open->lowfloor) { - topedge[hi] = P_GetFFloorBottomZAt(fofopen[hi].ceilingrover, cross.x, cross.y); + open->lowfloor = fofopen[hi].floor; } - - open->ceiling = fofopen[lo].ceiling; - open->ceilingrover = fofopen[lo].ceilingrover; - open->ceilingslope = *fofopen[lo].ceilingrover->b_slope; - open->ceilingpic = *fofopen[lo].ceilingrover->bottompic; - open->ceilingstep = ( thingtop - topedge[lo] ); - open->ceilingdrop = ( topedge[hi] - topedge[lo] ); - - if (fofopen[hi].ceiling < open->highceiling) - { - open->highceiling = fofopen[hi].ceiling; - } - } - else if (fofopen[lo].ceiling < open->highceiling) - { - open->highceiling = fofopen[lo].ceiling; - } - - hi = ( fofopen[0].floor < fofopen[1].floor ); - lo = ! hi; - - if (fofopen[hi].floor >= open->floor) - { - botedge[hi] = P_GetFFloorTopZAt(fofopen[hi].floorrover, cross.x, cross.y); - - if (fofopen[lo].floor > open->floor) - { - botedge[lo] = P_GetFFloorTopZAt(fofopen[lo].floorrover, cross.x, cross.y); - } - - open->floor = fofopen[hi].floor; - open->floorrover = fofopen[hi].floorrover; - open->floorslope = *fofopen[hi].floorrover->t_slope; - open->floorpic = *fofopen[hi].floorrover->toppic; - open->floorstep = ( botedge[hi] - mobj->z ); - open->floordrop = ( botedge[hi] - botedge[lo] ); - - if (fofopen[lo].floor > open->lowfloor) - { - open->lowfloor = fofopen[lo].floor; - } - } - else if (fofopen[hi].floor > open->lowfloor) - { - open->lowfloor = fofopen[hi].floor; } } } diff --git a/src/p_maputl.h b/src/p_maputl.h index 6ee7f91aa..caab92f60 100644 --- a/src/p_maputl.h +++ b/src/p_maputl.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // @@ -48,6 +48,7 @@ boolean P_PathTraverse(fixed_t px1, fixed_t py1, fixed_t px2, fixed_t py2, #define P_AproxDistance(dx, dy) FixedHypot(dx, dy) void P_ClosestPointOnLine(fixed_t x, fixed_t y, const line_t *line, vertex_t *result); +void P_ClosestPointOnLineWithinLine(fixed_t x, fixed_t y, line_t *line, vertex_t *result); void P_ClosestPointOnLine3D(const vector3_t *p, const vector3_t *line, vector3_t *result); INT32 P_PointOnLineSide(fixed_t x, fixed_t y, const line_t *line); void P_MakeDivline(const line_t *li, divline_t *dl); @@ -74,10 +75,19 @@ struct opening_t UINT8 fofType; // LO_FOF_ types for forcing FOF collide }; +struct fofopening_t +{ + fixed_t ceiling; + fixed_t floor; + ffloor_t * ceilingrover; + ffloor_t * floorrover; +}; + #define LO_FOF_ANY (0) #define LO_FOF_FLOORS (1) #define LO_FOF_CEILINGS (2) +boolean P_FoFOpening(sector_t *sector, line_t *linedef, mobj_t *mobj, opening_t *open, fofopening_t *fofopen); void P_LineOpening(line_t *plinedef, mobj_t *mobj, opening_t *open); typedef enum diff --git a/src/p_mobj.c b/src/p_mobj.c index 11a650ea4..d6782f179 100644 --- a/src/p_mobj.c +++ b/src/p_mobj.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -12,6 +12,7 @@ /// \file p_mobj.c /// \brief Moving object handling. Spawn functions +#include "d_think.h" #include "dehacked.h" #include "doomdef.h" #include "g_game.h" @@ -64,8 +65,6 @@ mobj_t *waypointcap = NULL; // general purpose. mobj_t *trackercap = NULL; -mobj_t *mobjcache = NULL; - void P_InitCachedActions(void) { actioncachehead.prev = actioncachehead.next = &actioncachehead; @@ -1613,8 +1612,9 @@ boolean P_XYMovement(mobj_t *mo) boolean moved; pslope_t *oldslope = NULL; vector3_t slopemom = {0,0,0}; - fixed_t predictedz = 0; + fixed_t predictedz = INT32_MAX; // Need a sentinel value TryMoveResult_t result = {0}; + fixed_t momentumzdelta = INT32_MAX; I_Assert(mo != NULL); I_Assert(!P_MobjWasRemoved(mo)); @@ -1650,11 +1650,13 @@ boolean P_XYMovement(mobj_t *mo) } // adjust various things based on slope - if (mo->standingslope && abs(mo->standingslope->zdelta) > FRACUNIT>>8) + if (mo->standingslope && mo->standingslope->zdelta != 0) { if (!P_IsObjectOnGround(mo)) { // We fell off at some point? Do the twisty thing! + // if (mo->player) + // CONS_Printf("Twisty thing launch?\n"); P_SlopeLaunch(mo); xmove = mo->momx; ymove = mo->momy; @@ -1670,9 +1672,16 @@ boolean P_XYMovement(mobj_t *mo) xmove = slopemom.x; ymove = slopemom.y; - predictedz = mo->z + slopemom.z; // We'll use this later... - + momentumzdelta = FixedDiv(slopemom.z, FixedHypot(xmove, ymove)); // so this lets us know what the zdelta is for the vector the player is travelling along, in addition to the slope's zdelta in its xydirection oldslope = mo->standingslope; + + // if (mo->player) + // CONS_Printf("%s zdelta %d momzdelta %d\n",player_names[mo->player-players], mo->standingslope->zdelta, momentumzdelta); + if (abs(momentumzdelta) >= FRACUNIT/6) + predictedz = mo->z + slopemom.z; + else + predictedz = mo->z; + } } else if (P_IsObjectOnGround(mo) && !mo->momz) predictedz = mo->z; @@ -1752,7 +1761,7 @@ boolean P_XYMovement(mobj_t *mo) // Wall transfer part 1. pslope_t *transferslope = NULL; fixed_t transfermomz = 0; - if (oldslope && (P_MobjFlip(mo)*(predictedz - mo->z) > 0)) // Only for moving up (relative to gravity), otherwise there's a failed launch when going down slopes and hitting walls + if (oldslope && (P_MobjFlip(mo)*slopemom.z > 0)) // Only for moving up (relative to gravity), otherwise there's a failed launch when going down slopes and hitting walls { transferslope = ((mo->standingslope) ? mo->standingslope : oldslope); if (((transferslope->zangle < ANGLE_180) ? transferslope->zangle : InvAngle(transferslope->zangle)) >= ANGLE_45) // Prevent some weird stuff going on on shallow slopes. @@ -1781,6 +1790,12 @@ boolean P_XYMovement(mobj_t *mo) if (P_MobjFlip(mo)*(transfermomz - mo->momz) > 2*FRACUNIT) // Do the actual launch! { mo->momz = transfermomz; + if (mo->player) + { + mo->player->transfer = transfermomz; + S_StartSound(mo, sfx_s3k98); + } + mo->standingslope = NULL; mo->terrain = NULL; P_SetPitchRoll(mo, ANGLE_90, @@ -1906,27 +1921,34 @@ boolean P_XYMovement(mobj_t *mo) mo->standingslope = oldslope; P_SetPitchRollFromSlope(mo, mo->standingslope); P_SlopeLaunch(mo); - - //CONS_Printf("launched off of slope - "); + // if (mo->player) + // CONS_Printf("%s Slope change launch old angle %f - new angle %f = %f\n", + // player_names[mo->player-players], + // FIXED_TO_FLOAT(AngleFixed(oldangle)), + // FIXED_TO_FLOAT(AngleFixed(newangle)), + // FIXED_TO_FLOAT(AngleFixed(oldangle-newangle)) + // ); } - - /* - CONS_Printf("old angle %f - new angle %f = %f\n", - FIXED_TO_FLOAT(AngleFixed(oldangle)), - FIXED_TO_FLOAT(AngleFixed(newangle)), - FIXED_TO_FLOAT(AngleFixed(oldangle-newangle)) - ); - */ - } - else if (predictedz - mo->z > abs(slopemom.z / 2)) + else { - // Now check if we were supposed to stick to this slope - //CONS_Printf("%d-%d > %d\n", (predictedz), (mo->z), (slopemom.z/2)); - P_SlopeLaunch(mo); + // if (mo->player) + // CONS_Printf("Ramp Launch %d %d+%d > 0 && %d-%d > %d ", mo->scale, FixedDiv(slopemom.z, mo->scale), P_GetMobjGravity(mo)*24, predictedz, mo->z, slopemom.z/2); + if ( // If slope aligned momz is more than gravity, and mobj clipped along ramp edge instead of following slope plane, then launch + ( !(mo->eflags & MFE_VERTICALFLIP) && FixedDiv(slopemom.z, mo->scale) + P_GetMobjGravity(mo)*24 > 0 && predictedz - mo->z > slopemom.z*4/5 ) + || ( (mo->eflags & MFE_VERTICALFLIP) && FixedDiv(slopemom.z, mo->scale) + P_GetMobjGravity(mo)*24 < 0 && predictedz - mo->z < slopemom.z*4/5 ) + ) + { + // if (mo->player) + // CONS_Printf("%s Ramp Launch %d %d %d+%d > 0 && %d-%d > %d True\n", player_names[mo->player-players], mo->scale, momentumzdelta, FixedDiv(slopemom.z, mo->scale), P_GetMobjGravity(mo)*24, predictedz, mo->z, slopemom.z*4/5); + P_SlopeLaunch(mo); + } + // else + // if (mo->player) + // CONS_Printf("False\n"); } } - else if (moved && mo->standingslope && predictedz) + else if (moved && mo->standingslope && predictedz != INT32_MAX) // Predicted z must be changed { angle_t moveangle = K_MomentumAngle(mo); angle_t newangle = FixedMul((signed)mo->standingslope->zangle, FINECOSINE((moveangle - mo->standingslope->xydirection) >> ANGLETOFINESHIFT)); @@ -2227,6 +2249,18 @@ boolean P_CheckSolidLava(mobj_t *mobj, ffloor_t *rover) return false; } +// Resets momz to 0 if the mo has just stepped up and is rising, but under a gravity and stepup derived momz threshold +static void P_CheckStepUpReset(mobj_t *mo) +{ + if (mo->eflags & MFE_JUSTSTEPPEDDOWN && (mo->momz * P_MobjFlip(mo)) > 0) + { + if (abs(mo->momz) < P_GetThingStepUp(mo, mo->x, mo->y)/6 + abs(P_GetMobjGravity(mo)*3)) + { + mo->momz = 0; + } + } +} + // // P_ZMovement // Returns false if the mobj was killed/exploded/removed, true otherwise. @@ -2255,6 +2289,9 @@ boolean P_ZMovement(mobj_t *mo) mo->pmomz = 0; mo->eflags &= ~MFE_APPLYPMOMZ; } + + P_CheckStepUpReset(mo); + mo->z += mo->momz; onground = P_IsObjectOnGround(mo); @@ -2264,7 +2301,11 @@ boolean P_ZMovement(mobj_t *mo) if (mo->flags & MF_NOCLIPHEIGHT) mo->standingslope = NULL; else if (!onground) + { + // if (mo->player) + // CONS_Printf("ZMovement launch?\n"); P_SlopeLaunch(mo); + } } switch (mo->type) @@ -2798,6 +2839,8 @@ void P_PlayerZMovement(mobj_t *mo) mo->eflags &= ~MFE_APPLYPMOMZ; } + P_CheckStepUpReset(mo); + mo->z += mo->momz; onground = P_IsObjectOnGround(mo); @@ -2811,7 +2854,11 @@ void P_PlayerZMovement(mobj_t *mo) if (mo->flags & MF_NOCLIPHEIGHT) mo->standingslope = NULL; else if (!onground) + { + // CONS_Printf("%s PlayerZMovement launch %d ", player_names[mo->player-players], mo->momz); P_SlopeLaunch(mo); + // CONS_Printf("%d\n", mo->momz); + } } // clip movement @@ -4010,12 +4057,15 @@ static void P_PlayerMobjThinker(mobj_t *mobj) || mobj->player->loop.radius != 0) { P_HitSpecialLines(mobj, mobj->x, mobj->y, mobj->momx, mobj->momy); + fixed_t oldx = mobj->x; + fixed_t oldy = mobj->y; P_UnsetThingPosition(mobj); mobj->x += mobj->momx; mobj->y += mobj->momy; mobj->z += mobj->momz; P_SetThingPosition(mobj); P_CheckPosition(mobj, mobj->x, mobj->y, NULL); + Obj_CrossCheckpoints(mobj->player, oldx, oldy); // I would put this inside P_HitSpecialLines, but its wants a player reference with post-move coords instead of and old and new mobj->floorz = g_tm.floorz; mobj->ceilingz = g_tm.ceilingz; mobj->terrain = NULL; @@ -5352,6 +5402,19 @@ static boolean P_IsTrackerType(INT32 type) case MT_GARDENTOP: // Frey return true; + case MT_JAWZ_SHIELD: // Pick-me-up + case MT_ORBINAUT: + case MT_ORBINAUT_SHIELD: + case MT_DROPTARGET: + case MT_DROPTARGET_SHIELD: + case MT_LANDMINE: + case MT_BANANA: + case MT_BANANA_SHIELD: + case MT_GACHABOM: + case MT_EGGMANITEM: + case MT_EGGMANITEM_SHIELD: + return true; + default: return false; } @@ -6595,6 +6658,30 @@ static void P_MobjSceneryThink(mobj_t *mobj) Obj_PulleyThink(mobj); return; } + case MT_BUBBLESHIELD_VISUAL: + { + if (!Obj_TickBubbleShieldVisual(mobj)) + { + return; + } + break; + } + case MT_LIGHTNINGSHIELD_VISUAL: + { + if (!Obj_TickLightningShieldVisual(mobj)) + { + return; + } + break; + } + case MT_FLAMESHIELD_VISUAL: + { + if (!Obj_TickFlameShieldVisual(mobj)) + { + return; + } + break; + } default: if (mobj->fuse) { // Scenery object fuse! Very basic! @@ -6807,12 +6894,8 @@ static void P_TracerAngleThink(mobj_t *mobj) if (!mobj->tracer) return; - if (!mobj->extravalue2) - return; - // mobj->lastlook - Don't disable behavior after first failure // mobj->extravalue1 - Angle tolerance - // mobj->extravalue2 - Exec tag upon failure // mobj->cvval - Allowable failure delay // mobj->cvmem - Failure timer @@ -6835,8 +6918,6 @@ static void P_TracerAngleThink(mobj_t *mobj) mobj->cvmem--; else { - INT32 exectag = mobj->extravalue2; // remember this before we erase the values - if (mobj->lastlook) mobj->cvmem = mobj->cusval; // reset timer for next failure else @@ -6846,7 +6927,7 @@ static void P_TracerAngleThink(mobj_t *mobj) mobj->lastlook = mobj->extravalue1 = mobj->extravalue2 = mobj->cvmem = mobj->cusval = 0; } - P_LinedefExecute(exectag, mobj, NULL); + P_ActivateThingSpecial(mobj->tracer, mobj); } } else @@ -7377,8 +7458,6 @@ static boolean P_MobjRegularThink(mobj_t *mobj) mobj->threshold--; break; case MT_LANDMINE: - mobj->friction = ORIG_FRICTION/4; - if (mobj->target && mobj->target->player) mobj->color = mobj->target->player->skincolor; else @@ -7390,10 +7469,21 @@ static boolean P_MobjRegularThink(mobj_t *mobj) ghost->colorized = true; // already has color! } + if (mobj->reactiontime > 0) + { + mobj->friction = ((2*ORIG_FRICTION)+FRACUNIT)/3; // too low still? + mobj->reactiontime--; + } + else + { + // Time to stop, ramp up the friction... + mobj->friction = ORIG_FRICTION/4; // too high still? + } + if (P_IsObjectOnGround(mobj) && mobj->health > 1) { S_StartSound(mobj, mobj->info->activesound); - mobj->momx = mobj->momy = 0; + // mobj->momx = mobj->momy = 0; mobj->health = 1; } @@ -7694,6 +7784,78 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } } break; + case MT_TRIPWIREAPPROACH: { + if (!mobj->target || !mobj->target->health || !mobj->target->player) + { + P_RemoveMobj(mobj); + return false; + } + + mobj_t *target = mobj->target; + player_t *player = target->player; + fixed_t myspeed = (player->speed); + + fixed_t maxspeed = K_PlayerTripwireSpeedThreshold(player); // Centered at this speed. + fixed_t minspeed = max(2 * maxspeed / 4, 16 * K_GetKartSpeed(player, false, false) / 10); // Starts appearing at this speed. + fixed_t alertspeed = 9 * maxspeed / 10; // When to flash? + fixed_t frontoffset = 5*target->scale; // How far in front? + + fixed_t percentvisible = 0; + if (myspeed > minspeed) + percentvisible = min(FRACUNIT, FixedDiv(myspeed - minspeed, maxspeed - minspeed)); + if (myspeed >= maxspeed || player->tripwireLeniency) + percentvisible = 0; + +#if 0 + fixed_t hang = 85*FRACUNIT/100; // Dampen inward movement past a certain point + if (percentvisible > hang && percentvisible < (95*FRACUNIT/100)) + percentvisible = (percentvisible + hang) / 2; +#endif + + fixed_t easedoffset = Easing_InOutCubic(percentvisible, 0, FRACUNIT); + fixed_t easedscale = FRACUNIT; + + fixed_t dynamicoffset = FixedMul(target->scale * 100, FRACUNIT - easedoffset); + + fixed_t xofs = (mobj->extravalue1) ? dynamicoffset : dynamicoffset * -1; + fixed_t zofs = (mobj->extravalue2) ? dynamicoffset : dynamicoffset * -1; + + angle_t facing = K_MomentumAngle(mobj->target); + fixed_t sin = FINESINE(facing >> ANGLETOFINESHIFT); + fixed_t cos = FINECOSINE(facing >> ANGLETOFINESHIFT); + + P_MoveOrigin(mobj, + target->x - FixedMul(xofs, sin) + FixedMul(frontoffset, cos), + target->y + FixedMul(xofs, cos) + FixedMul(frontoffset, sin), + target->z + zofs + (target->height / 2)); + mobj->angle = facing + ANGLE_90 + (mobj->extravalue1 ? ANGLE_45 : -1*ANGLE_45); + K_MatchGenericExtraFlags(mobj, target); + P_InstaScale(mobj, FixedMul(target->scale, easedscale)); + + UINT8 maxtranslevel = NUMTRANSMAPS - 2; + UINT8 trans = FixedInt(FixedMul(percentvisible, FRACUNIT*(maxtranslevel+1))); + if (trans > maxtranslevel) + trans = maxtranslevel; + trans = NUMTRANSMAPS - trans; + + mobj->renderflags &= ~(RF_TRANSMASK); + if (trans != 0) + { + mobj->renderflags |= (trans << RF_TRANSSHIFT); + } + + mobj->renderflags |= RF_PAPERSPRITE; + + mobj->colorized = true; + if (myspeed > alertspeed) + mobj->color = (leveltime & 1) ? SKINCOLOR_LILAC : SKINCOLOR_JAWZ; + else + mobj->color = SKINCOLOR_WHITE; + + mobj->renderflags |= (RF_DONTDRAW & ~K_GetPlayerDontDrawFlag(player)); + + break; + } case MT_TRIPWIREBOOST: { mobj_t *top; fixed_t newHeight; @@ -7702,12 +7864,18 @@ static boolean P_MobjRegularThink(mobj_t *mobj) if (!mobj->target || !mobj->target->health || !mobj->target->player || !mobj->target->player->tripwireLeniency) { + if (mobj->target && mobj->target->player && P_IsDisplayPlayer(mobj->target->player)) + { + S_StopSoundByID(mobj->target, sfx_s3k40); + S_StartSound(mobj->target, sfx_gshaf); + } + P_RemoveMobj(mobj); return false; } newHeight = mobj->target->height; - newScale = mobj->target->scale; + newScale = 3 * mobj->target->scale / 2; top = K_GetGardenTop(mobj->target->player); @@ -7769,7 +7937,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } else if (mobj->target->player->curshield == KSHIELD_FLAME) { - mobj->color = SKINCOLOR_KETCHUP; + mobj->color = SKINCOLOR_MAUVE; mobj->colorized = true; } else @@ -8375,7 +8543,19 @@ static boolean P_MobjRegularThink(mobj_t *mobj) P_MoveOrigin(mobj, mobj->target->x, mobj->target->y, mobj->target->z + mobj->target->height/2); // Taken from K_FlipFromObject. We just want to flip the visual according to its target, but that's it. mobj->eflags = (mobj->eflags & ~MFE_VERTICALFLIP)|(mobj->target->eflags & MFE_VERTICALFLIP); - + + break; + } + case MT_GOTIT: + { + if (!mobj->target || !mobj->target->health || !mobj->target->player) + { + P_RemoveMobj(mobj); + return false; + } + P_InstaScale(mobj, mobj->target->scale); + P_MoveOrigin(mobj, mobj->target->x, mobj->target->y, mobj->target->z + mobj->target->height/2); + K_MatchGenericExtraFlags(mobj, mobj->target); break; } case MT_BUBBLESHIELD: @@ -8390,6 +8570,12 @@ static boolean P_MobjRegularThink(mobj_t *mobj) return false; } + // FIXME: Old Bubble Shield code is still running. + // Some of it is visual, some gameplay. + // I left it alone and just tell it to go invisible~ + // See objects/bubble-shield.cpp + mobj->renderflags |= RF_DONTDRAW; + scale = (5*mobj->target->scale)>>2; curstate = ((mobj->tics == 1) ? (mobj->state->nextstate) : ((statenum_t)(mobj->state-states))); @@ -8481,7 +8667,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) mobj->extravalue2 = mobj->target->player->bubbleblowup; P_SetScale(mobj, (mobj->destscale = scale)); - + // For some weird reason, the Bubble Shield is the exception flip-wise, it has the offset baked into the sprite. // So instead of simply flipping the object, we have to do a position offset. fixed_t positionOffset = 0; @@ -8556,8 +8742,8 @@ static boolean P_MobjRegularThink(mobj_t *mobj) else { if (curstate >= S_FLAMESHIELDDASH1 && curstate <= S_FLAMESHIELDDASH12) - P_SetMobjState(mobj, S_FLAMESHIELD1); - mobj->dispoffset = ((curstate - S_FLAMESHIELD1) & 1) ? -1 : 1; + P_SetMobjState(mobj, S_INVISIBLE); + //mobj->dispoffset = ((curstate - S_FLAMESHIELD1) & 1) ? -1 : 1; } mobj->extravalue1 = mobj->target->player->flamedash; @@ -8893,7 +9079,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) if (plistlen > 1) { // Pick another player in the server! - plistlen = P_RandomKey(PR_SPARKLE, plistlen+1); + plistlen = P_RandomKey(PR_SPARKLE, plistlen); } else { @@ -8923,8 +9109,8 @@ static boolean P_MobjRegularThink(mobj_t *mobj) (newplayer != NULL) && (gamespeed != KARTSPEED_EASY) && (newplayer->tally.active == true) - && (newplayer->tally.totalLaps > 0) // Only true if not Time Attack - && (newplayer->tally.laps >= newplayer->tally.totalLaps) + && (newplayer->tally.totalExp > 0) // Only true if not Time Attack + && (newplayer->tally.exp >= newplayer->tally.totalExp) ) { UINT8 pnum = (newplayer-players); @@ -8983,7 +9169,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) { cur->skin = &skins[newplayer->skin]; cur->color = newplayer->skincolor; - + // Even if we didn't have the Perfect Sign to consider, // it's still necessary to refresh SPR2 on skin changes. P_SetMobjState(cur, (newperfect == true) ? S_KART_SIGL : S_KART_SIGN); @@ -9388,7 +9574,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) if (leveltime % 180 == 0) S_StartSound(mobj, sfx_s3kbfl); - if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer) && mobj->tracer->player) + if (mobj->tracer && !P_MobjWasRemoved(mobj->tracer) && mobj->tracer->tracer == mobj && mobj->tracer->player && mobj->tracer->player->carry == CR_TRAPBUBBLE) { player_t *player = mobj->tracer->player; fixed_t destx, desty, curfz, destfz; @@ -9399,7 +9585,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) mobj->cvmem /= 2; mobj->momz = 0; - mobj->destscale = ((5*mobj->tracer->scale)>>2) + (mobj->tracer->scale>>3); + mobj->destscale = ((8*mobj->tracer->scale)>>2) + (mobj->tracer->scale>>3); mobj->tracer->momz = (8*mobj->tracer->scale) * P_MobjFlip(mobj->tracer); @@ -9436,6 +9622,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) { S_StartSound(mobj->tracer, sfx_s3k77); mobj->tracer->flags &= ~MF_NOGRAVITY; + mobj->tracer->player->carry = CR_NONE; P_KillMobj(mobj, mobj->tracer, mobj->tracer, DMG_NORMAL); break; } @@ -9469,7 +9656,7 @@ static boolean P_MobjRegularThink(mobj_t *mobj) } else { - mobj->destscale = (5*mapobjectscale)>>2; + mobj->destscale = (8*mapobjectscale)>>2; if (mobj->threshold > 0) mobj->threshold--; @@ -9929,10 +10116,18 @@ static boolean P_MobjRegularThink(mobj_t *mobj) case MT_KART_LEFTOVER: { Obj_DestroyedKartThink(mobj); + if (P_MobjWasRemoved(mobj)) { return false; } + + break; + } + + case MT_FLYBOT767: + { + Obj_FlybotThink(mobj); break; } @@ -10796,6 +10991,14 @@ static void P_DefaultMobjShadowScale(mobj_t *thing) } } +mobj_t *P_AllocateMobj(void) +{ + mobj_t* mobj = (mobj_t*)Z_LevelPoolCalloc(sizeof(mobj_t)); + mobj->thinker.alloctype = TAT_LEVELPOOL; + mobj->thinker.size = sizeof(mobj_t); + return mobj; +} + // // P_SpawnMobj // @@ -10822,16 +11025,7 @@ mobj_t *P_SpawnMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype_t type) type = MT_RAY; } - if (mobjcache != NULL) - { - mobj = mobjcache; - mobjcache = mobjcache->hnext; - memset(mobj, 0, sizeof(*mobj)); - } - else - { - mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL); - } + mobj = P_AllocateMobj(); // this is officially a mobj, declared as soon as possible. mobj->thinker.function.acp1 = (actionf_p1)P_MobjThinker; @@ -11318,7 +11512,9 @@ static precipmobj_t *P_SpawnPrecipMobj(fixed_t x, fixed_t y, fixed_t z, mobjtype const mobjinfo_t *info = &mobjinfo[type]; state_t *st; fixed_t start_z = INT32_MIN; - precipmobj_t *mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL); + precipmobj_t *mobj = Z_LevelPoolCalloc(sizeof(precipmobj_t)); + mobj->thinker.alloctype = TAT_LEVELPOOL; + mobj->thinker.size = sizeof(precipmobj_t); mobj->type = type; mobj->info = info; @@ -11600,13 +11796,6 @@ void P_RemoveMobj(mobj_t *mobj) if (!mobj->thinker.next) { // Uh-oh, the mobj doesn't think, P_RemoveThinker would never go through! INT32 prevreferences; - if (!mobj->thinker.references) - { - // no references, dump it directly in the mobj cache - mobj->hnext = mobjcache; - mobjcache = mobj; - return; - } prevreferences = mobj->thinker.references; P_AddThinker(THINK_MOBJ, (thinker_t *)mobj); @@ -12120,34 +12309,13 @@ void P_SpawnPlayer(INT32 playernum) G_PlayerReborn(playernum, false); } - if (G_GametypeHasTeams()) + if (G_GametypeHasTeams() == true) { // If you're in a team game and you don't have a team assigned yet... - if (!p->spectator && p->ctfteam == 0) + if (p->spectator == false && p->team == TEAM_UNASSIGNED) { - changeteam_union NetPacket; - UINT16 usvalue; - NetPacket.value.l = NetPacket.value.b = 0; - - // Spawn as a spectator, - // yes even in splitscreen mode - p->spectator = true; - - // but immediately send a team change packet. - NetPacket.packet.playernum = playernum; - NetPacket.packet.verification = true; - NetPacket.packet.newteam = !(playernum&1) + 1; - - usvalue = SHORT(NetPacket.value.l|NetPacket.value.b); - SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue)); + G_AssignTeam(p, !(playernum & 1) + 1); } - - // Fix team colors. - // This code isn't being done right somewhere else. Oh well. - if (p->ctfteam == 1) - p->skincolor = skincolor_redteam; - else if (p->ctfteam == 2) - p->skincolor = skincolor_blueteam; } if (leveltime > introtime && K_PodiumSequence() == false) @@ -12200,6 +12368,17 @@ void P_SpawnPlayer(INT32 playernum) K_InitWavedashIndicator(p); K_InitTrickIndicator(p); + for (UINT8 approaches = 0; approaches < 4; approaches++) + { + mobj_t *approach = P_SpawnMobjFromMobj(p->mo, 0, 0, 0, MT_TRIPWIREAPPROACH); + P_SetTarget(&approach->target, p->mo); + approach->extravalue1 = (approaches == 0 || approaches == 2) ? 1 : 0; + approach->extravalue2 = (approaches == 0 || approaches == 1) ? 1 : 0; + approach->renderflags |= approach->extravalue1 ? 0 : RF_HORIZONTALFLIP; + approach->renderflags |= approach->extravalue2 ? 0 : RF_VERTICALFLIP; + } + + if ((gametyperules & GTR_BUMPERS) && !p->spectator) { mobj->health = K_BumpersToHealth(K_StartingBumperCount()); @@ -12227,7 +12406,7 @@ void P_SpawnPlayer(INT32 playernum) P_SetScale(aring, p->mo->scale); K_MatchGenericExtraFlags(aring, p->mo); aring->renderflags |= RF_DONTDRAW; - + mobj_t *abody = P_SpawnMobj(p->mo->x, p->mo->y, p->mo->z, MT_AMPBODY); P_SetTarget(&abody->target, p->mo); P_SetScale(abody, p->mo->scale); @@ -12557,23 +12736,23 @@ static boolean P_SpawnNonMobjMapThing(mapthing_t *mthing) } return true; } - else if (mthing->type == 34) // Red CTF starts + else if (mthing->type == 34) // Orange team starts { - if (numredctfstarts < MAXPLAYERS) + if (numteamstarts[TEAM_ORANGE] < MAXPLAYERS) { - redctfstarts[numredctfstarts] = mthing; + teamstarts[TEAM_ORANGE][numteamstarts[TEAM_ORANGE]] = mthing; mthing->type = 0; - numredctfstarts++; + numteamstarts[TEAM_ORANGE]++; } return true; } - else if (mthing->type == 35) // Blue CTF starts + else if (mthing->type == 35) // Blue team starts { - if (numbluectfstarts < MAXPLAYERS) + if (numteamstarts[TEAM_BLUE] < MAXPLAYERS) { - bluectfstarts[numbluectfstarts] = mthing; + teamstarts[TEAM_BLUE][numteamstarts[TEAM_BLUE]] = mthing; mthing->type = 0; - numbluectfstarts++; + numteamstarts[TEAM_BLUE]++; } return true; } @@ -12603,6 +12782,7 @@ static boolean P_AllowMobjSpawn(mapthing_t* mthing, mobjtype_t i) switch (i) { case MT_RING: + case MT_RANDOMITEM: if (modeattacking & ATTACKING_SPB) return false; break; @@ -12735,9 +12915,9 @@ static boolean P_SetupEmblem(mapthing_t *mthing, mobj_t *mobj) void P_SprayCanInit(mobj_t* mobj) { // See also P_TouchSpecialThing - UINT16 can_id = mapheaderinfo[gamemap-1]->cache_spraycan; + UINT16 can_id = mapheaderinfo[gamemap-1]->records.spraycan; - if (can_id < gamedata->numspraycans) + if (can_id < gamedata->numspraycans || can_id == MCAN_BONUS) { // Assigned to this level, has been grabbed mobj->renderflags = (tr_trans50 << RF_TRANSSHIFT); @@ -12745,19 +12925,38 @@ void P_SprayCanInit(mobj_t* mobj) // Prevent footguns - these won't persist when custom levels are unloaded else if (gamemap-1 < basenummapheaders) { - // Unassigned, get the next grabbable colour (offset by threshold) - can_id = gamedata->gotspraycans; + if (gamedata->gotspraycans >= gamedata->numspraycans) + { + can_id = MCAN_BONUS; + } + else + { + // Unassigned, get the next grabbable colour (offset by threshold) + can_id = gamedata->gotspraycans; - // It's ok if this goes over gamedata->numspraycans, as they're - // capped below in this func... but NEVER let this go backwards!! - if (mobj->threshold != 0) - can_id += (mobj->threshold & UINT8_MAX); + // It's ok if this goes over gamedata->numspraycans, as they're + // capped below in this func... but NEVER let this go backwards!! + if (mobj->threshold != 0) + can_id += (mobj->threshold & UINT8_MAX); + } mobj->renderflags = 0; } - - if (can_id < gamedata->numspraycans) + else { + // Custom course, bonus only + can_id = MCAN_BONUS; + } + + if (can_id == MCAN_BONUS && mobj->threshold == 0) + { + // Only one bonus possible + // We modify sprite instead of state for netsync reasons + mobj->sprite = SPR_SBON; + } + else if (can_id < gamedata->numspraycans) + { + mobj->sprite = mobj->state->sprite; mobj->color = gamedata->spraycans[can_id].col; } else @@ -14066,12 +14265,10 @@ static boolean P_SetupSpawnedMapThing(mapthing_t *mthing, mobj_t *mobj) return true; } -void P_CopyMapThingSpecialFieldsToMobj(const mapthing_t *mthing, mobj_t *mobj) +void P_CopyMapThingBehaviorFieldsToMobj(const mapthing_t *mthing, mobj_t *mobj) { size_t arg = SIZE_MAX; - mobj->special = mthing->special; - for (arg = 0; arg < NUM_MAPTHING_ARGS; arg++) { mobj->thing_args[arg] = mthing->thing_args[arg]; @@ -14096,6 +14293,13 @@ void P_CopyMapThingSpecialFieldsToMobj(const mapthing_t *mthing, mobj_t *mobj) mobj->thing_stringargs[arg] = Z_Realloc(mobj->thing_stringargs[arg], len + 1, PU_LEVEL, NULL); M_Memcpy(mobj->thing_stringargs[arg], mthing->thing_stringargs[arg], len + 1); } +} + +void P_CopyMapThingSpecialFieldsToMobj(const mapthing_t *mthing, mobj_t *mobj) +{ + size_t arg = SIZE_MAX; + + mobj->special = mthing->special; for (arg = 0; arg < NUM_SCRIPT_ARGS; arg++) { @@ -14143,6 +14347,7 @@ static mobj_t *P_SpawnMobjFromMapThing(mapthing_t *mthing, fixed_t x, fixed_t y, mobj->tid = mthing->tid; P_AddThingTID(mobj); + P_CopyMapThingBehaviorFieldsToMobj(mthing, mobj); P_CopyMapThingSpecialFieldsToMobj(mthing, mobj); if (!P_SetupSpawnedMapThing(mthing, mobj)) @@ -14861,17 +15066,19 @@ mobj_t *P_SpawnMissile(mobj_t *source, mobj_t *dest, mobjtype_t type) // void P_ColorTeamMissile(mobj_t *missile, player_t *source) { - if (G_GametypeHasTeams()) + if (missile == NULL || source == NULL) { - if (source->ctfteam == 2) - missile->color = skincolor_bluering; - else if (source->ctfteam == 1) - missile->color = skincolor_redring; + return; + } + + if (source->team > TEAM_UNASSIGNED && source->team < TEAM__MAX) + { + missile->color = g_teaminfo[source->team].color; } - /* else - missile->color = player->mo->color; //copy color - */ + { + missile->color = source->skincolor; + } } // diff --git a/src/p_mobj.h b/src/p_mobj.h index aa8efc687..08b41922e 100644 --- a/src/p_mobj.h +++ b/src/p_mobj.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -561,6 +561,7 @@ fixed_t P_GetMobjSpawnHeight(const mobjtype_t mobjtype, const fixed_t x, const f fixed_t P_GetMapThingSpawnHeight(const mobjtype_t mobjtype, const mapthing_t* mthing, const fixed_t x, const fixed_t y); mobj_t *P_SpawnMapThing(mapthing_t *mthing); +void P_CopyMapThingBehaviorFieldsToMobj(const mapthing_t *mthing, mobj_t *mobj); void P_CopyMapThingSpecialFieldsToMobj(const mapthing_t *mthing, mobj_t *mobj); void P_SpawnHoop(mapthing_t *mthing); void P_SpawnItemPattern(mapthing_t *mthing); diff --git a/src/p_polyobj.c b/src/p_polyobj.c index 82eb95432..b6e15204d 100644 --- a/src/p_polyobj.c +++ b/src/p_polyobj.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2006 by James Haley. // @@ -1995,7 +1995,9 @@ boolean EV_DoPolyObjRotate(polyrotdata_t *prdata) return false; // create a new thinker - th = Z_Malloc(sizeof(polyrotate_t), PU_LEVSPEC, NULL); + th = Z_LevelPoolMalloc(sizeof(polyrotate_t)); + th->thinker.alloctype = TAT_LEVELPOOL; + th->thinker.size = sizeof(polyrotate_t); th->thinker.function.acp1 = (actionf_p1)T_PolyObjRotate; P_AddThinker(THINK_POLYOBJ, &th->thinker); po->thinker = &th->thinker; @@ -2067,7 +2069,9 @@ boolean EV_DoPolyObjMove(polymovedata_t *pmdata) return false; // create a new thinker - th = Z_Malloc(sizeof(polymove_t), PU_LEVSPEC, NULL); + th = Z_LevelPoolMalloc(sizeof(polymove_t)); + th->thinker.alloctype = TAT_LEVELPOOL; + th->thinker.size = sizeof(polymove_t); th->thinker.function.acp1 = (actionf_p1)T_PolyObjMove; P_AddThinker(THINK_POLYOBJ, &th->thinker); po->thinker = &th->thinker; @@ -2129,7 +2133,9 @@ boolean EV_DoPolyObjWaypoint(polywaypointdata_t *pwdata) return false; // create a new thinker - th = Z_Malloc(sizeof(polywaypoint_t), PU_LEVSPEC, NULL); + th = Z_LevelPoolMalloc(sizeof(polywaypoint_t)); + th->thinker.alloctype = TAT_LEVELPOOL; + th->thinker.size = sizeof(polywaypoint_t); th->thinker.function.acp1 = (actionf_p1)T_PolyObjWaypoint; P_AddThinker(THINK_POLYOBJ, &th->thinker); po->thinker = &th->thinker; @@ -2197,7 +2203,9 @@ static void Polyobj_doSlideDoor(polyobj_t *po, polydoordata_t *doordata) INT32 start; // allocate and add a new slide door thinker - th = Z_Malloc(sizeof(polyslidedoor_t), PU_LEVSPEC, NULL); + th = Z_LevelPoolMalloc(sizeof(polyslidedoor_t)); + th->thinker.alloctype = TAT_LEVELPOOL; + th->thinker.size = sizeof(polyslidedoor_t); th->thinker.function.acp1 = (actionf_p1)T_PolyDoorSlide; P_AddThinker(THINK_POLYOBJ, &th->thinker); @@ -2248,7 +2256,9 @@ static void Polyobj_doSwingDoor(polyobj_t *po, polydoordata_t *doordata) INT32 start; // allocate and add a new swing door thinker - th = Z_Malloc(sizeof(polyswingdoor_t), PU_LEVSPEC, NULL); + th = Z_LevelPoolMalloc(sizeof(polyswingdoor_t)); + th->thinker.alloctype = TAT_LEVELPOOL; + th->thinker.size = sizeof(polyswingdoor_t); th->thinker.function.acp1 = (actionf_p1)T_PolyDoorSwing; P_AddThinker(THINK_POLYOBJ, &th->thinker); @@ -2333,7 +2343,9 @@ boolean EV_DoPolyObjDisplace(polydisplacedata_t *prdata) return false; // create a new thinker - th = Z_Malloc(sizeof(polydisplace_t), PU_LEVSPEC, NULL); + th = Z_LevelPoolMalloc(sizeof(polydisplace_t)); + th->thinker.alloctype = TAT_LEVELPOOL; + th->thinker.size = sizeof(polydisplace_t); th->thinker.function.acp1 = (actionf_p1)T_PolyObjDisplace; P_AddThinker(THINK_POLYOBJ, &th->thinker); po->thinker = &th->thinker; @@ -2382,7 +2394,9 @@ boolean EV_DoPolyObjRotDisplace(polyrotdisplacedata_t *prdata) return false; // create a new thinker - th = Z_Malloc(sizeof(polyrotdisplace_t), PU_LEVSPEC, NULL); + th = Z_LevelPoolMalloc(sizeof(polyrotdisplace_t)); + th->thinker.alloctype = TAT_LEVELPOOL; + th->thinker.size = sizeof(polyrotdisplace_t); th->thinker.function.acp1 = (actionf_p1)T_PolyObjRotDisplace; P_AddThinker(THINK_POLYOBJ, &th->thinker); po->thinker = &th->thinker; @@ -2483,7 +2497,9 @@ boolean EV_DoPolyObjFlag(polyflagdata_t *pfdata) } // create a new thinker - th = Z_Malloc(sizeof(polymove_t), PU_LEVSPEC, NULL); + th = Z_LevelPoolMalloc(sizeof(polymove_t)); + th->thinker.alloctype = TAT_LEVELPOOL; + th->thinker.size = sizeof(polymove_t); th->thinker.function.acp1 = (actionf_p1)T_PolyObjFlag; P_AddThinker(THINK_POLYOBJ, &th->thinker); po->thinker = &th->thinker; @@ -2632,7 +2648,9 @@ boolean EV_DoPolyObjFade(polyfadedata_t *pfdata) P_RemoveThinker(po->thinker); // create a new thinker - th = Z_Malloc(sizeof(polyfade_t), PU_LEVSPEC, NULL); + th = Z_LevelPoolMalloc(sizeof(polyfade_t)); + th->thinker.alloctype = TAT_LEVELPOOL; + th->thinker.size = sizeof(polyfade_t); th->thinker.function.acp1 = (actionf_p1)T_PolyObjFade; P_AddThinker(THINK_POLYOBJ, &th->thinker); po->thinker = &th->thinker; diff --git a/src/p_polyobj.h b/src/p_polyobj.h index 576c859d0..0c9954602 100644 --- a/src/p_polyobj.h +++ b/src/p_polyobj.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2006 by James Haley. // diff --git a/src/p_pspr.h b/src/p_pspr.h index 15145b3ca..31ebefd32 100644 --- a/src/p_pspr.h +++ b/src/p_pspr.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/p_saveg.c b/src/p_saveg.cpp similarity index 90% rename from src/p_saveg.c rename to src/p_saveg.cpp index 9fb6bc1d5..7fc84d0f8 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -9,9 +9,10 @@ // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -/// \file p_saveg.c +/// \file p_saveg.cpp /// \brief Archiving: SaveGame I/O +#include "d_think.h" #include "doomdef.h" #include "byteptr.h" #include "d_main.h" @@ -247,7 +248,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT16(save->p, players[i].flashpal); WRITEUINT16(save->p, players[i].flashcount); - WRITEUINT8(save->p, players[i].skincolor); + WRITEUINT16(save->p, players[i].skincolor); WRITEINT32(save->p, players[i].skin); for (j = 0; j < MAXAVAILABILITY; j++) @@ -257,6 +258,12 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].fakeskin); WRITEUINT8(save->p, players[i].lastfakeskin); + + WRITEUINT16(save->p, players[i].prefcolor); + WRITEINT32(save->p, players[i].prefskin); + WRITEUINT16(save->p, players[i].preffollowercolor); + WRITEINT32(save->p, players[i].preffollower); + WRITEUINT32(save->p, players[i].score); WRITESINT8(save->p, players[i].lives); WRITESINT8(save->p, players[i].xtralife); @@ -281,14 +288,14 @@ static void P_NetArchivePlayers(savebuffer_t *save) } WRITEUINT8(save->p, players[i].laps); WRITEUINT8(save->p, players[i].latestlap); - WRITEUINT32(save->p, players[i].lapPoints); - WRITEINT32(save->p, players[i].exp); + WRITEUINT32(save->p, players[i].exp); + WRITEINT32(save->p, players[i].gradingfactor); WRITEUINT16(save->p, players[i].gradingpointnum); WRITEINT16(save->p, players[i].duelscore); WRITEINT32(save->p, players[i].cheatchecknum); WRITEINT32(save->p, players[i].checkpointId); - WRITEUINT8(save->p, players[i].ctfteam); + WRITEUINT8(save->p, players[i].team); WRITEUINT8(save->p, players[i].checkskip); @@ -425,6 +432,8 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].position); WRITEUINT8(save->p, players[i].oldposition); WRITEUINT8(save->p, players[i].positiondelay); + WRITEUINT8(save->p, players[i].teamposition); + WRITEUINT8(save->p, players[i].teamimportance); WRITEUINT32(save->p, players[i].distancetofinish); WRITEUINT32(save->p, players[i].distancetofinishprev); WRITEUINT32(save->p, players[i].lastpickupdistance); @@ -447,10 +456,14 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].noEbrakeMagnet); WRITEUINT8(save->p, players[i].tumbleBounces); WRITEUINT16(save->p, players[i].tumbleHeight); + WRITEUINT16(save->p, players[i].stunned); + WRITEUINT8(save->p, players[i].stunnedCombo); WRITEUINT8(save->p, players[i].justDI); WRITEUINT8(save->p, players[i].flipDI); + WRITEUINT8(save->p, players[i].cangrabitems); + WRITESINT8(save->p, players[i].drift); WRITEFIXED(save->p, players[i].driftcharge); WRITEUINT16(save->p, players[i].driftboost); @@ -498,6 +511,8 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITESINT8(save->p, players[i].itemtype); WRITEUINT8(save->p, players[i].itemamount); + WRITESINT8(save->p, players[i].backupitemtype); + WRITEUINT8(save->p, players[i].backupitemamount); WRITESINT8(save->p, players[i].throwdir); WRITEUINT8(save->p, players[i].sadtimer); @@ -595,6 +610,8 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].tripwireReboundDelay); WRITEUINT16(save->p, players[i].wavedash); + WRITEUINT16(save->p, players[i].wavedashleft); + WRITEUINT16(save->p, players[i].wavedashright); WRITEUINT8(save->p, players[i].wavedashdelay); WRITEUINT16(save->p, players[i].wavedashboost); WRITEUINT16(save->p, players[i].overdrive); @@ -648,6 +665,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].ringboxdelay); WRITEUINT8(save->p, players[i].ringboxaward); + WRITEUINT32(save->p, players[i].lastringboost); WRITEUINT8(save->p, players[i].amps); WRITEUINT8(save->p, players[i].amppickup); @@ -656,6 +674,8 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].itemflags); WRITEFIXED(save->p, players[i].outrun); + WRITEFIXED(save->p, players[i].transfer); + WRITEUINT8(save->p, players[i].transfersound); WRITEUINT8(save->p, players[i].rideroid); WRITEUINT8(save->p, players[i].rdnodepull); @@ -718,6 +738,7 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].botvars.diffincrease); WRITEUINT8(save->p, players[i].botvars.rival); WRITEFIXED(save->p, players[i].botvars.rubberband); + WRITEUINT8(save->p, players[i].botvars.bumpslow); WRITEUINT32(save->p, players[i].botvars.itemdelay); WRITEUINT32(save->p, players[i].botvars.itemconfirm); WRITESINT8(save->p, players[i].botvars.turnconfirm); @@ -826,8 +847,8 @@ static void P_NetArchivePlayers(savebuffer_t *save) WRITEUINT8(save->p, players[i].tally.position); WRITEUINT8(save->p, players[i].tally.numPlayers); WRITEUINT8(save->p, players[i].tally.rings); - WRITEUINT16(save->p, players[i].tally.laps); - WRITEUINT16(save->p, players[i].tally.totalLaps); + WRITEUINT16(save->p, players[i].tally.exp); + WRITEUINT16(save->p, players[i].tally.totalExp); WRITEUINT16(save->p, players[i].tally.prisons); WRITEUINT16(save->p, players[i].tally.totalPrisons); WRITEINT32(save->p, players[i].tally.points); @@ -911,16 +932,16 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].tilt = READANGLE(save->p); players[i].awayview.tics = READINT32(save->p); - players[i].playerstate = READUINT8(save->p); + players[i].playerstate = (playerstate_t)READUINT8(save->p); players[i].pflags = READUINT32(save->p); - players[i].panim = READUINT8(save->p); + players[i].panim = (panim_t)READUINT8(save->p); players[i].spectator = READUINT8(save->p); players[i].spectatewait = READUINT32(save->p); players[i].flashpal = READUINT16(save->p); players[i].flashcount = READUINT16(save->p); - players[i].skincolor = READUINT8(save->p); + players[i].skincolor = READUINT16(save->p); players[i].skin = READINT32(save->p); for (j = 0; j < MAXAVAILABILITY; j++) @@ -930,6 +951,12 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].fakeskin = READUINT8(save->p); players[i].lastfakeskin = READUINT8(save->p); + + players[i].prefcolor = READUINT16(save->p); + players[i].prefskin = READINT32(save->p); + players[i].preffollowercolor = READUINT16(save->p); + players[i].preffollower = READINT32(save->p); + players[i].score = READUINT32(save->p); players[i].lives = READSINT8(save->p); players[i].xtralife = READSINT8(save->p); // Ring Extra Life counter @@ -954,14 +981,14 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) } players[i].laps = READUINT8(save->p); // Number of laps (optional) players[i].latestlap = READUINT8(save->p); - players[i].lapPoints = READUINT32(save->p); - players[i].exp = READINT32(save->p); + players[i].exp = READUINT32(save->p); + players[i].gradingfactor = READINT32(save->p); players[i].gradingpointnum = READUINT16(save->p); players[i].duelscore = READINT16(save->p); players[i].cheatchecknum = READINT32(save->p); players[i].checkpointId = READINT32(save->p); - players[i].ctfteam = READUINT8(save->p); // 1 == Red, 2 == Blue + players[i].team = READUINT8(save->p); players[i].checkskip = READUINT8(save->p); @@ -1051,6 +1078,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].position = READUINT8(save->p); players[i].oldposition = READUINT8(save->p); players[i].positiondelay = READUINT8(save->p); + players[i].teamposition = READUINT8(save->p); + players[i].teamimportance = READUINT8(save->p); players[i].distancetofinish = READUINT32(save->p); players[i].distancetofinishprev = READUINT32(save->p); players[i].lastpickupdistance = READUINT32(save->p); @@ -1073,10 +1102,14 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].noEbrakeMagnet = READUINT8(save->p); players[i].tumbleBounces = READUINT8(save->p); players[i].tumbleHeight = READUINT16(save->p); + players[i].stunned = READUINT16(save->p); + players[i].stunnedCombo = READUINT8(save->p); players[i].justDI = READUINT8(save->p); players[i].flipDI = (boolean)READUINT8(save->p); + players[i].cangrabitems = READUINT8(save->p); + players[i].drift = READSINT8(save->p); players[i].driftcharge = READFIXED(save->p); players[i].driftboost = READUINT16(save->p); @@ -1124,6 +1157,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].itemtype = READSINT8(save->p); players[i].itemamount = READUINT8(save->p); + players[i].backupitemtype = READSINT8(save->p); + players[i].backupitemamount = READUINT8(save->p); players[i].throwdir = READSINT8(save->p); players[i].sadtimer = READUINT8(save->p); @@ -1220,6 +1255,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].tripwireReboundDelay = READUINT8(save->p); players[i].wavedash = READUINT16(save->p); + players[i].wavedashleft = READUINT16(save->p); + players[i].wavedashright = READUINT16(save->p); players[i].wavedashdelay = READUINT8(save->p); players[i].wavedashboost = READUINT16(save->p); players[i].overdrive = READUINT16(save->p); @@ -1273,6 +1310,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].ringboxdelay = READUINT8(save->p); players[i].ringboxaward = READUINT8(save->p); + players[i].lastringboost = READUINT32(save->p); players[i].amps =READUINT8(save->p); players[i].amppickup =READUINT8(save->p); @@ -1281,6 +1319,8 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].itemflags = READUINT8(save->p); players[i].outrun = READFIXED(save->p); + players[i].transfer = READFIXED(save->p); + players[i].transfersound = READUINT8(save->p); players[i].rideroid = (boolean)READUINT8(save->p); players[i].rdnodepull = (boolean)READUINT8(save->p); @@ -1343,6 +1383,7 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].botvars.diffincrease = READUINT8(save->p); players[i].botvars.rival = (boolean)READUINT8(save->p); players[i].botvars.rubberband = READFIXED(save->p); + players[i].botvars.bumpslow = READUINT8(save->p); players[i].botvars.itemdelay = READUINT32(save->p); players[i].botvars.itemconfirm = READUINT32(save->p); players[i].botvars.turnconfirm = READSINT8(save->p); @@ -1456,28 +1497,28 @@ static void P_NetUnArchivePlayers(savebuffer_t *save) players[i].tally.header[63] = '\0'; players[i].tally.showRoundNum = (boolean)READUINT8(save->p); - players[i].tally.gradeVoice = READINT32(save->p); + players[i].tally.gradeVoice = (sfxenum_t)READINT32(save->p); players[i].tally.time = READINT32(save->p); players[i].tally.ringPool = READUINT16(save->p); for (q = 0; q < TALLY_WINDOW_SIZE; q++) - players[i].tally.stats[q] = READINT32(save->p); + players[i].tally.stats[q] = (tally_stat_e)READINT32(save->p); players[i].tally.position = READUINT8(save->p); players[i].tally.numPlayers = READUINT8(save->p); players[i].tally.rings = READUINT8(save->p); - players[i].tally.laps = READUINT16(save->p); - players[i].tally.totalLaps = READUINT16(save->p); + players[i].tally.exp = READUINT16(save->p); + players[i].tally.totalExp = READUINT16(save->p); players[i].tally.prisons = READUINT16(save->p); players[i].tally.totalPrisons = READUINT16(save->p); players[i].tally.points = READINT32(save->p); players[i].tally.pointLimit = READINT32(save->p); players[i].tally.powerStones = READUINT8(save->p); for (q = 0; q < TALLY_WINDOW_SIZE; q++) - players[i].tally.bonuses[q] = READINT32(save->p); + players[i].tally.bonuses[q] = (tally_bonus_e)READINT32(save->p); players[i].tally.rank = READINT32(save->p); - players[i].tally.state = READINT32(save->p); + players[i].tally.state = (tally_state_e)READINT32(save->p); players[i].tally.hudSlide = READINT32(save->p); players[i].tally.delay = READINT32(save->p); players[i].tally.transition = READINT32(save->p); @@ -1703,7 +1744,7 @@ static void P_NetUnArchiveZVote(savebuffer_t *save) g_midVote.votes[i] = (boolean)READUINT8(save->p); } - g_midVote.type = READUINT8(save->p); + g_midVote.type = (midVoteType_e)READUINT8(save->p); g_midVote.variable = READINT32(save->p); g_midVote.time = (tic_t)READUINT32(save->p); @@ -1982,7 +2023,7 @@ static void P_NetUnArchiveColormaps(savebuffer_t *save) #define SD_DIFF3 0x80 // diff3 flags -#define SD_TAGLIST 0x01 +#define SD__UNUSED 0x01 #define SD_COLORMAP 0x02 #define SD_CRUMBLESTATE 0x04 #define SD_FLOORLIGHT 0x08 @@ -2020,11 +2061,16 @@ static boolean P_SectorStringArgsEqual(const sector_t *sc, const sector_t *spawn UINT8 i; for (i = 0; i < NUM_SCRIPT_STRINGARGS; i++) { - if (!sc->stringargs[i]) - return !spawnsc->stringargs[i]; - - if (strcmp(sc->stringargs[i], spawnsc->stringargs[i])) - return false; + if (sc->stringargs[i] == NULL || spawnsc->stringargs[i] == NULL) + { + if (sc->stringargs[i] != spawnsc->stringargs[i]) + return false; + } + else + { + if (strcmp(sc->stringargs[i], spawnsc->stringargs[i])) + return false; + } } return true; @@ -2032,7 +2078,7 @@ static boolean P_SectorStringArgsEqual(const sector_t *sc, const sector_t *spawn #define LD_FLAG 0x01 #define LD_SPECIAL 0x02 -#define LD_CLLCOUNT 0x04 +#define LD_TAG 0x04 #define LD_S1TEXOFF 0x08 #define LD_S1TOPTEX 0x10 #define LD_S1BOTTEX 0x20 @@ -2046,7 +2092,7 @@ static boolean P_SectorStringArgsEqual(const sector_t *sc, const sector_t *spawn #define LD_S2MIDTEX 0x08 #define LD_ARGS 0x10 #define LD_STRINGARGS 0x20 -#define LD_EXECUTORDELAY 0x40 +#define LD__UNUSED 0x40 #define LD_DIFF3 0x80 // diff3 flags @@ -2067,11 +2113,16 @@ static boolean P_LineStringArgsEqual(const line_t *li, const line_t *spawnli) UINT8 i; for (i = 0; i < NUM_SCRIPT_STRINGARGS; i++) { - if (!li->stringargs[i]) - return !spawnli->stringargs[i]; - - if (strcmp(li->stringargs[i], spawnli->stringargs[i])) - return false; + if (li->stringargs[i] == NULL || spawnli->stringargs[i] == NULL) + { + if (li->stringargs[i] != spawnli->stringargs[i]) + return false; + } + else + { + if (strcmp(li->stringargs[i], spawnli->stringargs[i])) + return false; + } } return true; @@ -2155,7 +2206,7 @@ static void UnArchiveFFloors(savebuffer_t *save, const sector_t *ss) fflr_diff = READUINT8(save->p); if (fflr_diff & FD_FLAGS) - rover->fofflags = READUINT32(save->p); + rover->fofflags = (ffloortype_e)READUINT32(save->p); if (fflr_diff & FD_ALPHA) rover->alpha = READINT16(save->p); @@ -2450,7 +2501,7 @@ static void UnArchiveSectors(savebuffer_t *save) if (ncount != sectors[i].tags.count) { sectors[i].tags.count = ncount; - sectors[i].tags.tags = Z_Realloc(sectors[i].tags.tags, ncount*sizeof(mtag_t), PU_LEVEL, NULL); + sectors[i].tags.tags = (mtag_t*)Z_Realloc(sectors[i].tags.tags, ncount*sizeof(mtag_t), PU_LEVEL, NULL); } for (j = 0; j < ncount; j++) @@ -2458,7 +2509,7 @@ static void UnArchiveSectors(savebuffer_t *save) // Add new entries. for (j = 0; j < sectors[i].tags.count; j++) - Taggroup_Remove(tags_sectors, sectors[i].tags.tags[j], i); + Taggroup_Add(tags_sectors, sectors[i].tags.tags[j], i); } @@ -2478,11 +2529,11 @@ static void UnArchiveSectors(savebuffer_t *save) } if (diff3 & SD_FLAG) { - sectors[i].flags = READUINT32(save->p); + sectors[i].flags = (sectorflags_t)READUINT32(save->p); CheckForReverseGravity |= (sectors[i].flags & MSF_GRAVITYFLIP); } if (diff3 & SD_SPECIALFLAG) - sectors[i].specialflags = READUINT32(save->p); + sectors[i].specialflags = (sectorspecialflags_t)READUINT32(save->p); if (diff4 & SD_DAMAGETYPE) sectors[i].damagetype = READUINT8(save->p); if (diff4 & SD_TRIGGERTAG) @@ -2513,14 +2564,14 @@ static void UnArchiveSectors(savebuffer_t *save) continue; } - sectors[i].stringargs[j] = Z_Realloc(sectors[i].stringargs[j], len + 1, PU_LEVEL, NULL); + sectors[i].stringargs[j] = (char*)Z_Realloc(sectors[i].stringargs[j], len + 1, PU_LEVEL, NULL); for (k = 0; k < len; k++) sectors[i].stringargs[j][k] = READCHAR(save->p); sectors[i].stringargs[j][len] = '\0'; } } if (diff5 & SD_ACTIVATION) - sectors[i].activation = READUINT32(save->p); + sectors[i].activation = (sectoractionflags_t)READUINT32(save->p); if (diff5 & SD_BOTCONTROLLER) { sectors[i].botController.trick = READUINT8(save->p); @@ -2535,7 +2586,7 @@ static void UnArchiveSectors(savebuffer_t *save) static void ArchiveLines(savebuffer_t *save) { - size_t i; + size_t i, j; const line_t *li = lines; const line_t *spawnli = spawnlines; const side_t *si; @@ -2552,8 +2603,8 @@ static void ArchiveLines(savebuffer_t *save) if (li->special != spawnli->special) diff |= LD_SPECIAL; - if (spawnli->special == 321 || spawnli->special == 322) // only reason li->callcount would be non-zero is if either of these are involved - diff |= LD_CLLCOUNT; + if (!Tag_Compare(&li->tags, &spawnli->tags)) + diff |= LD_TAG; if (!P_LineArgsEqual(li, spawnli)) diff2 |= LD_ARGS; @@ -2561,9 +2612,6 @@ static void ArchiveLines(savebuffer_t *save) if (!P_LineStringArgsEqual(li, spawnli)) diff2 |= LD_STRINGARGS; - if (li->executordelay != spawnli->executordelay) - diff2 |= LD_EXECUTORDELAY; - if (li->activation != spawnli->activation) diff3 |= LD_ACTIVATION; @@ -2613,8 +2661,12 @@ static void ArchiveLines(savebuffer_t *save) WRITEUINT32(save->p, li->flags); if (diff & LD_SPECIAL) WRITEINT16(save->p, li->special); - if (diff & LD_CLLCOUNT) - WRITEINT16(save->p, li->callcount); + if (diff & LD_TAG) + { + WRITEUINT32(save->p, li->tags.count); + for (j = 0; j < li->tags.count; j++) + WRITEINT16(save->p, li->tags.tags[j]); + } si = &sides[li->sidenum[0]]; if (diff & LD_S1TEXOFF) @@ -2660,8 +2712,6 @@ static void ArchiveLines(savebuffer_t *save) WRITECHAR(save->p, li->stringargs[j][k]); } } - if (diff2 & LD_EXECUTORDELAY) - WRITEINT32(save->p, li->executordelay); if (diff3 & LD_ACTIVATION) WRITEUINT32(save->p, li->activation); } @@ -2671,7 +2721,7 @@ static void ArchiveLines(savebuffer_t *save) static void UnArchiveLines(savebuffer_t *save) { - UINT16 i; + UINT16 i, j; line_t *li; side_t *si; UINT8 diff, diff2, diff3; @@ -2702,8 +2752,28 @@ static void UnArchiveLines(savebuffer_t *save) li->flags = READUINT32(save->p); if (diff & LD_SPECIAL) li->special = READINT16(save->p); - if (diff & LD_CLLCOUNT) - li->callcount = READINT16(save->p); + if (diff & LD_TAG) + { + size_t ncount = READUINT32(save->p); + + // Remove entries from global lists. + for (j = 0; j < lines[i].tags.count; j++) + Taggroup_Remove(tags_lines, lines[i].tags.tags[j], i); + + // Reallocate if size differs. + if (ncount != lines[i].tags.count) + { + lines[i].tags.count = ncount; + lines[i].tags.tags = (mtag_t*)Z_Realloc(lines[i].tags.tags, ncount*sizeof(mtag_t), PU_LEVEL, NULL); + } + + for (j = 0; j < ncount; j++) + lines[i].tags.tags[j] = READINT16(save->p); + + // Add new entries. + for (j = 0; j < lines[i].tags.count; j++) + Taggroup_Add(tags_lines, lines[i].tags.tags[j], i); + } si = &sides[li->sidenum[0]]; if (diff & LD_S1TEXOFF) @@ -2726,13 +2796,11 @@ static void UnArchiveLines(savebuffer_t *save) si->midtexture = READINT32(save->p); if (diff2 & LD_ARGS) { - UINT8 j; for (j = 0; j < NUM_SCRIPT_ARGS; j++) li->args[j] = READINT32(save->p); } if (diff2 & LD_STRINGARGS) { - UINT8 j; for (j = 0; j < NUM_SCRIPT_STRINGARGS; j++) { size_t len = READINT32(save->p); @@ -2745,14 +2813,12 @@ static void UnArchiveLines(savebuffer_t *save) continue; } - li->stringargs[j] = Z_Realloc(li->stringargs[j], len + 1, PU_LEVEL, NULL); + li->stringargs[j] = (char*)Z_Realloc(li->stringargs[j], len + 1, PU_LEVEL, NULL); for (k = 0; k < len; k++) li->stringargs[j][k] = READCHAR(save->p); li->stringargs[j][len] = '\0'; } } - if (diff2 & LD_EXECUTORDELAY) - li->executordelay = READINT32(save->p); if (diff3 & LD_ACTIVATION) li->activation = READUINT32(save->p); } @@ -2811,19 +2877,18 @@ static boolean P_ThingArgsEqual(const mobj_t *mobj, const mapthing_t *mapthing) if (mobj->thing_args[i] != mapthing->thing_args[i]) return false; - return true; -} - -static boolean P_ThingStringArgsEqual(const mobj_t *mobj, const mapthing_t *mapthing) -{ - UINT8 i; for (i = 0; i < NUM_MAPTHING_STRINGARGS; i++) { - if (!mobj->thing_stringargs[i]) - return !mapthing->thing_stringargs[i]; - - if (strcmp(mobj->thing_stringargs[i], mapthing->thing_stringargs[i])) - return false; + if (mobj->thing_stringargs[i] == NULL || mapthing->thing_stringargs[i] == NULL) + { + if (mobj->thing_stringargs[i] != mapthing->thing_stringargs[i]) + return false; + } + else + { + if (strcmp(mobj->thing_stringargs[i], mapthing->thing_stringargs[i])) + return false; + } } return true; @@ -2841,11 +2906,16 @@ static boolean P_ThingScriptEqual(const mobj_t *mobj, const mapthing_t *mapthing for (i = 0; i < NUM_SCRIPT_STRINGARGS; i++) { - if (!mobj->script_stringargs[i]) - return !mapthing->script_stringargs[i]; - - if (strcmp(mobj->script_stringargs[i], mapthing->script_stringargs[i])) - return false; + if (mobj->script_stringargs[i] == NULL || mapthing->script_stringargs[i] == NULL) + { + if (mobj->script_stringargs[i] != mapthing->script_stringargs[i]) + return false; + } + else + { + if (strcmp(mobj->script_stringargs[i], mapthing->script_stringargs[i])) + return false; + } } return true; @@ -2883,7 +2953,7 @@ typedef enum MD_SCALE = 1<<27, MD_DSCALE = 1<<28, MD_ARGS = 1<<29, - MD_STRINGARGS = 1<<30, + MD__UNUSED = 1<<30, MD_MORE = (INT32)(1U<<31) } mobj_diff_t; @@ -3068,9 +3138,6 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 if (!P_ThingArgsEqual(mobj, mobj->spawnpoint)) diff |= MD_ARGS; - if (!P_ThingStringArgsEqual(mobj, mobj->spawnpoint)) - diff |= MD_STRINGARGS; - if (!P_ThingScriptEqual(mobj, mobj->spawnpoint)) diff2 |= MD2_SPECIAL; } @@ -3092,7 +3159,7 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 { if (mobj->thing_stringargs[j] != NULL) { - diff |= MD_STRINGARGS; + diff |= MD_ARGS; break; } } @@ -3124,9 +3191,9 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 // not the default but the most probable if (mobj->momx != 0 || mobj->momy != 0 || mobj->momz != 0 || mobj->pmomz != 0 || mobj->lastmomz != 0) diff |= MD_MOM; - if (mobj->radius != mobj->info->radius) + if (mobj->radius != FixedMul(mapobjectscale, mobj->info->radius)) diff |= MD_RADIUS; - if (mobj->height != mobj->info->height) + if (mobj->height != FixedMul(mapobjectscale, mobj->info->height)) diff |= MD_HEIGHT; if (mobj->flags != mobj->info->flags) diff |= MD_FLAGS; @@ -3170,11 +3237,11 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 diff |= MD_MOVEFACTOR; if (mobj->fuse) diff |= MD_FUSE; - if (mobj->watertop) + if (mobj->watertop != INT32_MAX) diff |= MD_WATERTOP; if (mobj->waterbottom) diff |= MD_WATERBOTTOM; - if (mobj->scale != FRACUNIT) + if (mobj->scale != mapobjectscale) diff |= MD_SCALE; if (mobj->destscale != mobj->scale) diff |= MD_DSCALE; @@ -3384,9 +3451,7 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 { for (j = 0; j < NUM_MAPTHING_ARGS; j++) WRITEINT32(save->p, mobj->thing_args[j]); - } - if (diff & MD_STRINGARGS) - { + for (j = 0; j < NUM_MAPTHING_STRINGARGS; j++) { size_t len, k; @@ -3527,7 +3592,7 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 } if (diff2 & MD2_TERRAIN) { - WRITEUINT32(save->p, K_GetTerrainHeapIndex(mobj->terrain)); + WRITEUINT32(save->p, K_GetTerrainHeapIndex(mobj->terrain) + 1); WRITEUINT32(save->p, SaveMobjnum(mobj->terrainOverlay)); } @@ -3553,14 +3618,14 @@ static void SaveMobjThinker(savebuffer_t *save, const thinker_t *th, const UINT8 static void SaveNoEnemiesThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const noenemies_t *ht = (const void *)th; + const noenemies_t *ht = (const noenemies_t*)th; WRITEUINT8(save->p, type); WRITEUINT32(save->p, SaveLine(ht->sourceline)); } static void SaveBounceCheeseThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const bouncecheese_t *ht = (const void *)th; + const bouncecheese_t *ht = (const bouncecheese_t*)th; WRITEUINT8(save->p, type); WRITEUINT32(save->p, SaveLine(ht->sourceline)); WRITEUINT32(save->p, SaveSector(ht->sector)); @@ -3573,7 +3638,7 @@ static void SaveBounceCheeseThinker(savebuffer_t *save, const thinker_t *th, con static void SaveContinuousFallThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const continuousfall_t *ht = (const void *)th; + const continuousfall_t *ht = (const continuousfall_t*)th; WRITEUINT8(save->p, type); WRITEUINT32(save->p, SaveSector(ht->sector)); WRITEFIXED(save->p, ht->speed); @@ -3585,7 +3650,7 @@ static void SaveContinuousFallThinker(savebuffer_t *save, const thinker_t *th, c static void SaveMarioBlockThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const mariothink_t *ht = (const void *)th; + const mariothink_t *ht = (const mariothink_t*)th; WRITEUINT8(save->p, type); WRITEUINT32(save->p, SaveSector(ht->sector)); WRITEFIXED(save->p, ht->speed); @@ -3597,7 +3662,7 @@ static void SaveMarioBlockThinker(savebuffer_t *save, const thinker_t *th, const static void SaveMarioCheckThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const mariocheck_t *ht = (const void *)th; + const mariocheck_t *ht = (const mariocheck_t*)th; WRITEUINT8(save->p, type); WRITEUINT32(save->p, SaveLine(ht->sourceline)); WRITEUINT32(save->p, SaveSector(ht->sector)); @@ -3605,7 +3670,7 @@ static void SaveMarioCheckThinker(savebuffer_t *save, const thinker_t *th, const static void SaveThwompThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const thwomp_t *ht = (const void *)th; + const thwomp_t *ht = (const thwomp_t*)th; WRITEUINT8(save->p, type); WRITEUINT32(save->p, SaveLine(ht->sourceline)); WRITEUINT32(save->p, SaveSector(ht->sector)); @@ -3622,7 +3687,7 @@ static void SaveThwompThinker(savebuffer_t *save, const thinker_t *th, const UIN static void SaveFloatThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const floatthink_t *ht = (const void *)th; + const floatthink_t *ht = (const floatthink_t*)th; WRITEUINT8(save->p, type); WRITEUINT32(save->p, SaveLine(ht->sourceline)); WRITEUINT32(save->p, SaveSector(ht->sector)); @@ -3631,7 +3696,7 @@ static void SaveFloatThinker(savebuffer_t *save, const thinker_t *th, const UINT static void SaveEachTimeThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const eachtime_t *ht = (const void *)th; + const eachtime_t *ht = (const eachtime_t*)th; size_t i; WRITEUINT8(save->p, type); WRITEUINT32(save->p, SaveLine(ht->sourceline)); @@ -3644,7 +3709,7 @@ static void SaveEachTimeThinker(savebuffer_t *save, const thinker_t *th, const U static void SaveRaiseThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const raise_t *ht = (const void *)th; + const raise_t *ht = (const raise_t *)th; WRITEUINT8(save->p, type); WRITEINT16(save->p, ht->tag); WRITEUINT32(save->p, SaveSector(ht->sector)); @@ -3658,7 +3723,7 @@ static void SaveRaiseThinker(savebuffer_t *save, const thinker_t *th, const UINT static void SaveCeilingThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const ceiling_t *ht = (const void *)th; + const ceiling_t *ht = (const ceiling_t *)th; WRITEUINT8(save->p, type); WRITEUINT8(save->p, ht->type); WRITEUINT32(save->p, SaveSector(ht->sector)); @@ -3680,7 +3745,7 @@ static void SaveCeilingThinker(savebuffer_t *save, const thinker_t *th, const UI static void SaveFloormoveThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const floormove_t *ht = (const void *)th; + const floormove_t *ht = (const floormove_t *)th; WRITEUINT8(save->p, type); WRITEUINT8(save->p, ht->type); WRITEUINT8(save->p, ht->crush); @@ -3701,7 +3766,7 @@ static void SaveFloormoveThinker(savebuffer_t *save, const thinker_t *th, const static void SaveLightflashThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const lightflash_t *ht = (const void *)th; + const lightflash_t *ht = (const lightflash_t *)th; WRITEUINT8(save->p, type); WRITEUINT32(save->p, SaveSector(ht->sector)); WRITEINT32(save->p, ht->maxlight); @@ -3710,7 +3775,7 @@ static void SaveLightflashThinker(savebuffer_t *save, const thinker_t *th, const static void SaveStrobeThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const strobe_t *ht = (const void *)th; + const strobe_t *ht = (const strobe_t *)th; WRITEUINT8(save->p, type); WRITEUINT32(save->p, SaveSector(ht->sector)); WRITEINT32(save->p, ht->count); @@ -3722,7 +3787,7 @@ static void SaveStrobeThinker(savebuffer_t *save, const thinker_t *th, const UIN static void SaveGlowThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const glow_t *ht = (const void *)th; + const glow_t *ht = (const glow_t *)th; WRITEUINT8(save->p, type); WRITEUINT32(save->p, SaveSector(ht->sector)); WRITEINT16(save->p, ht->minlight); @@ -3733,7 +3798,7 @@ static void SaveGlowThinker(savebuffer_t *save, const thinker_t *th, const UINT8 static inline void SaveFireflickerThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const fireflicker_t *ht = (const void *)th; + const fireflicker_t *ht = (const fireflicker_t *)th; WRITEUINT8(save->p, type); WRITEUINT32(save->p, SaveSector(ht->sector)); WRITEINT32(save->p, ht->count); @@ -3744,7 +3809,7 @@ static inline void SaveFireflickerThinker(savebuffer_t *save, const thinker_t *t static void SaveElevatorThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const elevator_t *ht = (const void *)th; + const elevator_t *ht = (const elevator_t *)th; WRITEUINT8(save->p, type); WRITEUINT8(save->p, ht->type); WRITEUINT32(save->p, SaveSector(ht->sector)); @@ -3765,7 +3830,7 @@ static void SaveElevatorThinker(savebuffer_t *save, const thinker_t *th, const U static void SaveCrumbleThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const crumble_t *ht = (const void *)th; + const crumble_t *ht = (const crumble_t *)th; WRITEUINT8(save->p, type); WRITEUINT32(save->p, SaveLine(ht->sourceline)); WRITEUINT32(save->p, SaveSector(ht->sector)); @@ -3782,7 +3847,7 @@ static void SaveCrumbleThinker(savebuffer_t *save, const thinker_t *th, const UI static inline void SaveScrollThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const scroll_t *ht = (const void *)th; + const scroll_t *ht = (const scroll_t *)th; WRITEUINT8(save->p, type); WRITEFIXED(save->p, ht->dx); WRITEFIXED(save->p, ht->dy); @@ -3798,7 +3863,7 @@ static inline void SaveScrollThinker(savebuffer_t *save, const thinker_t *th, co static inline void SaveFrictionThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const friction_t *ht = (const void *)th; + const friction_t *ht = (const friction_t *)th; WRITEUINT8(save->p, type); WRITEINT32(save->p, ht->friction); WRITEINT32(save->p, ht->movefactor); @@ -3809,7 +3874,7 @@ static inline void SaveFrictionThinker(savebuffer_t *save, const thinker_t *th, static inline void SavePusherThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const pusher_t *ht = (const void *)th; + const pusher_t *ht = (const pusher_t *)th; WRITEUINT8(save->p, type); WRITEUINT8(save->p, ht->type); WRITEFIXED(save->p, ht->x_mag); @@ -3824,7 +3889,7 @@ static inline void SavePusherThinker(savebuffer_t *save, const thinker_t *th, co static void SaveLaserThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const laserthink_t *ht = (const void *)th; + const laserthink_t *ht = (const laserthink_t *)th; WRITEUINT8(save->p, type); WRITEINT16(save->p, ht->tag); WRITEUINT32(save->p, SaveLine(ht->sourceline)); @@ -3833,7 +3898,7 @@ static void SaveLaserThinker(savebuffer_t *save, const thinker_t *th, const UINT static void SaveLightlevelThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const lightlevel_t *ht = (const void *)th; + const lightlevel_t *ht = (const lightlevel_t *)th; WRITEUINT8(save->p, type); WRITEUINT32(save->p, SaveSector(ht->sector)); WRITEINT16(save->p, ht->sourcelevel); @@ -3845,7 +3910,7 @@ static void SaveLightlevelThinker(savebuffer_t *save, const thinker_t *th, const static void SaveExecutorThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const executor_t *ht = (const void *)th; + const executor_t *ht = (const executor_t *)th; WRITEUINT8(save->p, type); WRITEUINT32(save->p, SaveLine(ht->line)); WRITEUINT32(save->p, SaveMobjnum(ht->caller)); @@ -3855,7 +3920,7 @@ static void SaveExecutorThinker(savebuffer_t *save, const thinker_t *th, const U static void SaveDisappearThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const disappear_t *ht = (const void *)th; + const disappear_t *ht = (const disappear_t *)th; WRITEUINT8(save->p, type); WRITEUINT32(save->p, ht->appeartime); WRITEUINT32(save->p, ht->disappeartime); @@ -3868,7 +3933,7 @@ static void SaveDisappearThinker(savebuffer_t *save, const thinker_t *th, const static void SaveFadeThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const fade_t *ht = (const void *)th; + const fade_t *ht = (const fade_t *)th; WRITEUINT8(save->p, type); WRITEUINT32(save->p, CheckAddNetColormapToList(ht->dest_exc)); WRITEUINT32(save->p, ht->sectornum); @@ -3891,7 +3956,7 @@ static void SaveFadeThinker(savebuffer_t *save, const thinker_t *th, const UINT8 static void SaveFadeColormapThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const fadecolormap_t *ht = (const void *)th; + const fadecolormap_t *ht = (const fadecolormap_t *)th; WRITEUINT8(save->p, type); WRITEUINT32(save->p, SaveSector(ht->sector)); WRITEUINT32(save->p, CheckAddNetColormapToList(ht->source_exc)); @@ -3903,7 +3968,7 @@ static void SaveFadeColormapThinker(savebuffer_t *save, const thinker_t *th, con static void SavePlaneDisplaceThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const planedisplace_t *ht = (const void *)th; + const planedisplace_t *ht = (const planedisplace_t *)th; WRITEUINT8(save->p, type); WRITEINT32(save->p, ht->affectee); WRITEINT32(save->p, ht->control); @@ -3914,7 +3979,7 @@ static void SavePlaneDisplaceThinker(savebuffer_t *save, const thinker_t *th, co static inline void SaveDynamicLineSlopeThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const dynlineplanethink_t* ht = (const void*)th; + const dynlineplanethink_t* ht = (const dynlineplanethink_t*)th; WRITEUINT8(save->p, type); WRITEUINT8(save->p, ht->type); @@ -3926,7 +3991,7 @@ static inline void SaveDynamicLineSlopeThinker(savebuffer_t *save, const thinker static inline void SaveDynamicVertexSlopeThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { size_t i; - const dynvertexplanethink_t* ht = (const void*)th; + const dynvertexplanethink_t* ht = (const dynvertexplanethink_t*)th; WRITEUINT8(save->p, type); WRITEUINT32(save->p, SaveSlope(ht->slope)); @@ -3940,7 +4005,7 @@ static inline void SaveDynamicVertexSlopeThinker(savebuffer_t *save, const think static inline void SavePolyrotatetThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const polyrotate_t *ht = (const void *)th; + const polyrotate_t *ht = (const polyrotate_t *)th; WRITEUINT8(save->p, type); WRITEINT32(save->p, ht->polyObjNum); WRITEINT32(save->p, ht->speed); @@ -3950,7 +4015,7 @@ static inline void SavePolyrotatetThinker(savebuffer_t *save, const thinker_t *t static void SavePolymoveThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const polymove_t *ht = (const void *)th; + const polymove_t *ht = (const polymove_t *)th; WRITEUINT8(save->p, type); WRITEINT32(save->p, ht->polyObjNum); WRITEINT32(save->p, ht->speed); @@ -3962,7 +4027,7 @@ static void SavePolymoveThinker(savebuffer_t *save, const thinker_t *th, const U static void SavePolywaypointThinker(savebuffer_t *save, const thinker_t *th, UINT8 type) { - const polywaypoint_t *ht = (const void *)th; + const polywaypoint_t *ht = (const polywaypoint_t *)th; WRITEUINT8(save->p, type); WRITEINT32(save->p, ht->polyObjNum); WRITEINT32(save->p, ht->speed); @@ -3976,7 +4041,7 @@ static void SavePolywaypointThinker(savebuffer_t *save, const thinker_t *th, UIN static void SavePolyslidedoorThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const polyslidedoor_t *ht = (const void *)th; + const polyslidedoor_t *ht = (const polyslidedoor_t *)th; WRITEUINT8(save->p, type); WRITEINT32(save->p, ht->polyObjNum); WRITEINT32(save->p, ht->delay); @@ -3995,7 +4060,7 @@ static void SavePolyslidedoorThinker(savebuffer_t *save, const thinker_t *th, co static void SavePolyswingdoorThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const polyswingdoor_t *ht = (const void *)th; + const polyswingdoor_t *ht = (const polyswingdoor_t *)th; WRITEUINT8(save->p, type); WRITEINT32(save->p, ht->polyObjNum); WRITEINT32(save->p, ht->delay); @@ -4009,7 +4074,7 @@ static void SavePolyswingdoorThinker(savebuffer_t *save, const thinker_t *th, co static void SavePolydisplaceThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const polydisplace_t *ht = (const void *)th; + const polydisplace_t *ht = (const polydisplace_t *)th; WRITEUINT8(save->p, type); WRITEINT32(save->p, ht->polyObjNum); WRITEUINT32(save->p, SaveSector(ht->controlSector)); @@ -4020,7 +4085,7 @@ static void SavePolydisplaceThinker(savebuffer_t *save, const thinker_t *th, con static void SavePolyrotdisplaceThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const polyrotdisplace_t *ht = (const void *)th; + const polyrotdisplace_t *ht = (const polyrotdisplace_t *)th; WRITEUINT8(save->p, type); WRITEINT32(save->p, ht->polyObjNum); WRITEUINT32(save->p, SaveSector(ht->controlSector)); @@ -4031,7 +4096,7 @@ static void SavePolyrotdisplaceThinker(savebuffer_t *save, const thinker_t *th, static void SavePolyfadeThinker(savebuffer_t *save, const thinker_t *th, const UINT8 type) { - const polyfade_t *ht = (const void *)th; + const polyfade_t *ht = (const polyfade_t *)th; WRITEUINT8(save->p, type); WRITEINT32(save->p, ht->polyObjNum); WRITEINT32(save->p, ht->sourcevalue); @@ -4443,6 +4508,21 @@ static inline pslope_t *LoadSlope(UINT32 slopeid) return NULL; } +static mobjtype_t g_doomednum_to_mobjtype[UINT16_MAX]; + +static void CalculateDoomednumToMobjtype(void) +{ + memset(g_doomednum_to_mobjtype, MT_NULL, sizeof(g_doomednum_to_mobjtype)); + + for (size_t i = MT_NULL+1; i < NUMMOBJTYPES; i++) + { + if (mobjinfo[i].doomednum > 0 && mobjinfo[i].doomednum <= UINT16_MAX) + { + g_doomednum_to_mobjtype[ mobjinfo[i].doomednum ] = static_cast(i); + } + } +} + static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) { mobj_t *mobj; @@ -4493,13 +4573,13 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) return NULL; } - mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL); + mobj = P_AllocateMobj(); mobj->spawnpoint = &mapthings[spawnpointnum]; mapthings[spawnpointnum].mobj = mobj; } else - mobj = Z_Calloc(sizeof (*mobj), PU_LEVEL, NULL); + mobj = P_AllocateMobj(); // declare this as a valid mobj as soon as possible. mobj->thinker.function.acp1 = thinker; @@ -4511,23 +4591,29 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) mobj->ceilingrover = ceilingrover; if (diff & MD_TYPE) - mobj->type = READUINT32(save->p); + mobj->type = (mobjtype_t)READUINT32(save->p); else { - for (i = 0; i < NUMMOBJTYPES; i++) - if (mobj->spawnpoint && mobj->spawnpoint->type == mobjinfo[i].doomednum) - break; - if (i == NUMMOBJTYPES) + mobjtype_t new_type = MT_NULL; + if (mobj->spawnpoint) + { + new_type = g_doomednum_to_mobjtype[mobj->spawnpoint->type]; + } + + if (new_type <= MT_NULL || new_type >= NUMMOBJTYPES) { if (mobj->spawnpoint) - CONS_Alert(CONS_ERROR, "Found mobj with unknown map thing type %d\n", mobj->spawnpoint->type); + CONS_Alert(CONS_ERROR, "Found mobj with unknown map thing doomednum %d\n", mobj->spawnpoint->type); else - CONS_Alert(CONS_ERROR, "Found mobj with unknown map thing type NULL\n"); + CONS_Alert(CONS_ERROR, "Found mobj with unknown map thing doomednum NULL\n"); + I_Error("Netsave corrupted"); } - mobj->type = i; + + mobj->type = new_type; } mobj->info = &mobjinfo[mobj->type]; + if (diff & MD_POS) { mobj->x = mobj->old_x = READFIXED(save->p); @@ -4556,11 +4642,11 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) if (diff & MD_RADIUS) mobj->radius = READFIXED(save->p); else - mobj->radius = mobj->info->radius; + mobj->radius = FixedMul(mobj->info->radius, mapobjectscale); if (diff & MD_HEIGHT) mobj->height = READFIXED(save->p); else - mobj->height = mobj->info->height; + mobj->height = FixedMul(mobj->info->height, mapobjectscale); if (diff & MD_FLAGS) mobj->flags = READUINT32(save->p); else @@ -4585,7 +4671,7 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) else mobj->tics = mobj->state->tics; if (diff & MD_SPRITE) { - mobj->sprite = READUINT16(save->p); + mobj->sprite = (spritenum_t)READUINT16(save->p); if (mobj->sprite == SPR_PLAY) mobj->sprite2 = READUINT8(save->p); } @@ -4638,12 +4724,14 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) mobj->fuse = READINT32(save->p); if (diff & MD_WATERTOP) mobj->watertop = READFIXED(save->p); + else + mobj->watertop = INT32_MAX; if (diff & MD_WATERBOTTOM) mobj->waterbottom = READFIXED(save->p); if (diff & MD_SCALE) mobj->scale = READFIXED(save->p); else - mobj->scale = FRACUNIT; + mobj->scale = mapobjectscale; if (diff & MD_DSCALE) mobj->destscale = READFIXED(save->p); else @@ -4656,9 +4744,7 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) { for (j = 0; j < NUM_MAPTHING_ARGS; j++) mobj->thing_args[j] = READINT32(save->p); - } - if (diff & MD_STRINGARGS) - { + for (j = 0; j < NUM_MAPTHING_STRINGARGS; j++) { size_t len = READINT32(save->p); @@ -4671,12 +4757,16 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) continue; } - mobj->thing_stringargs[j] = Z_Realloc(mobj->thing_stringargs[j], len + 1, PU_LEVEL, NULL); + mobj->thing_stringargs[j] = (char*)Z_Realloc(mobj->thing_stringargs[j], len + 1, PU_LEVEL, NULL); for (k = 0; k < len; k++) mobj->thing_stringargs[j][k] = READCHAR(save->p); mobj->thing_stringargs[j][len] = '\0'; } } + else if (mobj->spawnpoint) + { + P_CopyMapThingBehaviorFieldsToMobj(mobj->spawnpoint, mobj); + } if (diff2 & MD2_CUSVAL) mobj->cusval = READINT32(save->p); if (diff2 & MD2_CVMEM) @@ -4760,7 +4850,7 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) continue; } - mobj->script_stringargs[j] = Z_Realloc(mobj->script_stringargs[j], len + 1, PU_LEVEL, NULL); + mobj->script_stringargs[j] = (char*)Z_Realloc(mobj->script_stringargs[j], len + 1, PU_LEVEL, NULL); for (k = 0; k < len; k++) mobj->script_stringargs[j][k] = READCHAR(save->p); mobj->script_stringargs[j][len] = '\0'; @@ -4809,7 +4899,9 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) } if (diff2 & MD2_TERRAIN) { - mobj->terrain = (terrain_t *)(size_t)READUINT32(save->p); + UINT32 terrain_index = READUINT32(save->p); + if (terrain_index > 0) + mobj->terrain = K_GetTerrainByIndex(terrain_index - 1); mobj->terrainOverlay = (mobj_t *)(size_t)READUINT32(save->p); } else @@ -4872,7 +4964,9 @@ static thinker_t* LoadMobjThinker(savebuffer_t *save, actionf_p1 thinker) static thinker_t* LoadNoEnemiesThinker(savebuffer_t *save, actionf_p1 thinker) { - noenemies_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + noenemies_t *ht = (noenemies_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->sourceline = LoadLine(READUINT32(save->p)); return &ht->thinker; @@ -4880,7 +4974,9 @@ static thinker_t* LoadNoEnemiesThinker(savebuffer_t *save, actionf_p1 thinker) static thinker_t* LoadBounceCheeseThinker(savebuffer_t *save, actionf_p1 thinker) { - bouncecheese_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + bouncecheese_t *ht = (bouncecheese_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->sourceline = LoadLine(READUINT32(save->p)); ht->sector = LoadSector(READUINT32(save->p)); @@ -4898,7 +4994,9 @@ static thinker_t* LoadBounceCheeseThinker(savebuffer_t *save, actionf_p1 thinker static thinker_t* LoadContinuousFallThinker(savebuffer_t *save, actionf_p1 thinker) { - continuousfall_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + continuousfall_t *ht = (continuousfall_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->sector = LoadSector(READUINT32(save->p)); ht->speed = READFIXED(save->p); @@ -4918,7 +5016,9 @@ static thinker_t* LoadContinuousFallThinker(savebuffer_t *save, actionf_p1 think static thinker_t* LoadMarioBlockThinker(savebuffer_t *save, actionf_p1 thinker) { - mariothink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + mariothink_t *ht = (mariothink_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->sector = LoadSector(READUINT32(save->p)); ht->speed = READFIXED(save->p); @@ -4938,7 +5038,9 @@ static thinker_t* LoadMarioBlockThinker(savebuffer_t *save, actionf_p1 thinker) static thinker_t* LoadMarioCheckThinker(savebuffer_t *save, actionf_p1 thinker) { - mariocheck_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + mariocheck_t *ht = (mariocheck_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->sourceline = LoadLine(READUINT32(save->p)); ht->sector = LoadSector(READUINT32(save->p)); @@ -4947,7 +5049,9 @@ static thinker_t* LoadMarioCheckThinker(savebuffer_t *save, actionf_p1 thinker) static thinker_t* LoadThwompThinker(savebuffer_t *save, actionf_p1 thinker) { - thwomp_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + thwomp_t *ht = (thwomp_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->sourceline = LoadLine(READUINT32(save->p)); ht->sector = LoadSector(READUINT32(save->p)); @@ -4971,7 +5075,9 @@ static thinker_t* LoadThwompThinker(savebuffer_t *save, actionf_p1 thinker) static thinker_t* LoadFloatThinker(savebuffer_t *save, actionf_p1 thinker) { - floatthink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + floatthink_t *ht = (floatthink_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->sourceline = LoadLine(READUINT32(save->p)); ht->sector = LoadSector(READUINT32(save->p)); @@ -4982,7 +5088,9 @@ static thinker_t* LoadFloatThinker(savebuffer_t *save, actionf_p1 thinker) static thinker_t* LoadEachTimeThinker(savebuffer_t *save, actionf_p1 thinker) { size_t i; - eachtime_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + eachtime_t *ht = (eachtime_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->sourceline = LoadLine(READUINT32(save->p)); for (i = 0; i < MAXPLAYERS; i++) @@ -4995,7 +5103,9 @@ static thinker_t* LoadEachTimeThinker(savebuffer_t *save, actionf_p1 thinker) static thinker_t* LoadRaiseThinker(savebuffer_t *save, actionf_p1 thinker) { - raise_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + raise_t *ht = (raise_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->tag = READINT16(save->p); ht->sector = LoadSector(READUINT32(save->p)); @@ -5010,9 +5120,11 @@ static thinker_t* LoadRaiseThinker(savebuffer_t *save, actionf_p1 thinker) static thinker_t* LoadCeilingThinker(savebuffer_t *save, actionf_p1 thinker) { - ceiling_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + ceiling_t *ht = (ceiling_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; - ht->type = READUINT8(save->p); + ht->type = (ceiling_e)READUINT8(save->p); ht->sector = LoadSector(READUINT32(save->p)); ht->bottomheight = READFIXED(save->p); ht->topheight = READFIXED(save->p); @@ -5035,9 +5147,11 @@ static thinker_t* LoadCeilingThinker(savebuffer_t *save, actionf_p1 thinker) static thinker_t* LoadFloormoveThinker(savebuffer_t *save, actionf_p1 thinker) { - floormove_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + floormove_t *ht = (floormove_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; - ht->type = READUINT8(save->p); + ht->type = (floor_e)READUINT8(save->p); ht->crush = READUINT8(save->p); ht->sector = LoadSector(READUINT32(save->p)); ht->direction = READINT32(save->p); @@ -5059,7 +5173,9 @@ static thinker_t* LoadFloormoveThinker(savebuffer_t *save, actionf_p1 thinker) static thinker_t* LoadLightflashThinker(savebuffer_t *save, actionf_p1 thinker) { - lightflash_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + lightflash_t *ht = (lightflash_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->sector = LoadSector(READUINT32(save->p)); ht->maxlight = READINT32(save->p); @@ -5071,7 +5187,9 @@ static thinker_t* LoadLightflashThinker(savebuffer_t *save, actionf_p1 thinker) static thinker_t* LoadStrobeThinker(savebuffer_t *save, actionf_p1 thinker) { - strobe_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + strobe_t *ht = (strobe_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->sector = LoadSector(READUINT32(save->p)); ht->count = READINT32(save->p); @@ -5086,7 +5204,9 @@ static thinker_t* LoadStrobeThinker(savebuffer_t *save, actionf_p1 thinker) static thinker_t* LoadGlowThinker(savebuffer_t *save, actionf_p1 thinker) { - glow_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + glow_t *ht = (glow_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->sector = LoadSector(READUINT32(save->p)); ht->minlight = READINT16(save->p); @@ -5100,7 +5220,9 @@ static thinker_t* LoadGlowThinker(savebuffer_t *save, actionf_p1 thinker) static thinker_t* LoadFireflickerThinker(savebuffer_t *save, actionf_p1 thinker) { - fireflicker_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + fireflicker_t *ht = (fireflicker_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->sector = LoadSector(READUINT32(save->p)); ht->count = READINT32(save->p); @@ -5114,9 +5236,11 @@ static thinker_t* LoadFireflickerThinker(savebuffer_t *save, actionf_p1 thinker) static thinker_t* LoadElevatorThinker(savebuffer_t *save, actionf_p1 thinker, boolean setplanedata) { - elevator_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + elevator_t *ht = (elevator_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; - ht->type = READUINT8(save->p); + ht->type = (elevator_e)READUINT8(save->p); ht->sector = LoadSector(READUINT32(save->p)); ht->actionsector = LoadSector(READUINT32(save->p)); ht->direction = READINT32(save->p); @@ -5143,7 +5267,9 @@ static thinker_t* LoadElevatorThinker(savebuffer_t *save, actionf_p1 thinker, bo static thinker_t* LoadCrumbleThinker(savebuffer_t *save, actionf_p1 thinker) { - crumble_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + crumble_t *ht = (crumble_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->sourceline = LoadLine(READUINT32(save->p)); ht->sector = LoadSector(READUINT32(save->p)); @@ -5165,7 +5291,9 @@ static thinker_t* LoadCrumbleThinker(savebuffer_t *save, actionf_p1 thinker) static thinker_t* LoadScrollThinker(savebuffer_t *save, actionf_p1 thinker) { - scroll_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + scroll_t *ht = (scroll_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->dx = READFIXED(save->p); ht->dy = READFIXED(save->p); @@ -5176,13 +5304,15 @@ static thinker_t* LoadScrollThinker(savebuffer_t *save, actionf_p1 thinker) ht->vdy = READFIXED(save->p); ht->accel = READINT32(save->p); ht->exclusive = READINT32(save->p); - ht->type = READUINT8(save->p); + ht->type = static_cast(READUINT8(save->p)); // Whaaaaaaaat. return &ht->thinker; } static inline thinker_t* LoadFrictionThinker(savebuffer_t *save, actionf_p1 thinker) { - friction_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + friction_t *ht = (friction_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->friction = READINT32(save->p); ht->movefactor = READINT32(save->p); @@ -5194,9 +5324,11 @@ static inline thinker_t* LoadFrictionThinker(savebuffer_t *save, actionf_p1 thin static thinker_t* LoadPusherThinker(savebuffer_t *save, actionf_p1 thinker) { - pusher_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + pusher_t *ht = (pusher_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; - ht->type = READUINT8(save->p); + ht->type = (pushertype_e)READUINT8(save->p); ht->x_mag = READFIXED(save->p); ht->y_mag = READFIXED(save->p); ht->z_mag = READFIXED(save->p); @@ -5210,7 +5342,9 @@ static thinker_t* LoadPusherThinker(savebuffer_t *save, actionf_p1 thinker) static inline thinker_t* LoadLaserThinker(savebuffer_t *save, actionf_p1 thinker) { - laserthink_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + laserthink_t *ht = (laserthink_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->tag = READINT16(save->p); ht->sourceline = LoadLine(READUINT32(save->p)); @@ -5220,7 +5354,9 @@ static inline thinker_t* LoadLaserThinker(savebuffer_t *save, actionf_p1 thinker static inline thinker_t* LoadLightlevelThinker(savebuffer_t *save, actionf_p1 thinker) { - lightlevel_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + lightlevel_t *ht = (lightlevel_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->sector = LoadSector(READUINT32(save->p)); ht->sourcelevel = READINT16(save->p); @@ -5235,7 +5371,9 @@ static inline thinker_t* LoadLightlevelThinker(savebuffer_t *save, actionf_p1 th static inline thinker_t* LoadExecutorThinker(savebuffer_t *save, actionf_p1 thinker) { - executor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + executor_t *ht = (executor_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->line = LoadLine(READUINT32(save->p)); ht->caller = LoadMobj(READUINT32(save->p)); @@ -5246,7 +5384,9 @@ static inline thinker_t* LoadExecutorThinker(savebuffer_t *save, actionf_p1 thin static inline thinker_t* LoadDisappearThinker(savebuffer_t *save, actionf_p1 thinker) { - disappear_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + disappear_t *ht = (disappear_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->appeartime = READUINT32(save->p); ht->disappeartime = READUINT32(save->p); @@ -5261,7 +5401,9 @@ static inline thinker_t* LoadDisappearThinker(savebuffer_t *save, actionf_p1 thi static inline thinker_t* LoadFadeThinker(savebuffer_t *save, actionf_p1 thinker) { sector_t *ss; - fade_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + fade_t *ht = (fade_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->dest_exc = GetNetColormapFromList(READUINT32(save->p)); ht->sectornum = READUINT32(save->p); @@ -5302,7 +5444,9 @@ static inline thinker_t* LoadFadeThinker(savebuffer_t *save, actionf_p1 thinker) static inline thinker_t* LoadFadeColormapThinker(savebuffer_t *save, actionf_p1 thinker) { - fadecolormap_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + fadecolormap_t *ht = (fadecolormap_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->sector = LoadSector(READUINT32(save->p)); ht->source_exc = GetNetColormapFromList(READUINT32(save->p)); @@ -5317,23 +5461,27 @@ static inline thinker_t* LoadFadeColormapThinker(savebuffer_t *save, actionf_p1 static inline thinker_t* LoadPlaneDisplaceThinker(savebuffer_t *save, actionf_p1 thinker) { - planedisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + planedisplace_t *ht = (planedisplace_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->affectee = READINT32(save->p); ht->control = READINT32(save->p); ht->last_height = READFIXED(save->p); ht->speed = READFIXED(save->p); - ht->type = READUINT8(save->p); + ht->type = static_cast(READUINT8(save->p)); return &ht->thinker; } static inline thinker_t* LoadDynamicLineSlopeThinker(savebuffer_t *save, actionf_p1 thinker) { - dynlineplanethink_t* ht = Z_Malloc(sizeof(*ht), PU_LEVSPEC, NULL); + dynlineplanethink_t* ht = (dynlineplanethink_t*)Z_LevelPoolMalloc(sizeof(*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; - ht->type = READUINT8(save->p); + ht->type = (dynplanetype_t)READUINT8(save->p); ht->slope = LoadSlope(READUINT32(save->p)); ht->sourceline = LoadLine(READUINT32(save->p)); ht->extent = READFIXED(save->p); @@ -5343,7 +5491,9 @@ static inline thinker_t* LoadDynamicLineSlopeThinker(savebuffer_t *save, actionf static inline thinker_t* LoadDynamicVertexSlopeThinker(savebuffer_t *save, actionf_p1 thinker) { size_t i; - dynvertexplanethink_t* ht = Z_Malloc(sizeof(*ht), PU_LEVSPEC, NULL); + dynvertexplanethink_t* ht = (dynvertexplanethink_t*)Z_LevelPoolMalloc(sizeof(*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->slope = LoadSlope(READUINT32(save->p)); @@ -5358,7 +5508,9 @@ static inline thinker_t* LoadDynamicVertexSlopeThinker(savebuffer_t *save, actio static inline thinker_t* LoadPolyrotatetThinker(savebuffer_t *save, actionf_p1 thinker) { - polyrotate_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + polyrotate_t *ht = (polyrotate_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->polyObjNum = READINT32(save->p); ht->speed = READINT32(save->p); @@ -5369,7 +5521,9 @@ static inline thinker_t* LoadPolyrotatetThinker(savebuffer_t *save, actionf_p1 t static thinker_t* LoadPolymoveThinker(savebuffer_t *save, actionf_p1 thinker) { - polymove_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + polymove_t *ht = (polymove_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->polyObjNum = READINT32(save->p); ht->speed = READINT32(save->p); @@ -5382,7 +5536,9 @@ static thinker_t* LoadPolymoveThinker(savebuffer_t *save, actionf_p1 thinker) static inline thinker_t* LoadPolywaypointThinker(savebuffer_t *save, actionf_p1 thinker) { - polywaypoint_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + polywaypoint_t *ht = (polywaypoint_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->polyObjNum = READINT32(save->p); ht->speed = READINT32(save->p); @@ -5397,7 +5553,9 @@ static inline thinker_t* LoadPolywaypointThinker(savebuffer_t *save, actionf_p1 static inline thinker_t* LoadPolyslidedoorThinker(savebuffer_t *save, actionf_p1 thinker) { - polyslidedoor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + polyslidedoor_t *ht = (polyslidedoor_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->polyObjNum = READINT32(save->p); ht->delay = READINT32(save->p); @@ -5417,7 +5575,9 @@ static inline thinker_t* LoadPolyslidedoorThinker(savebuffer_t *save, actionf_p1 static inline thinker_t* LoadPolyswingdoorThinker(savebuffer_t *save, actionf_p1 thinker) { - polyswingdoor_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + polyswingdoor_t *ht = (polyswingdoor_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->polyObjNum = READINT32(save->p); ht->delay = READINT32(save->p); @@ -5432,7 +5592,9 @@ static inline thinker_t* LoadPolyswingdoorThinker(savebuffer_t *save, actionf_p1 static inline thinker_t* LoadPolydisplaceThinker(savebuffer_t *save, actionf_p1 thinker) { - polydisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + polydisplace_t *ht = (polydisplace_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->polyObjNum = READINT32(save->p); ht->controlSector = LoadSector(READUINT32(save->p)); @@ -5444,7 +5606,9 @@ static inline thinker_t* LoadPolydisplaceThinker(savebuffer_t *save, actionf_p1 static inline thinker_t* LoadPolyrotdisplaceThinker(savebuffer_t *save, actionf_p1 thinker) { - polyrotdisplace_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + polyrotdisplace_t *ht = (polyrotdisplace_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->polyObjNum = READINT32(save->p); ht->controlSector = LoadSector(READUINT32(save->p)); @@ -5456,7 +5620,9 @@ static inline thinker_t* LoadPolyrotdisplaceThinker(savebuffer_t *save, actionf_ static thinker_t* LoadPolyfadeThinker(savebuffer_t *save, actionf_p1 thinker) { - polyfade_t *ht = Z_Malloc(sizeof (*ht), PU_LEVSPEC, NULL); + polyfade_t *ht = (polyfade_t*)Z_LevelPoolMalloc(sizeof (*ht)); + ht->thinker.alloctype = TAT_LEVELPOOL; + ht->thinker.size = sizeof (*ht); ht->thinker.function.acp1 = thinker; ht->polyObjNum = READINT32(save->p); ht->sourcevalue = READINT32(save->p); @@ -5488,6 +5654,10 @@ static void P_NetUnArchiveThinkers(savebuffer_t *save) if (READUINT32(save->p) != ARCHIVEBLOCK_THINKERS) I_Error("Bad $$$.sav at archive block Thinkers"); + // Pre-calculate this lookup, because it was wasting + // a shit ton of time loading mobj thinkers. + CalculateDoomednumToMobjtype(); + // remove all the current thinkers for (i = 0; i < NUM_THINKERLISTS; i++) { @@ -5503,7 +5673,14 @@ static void P_NetUnArchiveThinkers(savebuffer_t *save) { (next->prev = currentthinker->prev)->next = next; R_DestroyLevelInterpolators(currentthinker); - Z_Free(currentthinker); + if (currentthinker->alloctype == TAT_LEVELPOOL) + { + Z_LevelPoolFree(currentthinker, currentthinker->size); + } + else + { + Z_Free(currentthinker); + } } } } @@ -5701,7 +5878,7 @@ static void P_NetUnArchiveThinkers(savebuffer_t *save) I_Error("P_UnarchiveSpecials: Unknown tclass %d in savegame", tclass); } if (th) - P_AddThinker(i, th); + P_AddThinker((thinklistnum_t)i, th); } CONS_Debug(DBG_NETPLAY, "%u thinkers loaded in list %d\n", numloaded, i); @@ -5715,7 +5892,7 @@ static void P_NetUnArchiveThinkers(savebuffer_t *save) { if (currentthinker->function.acp1 != (actionf_p1)T_ExecutorDelay) continue; - delay = (void *)currentthinker; + delay = (executor_t *)currentthinker; if (!(mobjnum = (UINT32)(size_t)delay->caller)) continue; delay->caller = P_FindNewPosition(mobjnum); @@ -5897,15 +6074,6 @@ static void P_RelinkPointers(void) if (!RelinkMobj(&mobj->itnext)) CONS_Debug(DBG_GAMELOGIC, "itnext not found on %d\n", mobj->type); } - if (mobj->terrain) - { - temp = (UINT32)(size_t)mobj->terrain; - mobj->terrain = K_GetTerrainByIndex(temp); - if (mobj->terrain == NULL) - { - CONS_Debug(DBG_GAMELOGIC, "terrain not found on %d\n", mobj->type); - } - } if (mobj->terrainOverlay) { if (!RelinkMobj(&mobj->terrainOverlay)) @@ -6096,7 +6264,7 @@ static void P_NetUnArchiveSpecials(savebuffer_t *save) if (strcmp(skytex, globallevelskytexture)) P_SetupLevelSky(skytex, true); - globalweather = READUINT8(save->p); + globalweather = (preciptype_t)READUINT8(save->p); if (globalweather) { @@ -6111,6 +6279,7 @@ static void P_NetUnArchiveSpecials(savebuffer_t *save) P_SwitchWeather(globalweather); } + TracyCZoneEnd(__zone); } @@ -6192,8 +6361,8 @@ static inline void P_ArchiveMisc(savebuffer_t *save) WRITEUINT32(save->p, rank->winPoints); WRITEUINT32(save->p, rank->totalPoints); - WRITEUINT32(save->p, rank->laps); - WRITEUINT32(save->p, rank->totalLaps); + WRITEUINT32(save->p, rank->exp); + WRITEUINT32(save->p, rank->totalExp); WRITEUINT32(save->p, (rank->continuesUsed + 1)); @@ -6207,7 +6376,7 @@ static inline void P_ArchiveMisc(savebuffer_t *save) WRITEINT32(save->p, rank->scorePosition); WRITEINT32(save->p, rank->scoreGPPoints); - WRITEINT32(save->p, rank->scoreLaps); + WRITEINT32(save->p, rank->scoreExp); WRITEINT32(save->p, rank->scorePrisons); WRITEINT32(save->p, rank->scoreRings); WRITEINT32(save->p, rank->scoreContinues); @@ -6230,7 +6399,7 @@ static inline void P_ArchiveMisc(savebuffer_t *save) WRITEINT32(save->p, lvl->event); WRITEUINT32(save->p, lvl->time); - WRITEUINT16(save->p, lvl->totalLapPoints); + WRITEUINT16(save->p, lvl->totalExp); WRITEUINT16(save->p, lvl->totalPrisons); UINT8 j; @@ -6240,7 +6409,7 @@ static inline void P_ArchiveMisc(savebuffer_t *save) WRITEUINT8(save->p, plr->position); WRITEUINT8(save->p, plr->rings); - WRITEUINT16(save->p, plr->lapPoints); + WRITEUINT16(save->p, plr->exp); WRITEUINT16(save->p, plr->prisons); WRITEUINT8(save->p, (UINT8)plr->gotSpecialPrize); WRITESINT8(save->p, (SINT8)plr->grade); @@ -6454,8 +6623,8 @@ static boolean P_UnArchiveSPGame(savebuffer_t *save) rank->winPoints = READUINT32(save->p); rank->totalPoints = READUINT32(save->p); - rank->laps = READUINT32(save->p); - rank->totalLaps = READUINT32(save->p); + rank->exp = READUINT32(save->p); + rank->totalExp = READUINT32(save->p); rank->continuesUsed = READUINT32(save->p); @@ -6469,7 +6638,7 @@ static boolean P_UnArchiveSPGame(savebuffer_t *save) rank->scorePosition = READINT32(save->p); rank->scoreGPPoints = READINT32(save->p); - rank->scoreLaps = READINT32(save->p); + rank->scoreExp = READINT32(save->p); rank->scorePrisons = READINT32(save->p); rank->scoreRings = READINT32(save->p); rank->scoreContinues = READINT32(save->p); @@ -6518,7 +6687,7 @@ static boolean P_UnArchiveSPGame(savebuffer_t *save) lvl->event = READINT32(save->p); lvl->time = READUINT32(save->p); - lvl->totalLapPoints = READUINT16(save->p); + lvl->totalExp = READUINT16(save->p); lvl->totalPrisons = READUINT16(save->p); for (j = 0; j < rank->numPlayers; j++) @@ -6527,7 +6696,7 @@ static boolean P_UnArchiveSPGame(savebuffer_t *save) plr->position = READUINT8(save->p); plr->rings = READUINT8(save->p); - plr->lapPoints = READUINT16(save->p); + plr->exp = READUINT16(save->p); plr->prisons = READUINT16(save->p); plr->gotSpecialPrize = (boolean)READUINT8(save->p); plr->grade = (gp_rank_e)READSINT8(save->p); @@ -6537,7 +6706,7 @@ static boolean P_UnArchiveSPGame(savebuffer_t *save) // Marathon information - marathonmode = READUINT8(save->p); + marathonmode = (marathonmode_t)READUINT8(save->p); marathontime = READUINT32(save->p); return true; @@ -6609,28 +6778,13 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) WRITEUINT8(save->p, globools); } - WRITEUINT32(save->p, bluescore); - WRITEUINT32(save->p, redscore); - - WRITEUINT16(save->p, skincolor_redteam); - WRITEUINT16(save->p, skincolor_blueteam); - WRITEUINT16(save->p, skincolor_redring); - WRITEUINT16(save->p, skincolor_bluering); + for (i = 0; i < TEAM__MAX; i++) + { + WRITEUINT32(save->p, g_teamscores[i]); + } WRITEINT32(save->p, modulothing); - WRITEINT16(save->p, autobalance); - WRITEINT16(save->p, teamscramble); - - for (i = 0; i < MAXPLAYERS; i++) - WRITEINT16(save->p, scrambleplayers[i]); - - for (i = 0; i < MAXPLAYERS; i++) - WRITEINT16(save->p, scrambleteams[i]); - - WRITEINT16(save->p, scrambletotal); - WRITEINT16(save->p, scramblecount); - WRITEUINT32(save->p, racecountdown); WRITEUINT32(save->p, exitcountdown); @@ -6653,6 +6807,7 @@ static void P_NetArchiveMisc(savebuffer_t *save, boolean resending) WRITEUINT8(save->p, gamespeed); WRITEUINT8(save->p, numlaps); WRITEUINT8(save->p, franticitems); + WRITEUINT8(save->p, g_teamplay); WRITESINT8(save->p, speedscramble); WRITESINT8(save->p, encorescramble); @@ -6751,7 +6906,7 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) if (!gamemap || gamemap > nummapheaders || !mapheaderinfo[gamemap-1]) I_Error("P_NetUnArchiveMisc: Internal map ID %d not found (nummapheaders = %d)", gamemap-1, nummapheaders); - G_SetGamestate(READINT16(save->p)); + G_SetGamestate((gamestate_t)READINT16(save->p)); gametype = READINT16(save->p); g_lastgametype = READINT16(save->p); @@ -6791,6 +6946,197 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) return false; } } + else + { + // Only reload stuff that can we modify in the save states themselves. + // This is still orders of magnitude faster than a full level reload. + // Considered memcpy, but it's complicated -- save that for local saves. + + sector_t *ss = sectors; + sector_t *spawnss = spawnsectors; + for (i = 0; i < numsectors; i++, ss++, spawnss++) + { + ss->floorheight = spawnss->floorheight; + ss->ceilingheight = spawnss->ceilingheight; + ss->floorpic = spawnss->floorpic; + ss->ceilingpic = spawnss->ceilingpic; + ss->lightlevel = spawnss->lightlevel; + ss->special = spawnss->special; + ss->floor_xoffs = spawnss->floor_xoffs; + ss->floor_yoffs = spawnss->floor_yoffs; + ss->ceiling_xoffs = spawnss->ceiling_xoffs; + ss->ceiling_yoffs = spawnss->ceiling_yoffs; + ss->floorpic_angle = spawnss->floorpic_angle; + ss->ceilingpic_angle = spawnss->ceilingpic_angle; + + if (Tag_Compare(&ss->tags, &spawnss->tags) == false) + { + if (spawnss->tags.count) + { + ss->tags.count = spawnss->tags.count; + ss->tags.tags = static_cast( + memcpy( + Z_Realloc( + ss->tags.tags, + spawnss->tags.count * sizeof(mtag_t), + PU_LEVEL, + nullptr + ), + spawnss->tags.tags, + spawnss->tags.count * sizeof(mtag_t) + ) + ); + + } + else + { + ss->tags.count = 0; + Z_Free(ss->tags.tags); + } + } + + ss->extra_colormap = ss->spawn_extra_colormap; + ss->crumblestate = CRUMBLE_NONE; + ss->floorlightlevel = spawnss->floorlightlevel; + ss->floorlightabsolute = spawnss->floorlightabsolute; + ss->ceilinglightlevel = spawnss->ceilinglightlevel; + ss->ceilinglightabsolute = spawnss->ceilinglightabsolute; + ss->flags = spawnss->flags; + ss->specialflags = spawnss->specialflags; + ss->damagetype = spawnss->damagetype; + ss->triggertag = spawnss->triggertag; + ss->triggerer = spawnss->triggerer; + ss->gravity = spawnss->gravity; + ss->action = spawnss->action; + + memcpy(ss->args, spawnss->args, NUM_SCRIPT_ARGS * sizeof(*ss->args)); + + for (j = 0; j < NUM_SCRIPT_STRINGARGS; j++) + { + size_t len = 0; + + if (spawnss->stringargs[j]) + { + len = strlen(spawnss->stringargs[j]); + } + + if (!len) + { + Z_Free(ss->stringargs[j]); + ss->stringargs[j] = nullptr; + } + else + { + ss->stringargs[j] = static_cast(Z_Realloc(ss->stringargs[j], len + 1, PU_LEVEL, nullptr)); + M_Memcpy(ss->stringargs[j], spawnss->stringargs[j], len); + ss->stringargs[j][len] = '\0'; + } + } + + ss->activation = spawnss->activation; + ss->botController.trick = spawnss->botController.trick; + ss->botController.flags = spawnss->botController.flags; + ss->botController.forceAngle = spawnss->botController.forceAngle; + + if (ss->ffloors) + { + ffloor_t *rover; + for (rover = ss->ffloors; rover; rover = rover->next) + { + rover->fofflags = rover->spawnflags; + rover->alpha = rover->spawnalpha; + } + } + } + + line_t *li = lines; + line_t *spawnli = spawnlines; + side_t *si = nullptr; + side_t *spawnsi = nullptr; + for (i = 0; i < numlines; i++, spawnli++, li++) + { + li->flags = spawnli->flags; + li->special = spawnli->special; + li->callcount = 0; + + if (Tag_Compare(&li->tags, &spawnli->tags) == false) + { + if (spawnli->tags.count) + { + li->tags.count = spawnli->tags.count; + li->tags.tags = static_cast( + memcpy( + Z_Realloc( + li->tags.tags, + spawnli->tags.count * sizeof(mtag_t), + PU_LEVEL, + nullptr + ), + spawnli->tags.tags, + spawnli->tags.count * sizeof(mtag_t) + ) + ); + + } + else + { + li->tags.count = 0; + Z_Free(li->tags.tags); + } + } + + memcpy(li->args, spawnli->args, NUM_SCRIPT_ARGS * sizeof(*li->args)); + + for (j = 0; j < NUM_SCRIPT_STRINGARGS; j++) + { + size_t len = 0; + + if (spawnli->stringargs[j]) + { + len = strlen(spawnli->stringargs[j]); + } + + if (!len) + { + Z_Free(li->stringargs[j]); + li->stringargs[j] = nullptr; + } + else + { + li->stringargs[j] = static_cast(Z_Realloc(li->stringargs[j], len + 1, PU_LEVEL, nullptr)); + M_Memcpy(li->stringargs[j], spawnli->stringargs[j], len); + li->stringargs[j][len] = '\0'; + } + } + + li->executordelay = spawnli->executordelay; + li->activation = spawnli->activation; + + if (li->sidenum[0] != 0xffff) + { + si = &sides[li->sidenum[0]]; + spawnsi = &spawnsides[li->sidenum[0]]; + + si->textureoffset = spawnsi->textureoffset; + si->toptexture = spawnsi->toptexture; + si->bottomtexture = spawnsi->bottomtexture; + si->midtexture = spawnsi->midtexture; + } + + if (li->sidenum[1] != 0xffff) + { + si = &sides[li->sidenum[1]]; + spawnsi = &spawnsides[li->sidenum[1]]; + + si->textureoffset = spawnsi->textureoffset; + si->toptexture = spawnsi->toptexture; + si->bottomtexture = spawnsi->bottomtexture; + si->midtexture = spawnsi->midtexture; + } + } + + Taglist_InitGlobalTables(); + } // get the time leveltime = READUINT32(save->p); @@ -6817,28 +7163,13 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) stoppedclock = !!(globools & (1<<1)); } - bluescore = READUINT32(save->p); - redscore = READUINT32(save->p); - - skincolor_redteam = READUINT16(save->p); - skincolor_blueteam = READUINT16(save->p); - skincolor_redring = READUINT16(save->p); - skincolor_bluering = READUINT16(save->p); + for (i = 0; i < TEAM__MAX; i++) + { + g_teamscores[i] = READUINT32(save->p); + } modulothing = READINT32(save->p); - autobalance = READINT16(save->p); - teamscramble = READINT16(save->p); - - for (i = 0; i < MAXPLAYERS; i++) - scrambleplayers[i] = READINT16(save->p); - - for (i = 0; i < MAXPLAYERS; i++) - scrambleteams[i] = READINT16(save->p); - - scrambletotal = READINT16(save->p); - scramblecount = READINT16(save->p); - racecountdown = READUINT32(save->p); exitcountdown = READUINT32(save->p); @@ -6860,6 +7191,7 @@ static boolean P_NetUnArchiveMisc(savebuffer_t *save, boolean reloading) gamespeed = READUINT8(save->p); numlaps = READUINT8(save->p); franticitems = (boolean)READUINT8(save->p); + g_teamplay = (boolean)READUINT8(save->p); speedscramble = READSINT8(save->p); encorescramble = READSINT8(save->p); @@ -6991,7 +7323,7 @@ static inline boolean P_UnArchiveLuabanksAndConsistency(savebuffer_t *save) case 0x1d: // consistency marker break; default: // anything else is nonsense - CONS_Alert(CONS_ERROR, M_GetText("Failed consistency check (???)\n")); + CONS_Alert(CONS_ERROR, M_GetText("Failed consistency check (?nonsense?)\n")); ret = false; break; } @@ -7010,8 +7342,8 @@ static void P_NetArchiveRNG(savebuffer_t *save) for (i = 0; i < PRNUMSYNCED; i++) { - WRITEUINT32(save->p, P_GetInitSeed(i)); - WRITEUINT32(save->p, P_GetRandSeed(i)); + WRITEUINT32(save->p, P_GetInitSeed((pr_class_t)i)); + WRITEUINT32(save->p, P_GetRandSeed((pr_class_t)i)); } TracyCZoneEnd(__zone); @@ -7031,7 +7363,7 @@ static inline void P_NetUnArchiveRNG(savebuffer_t *save) UINT32 init = READUINT32(save->p); UINT32 seed = READUINT32(save->p); - P_SetRandSeedNet(i, init, seed); + P_SetRandSeedNet((pr_class_t)i, init, seed); } TracyCZoneEnd(__zone); @@ -7124,7 +7456,7 @@ badloadgame: savedata.lives = 0; roundqueue.size = 0; grandprixinfo.gp = false; - marathonmode = 0; + marathonmode = (marathonmode_t)0; return false; } diff --git a/src/p_saveg.h b/src/p_saveg.h index eedfa46de..f0beb35e3 100644 --- a/src/p_saveg.h +++ b/src/p_saveg.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/p_setup.cpp b/src/p_setup.cpp index e719c755a..3ad9d6a16 100644 --- a/src/p_setup.cpp +++ b/src/p_setup.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -13,8 +13,6 @@ /// \brief Do all the WAD I/O, get map description, set up initial state and misc. LUTs #include -#include -#include #include @@ -96,6 +94,8 @@ #include "taglist.h" // SRB2Kart +#include "core/string.h" +#include "core/vector.hpp" #include "k_kart.h" #include "k_race.h" #include "k_battle.h" // K_BattleInit @@ -119,6 +119,7 @@ #include "k_endcam.h" #include "k_credits.h" #include "k_objects.h" +#include "p_deepcopy.h" // Replay names have time #if !defined (UNDER_CE) @@ -142,7 +143,7 @@ unsigned char mapmd5[16]; // boolean udmf; -static INT32 udmf_version; +INT32 udmf_version; size_t numvertexes, numsegs, numsectors, numsubsectors, numnodes, numlines, numsides, nummapthings; size_t num_orig_vertexes; vertex_t *vertexes; @@ -196,13 +197,12 @@ precipmobj_t **precipblocklinks; UINT8 *rejectmatrix; // Maintain single and multi player starting spots. -INT32 numdmstarts, numcoopstarts, numredctfstarts, numbluectfstarts; +INT32 numdmstarts, numcoopstarts, numteamstarts[TEAM__MAX]; INT32 numfaultstarts; mapthing_t *deathmatchstarts[MAX_DM_STARTS]; mapthing_t *playerstarts[MAXPLAYERS]; -mapthing_t *bluectfstarts[MAXPLAYERS]; -mapthing_t *redctfstarts[MAXPLAYERS]; +mapthing_t *teamstarts[TEAM__MAX][MAXPLAYERS]; mapthing_t *faultstart; // Global state for PartialAddWadFile/MultiSetupWadFiles @@ -498,14 +498,13 @@ static void P_ClearSingleMapHeaderInfo(INT16 num) #endif memset(&mapheaderinfo[num]->records, 0, sizeof(recorddata_t)); + mapheaderinfo[num]->records.spraycan = MCAN_INVALID; mapheaderinfo[num]->justPlayed = 0; mapheaderinfo[num]->anger = 0; mapheaderinfo[num]->destroyforchallenge_size = 0; - mapheaderinfo[num]->cache_spraycan = UINT16_MAX; - mapheaderinfo[num]->cache_maplock = MAXUNLOCKABLES; mapheaderinfo[num]->customopts = NULL; @@ -526,6 +525,7 @@ static void P_ClearSingleMapHeaderInfo(INT16 num) mapheaderinfo[num]->automedaltime[1] = 2; mapheaderinfo[num]->automedaltime[2] = 3; mapheaderinfo[num]->automedaltime[3] = 4; + mapheaderinfo[num]->cameraHeight = INT32_MIN; } /** Allocates a new map-header structure. @@ -785,9 +785,13 @@ static int cmp_loopends(const void *a, const void *b) *mt2 = *(const mapthing_t*const*)b; // weighted sorting; tag takes precedence over type - return - intsign(mt1->tid - mt2->tid) * 2 + + const int maincomp = intsign(mt1->tid - mt2->tid) * 2 + intsign(mt1->thing_args[0] - mt2->thing_args[0]); + + // JugadorXEI (04/20/25): If a qsort comparison ends up with an equal result, + // it results in UNSPECIFIED BEHAVIOR, so assuming the previous two comparisons + // are equal, let's make it consistent with Linux behaviour (ascending order). + return maincomp != 0 ? maincomp : intsign((mt1 - mapthings) - (mt2 - mapthings)); } static void P_SpawnMapThings(boolean spawnemblems) @@ -3683,9 +3687,7 @@ void P_UpdateSegLightOffset(seg_t *li) // Between -2 and 2 for software, -16 and 16 for hardware li->lightOffset = FixedFloor((extralight / 8) + (FRACUNIT / 2)) / FRACUNIT; -#ifdef HWRENDER li->hwLightOffset = FixedFloor(extralight + (FRACUNIT / 2)) / FRACUNIT; -#endif } boolean P_SectorUsesDirectionalLighting(const sector_t *sector) @@ -3790,9 +3792,7 @@ static void P_LoadSegs(UINT8 *data) seg->linedef = &lines[SHORT(ms->linedef)]; seg->length = P_SegLength(seg); -#ifdef HWRENDER - seg->flength = (rendermode == render_opengl) ? P_SegLengthFloat(seg) : 0; -#endif + seg->flength = P_SegLengthFloat(seg); seg->glseg = false; P_InitializeSeg(seg); @@ -4020,9 +4020,7 @@ static boolean P_LoadExtendedSubsectorsAndSegs(UINT8 **data, nodetype_t nodetype segs[i].offset = FixedHypot(v1->x - v->x, v1->y - v->y); } seg->length = P_SegLength(seg); -#ifdef HWRENDER - seg->flength = (rendermode == render_opengl) ? P_SegLengthFloat(seg) : 0; -#endif + seg->flength = P_SegLengthFloat(seg); } return true; @@ -5508,7 +5506,7 @@ static void P_ConvertBinaryLinedefTypes(void) lines[i].args[0] = (lines[i].flags & ML_NOTBOUNCY) ? TMT_EACHTIMEENTERANDEXIT : TMT_EACHTIMEENTER; else lines[i].args[0] = TMT_CONTINUOUS; - lines[i].args[1] = (lines[i].special > 310) ? TMT_BLUE : TMT_RED; + lines[i].args[1] = (lines[i].special > 310) ? TMT_BLUE : TMT_ORANGE; lines[i].special = 309; break; case 313: //No more enemies - once @@ -7536,7 +7534,6 @@ static boolean P_LoadMapFromFile(void) TracyCZone(__zone, true); virtlump_t *textmap = vres_Find(curmapvirt, "TEXTMAP"); - size_t i; udmf = textmap != NULL; udmf_version = 0; @@ -7564,17 +7561,9 @@ static boolean P_LoadMapFromFile(void) P_WriteTextmap(); // Copy relevant map data for NetArchive purposes. - spawnsectors = static_cast(Z_Calloc(numsectors * sizeof(*sectors), PU_LEVEL, NULL)); - spawnlines = static_cast(Z_Calloc(numlines * sizeof(*lines), PU_LEVEL, NULL)); - spawnsides = static_cast(Z_Calloc(numsides * sizeof(*sides), PU_LEVEL, NULL)); - - memcpy(spawnsectors, sectors, numsectors * sizeof(*sectors)); - memcpy(spawnlines, lines, numlines * sizeof(*lines)); - memcpy(spawnsides, sides, numsides * sizeof(*sides)); - - for (i = 0; i < numsectors; i++) - if (sectors[i].tags.count) - spawnsectors[i].tags.tags = static_cast(memcpy(Z_Malloc(sectors[i].tags.count*sizeof(mtag_t), PU_LEVEL, NULL), sectors[i].tags.tags, sectors[i].tags.count*sizeof(mtag_t))); + P_DeepCopySectors(&spawnsectors, §ors, numsectors); + P_DeepCopyLines(&spawnlines, &lines, numlines); + P_DeepCopySides(&spawnsides, &sides, numsides); P_MakeMapMD5(curmapvirt, &mapmd5); @@ -7684,6 +7673,7 @@ static void P_InitLevelSettings(void) const boolean multi_speed = (gametypes[gametype]->speed == KARTSPEED_AUTO); gamespeed = multi_speed ? KARTSPEED_EASY : gametypes[gametype]->speed; franticitems = false; + g_teamplay = false; if (K_PodiumSequence() == true) { @@ -7730,6 +7720,7 @@ static void P_InitLevelSettings(void) gamespeed = (UINT8)cv_kartspeed.value; } franticitems = (boolean)cv_kartfrantic.value; + g_teamplay = (boolean)cv_teamplay.value; // we will overwrite this later if there is not enough players } memset(&battleovertime, 0, sizeof(struct battleovertime)); @@ -7780,16 +7771,12 @@ void P_RespawnThings(void) static void P_ResetSpawnpoints(void) { - UINT8 i; - - numdmstarts = numredctfstarts = numbluectfstarts = 0; - numfaultstarts = 0; - faultstart = NULL; + UINT8 i, j; // reset the player starts for (i = 0; i < MAXPLAYERS; i++) { - playerstarts[i] = bluectfstarts[i] = redctfstarts[i] = NULL; + playerstarts[i] = NULL; if (playeringame[i]) { @@ -7798,9 +7785,23 @@ static void P_ResetSpawnpoints(void) } } + numfaultstarts = 0; + faultstart = NULL; + + numdmstarts = 0; for (i = 0; i < MAX_DM_STARTS; i++) deathmatchstarts[i] = NULL; + for (i = 0; i < TEAM__MAX; i++) + { + numteamstarts[i] = 0; + + for (j = 0; j < MAXPLAYERS; j++) + { + teamstarts[i][j] = NULL; + } + } + for (i = 0; i < 16; i++) skyboxviewpnts[i] = skyboxcenterpnts[i] = NULL; } @@ -7857,7 +7858,7 @@ static void P_LoadRecordGhosts(void) map(cv_ghost_last, value, kLast); }; - auto add_ghosts = [gpath](const std::string& base, UINT8 bits) + auto add_ghosts = [gpath](const srb2::String& base, UINT8 bits) { auto load = [base](const char* suffix) { P_TryAddExternalGhost(fmt::format("{}-{}.lmp", base, suffix).c_str()); }; @@ -7971,6 +7972,75 @@ static void P_InitCamera(void) } } +static void P_ShuffleTeams(void) +{ + size_t i; + + if (G_GametypeHasTeams() == false) + { + // Teams are not enabled, force to TEAM_UNASSIGNED + + for (i = 0; i < MAXPLAYERS; i++) + { + players[i].team = TEAM_UNASSIGNED; + } + + return; + } + + // The following will sort TEAM_UNASSIGNED players at random. + // In addition, you should know all players will have their team + // unset every round unless certain conditions are met. + // See Y_MidIntermission, G_InitNew (where resetplayer == true) + + CONS_Debug(DBG_TEAMS, "Shuffling player teams...\n"); + + srb2::Vector player_shuffle; + for (i = 0; i < MAXPLAYERS; i++) + { + if (playeringame[i] == false || players[i].spectator == true) + { + continue; + } + player_shuffle.push_back(i); + } + + size_t n = player_shuffle.size(); + if (inDuel == true || n <= 2) // cv_teamplay_min.value + { + CONS_Debug(DBG_TEAMS, "Not enough players to support teams; forcing teamplay preference off.\n"); + + // Not enough players for teams. + // Turn off the preference for this match. + g_teamplay = false; + + // But we may still be in a forced + // teams gametype, so only return false + // if our preference means anything. + if (G_GametypeHasTeams() == false) + { + return; + } + } + + if (n > 1) + { + for (i = n - 1; i > 0; i--) + { + size_t j = P_RandomKey(PR_TEAMS, i + 1); + + size_t temp = player_shuffle[i]; + player_shuffle[i] = player_shuffle[j]; + player_shuffle[j] = temp; + } + } + + for (i = 0; i < n; i++) + { + G_AutoAssignTeam(&players[ player_shuffle[i] ]); + } +} + static void P_InitPlayers(void) { INT32 i, skin = -1, follower = -1; @@ -7978,6 +8048,9 @@ static void P_InitPlayers(void) // Make sure objectplace is OFF when you first start the level! OP_ResetObjectplace(); + // Update skins / colors between levels. + G_UpdateAllPlayerPreferences(); + // Are we forcing a character? if (gametype == GT_TUTORIAL) { @@ -8391,7 +8464,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) lastwipetic = nowtime; \ if (moviemode && rendermode == render_opengl) \ M_LegacySaveFrame(); \ - else if (moviemode && rendermode != render_none) \ + else if (moviemode && rendermode == render_soft) \ I_CaptureVideoFrame(); \ NetKeepAlive(); \ } \ @@ -8608,7 +8681,6 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) Patch_FreeTag(PU_PATCH_LOWPRIORITY); Patch_FreeTag(PU_PATCH_ROTATED); Z_FreeTags(PU_LEVEL, PU_PURGELEVEL - 1); - mobjcache = NULL; R_InitializeLevelInterpolators(); @@ -8900,6 +8972,8 @@ void P_PostLoadLevel(void) } } + P_ShuffleTeams(); + K_TimerInit(); P_InitPlayers(); @@ -9065,7 +9139,7 @@ static tic_t round_to_next_second(tic_t time) static void P_DeriveAutoMedalTimes(mapheader_t& map) { // Gather staff ghost times - std::vector stafftimes; + srb2::Vector stafftimes; for (int i = 0; i < map.ghostCount; i++) { tic_t time = map.ghostBrief[i]->time; @@ -9255,7 +9329,7 @@ UINT8 P_InitMapData(void) { continue; } - std::string ghostdirname = fmt::format("staffghosts/{}/", mapheaderinfo[i]->lumpname); + srb2::String ghostdirname = srb2::format("staffghosts/{}/", mapheaderinfo[i]->lumpname); UINT16 lumpstart = W_CheckNumForFolderStartPK3(ghostdirname.c_str(), wadindex, 0); if (lumpstart == INT16_MAX) @@ -9358,7 +9432,7 @@ UINT16 P_PartialAddWadFile(const char *wadfilename) // UINT16 mapPos, mapNum = 0; // Init file. - if ((numlumps = W_InitFile(wadfilename, false, false)) == INT16_MAX) + if ((numlumps = W_InitFile(wadfilename, false, false, nullptr)) == INT16_MAX) { refreshdirmenu |= REFRESHDIR_NOTLOADED; return false; diff --git a/src/p_setup.h b/src/p_setup.h index 012094ea9..b16db6bb5 100644 --- a/src/p_setup.h +++ b/src/p_setup.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -30,7 +30,7 @@ extern unsigned char mapmd5[16]; // Player spawn spots for deathmatch. #define MAX_DM_STARTS 64 extern mapthing_t *deathmatchstarts[MAX_DM_STARTS]; -extern INT32 numdmstarts, numcoopstarts, numredctfstarts, numbluectfstarts, numfaultstarts; +extern INT32 numdmstarts, numcoopstarts, numteamstarts[TEAM__MAX], numfaultstarts; extern boolean levelloading; extern boolean g_reloadinggamestate; diff --git a/src/p_sight.c b/src/p_sight.c index f934e1a6d..a7ea2a685 100644 --- a/src/p_sight.c +++ b/src/p_sight.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Sally "TehRealSalt" Cochenour. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/p_slopes.c b/src/p_slopes.c index f13c60d7b..7ae2567bd 100644 --- a/src/p_slopes.c +++ b/src/p_slopes.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2004 by Stephen McGranahan. // @@ -11,6 +11,7 @@ /// \file p_slopes.c /// \brief ZDoom + Eternity Engine Slopes, ported and enhanced by Kalaron +#include "d_think.h" #include "doomdef.h" #include "r_defs.h" #include "r_state.h" @@ -90,9 +91,7 @@ void P_UpdateSlopeLightOffset(pslope_t *slope) // Between -2 and 2 for software, -16 and 16 for hardware slope->lightOffset = FixedFloor((extralight / 8) + (FRACUNIT / 2)) / FRACUNIT; -#ifdef HWRENDER slope->hwLightOffset = FixedFloor(extralight + (FRACUNIT / 2)) / FRACUNIT; -#endif } // Calculate line normal @@ -300,7 +299,9 @@ void T_DynamicSlopeVert (dynvertexplanethink_t* th) static inline void P_AddDynLineSlopeThinker (pslope_t* slope, dynplanetype_t type, line_t* sourceline, fixed_t extent) { - dynlineplanethink_t* th = Z_Calloc(sizeof (*th), PU_LEVSPEC, NULL); + dynlineplanethink_t* th = Z_LevelPoolCalloc(sizeof (*th)); + th->thinker.alloctype = TAT_LEVELPOOL; + th->thinker.size = sizeof(*th); th->thinker.function.acp1 = (actionf_p1)T_DynamicSlopeLine; th->slope = slope; th->type = type; @@ -314,7 +315,9 @@ static inline void P_AddDynLineSlopeThinker (pslope_t* slope, dynplanetype_t typ static inline void P_AddDynVertexSlopeThinker (pslope_t* slope, const INT16 tags[3], const vector3_t vx[3]) { - dynvertexplanethink_t* th = Z_Calloc(sizeof (*th), PU_LEVSPEC, NULL); + dynvertexplanethink_t* th = Z_LevelPoolCalloc(sizeof (*th)); + th->thinker.alloctype = TAT_LEVELPOOL; + th->thinker.size = sizeof(*th); size_t i; INT32 l; th->thinker.function.acp1 = (actionf_p1)T_DynamicSlopeVert; @@ -1091,7 +1094,7 @@ void P_HandleSlopeLanding(mobj_t *thing, pslope_t *slope) vector3_t mom; // Ditto. if (P_CanApplySlopePhysics(thing, slope) == false) // No physics, no need to make anything complicated. - { + { if (P_MobjFlip(thing)*(thing->momz) < 0) // falling, land on slope { thing->standingslope = slope; @@ -1151,7 +1154,7 @@ void P_ButteredSlope(mobj_t *mo) { // Allow the player to stand still on slopes below a certain steepness. // 45 degree angle steep, to be exact. - return; + return; } } diff --git a/src/p_slopes.h b/src/p_slopes.h index 690e06f54..b9708bff1 100644 --- a/src/p_slopes.h +++ b/src/p_slopes.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2004 by Stephen McGranahan. // diff --git a/src/p_spec.c b/src/p_spec.c index f9091c145..074819acb 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -16,6 +16,7 @@ /// utility functions, etc. /// Line Tag handling. Line and Sector triggers. +#include "d_think.h" #include "dehacked.h" #include "doomdef.h" #include "g_game.h" @@ -1298,7 +1299,9 @@ static void P_AddExecutorDelay(line_t *line, mobj_t *mobj, sector_t *sector) delay = (line->backsector->ceilingheight >> FRACBITS) + (line->backsector->floorheight >> FRACBITS); } - e = Z_Calloc(sizeof (*e), PU_LEVSPEC, NULL); + e = Z_LevelPoolCalloc(sizeof (*e)); + e->thinker.alloctype = TAT_LEVELPOOL; + e->thinker.size = sizeof (*e); e->thinker.function.acp1 = (actionf_p1)T_ExecutorDelay; e->line = line; @@ -1536,7 +1539,7 @@ boolean P_RunTriggerLinedef(line_t *triggerline, mobj_t *actor, sector_t *caller // Only red/blue team members can activate this. if (!(actor && actor->player)) return false; - if (actor->player->ctfteam != ((triggerline->args[1] == TMT_RED) ? 1 : 2)) + if (actor->player->team != ((triggerline->args[1] == TMT_ORANGE) ? TEAM_ORANGE : TEAM_BLUE)) return false; break; case 314: @@ -2037,7 +2040,25 @@ static void K_HandleLapIncrement(player_t *player) { linecrossed = leveltime; if (starttime > leveltime) // Overlong starts shouldn't reset time on cross + { + // Award some Amps for a fast start, to counterbalance Obvious Rainbow Driftboost + + tic_t starthaste = starttime - leveltime; // How much time we had left to cross + starthaste = TIMEATTACK_START - starthaste; // How much time we wasted before crossing + + tic_t leniency = TICRATE*2; // How long we can take to cross with no penalty to amp payout + + if (starthaste <= leniency) + starthaste = 0; + else + starthaste -= leniency; + + fixed_t ampreward = Easing_OutQuart(starthaste*FRACUNIT/TIMEATTACK_START, 100*FRACUNIT, 0); + K_SpawnAmps(player, ampreward/FRACUNIT, player->mo); + + // And reset our time to 0. starttime = leveltime; + } if (demo.recording) demo_extradata[player-players] |= DXD_START; Music_Stop("position"); @@ -2050,7 +2071,21 @@ static void K_HandleLapIncrement(player_t *player) K_SpawnDriftBoostExplosion(player, 4); K_SpawnDriftElectricSparks(player, SKINCOLOR_SILVER, false); - K_SpawnAmps(player, (K_InRaceDuel()) ? 20 : 50, player->mo); + K_SpawnAmps(player, (K_InRaceDuel()) ? 20 : 35, player->mo); + + if (g_teamplay) + { + for (UINT8 j = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[j] || players[j].spectator || !players[j].mo || P_MobjWasRemoved(players[j].mo)) + continue; + if (!G_SameTeam(player, &players[j])) + continue; + if (player == &players[j]) + continue; + K_SpawnAmps(&players[j], 10, player->mo); + } + } rainbowstartavailable = false; } @@ -2110,20 +2145,7 @@ static void K_HandleLapIncrement(player_t *player) // Update power levels for this lap. K_UpdatePowerLevels(player, player->laps, false); - if (nump > 1 && K_IsPlayerLosing(player) == false) - { - if (inDuel == false && player->position == 1) // 1st place in 1v1 uses thumbs up - { - player->lapPoints += 2; - } - else - { - player->lapPoints++; - } - } - K_CheckpointCrossAward(player); - player->gradingpointnum++; if (player->position == 1 && !(gametyperules & GTR_CHECKPOINTS)) { @@ -3209,14 +3231,31 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha break; case 424: // Change Weather + { + preciptype_t new_precip = PRECIP_NONE; + if (udmf_version < 2) + { + new_precip = args[0]; + } + else + { + new_precip = stringargs[0] ? get_number(stringargs[0]) : PRECIP_NONE; + } + if (args[1]) { - globalweather = (UINT8)(args[0]); + globalweather = new_precip; P_SwitchWeather(globalweather); } - else if (mo && mo->player && P_IsPartyPlayer(mo->player)) - P_SwitchWeather(args[0]); + else + { + if (mo && mo->player && P_IsPartyPlayer(mo->player)) + { + P_SwitchWeather(new_precip); + } + } break; + } case 425: // Calls P_SetMobjState on calling mobj { @@ -3867,19 +3906,18 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha foundrover = true; // If fading an invisible FOF whose render flags we did not yet set, - // initialize its alpha to 1 - // for relative alpha calc + // initialize its alpha to 0 for relative alpha calculation if (!(args[3] & TMST_DONTDOTRANSLUCENT) && // do translucent (rover->spawnflags & FOF_NOSHADE) && // do not include light blocks, which don't set FOF_NOSHADE !(rover->spawnflags & FOF_RENDERSIDES) && !(rover->spawnflags & FOF_RENDERPLANES) && !(rover->fofflags & FOF_RENDERALL)) - rover->alpha = 1; + rover->alpha = 0; P_RemoveFakeFloorFader(rover); P_FadeFakeFloor(rover, rover->alpha, - max(1, min(256, (args[3] & TMST_RELATIVE) ? rover->alpha + destvalue : destvalue)), + max(0, min(255, (args[3] & TMST_RELATIVE) ? rover->alpha + destvalue : destvalue)), 0, // set alpha immediately false, NULL, // tic-based logic false, // do not handle FOF_EXISTS @@ -3953,19 +3991,18 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha else { // If fading an invisible FOF whose render flags we did not yet set, - // initialize its alpha to 1 - // for relative alpha calc + // initialize its alpha to 0 for relative alpha calculation if (!(args[4] & TMFT_DONTDOTRANSLUCENT) && // do translucent (rover->spawnflags & FOF_NOSHADE) && // do not include light blocks, which don't set FOF_NOSHADE !(rover->spawnflags & FOF_RENDERSIDES) && !(rover->spawnflags & FOF_RENDERPLANES) && !(rover->fofflags & FOF_RENDERALL)) - rover->alpha = 1; + rover->alpha = 0; P_RemoveFakeFloorFader(rover); P_FadeFakeFloor(rover, rover->alpha, - max(1, min(256, (args[4] & TMFT_RELATIVE) ? rover->alpha + destvalue : destvalue)), + max(0, min(255, (args[4] & TMFT_RELATIVE) ? rover->alpha + destvalue : destvalue)), 0, // set alpha immediately false, NULL, // tic-based logic !(args[4] & TMFT_DONTDOEXISTS), // do not handle FOF_EXISTS @@ -4153,8 +4190,7 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha { INT32 failureangle = FixedAngle((min(max(abs(args[1]), 0), 360))*FRACUNIT); INT32 failuredelay = abs(args[2]); - INT32 failureexectag = args[3]; - boolean persist = !!(args[4]); + boolean persist = !!(args[3]); mobj_t *anchormo; anchormo = P_FindObjectTypeFromTag(MT_ANGLEMAN, args[0]); @@ -4165,7 +4201,6 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha P_SetTarget(&mo->tracer, anchormo); mo->lastlook = persist; // don't disable behavior after first failure mo->extravalue1 = failureangle; // angle to exceed for failure state - mo->extravalue2 = failureexectag; // exec tag for failure state (angle is not within range) mo->cusval = mo->cvmem = failuredelay; // cusval = tics to allow failure before line trigger; cvmem = decrement timer } break; @@ -4175,7 +4210,7 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha { mo->eflags &= ~MFE_TRACERANGLE; P_SetTarget(&mo->tracer, NULL); - mo->lastlook = mo->cvmem = mo->cusval = mo->extravalue1 = mo->extravalue2 = 0; + mo->lastlook = mo->cvmem = mo->cusval = mo->extravalue1 = 0; } break; @@ -6397,7 +6432,9 @@ static void P_AddFloatThinker(sector_t *sec, UINT16 tag, line_t *sourceline) floatthink_t *floater; // create and initialize new thinker - floater = Z_Calloc(sizeof (*floater), PU_LEVSPEC, NULL); + floater = Z_LevelPoolCalloc(sizeof (*floater)); + floater->thinker.alloctype = TAT_LEVELPOOL; + floater->thinker.size = sizeof (*floater); P_AddThinker(THINK_MAIN, &floater->thinker); floater->thinker.function.acp1 = (actionf_p1)T_FloatSector; @@ -6428,7 +6465,9 @@ static void P_AddPlaneDisplaceThinker(INT32 type, fixed_t speed, INT32 control, planedisplace_t *displace; // create and initialize new displacement thinker - displace = Z_Calloc(sizeof (*displace), PU_LEVSPEC, NULL); + displace = Z_LevelPoolCalloc(sizeof (*displace)); + displace->thinker.alloctype = TAT_LEVELPOOL; + displace->thinker.size = sizeof (*displace); P_AddThinker(THINK_MAIN, &displace->thinker); displace->thinker.function.acp1 = (actionf_p1)T_PlaneDisplace; @@ -6458,7 +6497,9 @@ static void P_AddBlockThinker(sector_t *sec, line_t *sourceline) mariocheck_t *block; // create and initialize new elevator thinker - block = Z_Calloc(sizeof (*block), PU_LEVSPEC, NULL); + block = Z_LevelPoolCalloc(sizeof (*block)); + block->thinker.alloctype = TAT_LEVELPOOL; + block->thinker.size = sizeof (*block); P_AddThinker(THINK_MAIN, &block->thinker); block->thinker.function.acp1 = (actionf_p1)T_MarioBlockChecker; @@ -6483,7 +6524,9 @@ static void P_AddRaiseThinker(sector_t *sec, INT16 tag, fixed_t speed, fixed_t c { raise_t *raise; - raise = Z_Calloc(sizeof (*raise), PU_LEVSPEC, NULL); + raise = Z_LevelPoolCalloc(sizeof (*raise)); + raise->thinker.alloctype = TAT_LEVELPOOL; + raise->thinker.size = sizeof (*raise); P_AddThinker(THINK_MAIN, &raise->thinker); raise->thinker.function.acp1 = (actionf_p1)T_RaiseSector; @@ -6510,7 +6553,9 @@ static void P_AddAirbob(sector_t *sec, INT16 tag, fixed_t dist, boolean raise, b { raise_t *airbob; - airbob = Z_Calloc(sizeof (*airbob), PU_LEVSPEC, NULL); + airbob = Z_LevelPoolCalloc(sizeof (*airbob)); + airbob->thinker.alloctype = TAT_LEVELPOOL; + airbob->thinker.size = sizeof (*airbob); P_AddThinker(THINK_MAIN, &airbob->thinker); airbob->thinker.function.acp1 = (actionf_p1)T_RaiseSector; @@ -6552,7 +6597,9 @@ static inline void P_AddThwompThinker(sector_t *sec, line_t *sourceline, fixed_t return; // create and initialize new elevator thinker - thwomp = Z_Calloc(sizeof (*thwomp), PU_LEVSPEC, NULL); + thwomp = Z_LevelPoolCalloc(sizeof (*thwomp)); + thwomp->thinker.alloctype = TAT_LEVELPOOL; + thwomp->thinker.size = sizeof (*thwomp); P_AddThinker(THINK_MAIN, &thwomp->thinker); thwomp->thinker.function.acp1 = (actionf_p1)T_ThwompSector; @@ -6592,7 +6639,9 @@ static inline void P_AddNoEnemiesThinker(line_t *sourceline) noenemies_t *nobaddies; // create and initialize new thinker - nobaddies = Z_Calloc(sizeof (*nobaddies), PU_LEVSPEC, NULL); + nobaddies = Z_LevelPoolCalloc(sizeof (*nobaddies)); + nobaddies->thinker.alloctype = TAT_LEVELPOOL; + nobaddies->thinker.size = sizeof (*nobaddies); P_AddThinker(THINK_MAIN, &nobaddies->thinker); nobaddies->thinker.function.acp1 = (actionf_p1)T_NoEnemiesSector; @@ -6612,7 +6661,9 @@ static void P_AddEachTimeThinker(line_t *sourceline, boolean triggerOnExit) eachtime_t *eachtime; // create and initialize new thinker - eachtime = Z_Calloc(sizeof (*eachtime), PU_LEVSPEC, NULL); + eachtime = Z_LevelPoolCalloc(sizeof (*eachtime)); + eachtime->thinker.alloctype = TAT_LEVELPOOL; + eachtime->thinker.size = sizeof (*eachtime); P_AddThinker(THINK_MAIN, &eachtime->thinker); eachtime->thinker.function.acp1 = (actionf_p1)T_EachTimeThinker; @@ -6636,7 +6687,9 @@ static inline void P_AddCameraScanner(sector_t *sourcesec, sector_t *actionsecto CONS_Alert(CONS_WARNING, M_GetText("Detected a camera scanner effect (linedef type 5). This effect is deprecated and will be removed in the future!\n")); // create and initialize new elevator thinker - elevator = Z_Calloc(sizeof (*elevator), PU_LEVSPEC, NULL); + elevator = Z_LevelPoolCalloc(sizeof (*elevator)); + elevator->thinker.alloctype = TAT_LEVELPOOL; + elevator->thinker.size = sizeof (*elevator); P_AddThinker(THINK_MAIN, &elevator->thinker); elevator->thinker.function.acp1 = (actionf_p1)T_CameraScanner; @@ -6717,7 +6770,9 @@ void T_LaserFlash(laserthink_t *flash) static inline void P_AddLaserThinker(INT16 tag, line_t *line, boolean nobosses) { - laserthink_t *flash = Z_Calloc(sizeof (*flash), PU_LEVSPEC, NULL); + laserthink_t *flash = Z_LevelPoolCalloc(sizeof (*flash)); + flash->thinker.alloctype = TAT_LEVELPOOL; + flash->thinker.size = sizeof (*flash); P_AddThinker(THINK_MAIN, &flash->thinker); @@ -8248,7 +8303,9 @@ void T_Scroll(scroll_t *s) */ static void Add_Scroller(INT32 type, fixed_t dx, fixed_t dy, INT32 control, INT32 affectee, INT32 accel, INT32 exclusive) { - scroll_t *s = Z_Calloc(sizeof *s, PU_LEVSPEC, NULL); + scroll_t *s = Z_LevelPoolCalloc(sizeof (*s)); + s->thinker.alloctype = TAT_LEVELPOOL; + s->thinker.size = sizeof (*s); s->thinker.function.acp1 = (actionf_p1)T_Scroll; s->type = type; s->dx = dx; @@ -8393,7 +8450,9 @@ static void P_SpawnScrollers(void) */ static void Add_MasterDisappearer(tic_t appeartime, tic_t disappeartime, tic_t offset, INT32 line, INT32 sourceline) { - disappear_t *d = Z_Malloc(sizeof *d, PU_LEVSPEC, NULL); + disappear_t *d = Z_LevelPoolCalloc(sizeof (*d)); + d->thinker.alloctype = TAT_LEVELPOOL; + d->thinker.size = sizeof (*d); d->thinker.function.acp1 = (actionf_p1)T_Disappear; d->appeartime = appeartime; @@ -8519,15 +8578,14 @@ static boolean P_FadeFakeFloor(ffloor_t *rover, INT16 sourcevalue, INT16 destval if (rover->master->special == 258) // Laser block return false; - // If fading an invisible FOF whose render flags we did not yet set, - // initialize its alpha to 1 + // If fading an invisible FOF whose render flags we did not yet set, initialize its alpha to 0 if (dotranslucent && (rover->spawnflags & FOF_NOSHADE) && // do not include light blocks, which don't set FOF_NOSHADE !(rover->fofflags & FOF_FOG) && // do not include fog !(rover->spawnflags & FOF_RENDERSIDES) && !(rover->spawnflags & FOF_RENDERPLANES) && !(rover->fofflags & FOF_RENDERALL)) - rover->alpha = 1; + rover->alpha = 0; if (fadingdata) alpha = fadingdata->alpha; @@ -8613,7 +8671,7 @@ static boolean P_FadeFakeFloor(ffloor_t *rover, INT16 sourcevalue, INT16 destval { if (doexists && !(rover->spawnflags & FOF_BUSTUP)) { - if (alpha <= 1) + if (alpha <= 0) rover->fofflags &= ~FOF_EXISTS; else rover->fofflags |= FOF_EXISTS; @@ -8625,7 +8683,7 @@ static boolean P_FadeFakeFloor(ffloor_t *rover, INT16 sourcevalue, INT16 destval if (dotranslucent && !(rover->fofflags & FOF_FOG)) { - if (alpha >= 256) + if (alpha >= 255) { if (!(rover->fofflags & FOF_CUTSOLIDS) && (rover->spawnflags & FOF_CUTSOLIDS)) @@ -8725,11 +8783,11 @@ static boolean P_FadeFakeFloor(ffloor_t *rover, INT16 sourcevalue, INT16 destval else // clamp fadingdata->alpha to software's alpha levels { if (alpha < 12) - rover->alpha = destvalue < 12 ? destvalue : 1; // Don't even draw it + rover->alpha = destvalue < 12 ? destvalue : 0; // Don't even draw it else if (alpha < 38) rover->alpha = destvalue >= 12 && destvalue < 38 ? destvalue : 25; else if (alpha < 64) - rover->alpha = destvalue >=38 && destvalue < 64 ? destvalue : 51; + rover->alpha = destvalue >= 38 && destvalue < 64 ? destvalue : 51; else if (alpha < 89) rover->alpha = destvalue >= 64 && destvalue < 89 ? destvalue : 76; else if (alpha < 115) @@ -8745,7 +8803,7 @@ static boolean P_FadeFakeFloor(ffloor_t *rover, INT16 sourcevalue, INT16 destval else if (alpha < 243) rover->alpha = destvalue >= 217 && destvalue < 243 ? destvalue : 230; else // Opaque - rover->alpha = destvalue >= 243 ? destvalue : 256; + rover->alpha = destvalue >= 243 ? destvalue : 255; } } @@ -8775,20 +8833,21 @@ static void P_AddFakeFloorFader(ffloor_t *rover, size_t sectornum, size_t ffloor { fade_t *d; - // If fading an invisible FOF whose render flags we did not yet set, - // initialize its alpha to 1 + // If fading an invisible FOF whose render flags we did not yet set, initialize its alpha to 0 if (dotranslucent && (rover->spawnflags & FOF_NOSHADE) && // do not include light blocks, which don't set FOF_NOSHADE !(rover->spawnflags & FOF_RENDERSIDES) && !(rover->spawnflags & FOF_RENDERPLANES) && !(rover->fofflags & FOF_RENDERALL)) - rover->alpha = 1; + rover->alpha = 0; // already equal, nothing to do - if (rover->alpha == max(1, min(256, relative ? rover->alpha + destvalue : destvalue))) + if (rover->alpha == max(0, min(255, relative ? rover->alpha + destvalue : destvalue))) return; - d = Z_Malloc(sizeof *d, PU_LEVSPEC, NULL); + d = Z_LevelPoolCalloc(sizeof (*d)); + d->thinker.alloctype = TAT_LEVELPOOL; + d->thinker.size = sizeof (*d); d->thinker.function.acp1 = (actionf_p1)T_Fade; d->rover = rover; @@ -8796,7 +8855,7 @@ static void P_AddFakeFloorFader(ffloor_t *rover, size_t sectornum, size_t ffloor d->ffloornum = (UINT32)ffloornum; d->alpha = d->sourcevalue = rover->alpha; - d->destvalue = max(1, min(256, relative ? rover->alpha + destvalue : destvalue)); // rover->alpha is 1-256 + d->destvalue = max(0, min(255, relative ? rover->alpha + destvalue : destvalue)); // rover->alpha is 0-255 if (ticbased) { @@ -8941,7 +9000,9 @@ static void Add_ColormapFader(sector_t *sector, extracolormap_t *source_exc, ext return; } - d = Z_Malloc(sizeof *d, PU_LEVSPEC, NULL); + d = Z_LevelPoolCalloc(sizeof (*d)); + d->thinker.alloctype = TAT_LEVELPOOL; + d->thinker.size = sizeof (*d); d->thinker.function.acp1 = (actionf_p1)T_FadeColormap; d->sector = sector; d->source_exc = source_exc; @@ -9064,7 +9125,9 @@ void T_FadeColormap(fadecolormap_t *d) */ static void Add_Friction(INT32 friction, INT32 movefactor, INT32 affectee, INT32 referrer) { - friction_t *f = Z_Calloc(sizeof *f, PU_LEVSPEC, NULL); + friction_t *f = Z_LevelPoolCalloc(sizeof (*f)); + f->thinker.alloctype = TAT_LEVELPOOL; + f->thinker.size = sizeof (*f); f->thinker.function.acp1 = (actionf_p1)T_Friction; f->friction = friction; @@ -9198,7 +9261,9 @@ static void P_SpawnFriction(void) */ static void Add_Pusher(pushertype_e type, fixed_t x_mag, fixed_t y_mag, fixed_t z_mag, INT32 affectee, INT32 referrer, INT32 exclusive, INT32 slider) { - pusher_t *p = Z_Calloc(sizeof *p, PU_LEVSPEC, NULL); + pusher_t *p = Z_LevelPoolCalloc(sizeof (*p)); + p->thinker.alloctype = TAT_LEVELPOOL; + p->thinker.size = sizeof (*p); p->thinker.function.acp1 = (actionf_p1)T_Pusher; p->type = type; @@ -9572,6 +9637,11 @@ void P_DoQuakeOffset(UINT8 view, mappoint_t *viewPos, mappoint_t *offset) fixed_t maxShake = FixedMul(cv_cam_height[view].value, mapobjectscale) * 3 / 4; + if (mapheaderinfo[gamemap-1]->cameraHeight >= 0) + { + maxShake = FixedMul(mapheaderinfo[gamemap-1]->cameraHeight, mapobjectscale) * 3 / 4; + } + if (battle) { addZ /= 2; diff --git a/src/p_spec.h b/src/p_spec.h index 39ec0752c..e637e4fe0 100644 --- a/src/p_spec.h +++ b/src/p_spec.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -289,7 +289,7 @@ typedef enum typedef enum { - TMT_RED = 0, + TMT_ORANGE = 0, TMT_BLUE = 1, } textmapteam_t; diff --git a/src/p_sweep.cpp b/src/p_sweep.cpp index 971e2bdb5..c475a9f16 100644 --- a/src/p_sweep.cpp +++ b/src/p_sweep.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/p_sweep.hpp b/src/p_sweep.hpp index 6172dbe3c..65790091b 100644 --- a/src/p_sweep.hpp +++ b/src/p_sweep.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/p_telept.c b/src/p_telept.c index 09db59477..55ce5f993 100644 --- a/src/p_telept.c +++ b/src/p_telept.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/p_test.cpp b/src/p_test.cpp index 8485e7ba9..5b9b07e42 100644 --- a/src/p_test.cpp +++ b/src/p_test.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/p_tick.c b/src/p_tick.c index 796724e5b..29cf6d62c 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -12,6 +12,7 @@ /// \file p_tick.c /// \brief Archiving: SaveGame I/O, Thinker, Ticker +#include "d_think.h" #include "doomstat.h" #include "d_main.h" #include "g_game.h" @@ -330,7 +331,6 @@ void P_AddThinker(const thinklistnum_t n, thinker_t *thinker) thlist[n].prev = thinker; thinker->references = 0; // killough 11/98: init reference counter to 0 - thinker->cachable = n == THINK_MOBJ; #ifdef PARANOIA thinker->debug_mobjtype = MT_NULL; @@ -448,11 +448,9 @@ void P_UnlinkThinker(thinker_t *thinker) I_Assert(thinker->references == 0); (next->prev = thinker->prev)->next = next; - if (thinker->cachable) + if (thinker->alloctype == TAT_LEVELPOOL) { - // put cachable thinkers in the mobj cache, so we can avoid allocations - ((mobj_t *)thinker)->hnext = mobjcache; - mobjcache = (mobj_t *)thinker; + Z_LevelPoolFree(thinker, thinker->size); } else { @@ -585,146 +583,6 @@ static void P_RunThinkers(void) ps_acs_time = I_GetPreciseTime() - ps_acs_time; } -// -// P_DoAutobalanceTeams() -// -// Determine if the teams are unbalanced, and if so, move a player to the other team. -// -static void P_DoAutobalanceTeams(void) -{ - changeteam_union NetPacket; - UINT16 usvalue; - INT32 i=0; - INT32 red=0, blue=0; - INT32 redarray[MAXPLAYERS], bluearray[MAXPLAYERS]; - //INT32 redflagcarrier = 0, blueflagcarrier = 0; - INT32 totalred = 0, totalblue = 0; - - NetPacket.value.l = NetPacket.value.b = 0; - memset(redarray, 0, sizeof(redarray)); - memset(bluearray, 0, sizeof(bluearray)); - - // Only do it if we have enough room in the net buffer to send it. - // Otherwise, come back next time and try again. - if (sizeof(usvalue) > GetFreeXCmdSize(0)) - return; - - //We have to store the players in an array with the rest of their team. - //We can then pick a random player to be forced to change teams. - for (i = 0; i < MAXPLAYERS; i++) - { - if (playeringame[i] && players[i].ctfteam) - { - if (players[i].ctfteam == 1) - { - //if (!players[i].gotflag) - { - redarray[red] = i; //store the player's node. - red++; - } - /*else - redflagcarrier++;*/ - } - else - { - //if (!players[i].gotflag) - { - bluearray[blue] = i; //store the player's node. - blue++; - } - /*else - blueflagcarrier++;*/ - } - } - } - - totalred = red;// + redflagcarrier; - totalblue = blue;// + blueflagcarrier; - - if ((abs(totalred - totalblue) > max(1, (totalred + totalblue) / 8))) - { - if (totalred > totalblue) - { - i = M_RandomKey(red); - NetPacket.packet.newteam = 2; - NetPacket.packet.playernum = redarray[i]; - NetPacket.packet.verification = true; - NetPacket.packet.autobalance = true; - - usvalue = SHORT(NetPacket.value.l|NetPacket.value.b); - SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue)); - } - else //if (totalblue > totalred) - { - i = M_RandomKey(blue); - NetPacket.packet.newteam = 1; - NetPacket.packet.playernum = bluearray[i]; - NetPacket.packet.verification = true; - NetPacket.packet.autobalance = true; - - usvalue = SHORT(NetPacket.value.l|NetPacket.value.b); - SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue)); - } - } -} - -// -// P_DoTeamscrambling() -// -// If a team scramble has been started, scramble one person from the -// pre-made scramble array. Said array is created in TeamScramble_OnChange() -// -void P_DoTeamscrambling(void) -{ - changeteam_union NetPacket; - UINT16 usvalue; - NetPacket.value.l = NetPacket.value.b = 0; - - // Only do it if we have enough room in the net buffer to send it. - // Otherwise, come back next time and try again. - if (sizeof(usvalue) > GetFreeXCmdSize(0)) - return; - - if (scramblecount < scrambletotal) - { - if (players[scrambleplayers[scramblecount]].ctfteam != scrambleteams[scramblecount]) - { - NetPacket.packet.newteam = scrambleteams[scramblecount]; - NetPacket.packet.playernum = scrambleplayers[scramblecount]; - NetPacket.packet.verification = true; - NetPacket.packet.scrambled = true; - - usvalue = SHORT(NetPacket.value.l|NetPacket.value.b); - SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue)); - } - - scramblecount++; //Increment, and get to the next player when we come back here next time. - } - else - CV_SetValue(&cv_teamscramble, 0); -} - -static inline void P_DoTeamStuff(void) -{ - // Automatic team balance for CTF and team match - if (leveltime % (TICRATE * 5) == 0) //only check once per five seconds for the sake of CPU conservation. - { - // Do not attempt to autobalance and scramble teams at the same time. - // Only the server should execute this. No verified admins, please. - if ((cv_autobalance.value && !cv_teamscramble.value) && cv_allowteamchange.value && server) - P_DoAutobalanceTeams(); - } - - // Team scramble code for team match and CTF. - if ((leveltime % (TICRATE/7)) == 0) - { - // If we run out of time in the level, the beauty is that - // the Y_Ticker() team scramble code will pick it up. - if (cv_teamscramble.value && server) - P_DoTeamscrambling(); - } -} - static inline void P_DeviceRumbleTick(void) { UINT8 i; @@ -1225,9 +1083,6 @@ void P_Ticker(boolean run) timeinmap++; } - if (G_GametypeHasTeams()) - P_DoTeamStuff(); - if (run) { if (racecountdown > 1) diff --git a/src/p_tick.h b/src/p_tick.h index b2af7a378..b735d9b1d 100644 --- a/src/p_tick.h +++ b/src/p_tick.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/p_user.c b/src/p_user.c index fc19292a6..c5c9e6207 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -73,6 +73,7 @@ #include "k_hud.h" // K_AddMessage #include "m_easing.h" #include "acs/interface.h" +#include "byteptr.h" #ifdef HWRENDER #include "hardware/hw_light.h" @@ -418,7 +419,8 @@ void P_ResetPlayer(player_t *player) { //player->pflags &= ~(PF_); - player->carry = CR_NONE; + if (player->carry != CR_TRAPBUBBLE) + player->carry = CR_NONE; player->onconveyor = 0; //player->drift = player->driftcharge = 0; @@ -514,25 +516,44 @@ void P_GivePlayerLives(player_t *player, INT32 numlives) // Adds to the player's score void P_AddPlayerScore(player_t *player, INT32 amount) { - if (!((gametyperules & GTR_POINTLIMIT))) + if ((gametyperules & GTR_POINTLIMIT) == 0) + { return; + } if (player->exiting) // srb2kart + { return; + } + + const boolean teams = G_GametypeHasTeams(); // Don't underflow. // Don't go above MAXSCORE. if (amount < 0 && (UINT32)-amount > player->roundscore) + { player->roundscore = 0; + } else if (player->roundscore + amount < MAXSCORE) { - if (player->roundscore < g_pointlimit && g_pointlimit <= player->roundscore + amount) + if (player->roundscore < g_pointlimit + && g_pointlimit <= player->roundscore + amount + && teams == false) // We want the normal scoring function to update roundscore, but this notification will be done by G_AddTeamScore. + { HU_DoTitlecardCEchoForDuration(player, "K.O. READY!", true, 5*TICRATE/2); + } player->roundscore += amount; } else + { player->roundscore = MAXSCORE; + } + + if (teams == true) + { + G_AddTeamScore(player->team, amount, player); + } } void P_PlayRinglossSound(mobj_t *source) @@ -1105,7 +1126,7 @@ mobj_t *P_SpawnGhostMobj(mobj_t *mobj) ghost->spriteyoffset = mobj->spriteyoffset; if (mobj->flags2 & MF2_OBJECTFLIP) - ghost->flags |= MF2_OBJECTFLIP; + ghost->flags2 |= MF2_OBJECTFLIP; if (!(mobj->flags & MF_DONTENCOREMAP)) ghost->flags &= ~MF_DONTENCOREMAP; @@ -1310,7 +1331,7 @@ void P_DoPlayerExit(player_t *player, pflags_t flags) if (demo.playback == false) { - if (modeattacking) + if (modeattacking && !K_LegacyRingboost(player)) { G_UpdateRecords(); } @@ -1958,9 +1979,7 @@ static void P_3dMovement(player_t *player) else if (player->onconveyor == 4 && !P_IsObjectOnGround(player->mo)) // Actual conveyor belt player->cmomx = player->cmomy = 0; else if (player->onconveyor != 2 && player->onconveyor != 4 -#ifdef POLYOBJECTS && player->onconveyor != 1 -#endif ) player->cmomx = player->cmomy = 0; @@ -2318,18 +2337,20 @@ static void P_UpdatePlayerAngle(player_t *player) else { // With a full slam on the analog stick, how far could we steer in either direction? - INT16 steeringRight = K_UpdateSteeringValue(player->steering, KART_FULLTURN); - INT16 steeringLeft = K_UpdateSteeringValue(player->steering, -KART_FULLTURN); + INT16 steeringRight = K_UpdateSteeringValue(player->steering, KART_FULLTURN); + INT16 steeringLeft = K_UpdateSteeringValue(player->steering, -KART_FULLTURN); +#if 1 // When entering/leaving drifts, allow all legal turns with no easing. // This is the hardest case for the turn solver, because your handling properties on // client side are very different than your handling properties on server side—at least, // until your drift status makes the full round-trip and is reflected in your gamestate. - if (player->drift && abs(player->drift) < 5) + if (player->drift && abs(player->drift) < 5 && player->cmd.latency) { steeringRight = KART_FULLTURN; steeringLeft = -KART_FULLTURN; } +#endif angle_t maxTurnRight = K_GetKartTurnValue(player, steeringRight) << TICCMD_REDUCE; angle_t maxTurnLeft = K_GetKartTurnValue(player, steeringLeft) << TICCMD_REDUCE; @@ -2338,47 +2359,33 @@ static void P_UpdatePlayerAngle(player_t *player) angle_t targetAngle = (player->cmd.angle) << TICCMD_REDUCE; angle_t targetDelta = targetAngle - (player->mo->angle); +#define SOLVERANGLECHEATS + +#ifdef SOLVERANGLECHEATS // Corrections via fake turn go through easing. // That means undoing them takes the same amount of time as doing them. // This can lead to oscillating death spiral states on a multi-tic correction, as we swing past the target angle. // So before we go into death-spirals, if our predicton is _almost_ right... - angle_t leniency_base; - if (G_CompatLevel(0x000A)) - { - // Compat level for 2.0 staff ghosts - leniency_base = 4 * ANG1 / 3; - } - else - { - leniency_base = 8 * ANG1 / 3; - } - - // Gross. Take a look at sliptide starts properly for 2.4. - // Yell at Tyron! - if (!G_CompatLevel(0x000C)) - { - leniency_base = 6 * ANG1 / 3; - } - + angle_t leniency_base = 2 * ANG1; angle_t leniency = leniency_base * min(player->cmd.latency, 6); // Don't force another turning tic, just give them the desired angle! +#endif -#if 0 // Old sliptide preservation behavior - if (K_Sliptiding(player) && P_IsObjectOnGround(player->mo) && (player->cmd.turning != 0) && ((player->cmd.turning > 0) == (player->aizdriftstrat > 0))) + if (!(player->cmd.buttons & BT_DRIFT) && (abs(player->drift) == 1) && ((player->cmd.turning > 0) == (player->drift > 0)) && player->handleboost > SLIPTIDEHANDLING) { - // Don't change handling direction if someone's inputs are sliptiding, you'll break the sliptide! - if (player->cmd.turning > 0) + // This drift release is eligible to start a sliptide. Don't do lag-compensation countersteer behavior that could destroy it! + if (player->cmd.turning >= 0) { steeringLeft = max(steeringLeft, 1); steeringRight = max(steeringRight, steeringLeft); } - else + else if (player->cmd.turning <= 0) { steeringRight = min(steeringRight, -1); steeringLeft = min(steeringLeft, steeringRight); } } -#else // Digital-friendly sliptide preservation behavior + if (K_Sliptiding(player) && P_IsObjectOnGround(player->mo)) { // Unless someone explicitly inputs a turn that would break their sliptide, keep sliptiding. @@ -2392,12 +2399,7 @@ static void P_UpdatePlayerAngle(player_t *player) steeringRight = min(steeringRight, -1); steeringLeft = min(steeringLeft, steeringRight); } - else - { - // :V - } } -#endif if (maxTurnRight == 0 && maxTurnLeft == 0) { @@ -2407,13 +2409,17 @@ static void P_UpdatePlayerAngle(player_t *player) else { // We're off. Try to legally steer the player towards their camera. - player->steering = P_FindClosestTurningForAngle(player, targetDelta, steeringLeft, steeringRight); + //CONS_Printf("aiz %d - dr %d - hb %d\n", player->aizdriftstrat, player->drift, player->handleboost); + //CONS_Printf("st %d - ts %d - t %d\n", player->steering, targetsteering, player->cmd.turning); + //CONS_Printf("%d\n", player->steering - targetsteering); angleChange = K_GetKartTurnValue(player, player->steering) << TICCMD_REDUCE; +#ifdef SOLVERANGLECHEATS // And if the resulting steering input is close enough, snap them exactly. if (min(targetDelta - angleChange, angleChange - targetDelta) <= leniency) angleChange = targetDelta; +#endif } } @@ -3355,6 +3361,11 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall camdist = FixedMul(cv_cam_dist[num].value, cameraScale); camheight = FixedMul(cv_cam_height[num].value, cameraScale); + if (mapheaderinfo[gamemap-1]->cameraHeight >= 0) + { + camheight = FixedMul(mapheaderinfo[gamemap-1]->cameraHeight, cameraScale); + } + if (loop_in < loop->zoom_in_speed) { fixed_t f = loop_out < loop->zoom_out_speed @@ -3742,49 +3753,9 @@ boolean P_MoveChaseCamera(player_t *player, camera_t *thiscam, boolean resetcall boolean P_SpectatorJoinGame(player_t *player) { - INT32 changeto = 0; const char *text = NULL; - // Team changing isn't allowed. - if (!cv_allowteamchange.value) - return false; - - // Team changing in Team Match and CTF - // Pressing fire assigns you to a team that needs players if allowed. - // Partial code reproduction from p_tick.c autobalance code. - // a surprise tool that will help us later... - if (G_GametypeHasTeams() && player->ctfteam == 0) - { - INT32 z, numplayersred = 0, numplayersblue = 0; - - //find a team by num players, score, or random if all else fails. - for (z = 0; z < MAXPLAYERS; ++z) - if (playeringame[z]) - { - if (players[z].ctfteam == 1) - ++numplayersred; - else if (players[z].ctfteam == 2) - ++numplayersblue; - } - // for z - - if (numplayersblue > numplayersred) - changeto = 1; - else if (numplayersred > numplayersblue) - changeto = 2; - else if (bluescore > redscore) - changeto = 1; - else if (redscore > bluescore) - changeto = 2; - else - changeto = (P_RandomFixed(PR_RULESCRAMBLE) & 1) + 1; - - if (!LUA_HookTeamSwitch(player, changeto, true, false, false)) - return false; - } - // no conditions that could cause the gamejoin to fail below this line - if (player->mo) { P_RemoveMobj(player->mo); @@ -3793,7 +3764,7 @@ boolean P_SpectatorJoinGame(player_t *player) player->spectator = false; player->pflags &= ~PF_WANTSTOJOIN; player->spectatewait = 0; - player->ctfteam = changeto; + player->team = TEAM_UNASSIGNED; // We will auto-assign later. player->playerstate = PST_REBORN; player->enteredGame = true; @@ -3805,12 +3776,7 @@ boolean P_SpectatorJoinGame(player_t *player) } // a surprise tool that will help us later... - if (changeto == 1) - text = va("\x82*%s switched to the %c%s%c team.\n", player_names[player-players], '\x85', "RED", '\x82'); - else if (changeto == 2) - text = va("\x82*%s switched to the %c%s%c team.\n", player_names[player-players], '\x85', "BLU", '\x82'); - else - text = va("\x82*%s entered the game.", player_names[player-players]); + text = va("\x82*%s entered the game.", player_names[player-players]); HU_AddChatText(text, false); return true; // no more player->mo, cannot continue. @@ -4116,6 +4082,9 @@ Quaketilt (player_t *player) static void DoABarrelRoll (player_t *player) { + UINT8 viewnum = R_GetViewNumber(); + camera_t *cam = &camera[viewnum]; + angle_t slope; angle_t delta; @@ -4144,9 +4113,17 @@ DoABarrelRoll (player_t *player) slope = 0; } - if (AbsAngle(slope) > ANGLE_45) + if (cam->chase) { - slope = slope & ANGLE_180 ? InvAngle(ANGLE_45) : ANGLE_45; + if (AbsAngle(slope) > ANGLE_45) + { + slope = slope & ANGLE_180 ? InvAngle(ANGLE_45) : ANGLE_45; + } + } else { + if (AbsAngle(slope) > ANGLE_90) + { + slope = slope & ANGLE_180 ? InvAngle(ANGLE_90) : ANGLE_90; + } } slope -= Quaketilt(player); @@ -4154,7 +4131,7 @@ DoABarrelRoll (player_t *player) delta = slope - player->tilt; smoothing = FixedDiv(AbsAngle(slope), ANGLE_45); - delta = FixedDiv(delta, 33 * + delta = FixedDiv(delta, (cam->chase ? 33 : 11) * FixedDiv(FRACUNIT, FRACUNIT + smoothing)); if (delta) @@ -4271,6 +4248,12 @@ void P_PlayerThink(player_t *player) player->airtime++; } + if ((player->pflags & PF_FAULT) || (player->pflags & PF_VOID)) + { + player->lastairtime = 0; + player->airtime = 0; + } + cmd = &player->cmd; // SRB2kart @@ -4561,15 +4544,15 @@ void P_PlayerThink(player_t *player) if (player->nocontrol && player->nocontrol < UINT16_MAX) { - if (!(--player->nocontrol)) - { - if (player->pflags & PF_FAULT) - { - player->pflags &= ~PF_FAULT; - player->mo->renderflags &= ~RF_DONTDRAW; - player->mo->flags &= ~MF_NOCLIPTHING; - } - } + player->nocontrol--; + } + + // tic down the var normaly and remove the flag upon respawn so its guaranteed to be removed from the player + if (!player->nocontrol && !player->respawn.timer && player->respawn.state == RESPAWNST_DROP && (player->pflags & PF_FAULT)) + { + player->pflags &= ~PF_FAULT; + player->mo->renderflags &= ~RF_DONTDRAW; + player->mo->flags &= ~MF_NOCLIPTHING; } boolean deathcontrolled = (player->respawn.state != RESPAWNST_NONE && player->respawn.truedeath == true) @@ -4891,16 +4874,13 @@ void P_CheckRaceGriefing(player_t *player, boolean dopunishment) else { // Send spectate - changeteam_union NetPacket; - UINT16 usvalue; + UINT8 buf[2]; + UINT8 *p = buf; - NetPacket.value.l = NetPacket.value.b = 0; - NetPacket.packet.newteam = 0; - NetPacket.packet.playernum = n; - NetPacket.packet.verification = true; + WRITEUINT8(p, n); + WRITEUINT8(p, 0); - usvalue = SHORT(NetPacket.value.l|NetPacket.value.b); - SendNetXCmd(XD_TEAMCHANGE, &usvalue, sizeof(usvalue)); + SendNetXCmd(XD_SPECTATE, &buf, p - buf); } } } diff --git a/src/r_bbox.c b/src/r_bbox.c index fe49495a1..eb3a0b4fd 100644 --- a/src/r_bbox.c +++ b/src/r_bbox.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/r_bsp.cpp b/src/r_bsp.cpp index a50410ab9..73474d1f8 100644 --- a/src/r_bsp.cpp +++ b/src/r_bsp.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/r_bsp.h b/src/r_bsp.h index 658f8acdc..6c2848ef0 100644 --- a/src/r_bsp.h +++ b/src/r_bsp.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/r_data.c b/src/r_data.c index e4993ffad..a43c42197 100644 --- a/src/r_data.c +++ b/src/r_data.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -1162,7 +1162,7 @@ void R_PrecacheLevel(void) return; // do not flush the memory, Z_Malloc twice with same user will cause error in Z_CheckHeap() - if (rendermode != render_soft) + if (rendermode == render_opengl) return; // Precache flats. diff --git a/src/r_data.h b/src/r_data.h index fdbca564a..9368f6f9c 100644 --- a/src/r_data.h +++ b/src/r_data.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/r_debug.cpp b/src/r_debug.cpp index f03ec22d1..660f2dc02 100644 --- a/src/r_debug.cpp +++ b/src/r_debug.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/r_debug.hpp b/src/r_debug.hpp index a62ded790..3613dbb76 100644 --- a/src/r_debug.hpp +++ b/src/r_debug.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/r_debug_detail.hpp b/src/r_debug_detail.hpp index e2419e240..b952b65ab 100644 --- a/src/r_debug_detail.hpp +++ b/src/r_debug_detail.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/r_debug_parser.cpp b/src/r_debug_parser.cpp index 538c5f157..5f7154cb5 100644 --- a/src/r_debug_parser.cpp +++ b/src/r_debug_parser.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/r_debug_printer.cpp b/src/r_debug_printer.cpp index c736f32c9..d19569dcf 100644 --- a/src/r_debug_printer.cpp +++ b/src/r_debug_printer.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -9,11 +9,12 @@ //----------------------------------------------------------------------------- #include -#include #include "r_debug.hpp" #include "v_draw.hpp" +#include "core/hash_map.hpp" +#include "core/hash_set.hpp" #include "doomdef.h" #include "doomtype.h" #include "r_textures.h" @@ -24,7 +25,7 @@ using srb2::Draw; namespace { -std::unordered_set frame_list; +srb2::HashSet frame_list; }; // namespace diff --git a/src/r_defs.h b/src/r_defs.h index 5fd517a91..af7e154f3 100644 --- a/src/r_defs.h +++ b/src/r_defs.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -329,9 +329,7 @@ struct pslope_t // Light offsets (see seg_t) SINT8 lightOffset; -#ifdef HWRENDER INT16 hwLightOffset; -#endif }; // Per-sector bot controller override @@ -762,14 +760,12 @@ struct seg_t sector_t *backsector; fixed_t length; // precalculated seg length -#ifdef HWRENDER // new pointers so that AdjustSegs doesn't mess with v1/v2 void *pv1; // polyvertex_t void *pv2; // polyvertex_t float flength; // length of the seg, used by hardware renderer lightmap_t *lightmaps; // for static lightmap -#endif // Why slow things down by calculating lightlists for every thick side? size_t numlights; @@ -780,9 +776,7 @@ struct seg_t // Fake contrast calculated on level load SINT8 lightOffset; -#ifdef HWRENDER INT16 hwLightOffset; -#endif }; // diff --git a/src/r_draw.cpp b/src/r_draw.cpp index 698bf7fad..75712eca1 100644 --- a/src/r_draw.cpp +++ b/src/r_draw.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -524,7 +524,7 @@ void R_FillBackScreen(void) INT32 x, y, step, boff; // quickfix, don't cache lumps in both modes - if (rendermode != render_soft) + if (rendermode == render_opengl) return; // draw pattern around the status bar too (when hires), @@ -620,7 +620,7 @@ void R_DrawViewBorder(void) if (rendermode == render_none) return; #ifdef HWRENDER - if (rendermode != render_soft) + if (rendermode == render_opengl) { HWR_DrawViewBorder(0); return; diff --git a/src/r_draw.h b/src/r_draw.h index e7c75b352..b3b71e176 100644 --- a/src/r_draw.h +++ b/src/r_draw.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index 31f1501a8..c867aa6ff 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/r_draw_span.cpp b/src/r_draw_span.cpp index 9d0d0fcae..33c4c5273 100644 --- a/src/r_draw_span.cpp +++ b/src/r_draw_span.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/r_fps.c b/src/r_fps.cpp similarity index 86% rename from src/r_fps.c rename to src/r_fps.cpp index 06016e2e3..08750043b 100644 --- a/src/r_fps.c +++ b/src/r_fps.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze, Andrey Budko (prboom) // Copyright (C) 2000 by DooM Legacy Team. @@ -15,14 +15,15 @@ #include "r_fps.h" +#include + +#include "p_mobj.h" #include "r_main.h" #include "g_game.h" #include "i_video.h" #include "r_plane.h" -#include "p_spec.h" #include "r_state.h" #include "z_zone.h" -#include "console.h" // con_startup_loadprogress #include "i_time.h" UINT32 R_GetFramerateCap(void) @@ -67,10 +68,7 @@ viewvars_t *newview = &pview_new[0]; enum viewcontext_e viewcontext = VIEWCONTEXT_PLAYER1; -static levelinterpolator_t **levelinterpolators; -static size_t levelinterpolators_len; -static size_t levelinterpolators_size; - +static std::vector levelinterpolators; static fixed_t R_LerpFixed(fixed_t from, fixed_t to, fixed_t frac) { @@ -324,42 +322,9 @@ void R_InterpolatePrecipMobjState(precipmobj_t *mobj, fixed_t frac, interpmobjst out->angle = R_LerpAngle(mobj->old_angle, mobj->angle, frac); } -static void AddInterpolator(levelinterpolator_t* interpolator) -{ - if (levelinterpolators_len >= levelinterpolators_size) - { - if (levelinterpolators_size == 0) - { - levelinterpolators_size = 128; - } - else - { - levelinterpolators_size *= 2; - } - - levelinterpolators = Z_Realloc( - (void*) levelinterpolators, - sizeof(levelinterpolator_t*) * levelinterpolators_size, - PU_LEVEL, - NULL - ); - } - - levelinterpolators[levelinterpolators_len] = interpolator; - levelinterpolators_len += 1; -} - static levelinterpolator_t *CreateInterpolator(levelinterpolator_type_e type, thinker_t *thinker) { - levelinterpolator_t *ret = (levelinterpolator_t*) Z_Calloc( - sizeof(levelinterpolator_t), PU_LEVEL, NULL - ); - - ret->type = type; - ret->thinker = thinker; - - AddInterpolator(ret); - + auto* ret = &levelinterpolators.emplace_back(levelinterpolator_t { type, thinker }); return ret; } @@ -409,8 +374,8 @@ void R_CreateInterpolator_Polyobj(thinker_t *thinker, polyobj_t *polyobj) interp->polyobj.polyobj = polyobj; interp->polyobj.vertices_size = polyobj->numVertices; - interp->polyobj.oldvertices = Z_Calloc(sizeof(fixed_t) * 2 * polyobj->numVertices, PU_LEVEL, NULL); - interp->polyobj.bakvertices = Z_Calloc(sizeof(fixed_t) * 2 * polyobj->numVertices, PU_LEVEL, NULL); + interp->polyobj.oldvertices = static_cast(Z_Calloc(sizeof(fixed_t) * 2 * polyobj->numVertices, PU_LEVEL, NULL)); + interp->polyobj.bakvertices = static_cast(Z_Calloc(sizeof(fixed_t) * 2 * polyobj->numVertices, PU_LEVEL, NULL)); for (size_t i = 0; i < polyobj->numVertices; i++) { interp->polyobj.oldvertices[i * 2 ] = interp->polyobj.bakvertices[i * 2 ] = polyobj->vertices[i]->x; @@ -437,9 +402,7 @@ void R_CreateInterpolator_DynSlope(thinker_t *thinker, pslope_t *slope) void R_InitializeLevelInterpolators(void) { - levelinterpolators_len = 0; - levelinterpolators_size = 0; - levelinterpolators = NULL; + levelinterpolators.clear(); } static void UpdateLevelInterpolatorState(levelinterpolator_t *interp) @@ -491,40 +454,32 @@ static void UpdateLevelInterpolatorState(levelinterpolator_t *interp) void R_UpdateLevelInterpolators(void) { - size_t i; - - for (i = 0; i < levelinterpolators_len; i++) + for (levelinterpolator_t& interp : levelinterpolators) { - levelinterpolator_t *interp = levelinterpolators[i]; - - UpdateLevelInterpolatorState(interp); + UpdateLevelInterpolatorState(&interp); } } void R_ClearLevelInterpolatorState(thinker_t *thinker) { - size_t i; - - for (i = 0; i < levelinterpolators_len; i++) + for (levelinterpolator_t& interp : levelinterpolators) { - levelinterpolator_t *interp = levelinterpolators[i]; - - if (interp->thinker == thinker) + if (interp.thinker == thinker) { // Do it twice to make the old state match the new - UpdateLevelInterpolatorState(interp); - UpdateLevelInterpolatorState(interp); + UpdateLevelInterpolatorState(&interp); + UpdateLevelInterpolatorState(&interp); } } } void R_ApplyLevelInterpolators(fixed_t frac) { - size_t i, ii; + size_t ii; - for (i = 0; i < levelinterpolators_len; i++) + for (levelinterpolator_t& i : levelinterpolators) { - levelinterpolator_t *interp = levelinterpolators[i]; + levelinterpolator_t* interp = &i; switch (interp->type) { @@ -575,11 +530,11 @@ void R_ApplyLevelInterpolators(fixed_t frac) void R_RestoreLevelInterpolators(void) { - size_t i, ii; + size_t ii; - for (i = 0; i < levelinterpolators_len; i++) + for (levelinterpolator_t& i : levelinterpolators) { - levelinterpolator_t *interp = levelinterpolators[i]; + levelinterpolator_t* interp = &i; switch (interp->type) { @@ -632,51 +587,27 @@ void R_DestroyLevelInterpolators(thinker_t *thinker) { size_t i; - for (i = 0; i < levelinterpolators_len; i++) + for (i = 0; i < levelinterpolators.size(); i++) { - levelinterpolator_t *interp = levelinterpolators[i]; + levelinterpolator_t* interp = &levelinterpolators[i]; if (interp->thinker == thinker) { // Swap the tail of the level interpolators to this spot - levelinterpolators[i] = levelinterpolators[levelinterpolators_len - 1]; - levelinterpolators_len -= 1; + levelinterpolators[i] = *levelinterpolators.rbegin(); - Z_Free(interp); - i -= 1; + levelinterpolators.pop_back(); } } } -static mobj_t **interpolated_mobjs = NULL; -static size_t interpolated_mobjs_len = 0; -static size_t interpolated_mobjs_capacity = 0; +static std::vector interpolated_mobjs; // NOTE: This will NOT check that the mobj has already been added, for perf // reasons. void R_AddMobjInterpolator(mobj_t *mobj) { - if (interpolated_mobjs_len >= interpolated_mobjs_capacity) - { - if (interpolated_mobjs_capacity == 0) - { - interpolated_mobjs_capacity = 256; - } - else - { - interpolated_mobjs_capacity *= 2; - } - - interpolated_mobjs = Z_Realloc( - interpolated_mobjs, - sizeof(mobj_t *) * interpolated_mobjs_capacity, - PU_LEVEL, - NULL - ); - } - - interpolated_mobjs[interpolated_mobjs_len] = mobj; - interpolated_mobjs_len += 1; + interpolated_mobjs.push_back(mobj); R_ResetMobjInterpolationState(mobj); mobj->resetinterp = true; @@ -684,18 +615,12 @@ void R_AddMobjInterpolator(mobj_t *mobj) void R_RemoveMobjInterpolator(mobj_t *mobj) { - size_t i; - - if (interpolated_mobjs_len == 0) return; - - for (i = 0; i < interpolated_mobjs_len; i++) + for (size_t i = 0; i < interpolated_mobjs.size(); i++) { if (interpolated_mobjs[i] == mobj) { - interpolated_mobjs[i] = interpolated_mobjs[ - interpolated_mobjs_len - 1 - ]; - interpolated_mobjs_len -= 1; + interpolated_mobjs[i] = *interpolated_mobjs.rbegin(); + interpolated_mobjs.pop_back(); return; } } @@ -703,19 +628,13 @@ void R_RemoveMobjInterpolator(mobj_t *mobj) void R_InitMobjInterpolators(void) { - // apparently it's not acceptable to free something already unallocated - // Z_Free(interpolated_mobjs); - interpolated_mobjs = NULL; - interpolated_mobjs_len = 0; - interpolated_mobjs_capacity = 0; + interpolated_mobjs.clear(); } void R_UpdateMobjInterpolators(void) { - size_t i; - for (i = 0; i < interpolated_mobjs_len; i++) + for (mobj_t* mobj : interpolated_mobjs) { - mobj_t *mobj = interpolated_mobjs[i]; if (!P_MobjWasRemoved(mobj)) R_ResetMobjInterpolationState(mobj); } diff --git a/src/r_fps.h b/src/r_fps.h index fff0a35f2..ddd474c07 100644 --- a/src/r_fps.h +++ b/src/r_fps.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze, Andrey Budko (prboom) // Copyright (C) 2000 by DooM Legacy Team. diff --git a/src/r_local.h b/src/r_local.h index a88ff4340..7bedf0484 100644 --- a/src/r_local.h +++ b/src/r_local.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/r_main.cpp b/src/r_main.cpp index d129a235b..639f74437 100644 --- a/src/r_main.cpp +++ b/src/r_main.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -308,8 +308,18 @@ angle_t R_PointToAnglePlayer(player_t *player, fixed_t x, fixed_t y) { refx = cam->x; refy = cam->y; - } + // Bandaid for two very specific bugs that arise with chasecam off. + // 1: Camera tilt from slopes wouldn't apply correctly in first person. + // 2: Trick pies would appear strangely in first person. + if (player->mo) + { + if ((!cam->chase) && player->mo->x == x && player->mo->y == y) + { + return player->mo->angle; + } + } + } return R_PointToAngle2(refx, refy, x, y); } @@ -1090,7 +1100,7 @@ void R_ExecuteSetViewSize(void) // continue to do the software setviewsize as long as we use the reference software view #ifdef HWRENDER - if (rendermode != render_soft) + if (rendermode == render_opengl) HWR_SetViewSize(); #endif @@ -1431,7 +1441,13 @@ boolean R_ViewpointHasChasecam(player_t *player) boolean R_IsViewpointThirdPerson(player_t *player, boolean skybox) { - boolean chasecam = R_ViewpointHasChasecam(player); + boolean chasecam = false; + + // Prevent game crash if player is ever invalid. + if (!player) + return false; + + chasecam = R_ViewpointHasChasecam(player); // cut-away view stuff if (player->awayview.tics || skybox) diff --git a/src/r_main.h b/src/r_main.h index e174f1b4b..79005f7cc 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -137,6 +137,8 @@ extern consvar_t cv_skybox; extern consvar_t cv_drawpickups; extern consvar_t cv_debugfinishline; extern consvar_t cv_drawinput; +extern consvar_t cv_drawtimer; +extern consvar_t cv_debugfonts; // debugging diff --git a/src/r_patch.cpp b/src/r_patch.cpp index c1cbe37db..83333275e 100644 --- a/src/r_patch.cpp +++ b/src/r_patch.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Jaime "Lactozilla" Passos. // Copyright (C) 2020 by Sonic Team Junior. // diff --git a/src/r_patch.h b/src/r_patch.h index 53de078c8..2c5fd6596 100644 --- a/src/r_patch.h +++ b/src/r_patch.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Jaime "Lactozilla" Passos. // Copyright (C) 2020 by Sonic Team Junior. // diff --git a/src/r_patchrotation.c b/src/r_patchrotation.c index 98a8db190..215246347 100644 --- a/src/r_patchrotation.c +++ b/src/r_patchrotation.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Jaime "Lactozilla" Passos. // Copyright (C) 2020 by Sonic Team Junior. // diff --git a/src/r_patchrotation.h b/src/r_patchrotation.h index 56887b25e..030c7a27e 100644 --- a/src/r_patchrotation.h +++ b/src/r_patchrotation.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Jaime "Lactozilla" Passos. // Copyright (C) 2020 by Sonic Team Junior. // diff --git a/src/r_picformats.c b/src/r_picformats.c index 7eed58392..d168dda19 100644 --- a/src/r_picformats.c +++ b/src/r_picformats.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Jaime "Lactozilla" Passos. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2009 by Andrey "entryway" Budko. @@ -52,8 +52,6 @@ #endif #endif -static unsigned char imgbuf[1<<26]; - #ifdef PICTURE_PNG_USELOOKUP static colorlookup_t png_colorlookup; #endif @@ -119,6 +117,7 @@ void *Picture_PatchConvert( { INT16 x, y; UINT8 *img; + UINT8 *imgbuf = Z_Malloc(1<<26, PU_STATIC, NULL); UINT8 *imgptr = imgbuf; UINT8 *colpointers, *startofspan; size_t size = 0; @@ -352,6 +351,7 @@ void *Picture_PatchConvert( size = imgptr-imgbuf; img = Z_Malloc(size, PU_STATIC, NULL); memcpy(img, imgbuf, size); + Z_Free(imgbuf); if (Picture_IsInternalPatchFormat(outformat)) { diff --git a/src/r_picformats.h b/src/r_picformats.h index dc1a67ef6..85c82d3d0 100644 --- a/src/r_picformats.h +++ b/src/r_picformats.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Jaime "Lactozilla" Passos. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. diff --git a/src/r_plane.cpp b/src/r_plane.cpp index 56a2fcfaa..36f9b6b03 100644 --- a/src/r_plane.cpp +++ b/src/r_plane.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -14,6 +14,8 @@ /// while maintaining a per column clipping list only. /// Moreover, the sky areas have to be determined. +#include + #include #include "command.h" diff --git a/src/r_plane.h b/src/r_plane.h index 301d3e6f8..22f0a609e 100644 --- a/src/r_plane.h +++ b/src/r_plane.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/r_portal.c b/src/r_portal.c index 2944d8475..3e893054d 100644 --- a/src/r_portal.c +++ b/src/r_portal.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/r_portal.h b/src/r_portal.h index d51e39383..b8107f2c7 100644 --- a/src/r_portal.h +++ b/src/r_portal.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/r_segs.cpp b/src/r_segs.cpp index 65a68c7b3..0867960f5 100644 --- a/src/r_segs.cpp +++ b/src/r_segs.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/r_segs.h b/src/r_segs.h index 070ebbd0c..887274243 100644 --- a/src/r_segs.h +++ b/src/r_segs.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/r_skins.c b/src/r_skins.c index fb184172f..8ddf839d8 100644 --- a/src/r_skins.c +++ b/src/r_skins.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -379,6 +379,9 @@ engineclass_t R_GetEngineClass(SINT8 speed, SINT8 weight, skinflags_t flags) if (flags & SF_IRONMAN) return ENGINECLASS_J; + if (flags & SF_HIVOLT) + return ENGINECLASS_R; + speed = (speed - 1) / 3; weight = (weight - 1) / 3; @@ -408,22 +411,6 @@ static void SetSkin(player_t *player, INT32 skinnum) player->kartweight = skin->kartweight; player->charflags = skin->flags; -#if 0 - if (!CV_CheatsEnabled() && !(netgame || multiplayer || demo.playback)) - { - for (i = 0; i <= r_splitscreen; i++) - { - if (playernum == g_localplayers[i]) - { - CV_StealthSetValue(&cv_playercolor[i], skin->prefcolor); - } - } - - player->skincolor = skin->prefcolor; - K_KartResetPlayerColor(player); - } -#endif - if (player->followmobj) { P_RemoveMobj(player->followmobj); @@ -890,6 +877,7 @@ static boolean R_ProcessPatchableFields(skin_t *skin, char *stoken, char *value) GETFLAG(MACHINE) GETFLAG(IRONMAN) GETFLAG(BADNIK) + GETFLAG(HIVOLT) #undef GETFLAG else // let's check if it's a sound, otherwise error out diff --git a/src/r_skins.h b/src/r_skins.h index 6e1d2a455..2f32c9fbe 100644 --- a/src/r_skins.h +++ b/src/r_skins.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -92,7 +92,8 @@ typedef enum { ENGINECLASS_H, ENGINECLASS_I, - ENGINECLASS_J + ENGINECLASS_J, + ENGINECLASS_R = 17, } engineclass_t; diff --git a/src/r_sky.c b/src/r_sky.c index b098faa37..d66d0e3e5 100644 --- a/src/r_sky.c +++ b/src/r_sky.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/r_sky.h b/src/r_sky.h index 0114975da..ae33a9c02 100644 --- a/src/r_sky.h +++ b/src/r_sky.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/r_splats.c b/src/r_splats.c index d89057764..e2d39fe99 100644 --- a/src/r_splats.c +++ b/src/r_splats.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2021 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // @@ -53,7 +53,7 @@ static void rasterize_segment_tex(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 if (dir == 0) { - for (;;) + for (; count > 0; count--) { rastertab[y1].maxx = xs; rastertab[y1].tx2 = xe; @@ -62,13 +62,11 @@ static void rasterize_segment_tex(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 xs += dx0; xe += dx1; y1++; - - if (count-- < 1) break; } } else { - for (;;) + for (; count > 0; count--) { rastertab[y1].maxx = xs; rastertab[y1].tx2 = tc; @@ -77,8 +75,6 @@ static void rasterize_segment_tex(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 xs += dx0; xe += dx1; y1++; - - if (count-- < 1) break; } } } @@ -95,7 +91,7 @@ static void rasterize_segment_tex(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 if (dir == 0) { - for (;;) + for (; count > 0; count--) { rastertab[y2].minx = xs; rastertab[y2].tx1 = xe; @@ -104,13 +100,11 @@ static void rasterize_segment_tex(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 xs += dx0; xe += dx1; y2++; - - if (count-- < 1) break; } } else { - for (;;) + for (; count > 0; count--) { rastertab[y2].minx = xs; rastertab[y2].tx1 = tc; @@ -119,8 +113,6 @@ static void rasterize_segment_tex(INT32 x1, INT32 y1, INT32 x2, INT32 y2, INT32 xs += dx0; xe += dx1; y2++; - - if (count-- < 1) break; } } } diff --git a/src/r_splats.h b/src/r_splats.h index 324fd1d11..4f0a336d1 100644 --- a/src/r_splats.h +++ b/src/r_splats.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/r_spritefx.cpp b/src/r_spritefx.cpp index b5a2453dd..03b2db6d6 100644 --- a/src/r_spritefx.cpp +++ b/src/r_spritefx.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/r_state.h b/src/r_state.h index 5b9303f2d..111c3f95a 100644 --- a/src/r_state.h +++ b/src/r_state.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -64,8 +64,9 @@ extern size_t numspritelumps, max_spritelumps; // // Lookup tables for map data. // -#define UDMF_CURRENT_VERSION (1) +#define UDMF_CURRENT_VERSION (2) extern boolean udmf; +extern INT32 udmf_version; extern size_t numsprites; extern spritedef_t *sprites; diff --git a/src/r_textures.c b/src/r_textures.cpp similarity index 96% rename from src/r_textures.c rename to src/r_textures.cpp index 919036e1e..f582f50d4 100644 --- a/src/r_textures.c +++ b/src/r_textures.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2021 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -9,9 +9,13 @@ // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -/// \file r_textures.c +/// \file r_textures.cpp /// \brief Texture generation. +#include "r_textures.h" + +#include + #include "doomdef.h" #include "g_game.h" #include "i_video.h" @@ -20,7 +24,6 @@ #include "p_local.h" #include "m_misc.h" #include "r_data.h" -#include "r_textures.h" #include "r_patch.h" #include "r_picformats.h" #include "w_wad.h" @@ -256,7 +259,7 @@ static UINT8 *R_AllocateTextureBlock(size_t blocksize, UINT8 **user) { texturememory += blocksize; - return Z_Malloc(blocksize, PU_LEVEL, user); + return (UINT8*)Z_Malloc(blocksize, PU_LEVEL, user); } static UINT8 *R_AllocateDummyTextureBlock(size_t width, UINT8 **user) @@ -352,7 +355,7 @@ UINT8 *R_GenerateTexture(size_t texnum) return block; } - pdata = W_CacheLumpNumPwad(wadnum, lumpnum, PU_LEVEL); + pdata = (UINT8*)W_CacheLumpNumPwad(wadnum, lumpnum, PU_LEVEL); realpatch = (softwarepatch_t *)pdata; #ifndef NO_PNG_LUMPS @@ -393,7 +396,7 @@ UINT8 *R_GenerateTexture(size_t texnum) texture->holes = true; texture->flip = patch->flip; blocksize = lumplength; - block = Z_Calloc(blocksize, PU_LEVEL, // will change tag at end of this function + block = (UINT8*)Z_Calloc(blocksize, PU_LEVEL, // will change tag at end of this function &texturecache[texnum]); M_Memcpy(block, realpatch, blocksize); texturememory += blocksize; @@ -424,7 +427,7 @@ UINT8 *R_GenerateTexture(size_t texnum) texture->flip = 0; blocksize = (texture->width * 4) + (texture->width * texture->height); texturememory += blocksize; - block = Z_Malloc(blocksize+1, PU_LEVEL, &texturecache[texnum]); + block = (UINT8*)Z_Malloc(blocksize+1, PU_LEVEL, &texturecache[texnum]); memset(block, TRANSPARENTPIXEL, blocksize+1); // Transparency hack @@ -447,19 +450,19 @@ UINT8 *R_GenerateTexture(size_t texnum) wadnum = patch->wad; lumpnum = patch->lump; - pdata = W_CacheLumpNumPwad(wadnum, lumpnum, PU_LEVEL); + pdata = (UINT8*)W_CacheLumpNumPwad(wadnum, lumpnum, PU_LEVEL); lumplength = W_LumpLengthPwad(wadnum, lumpnum); realpatch = (softwarepatch_t *)pdata; dealloc = true; #ifndef NO_PNG_LUMPS if (Picture_IsLumpPNG((UINT8 *)realpatch, lumplength)) - realpatch = (softwarepatch_t *)Picture_PNGConvert((UINT8 *)realpatch, PICFMT_DOOMPATCH, NULL, NULL, NULL, NULL, lumplength, NULL, 0); + realpatch = (softwarepatch_t *)Picture_PNGConvert((UINT8 *)realpatch, PICFMT_DOOMPATCH, NULL, NULL, NULL, NULL, lumplength, NULL, (pictureflags_t)0); else #endif #ifdef WALLFLATS if (texture->type == TEXTURETYPE_FLAT) - realpatch = (softwarepatch_t *)Picture_Convert(PICFMT_FLAT, pdata, PICFMT_DOOMPATCH, 0, NULL, texture->width, texture->height, 0, 0, 0); + realpatch = (softwarepatch_t *)Picture_Convert(PICFMT_FLAT, pdata, PICFMT_DOOMPATCH, 0, NULL, texture->width, texture->height, 0, 0, (pictureflags_t)0); else #endif { @@ -542,7 +545,7 @@ UINT8 *R_GenerateTextureAsFlat(size_t texnum) Z_Free(converted); } - return texture->flat; + return (UINT8*)texture->flat; } // This function writes a column to p, using the posts from @@ -595,7 +598,7 @@ static void R_ConvertBrightmapColumn(UINT8 *p, const column_t *tcol, const colum // texture column, so pad it with black // pixels. - n = max(0, min(btop, tbot) - y); + n = std::max(0, std::min(btop, tbot) - y); memset(&p[y - ttop], TRANSPARENTPIXEL, n); y += n; @@ -607,7 +610,7 @@ static void R_ConvertBrightmapColumn(UINT8 *p, const column_t *tcol, const colum // Copy parts of the brightmap column which // line up with the texture column. - n = max(0, min(bbot, tbot) - y); + n = std::max(0, std::min(bbot, tbot) - y); memcpy(&p[y - ttop], (const UINT8*)bcol + 3 + (y - btop), n); y += n; @@ -752,7 +755,7 @@ UINT8 *R_GenerateTextureBrightmap(size_t texnum) INT32 wad = bright->patches[0].wad; INT32 lump = bright->patches[0].lump; - bmap = W_CacheLumpNumPwad(wad, lump, PU_STATIC); + bmap = (softwarepatch_t*)W_CacheLumpNumPwad(wad, lump, PU_STATIC); R_InitRawCheckColumn(&rchk, bmap, W_LumpLengthPwad(wad, lump), bright->name); } else @@ -914,7 +917,7 @@ void *R_GetLevelFlat(drawspandata_t* ds, levelflat_t *levelflat) { if (texture->flat) { - flatdata = texture->flat; + flatdata = (UINT8*)texture->flat; ds->flatwidth = texture->width; ds->flatheight = texture->height; texturechanged = false; @@ -940,7 +943,7 @@ void *R_GetLevelFlat(drawspandata_t* ds, levelflat_t *levelflat) { INT32 pngwidth, pngheight; - levelflat->picture = Picture_PNGConvert(W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_LEVEL), PICFMT_FLAT, &pngwidth, &pngheight, NULL, NULL, W_LumpLength(levelflat->u.flat.lumpnum), NULL, 0); + levelflat->picture = (UINT8*)Picture_PNGConvert((UINT8*)W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_LEVEL), PICFMT_FLAT, &pngwidth, &pngheight, NULL, NULL, W_LumpLength(levelflat->u.flat.lumpnum), NULL, (pictureflags_t)0); levelflat->width = (UINT16)pngwidth; levelflat->height = (UINT16)pngheight; @@ -953,13 +956,13 @@ void *R_GetLevelFlat(drawspandata_t* ds, levelflat_t *levelflat) { UINT8 *converted; size_t size; - softwarepatch_t *patch = W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_LEVEL); + softwarepatch_t *patch = (softwarepatch_t*)W_CacheLumpNum(levelflat->u.flat.lumpnum, PU_LEVEL); levelflat->width = ds->flatwidth = SHORT(patch->width); levelflat->height = ds->flatheight = SHORT(patch->height); - levelflat->picture = Z_Malloc(levelflat->width * levelflat->height, PU_LEVEL, NULL); - converted = Picture_FlatConvert(PICFMT_DOOMPATCH, patch, PICFMT_FLAT, 0, &size, levelflat->width, levelflat->height, SHORT(patch->topoffset), SHORT(patch->leftoffset), 0); + levelflat->picture = (UINT8*)Z_Malloc(levelflat->width * levelflat->height, PU_LEVEL, NULL); + converted = (UINT8*)Picture_FlatConvert(PICFMT_DOOMPATCH, patch, PICFMT_FLAT, 0, &size, levelflat->width, levelflat->height, SHORT(patch->topoffset), SHORT(patch->leftoffset), (pictureflags_t)0); M_Memcpy(levelflat->picture, converted, size); Z_Free(converted); } @@ -1173,7 +1176,7 @@ Rloadflats (INT32 i, INT32 w) flatsize = R_FlatDimensionsFromLumpSize(lumplength); //CONS_Printf("\n\"%s\" is a flat, dimensions %d x %d",W_CheckNameForNumPwad((UINT16)w,texstart+j),flatsize,flatsize); - texture = textures[i] = Z_Calloc(sizeof(texture_t) + sizeof(texpatch_t), PU_STATIC, NULL); + texture = textures[i] = (texture_t*)Z_Calloc(sizeof(texture_t) + sizeof(texpatch_t), PU_STATIC, NULL); // Set texture properties. M_Memcpy(texture->name, W_CheckNameForNumPwad(wadnum, lumpnum), sizeof(texture->name)); @@ -1182,7 +1185,7 @@ Rloadflats (INT32 i, INT32 w) #ifndef NO_PNG_LUMPS if (Picture_IsLumpPNG(header, lumplength)) { - UINT8 *flatlump = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE); + UINT8 *flatlump = (UINT8*)W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE); INT32 width, height; Picture_PNGDimensions((UINT8 *)flatlump, &width, &height, NULL, NULL, lumplength); texture->width = (INT16)width; @@ -1276,7 +1279,7 @@ Rloadtextures (INT32 i, INT32 w) #ifndef NO_PNG_LUMPS if (Picture_IsLumpPNG((UINT8 *)&patchlump, lumplength)) { - UINT8 *png = W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE); + UINT8 *png = (UINT8*)W_CacheLumpNumPwad(wadnum, lumpnum, PU_CACHE); Picture_PNGDimensions(png, &width, &height, NULL, NULL, lumplength); width = (INT16)width; height = (INT16)height; @@ -1322,7 +1325,7 @@ Rloadtextures (INT32 i, INT32 w) } //CONS_Printf("\n\"%s\" is a single patch, dimensions %d x %d",W_CheckNameForNumPwad((UINT16)w,texstart+j),patchlump->width, patchlump->height); - texture = textures[i] = Z_Calloc(sizeof(texture_t) + sizeof(texpatch_t), PU_STATIC, NULL); + texture = textures[i] = (texture_t*)Z_Calloc(sizeof(texture_t) + sizeof(texpatch_t), PU_STATIC, NULL); // Set texture properties. M_Memcpy(texture->name, W_CheckNameForNumPwad(wadnum, lumpnum), sizeof(texture->name)); @@ -1433,13 +1436,13 @@ static void recallocuser ( void * user, size_t old, - size_t new) + size_t newsize) { - char *p = Z_Realloc(*(void**)user, - new, PU_STATIC, user); + char *p = (char*)Z_Realloc(*(void**)user, + newsize, PU_STATIC, user); - if (new > old) - memset(&p[old], 0, (new - old)); + if (newsize > old) + memset(&p[old], 0, (newsize - old)); } static void R_AllocateTextures(INT32 add) @@ -1895,7 +1898,7 @@ static texture_t *R_ParseTexture(boolean actuallyLoadTexture) // Get that new patch newPatch = R_ParsePatch(true); // Make room for the new patch - resultTexture = Z_Realloc(resultTexture, sizeof(texture_t) + (resultTexture->patchcount+1)*sizeof(texpatch_t), PU_STATIC, NULL); + resultTexture = (texture_t*)Z_Realloc(resultTexture, sizeof(texture_t) + (resultTexture->patchcount+1)*sizeof(texpatch_t), PU_STATIC, NULL); // Populate the uninitialized values in the new patch entry of our array M_Memcpy(&resultTexture->patches[resultTexture->patchcount], newPatch, sizeof(texpatch_t)); // Account for the new number of patches in the texture diff --git a/src/r_textures.h b/src/r_textures.h index 88ef22a8a..4f0447989 100644 --- a/src/r_textures.h +++ b/src/r_textures.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/r_textures_dups.cpp b/src/r_textures_dups.cpp index 0ecf71dd8..eed8b2872 100644 --- a/src/r_textures_dups.cpp +++ b/src/r_textures_dups.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -13,14 +13,14 @@ #include #include #include -#include #include #include -#include -#include #include +#include "core/hash_map.hpp" +#include "core/string.h" +#include "core/vector.hpp" #include "cxxutil.hpp" #include "doomstat.h" #include "r_textures.h" @@ -29,14 +29,14 @@ namespace { -std::unordered_map> g_dups; -std::map> g_warnings; +srb2::HashMap> g_dups; +std::map> g_warnings; std::thread g_dups_thread; -std::string key8char(const char cstr[8]) +srb2::String key8char(const char cstr[8]) { std::string_view view(cstr, 8); - std::string key; + srb2::String key; view = view.substr(0, view.find('\0')); // terminate by '\0' key.reserve(view.size()); @@ -50,7 +50,7 @@ std::string key8char(const char cstr[8]) return key; } -std::string texture_location(const texture_t& tex) +srb2::String texture_location(const texture_t& tex) { if (tex.type == TEXTURETYPE_SINGLEPATCH) { @@ -59,7 +59,7 @@ std::string texture_location(const texture_t& tex) const texpatch_t& texpat = tex.patches[0]; const wadfile_t& wad = *wadfiles[texpat.wad]; - return fmt::format( + return srb2::format( "'{}/{}'", std::filesystem::path(wad.filename).filename().string(), wad.lumpinfo[texpat.lump].fullname @@ -108,7 +108,7 @@ void R_CheckTextureDuplicates(INT32 start, INT32 end) auto collate_dups = [end, find_dup](int32_t start) { const texture_t* t1 = textures[start]; - const std::string key = key8char(t1->name); + const srb2::String key = key8char(t1->name); if (g_dups.find(key) != g_dups.end()) { @@ -119,7 +119,7 @@ void R_CheckTextureDuplicates(INT32 start, INT32 end) if (idx < end) { - std::vector& v = g_dups[key]; + srb2::Vector& v = g_dups[key]; v.push_back(textures[start]); @@ -175,7 +175,7 @@ void R_PrintTextureWarnings(void) { CONS_Alert(CONS_WARNING, "\n%s", header.c_str()); - for (const std::string& warning : v) + for (const srb2::String& warning : v) { CONS_Printf("%s\n", warning.c_str()); } diff --git a/src/r_things.cpp b/src/r_things.cpp index 918db5f02..6fca80e5f 100644 --- a/src/r_things.cpp +++ b/src/r_things.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -2784,7 +2784,7 @@ void R_AddSprites(sector_t *sec, INT32 lightlevel) INT32 lightnum; fixed_t limit_dist; - if (rendermode != render_soft) + if (rendermode == render_opengl) return; // BSP is traversed by subsector. diff --git a/src/r_things.h b/src/r_things.h index e2bdbfb33..b8e67b7e6 100644 --- a/src/r_things.h +++ b/src/r_things.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/rhi/gl2/gl2_rhi.cpp b/src/rhi/gl2/gl2_rhi.cpp index 340d24dbb..cf35eebb5 100644 --- a/src/rhi/gl2/gl2_rhi.cpp +++ b/src/rhi/gl2/gl2_rhi.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,13 +14,13 @@ #include #include #include -#include #include #include #include #include +#include "../../core/vector.hpp" #include "../shader_load_context.hpp" using namespace srb2; @@ -573,6 +573,31 @@ constexpr GLenum map_uniform_format(rhi::UniformFormat format) } } +constexpr GLenum map_stencil_op(rhi::StencilOp op) +{ + switch (op) + { + case rhi::StencilOp::kKeep: + return GL_KEEP; + case rhi::StencilOp::kZero: + return GL_ZERO; + case rhi::StencilOp::kReplace: + return GL_REPLACE; + case rhi::StencilOp::kIncrementClamp: + return GL_INCR; + case rhi::StencilOp::kIncrementWrap: + return GL_INCR_WRAP; + case rhi::StencilOp::kDecrementClamp: + return GL_DECR; + case rhi::StencilOp::kDecrementWrap: + return GL_DECR_WRAP; + case rhi::StencilOp::kInvert: + return GL_INVERT; + default: + return GL_ZERO; + } +} + } // namespace Gl2Platform::~Gl2Platform() = default; @@ -585,19 +610,6 @@ Gl2Rhi::Gl2Rhi(std::unique_ptr&& platform, GlLoadFunc load_func) : Gl2Rhi::~Gl2Rhi() = default; -rhi::Handle Gl2Rhi::create_render_pass(const rhi::RenderPassDesc& desc) -{ - // GL has no formal render pass object - Gl2RenderPass pass; - pass.desc = desc; - return render_pass_slab_.insert(std::move(pass)); -} - -void Gl2Rhi::destroy_render_pass(rhi::Handle handle) -{ - render_pass_slab_.remove(handle); -} - rhi::Handle Gl2Rhi::create_texture(const rhi::TextureDesc& desc) { GLenum internal_format = map_internal_texture_format(desc.format); @@ -636,15 +648,12 @@ void Gl2Rhi::destroy_texture(rhi::Handle handle) } void Gl2Rhi::update_texture( - Handle ctx, Handle texture, Rect region, srb2::rhi::PixelFormat data_format, tcb::span data ) { - SRB2_ASSERT(graphics_context_active_ == true); - if (data.empty()) { return; @@ -687,7 +696,6 @@ void Gl2Rhi::update_texture( } void Gl2Rhi::update_texture_settings( - Handle ctx, Handle texture, TextureWrapMode u_wrap, TextureWrapMode v_wrap, @@ -695,8 +703,6 @@ void Gl2Rhi::update_texture_settings( TextureFilterMode mag ) { - SRB2_ASSERT(graphics_context_active_ == true); - SRB2_ASSERT(texture_slab_.is_valid(texture) == true); auto& t = texture_slab_[texture]; @@ -749,15 +755,11 @@ void Gl2Rhi::destroy_buffer(rhi::Handle handle) } void Gl2Rhi::update_buffer( - rhi::Handle ctx, rhi::Handle handle, uint32_t offset, tcb::span data ) { - SRB2_ASSERT(graphics_context_active_ == true); - SRB2_ASSERT(ctx.generation() == graphics_context_generation_); - if (data.empty()) { return; @@ -785,58 +787,6 @@ void Gl2Rhi::update_buffer( GL_ASSERT; } -rhi::Handle -Gl2Rhi::create_uniform_set(rhi::Handle ctx, const rhi::CreateUniformSetInfo& info) -{ - SRB2_ASSERT(graphics_context_active_ == true); - SRB2_ASSERT(ctx.generation() == graphics_context_generation_); - - Gl2UniformSet uniform_set; - - for (auto& uniform : info.uniforms) - { - uniform_set.uniforms.push_back(uniform); - } - - return uniform_set_slab_.insert(std::move(uniform_set)); -} - -rhi::Handle Gl2Rhi::create_binding_set( - rhi::Handle ctx, - Handle pipeline, - const rhi::CreateBindingSetInfo& info -) -{ - SRB2_ASSERT(graphics_context_active_ == true); - SRB2_ASSERT(ctx.generation() == graphics_context_generation_); - - SRB2_ASSERT(pipeline_slab_.is_valid(pipeline) == true); - auto& pl = pipeline_slab_[pipeline]; - - SRB2_ASSERT(info.vertex_buffers.size() == pl.desc.vertex_input.buffer_layouts.size()); - - Gl2BindingSet binding_set; - - for (auto& vertex_buffer : info.vertex_buffers) - { - binding_set.vertex_buffer_bindings.push_back(vertex_buffer); - } - - // Set textures - for (size_t i = 0; i < info.sampler_textures.size(); i++) - { - auto& binding = info.sampler_textures[i]; - auto& sampler_name = pl.desc.sampler_input.enabled_samplers[i]; - SRB2_ASSERT(binding.name == sampler_name); - - SRB2_ASSERT(texture_slab_.is_valid(binding.texture)); - auto& tx = texture_slab_[binding.texture]; - binding_set.textures.insert({sampler_name, tx.texture}); - } - - return binding_set_slab_.insert(std::move(binding_set)); -} - rhi::Handle Gl2Rhi::create_renderbuffer(const rhi::RenderbufferDesc& desc) { GLuint name = 0; @@ -878,367 +828,165 @@ void Gl2Rhi::destroy_renderbuffer(rhi::Handle handle) GL_ASSERT; } -rhi::Handle Gl2Rhi::create_pipeline(const PipelineDesc& desc) +rhi::Handle Gl2Rhi::create_program(const ProgramDesc& desc) { SRB2_ASSERT(platform_ != nullptr); - // TODO assert compatibility of pipeline description with program using ProgramRequirements + Gl2Program program; - const rhi::ProgramRequirements& reqs = rhi::program_requirements_for_program(desc.program); - - GLuint vertex = 0; - GLuint fragment = 0; - GLuint program = 0; - Gl2Pipeline pipeline; - - auto [vert_srcs, frag_srcs] = platform_->find_shader_sources(desc.program); + auto [vert_srcs, frag_srcs] = platform_->find_shader_sources(desc.name); // GL 2 note: // Do not explicitly set GLSL version. Unversioned sources are required to be treated as 110, but writing 110 // breaks the AMD driver's program linker in a bizarre way. - // Process vertex shader sources - std::vector vert_sources; + // Process shader sources + srb2::Vector vert_sources; + srb2::Vector frag_sources; ShaderLoadContext vert_ctx; + ShaderLoadContext frag_ctx; vert_ctx.set_version("120"); - for (auto& attribute : desc.vertex_input.attr_layouts) + frag_ctx.set_version("120"); + for (auto def : desc.defines) { - for (auto const& require_attr : reqs.vertex_input.attributes) - { - if (require_attr.name == attribute.name && !require_attr.required) - { - vert_ctx.define(map_vertex_attribute_enable_define(attribute.name)); - } - } - } - for (auto& uniform_group : desc.uniform_input.enabled_uniforms) - { - for (auto& uniform : uniform_group) - { - for (auto const& req_uni_group : reqs.uniforms.uniform_groups) - { - for (auto const& req_uni : req_uni_group) - { - if (req_uni.name == uniform && !req_uni.required) - { - vert_ctx.define(map_uniform_enable_define(uniform)); - } - } - } - } + vert_ctx.define(def); + frag_ctx.define(def); } for (auto& src : vert_srcs) { vert_ctx.add_source(std::move(src)); } - vert_sources = vert_ctx.get_sources_array(); - - // Process vertex shader sources - std::vector frag_sources; - ShaderLoadContext frag_ctx; - frag_ctx.set_version("120"); - for (auto& sampler : desc.sampler_input.enabled_samplers) - { - for (auto const& require_sampler : reqs.samplers.samplers) - { - if (sampler == require_sampler.name && !require_sampler.required) - { - frag_ctx.define(map_sampler_enable_define(sampler)); - } - } - } - for (auto& uniform_group : desc.uniform_input.enabled_uniforms) - { - for (auto& uniform : uniform_group) - { - for (auto const& req_uni_group : reqs.uniforms.uniform_groups) - { - for (auto const& req_uni : req_uni_group) - { - if (req_uni.name == uniform && !req_uni.required) - { - frag_ctx.define(map_uniform_enable_define(uniform)); - } - } - } - } - } for (auto& src : frag_srcs) { frag_ctx.add_source(std::move(src)); } + vert_sources = vert_ctx.get_sources_array(); frag_sources = frag_ctx.get_sources_array(); - vertex = gl_->CreateShader(GL_VERTEX_SHADER); - gl_->ShaderSource(vertex, vert_sources.size(), vert_sources.data(), NULL); - gl_->CompileShader(vertex); GLint is_compiled = 0; - gl_->GetShaderiv(vertex, GL_COMPILE_STATUS, &is_compiled); + + program.vertex_shader = gl_->CreateShader(GL_VERTEX_SHADER); + GL_ASSERT; + gl_->ShaderSource(program.vertex_shader, vert_sources.size(), vert_sources.data(), nullptr); + gl_->CompileShader(program.vertex_shader); + gl_->GetShaderiv(program.vertex_shader, GL_COMPILE_STATUS, &is_compiled); if (is_compiled == GL_FALSE) { GLint max_length = 0; - gl_->GetShaderiv(vertex, GL_INFO_LOG_LENGTH, &max_length); - std::vector compile_error(max_length); - gl_->GetShaderInfoLog(vertex, max_length, &max_length, compile_error.data()); + gl_->GetShaderiv(program.vertex_shader, GL_INFO_LOG_LENGTH, &max_length); + srb2::Vector compile_error(max_length); + gl_->GetShaderInfoLog(program.vertex_shader, max_length, &max_length, compile_error.data()); - gl_->DeleteShader(vertex); - throw std::runtime_error(fmt::format("Vertex shader compilation failed: {}", std::string(compile_error.data())) - ); + gl_->DeleteShader(program.vertex_shader); + throw std::runtime_error(fmt::format("Vertex shader compilation failed: {}", String(compile_error.data()))); } - fragment = gl_->CreateShader(GL_FRAGMENT_SHADER); - gl_->ShaderSource(fragment, frag_sources.size(), frag_sources.data(), NULL); - gl_->CompileShader(fragment); - gl_->GetShaderiv(vertex, GL_COMPILE_STATUS, &is_compiled); + + program.fragment_shader = gl_->CreateShader(GL_FRAGMENT_SHADER); + GL_ASSERT; + gl_->ShaderSource(program.fragment_shader, frag_sources.size(), frag_sources.data(), nullptr); + gl_->CompileShader(program.fragment_shader); + gl_->GetShaderiv(program.fragment_shader, GL_COMPILE_STATUS, &is_compiled); if (is_compiled == GL_FALSE) { GLint max_length = 0; - gl_->GetShaderiv(fragment, GL_INFO_LOG_LENGTH, &max_length); - std::vector compile_error(max_length); - gl_->GetShaderInfoLog(fragment, max_length, &max_length, compile_error.data()); + gl_->GetShaderiv(program.fragment_shader, GL_INFO_LOG_LENGTH, &max_length); + srb2::Vector compile_error(max_length); + gl_->GetShaderInfoLog(program.fragment_shader, max_length, &max_length, compile_error.data()); - gl_->DeleteShader(fragment); - gl_->DeleteShader(vertex); - throw std::runtime_error( - fmt::format("Fragment shader compilation failed: {}", std::string(compile_error.data())) - ); + gl_->DeleteShader(program.fragment_shader); + throw std::runtime_error(fmt::format("Fragment shader compilation failed: {}", String(compile_error.data()))); } - // Program link + program.program = gl_->CreateProgram(); + GL_ASSERT; - program = gl_->CreateProgram(); - gl_->AttachShader(program, vertex); - gl_->AttachShader(program, fragment); - gl_->LinkProgram(program); - gl_->GetProgramiv(program, GL_LINK_STATUS, &is_compiled); + gl_->AttachShader(program.program, program.vertex_shader); + gl_->AttachShader(program.program, program.fragment_shader); + gl_->LinkProgram(program.program); + gl_->GetProgramiv(program.program, GL_LINK_STATUS, &is_compiled); if (is_compiled == GL_FALSE) { GLint max_length = 0; - gl_->GetProgramiv(program, GL_INFO_LOG_LENGTH, &max_length); - std::vector link_error(max_length); - gl_->GetProgramInfoLog(program, max_length, &max_length, link_error.data()); + gl_->GetProgramiv(program.program, GL_INFO_LOG_LENGTH, &max_length); + srb2::Vector link_error(max_length); + gl_->GetProgramInfoLog(program.program, max_length, &max_length, link_error.data()); - gl_->DeleteProgram(program); - gl_->DeleteShader(fragment); - gl_->DeleteShader(vertex); - throw std::runtime_error(fmt::format("Pipeline program link failed: {}", std::string(link_error.data()))); + gl_->DeleteProgram(program.program); + gl_->DeleteShader(program.fragment_shader); + gl_->DeleteShader(program.vertex_shader); + throw std::runtime_error(fmt::format("Pipeline program link failed: {}", String(link_error.data()))); } - std::unordered_map active_attributes; + // get attribute information GLint active_attribute_total = -1; - gl_->GetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &active_attribute_total); + gl_->GetProgramiv(program.program, GL_ACTIVE_ATTRIBUTES, &active_attribute_total); if (active_attribute_total < 0) { - gl_->DeleteProgram(program); - gl_->DeleteShader(fragment); - gl_->DeleteShader(vertex); + gl_->DeleteProgram(program.program); + gl_->DeleteShader(program.fragment_shader); + gl_->DeleteShader(program.vertex_shader); throw std::runtime_error("Unable to retrieve program active attributes"); } - if (desc.vertex_input.attr_layouts.size() != static_cast(active_attribute_total)) - { - gl_->DeleteProgram(program); - gl_->DeleteShader(fragment); - gl_->DeleteShader(vertex); - throw std::runtime_error(fmt::format( - "Pipeline's enabled attribute count does not match the linked program's total: {} vs {}", - desc.vertex_input.attr_layouts.size(), - static_cast(active_attribute_total) - )); - } for (GLint i = 0; i < active_attribute_total; i++) { GLsizei name_len = 0; GLint size = 0; GLenum type = GL_ZERO; char name[256]; - gl_->GetActiveAttrib(program, i, 255, &name_len, &size, &type, name); + gl_->GetActiveAttrib(program.program, i, 255, &name_len, &size, &type, name); GL_ASSERT; - GLint location = gl_->GetAttribLocation(program, name); + GLint location = gl_->GetAttribLocation(program.program, name); GL_ASSERT; - active_attributes.insert({std::string(name), Gl2ActiveUniform {type, static_cast(location)}}); + program.attrib_locations[String(name)] = location; } - std::unordered_map active_uniforms; - size_t total_enabled_uniforms = 0; - for (auto g = desc.uniform_input.enabled_uniforms.cbegin(); g != desc.uniform_input.enabled_uniforms.cend(); - g = std::next(g)) - { - total_enabled_uniforms += g->size(); - } + // get uniform information GLint active_uniform_total = -1; - gl_->GetProgramiv(program, GL_ACTIVE_UNIFORMS, &active_uniform_total); + gl_->GetProgramiv(program.program, GL_ACTIVE_UNIFORMS, &active_uniform_total); if (active_uniform_total < 0) { - gl_->DeleteProgram(program); - gl_->DeleteShader(fragment); - gl_->DeleteShader(vertex); + gl_->DeleteProgram(program.program); + gl_->DeleteShader(program.fragment_shader); + gl_->DeleteShader(program.vertex_shader); throw std::runtime_error("Unable to retrieve program active uniforms"); } - if (total_enabled_uniforms + desc.sampler_input.enabled_samplers.size() != - static_cast(active_uniform_total)) - { - gl_->DeleteProgram(program); - gl_->DeleteShader(fragment); - gl_->DeleteShader(vertex); - throw std::runtime_error(fmt::format( - "Pipeline's enabled uniform count (uniforms + samplers) does not match the linked program's total: {} vs " - "{}", - total_enabled_uniforms + desc.sampler_input.enabled_samplers.size(), - static_cast(active_uniform_total) - )); - } for (GLint i = 0; i < active_uniform_total; i++) { GLsizei name_len = 0; GLint size = 0; GLenum type = GL_ZERO; char name[256]; - gl_->GetActiveUniform(program, i, 255, &name_len, &size, &type, name); + gl_->GetActiveUniform(program.program, i, 255, &name_len, &size, &type, name); GL_ASSERT; - GLint location = gl_->GetUniformLocation(program, name); + GLint location = gl_->GetUniformLocation(program.program, name); GL_ASSERT; - active_uniforms.insert({std::string(name), Gl2ActiveUniform {type, static_cast(location)}}); + program.uniform_locations[String(name)] = location; } - for (auto& attr : desc.vertex_input.attr_layouts) - { - const char* symbol_name = map_vertex_attribute_symbol_name(attr.name); - SRB2_ASSERT(symbol_name != nullptr); - if (active_attributes.find(symbol_name) == active_attributes.end()) - { - gl_->DeleteProgram(program); - gl_->DeleteShader(fragment); - gl_->DeleteShader(vertex); - throw std::runtime_error("Enabled attribute not found in linked program"); - } - auto& active_attr = active_attributes[symbol_name]; - auto expected_format = rhi::vertex_attribute_format(attr.name); - auto expected_gl_type = map_vertex_attribute_format(expected_format); - SRB2_ASSERT(expected_gl_type != GL_ZERO); - if (expected_gl_type != active_attr.type) - { - gl_->DeleteProgram(program); - gl_->DeleteShader(fragment); - gl_->DeleteShader(vertex); - throw std::runtime_error("Active attribute type does not match expected type"); - } - - pipeline.attrib_locations.insert({attr.name, active_attr.location}); - } - - for (auto group_itr = desc.uniform_input.enabled_uniforms.cbegin(); - group_itr != desc.uniform_input.enabled_uniforms.cend(); - group_itr = std::next(group_itr)) - { - auto& group = *group_itr; - for (auto itr = group.cbegin(); itr != group.cend(); itr = std::next(itr)) - { - auto& uniform = *itr; - const char* symbol_name = map_uniform_attribute_symbol_name(uniform); - SRB2_ASSERT(symbol_name != nullptr); - if (active_uniforms.find(symbol_name) == active_uniforms.end()) - { - gl_->DeleteProgram(program); - gl_->DeleteShader(fragment); - gl_->DeleteShader(vertex); - throw std::runtime_error("Enabled uniform not found in linked program"); - } - auto& active_uniform = active_uniforms[symbol_name]; - auto expected_format = rhi::uniform_format(uniform); - auto expected_gl_type = map_uniform_format(expected_format); - SRB2_ASSERT(expected_gl_type != GL_ZERO); - if (expected_gl_type != active_uniform.type) - { - gl_->DeleteProgram(program); - gl_->DeleteShader(fragment); - gl_->DeleteShader(vertex); - throw std::runtime_error("Active uniform type does not match expected type"); - } - SRB2_ASSERT(pipeline.uniform_locations.find(uniform) == pipeline.uniform_locations.end()); - pipeline.uniform_locations.insert({uniform, active_uniform.location}); - } - } - - for (auto& sampler : desc.sampler_input.enabled_samplers) - { - const char* symbol_name = map_sampler_symbol_name(sampler); - SRB2_ASSERT(symbol_name != nullptr); - if (active_uniforms.find(symbol_name) == active_uniforms.end()) - { - gl_->DeleteProgram(program); - gl_->DeleteShader(fragment); - gl_->DeleteShader(vertex); - throw std::runtime_error("Enabled sampler not found in linked program"); - } - auto& active_sampler = active_uniforms[symbol_name]; - if (active_sampler.type != GL_SAMPLER_2D) - { - gl_->DeleteProgram(program); - gl_->DeleteShader(fragment); - gl_->DeleteShader(vertex); - throw std::runtime_error("Active sampler type does not match expected type"); - } - - pipeline.sampler_locations.insert({sampler, active_sampler.location}); - } - - pipeline.desc = desc; - pipeline.vertex_shader = vertex; - pipeline.fragment_shader = fragment; - pipeline.program = program; - - return pipeline_slab_.insert(std::move(pipeline)); + Handle program_handle = program_slab_.insert(std::move(program)); + return program_handle; } -void Gl2Rhi::destroy_pipeline(rhi::Handle handle) +void Gl2Rhi::destroy_program(Handle handle) { - SRB2_ASSERT(pipeline_slab_.is_valid(handle) == true); - Gl2Pipeline casted = pipeline_slab_.remove(handle); - GLuint vertex_shader = casted.vertex_shader; - GLuint fragment_shader = casted.fragment_shader; - GLuint program = casted.program; - - gl_->DeleteProgram(program); + SRB2_ASSERT(program_slab_.is_valid(handle) == true); + Gl2Program casted = program_slab_.remove(handle); + gl_->DeleteProgram(casted.program); GL_ASSERT; - gl_->DeleteShader(vertex_shader); + gl_->DeleteShader(casted.fragment_shader); GL_ASSERT; - gl_->DeleteShader(fragment_shader); - GL_ASSERT; -} - -rhi::Handle Gl2Rhi::begin_graphics() -{ - SRB2_ASSERT(graphics_context_active_ == false); - graphics_context_active_ = true; - return rhi::Handle(0, graphics_context_generation_); -} - -void Gl2Rhi::end_graphics(rhi::Handle handle) -{ - SRB2_ASSERT(graphics_context_active_ == true); - SRB2_ASSERT(current_pipeline_.has_value() == false && current_render_pass_.has_value() == false); - graphics_context_generation_ += 1; - if (graphics_context_generation_ == 0) - { - graphics_context_generation_ = 1; - } - graphics_context_active_ = false; - gl_->Flush(); + gl_->DeleteShader(casted.vertex_shader); GL_ASSERT; } void Gl2Rhi::present() { SRB2_ASSERT(platform_ != nullptr); - SRB2_ASSERT(graphics_context_active_ == false); platform_->present(); } -void Gl2Rhi::begin_default_render_pass(Handle ctx, bool clear) +void Gl2Rhi::apply_default_framebuffer(bool clear) { - SRB2_ASSERT(platform_ != nullptr); - SRB2_ASSERT(graphics_context_active_ == true); - SRB2_ASSERT(current_render_pass_.has_value() == false); - const Rect fb_rect = platform_->get_default_framebuffer_dimensions(); gl_->BindFramebuffer(GL_FRAMEBUFFER, 0); @@ -1256,19 +1004,10 @@ void Gl2Rhi::begin_default_render_pass(Handle ctx, bool clear) gl_->Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); GL_ASSERT; } - - current_render_pass_ = Gl2Rhi::DefaultRenderPassState {}; } -void Gl2Rhi::begin_render_pass(Handle ctx, const RenderPassBeginInfo& info) +void Gl2Rhi::apply_framebuffer(const RenderPassBeginInfo& info, bool allow_clear) { - SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); - SRB2_ASSERT(current_render_pass_.has_value() == false); - - SRB2_ASSERT(render_pass_slab_.is_valid(info.render_pass) == true); - auto& rp = render_pass_slab_[info.render_pass]; - SRB2_ASSERT(rp.desc.use_depth_stencil == info.depth_stencil_attachment.has_value()); - auto fb_itr = framebuffers_.find(Gl2FramebufferKey {info.color_attachment, info.depth_stencil_attachment}); if (fb_itr == framebuffers_.end()) { @@ -1278,12 +1017,10 @@ void Gl2Rhi::begin_render_pass(Handle ctx, const RenderPassBegi GL_ASSERT; gl_->BindFramebuffer(GL_FRAMEBUFFER, fb_name); GL_ASSERT; - fb_itr = framebuffers_ - .insert( - {Gl2FramebufferKey {info.color_attachment, info.depth_stencil_attachment}, - static_cast(fb_name)} - ) - .first; + fb_itr = framebuffers_.insert({ + Gl2FramebufferKey {info.color_attachment, info.depth_stencil_attachment}, + static_cast(fb_name) + }).first; SRB2_ASSERT(texture_slab_.is_valid(info.color_attachment)); auto& texture = texture_slab_[info.color_attachment]; @@ -1291,7 +1028,7 @@ void Gl2Rhi::begin_render_pass(Handle ctx, const RenderPassBegi gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture.texture, 0); GL_ASSERT; - if (rp.desc.use_depth_stencil && info.depth_stencil_attachment.has_value()) + if (info.depth_stencil_attachment.has_value()) { SRB2_ASSERT(renderbuffer_slab_.is_valid(*info.depth_stencil_attachment)); auto& renderbuffer = renderbuffer_slab_[*info.depth_stencil_attachment]; @@ -1307,388 +1044,124 @@ void Gl2Rhi::begin_render_pass(Handle ctx, const RenderPassBegi auto& fb = *fb_itr; gl_->BindFramebuffer(GL_FRAMEBUFFER, fb.second); GL_ASSERT; - gl_->Disable(GL_SCISSOR_TEST); - GL_ASSERT; GLint clear_bits = 0; - if (rp.desc.color_load_op == rhi::AttachmentLoadOp::kClear) + if (info.color_load_op == rhi::AttachmentLoadOp::kClear) { gl_->ClearColor(info.clear_color.r, info.clear_color.g, info.clear_color.b, info.clear_color.a); clear_bits |= GL_COLOR_BUFFER_BIT; } - if (rp.desc.use_depth_stencil) + if (info.depth_load_op == rhi::AttachmentLoadOp::kClear) { - if (rp.desc.depth_load_op == rhi::AttachmentLoadOp::kClear) - { - gl_->ClearDepth(1.f); - clear_bits |= GL_DEPTH_BUFFER_BIT; - } - if (rp.desc.stencil_load_op == rhi::AttachmentLoadOp::kClear) - { - gl_->ClearStencil(0); - clear_bits |= GL_STENCIL_BUFFER_BIT; - } + gl_->ClearDepth(1.f); + clear_bits |= GL_DEPTH_BUFFER_BIT; + } + if (info.stencil_load_op == rhi::AttachmentLoadOp::kClear) + { + gl_->ClearStencil(0); + clear_bits |= GL_STENCIL_BUFFER_BIT; } - if (clear_bits != 0) + if (clear_bits != 0 && allow_clear) { gl_->Clear(clear_bits); GL_ASSERT; } - - current_render_pass_ = info; } -void Gl2Rhi::end_render_pass(Handle ctx) +void Gl2Rhi::push_default_render_pass(bool clear) { - SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); - SRB2_ASSERT(current_render_pass_.has_value() == true); + SRB2_ASSERT(platform_ != nullptr); - current_pipeline_ = std::nullopt; - current_render_pass_ = std::nullopt; + render_pass_stack_.emplace_back(Gl2Rhi::DefaultRenderPassState { clear }); + apply_default_framebuffer(clear); } -void Gl2Rhi::bind_pipeline(Handle ctx, Handle pipeline) +void Gl2Rhi::push_render_pass(const RenderPassBeginInfo& info) { - SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); - SRB2_ASSERT(current_render_pass_.has_value() == true); - - SRB2_ASSERT(pipeline_slab_.is_valid(pipeline) == true); - auto& pl = pipeline_slab_[pipeline]; - auto& desc = pl.desc; - - gl_->UseProgram(pl.program); - GL_ASSERT; - - gl_->Disable(GL_SCISSOR_TEST); - GL_ASSERT; - - if (desc.depth_stencil_state) - { - if (desc.depth_stencil_state->depth_test) - { - gl_->Enable(GL_DEPTH_TEST); - GL_ASSERT; - GLenum depth_func = map_compare_func(desc.depth_stencil_state->depth_func); - SRB2_ASSERT(depth_func != GL_ZERO); - gl_->DepthFunc(depth_func); - GL_ASSERT; - gl_->DepthMask(desc.depth_stencil_state->depth_write ? GL_TRUE : GL_FALSE); - GL_ASSERT; - } - else - { - gl_->Disable(GL_DEPTH_TEST); - GL_ASSERT; - } - - if (desc.depth_stencil_state->depth_write) - { - gl_->DepthMask(GL_TRUE); - GL_ASSERT; - } - else - { - gl_->DepthMask(GL_FALSE); - GL_ASSERT; - } - - if (desc.depth_stencil_state->stencil_test) - { - gl_->Enable(GL_STENCIL_TEST); - stencil_front_reference_ = 0; - stencil_back_reference_ = 0; - stencil_front_compare_mask_ = 0xFF; - stencil_back_compare_mask_ = 0xFF; - stencil_front_write_mask_ = 0xFF; - stencil_back_write_mask_ = 0xFF; - GL_ASSERT; - - gl_->StencilFuncSeparate( - GL_FRONT, - map_compare_func(desc.depth_stencil_state->front.stencil_compare), - stencil_front_reference_, - stencil_front_compare_mask_ - ); - GL_ASSERT; - gl_->StencilFuncSeparate( - GL_BACK, - map_compare_func(desc.depth_stencil_state->back.stencil_compare), - stencil_back_reference_, - stencil_back_compare_mask_ - ); - GL_ASSERT; - - gl_->StencilMaskSeparate(GL_FRONT, stencil_front_write_mask_); - GL_ASSERT; - gl_->StencilMaskSeparate(GL_BACK, stencil_back_write_mask_); - GL_ASSERT; - } - else - { - gl_->Disable(GL_STENCIL_TEST); - GL_ASSERT; - } - } - else - { - gl_->Disable(GL_DEPTH_TEST); - GL_ASSERT; - gl_->Disable(GL_STENCIL_TEST); - GL_ASSERT; - gl_->StencilMask(0); - GL_ASSERT; - } - - if (desc.color_state.blend) - { - rhi::BlendDesc& bl = *desc.color_state.blend; - gl_->Enable(GL_BLEND); - GL_ASSERT; - gl_->BlendFuncSeparate( - map_blend_factor(bl.source_factor_color), - map_blend_factor(bl.dest_factor_color), - map_blend_factor(bl.source_factor_alpha), - map_blend_factor(bl.dest_factor_alpha) - ); - GL_ASSERT; - gl_->BlendEquationSeparate(map_blend_function(bl.color_function), map_blend_function(bl.alpha_function)); - GL_ASSERT; - gl_->BlendColor(desc.blend_color.r, desc.blend_color.g, desc.blend_color.b, desc.blend_color.a); - GL_ASSERT; - } - else - { - gl_->Disable(GL_BLEND); - } - - gl_->ColorMask( - desc.color_state.color_mask.r ? GL_TRUE : GL_FALSE, - desc.color_state.color_mask.g ? GL_TRUE : GL_FALSE, - desc.color_state.color_mask.b ? GL_TRUE : GL_FALSE, - desc.color_state.color_mask.a ? GL_TRUE : GL_FALSE - ); - GL_ASSERT; - - GLenum cull_face = map_cull_mode(desc.cull); - if (cull_face == GL_NONE) - { - gl_->Disable(GL_CULL_FACE); - GL_ASSERT; - } - else - { - gl_->Enable(GL_CULL_FACE); - GL_ASSERT; - gl_->CullFace(cull_face); - GL_ASSERT; - } - gl_->FrontFace(map_winding(desc.winding)); - GL_ASSERT; - - current_pipeline_ = pipeline; - current_primitive_type_ = desc.primitive; + render_pass_stack_.push_back(info); + apply_framebuffer(info, false); } -void Gl2Rhi::bind_uniform_set(Handle ctx, uint32_t slot, Handle set) +void Gl2Rhi::pop_render_pass() { - SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); - SRB2_ASSERT(current_render_pass_.has_value() == true && current_pipeline_.has_value() == true); + SRB2_ASSERT(render_pass_stack_.empty() == false); - SRB2_ASSERT(pipeline_slab_.is_valid(*current_pipeline_)); - auto& pl = pipeline_slab_[*current_pipeline_]; + current_program_ = std::nullopt; - SRB2_ASSERT(uniform_set_slab_.is_valid(set)); - auto& us = uniform_set_slab_[set]; + render_pass_stack_.pop_back(); - auto& uniform_input = pl.desc.uniform_input; - SRB2_ASSERT(slot < uniform_input.enabled_uniforms.size()); - SRB2_ASSERT(us.uniforms.size() == uniform_input.enabled_uniforms[slot].size()); - - // Assert compatibility of uniform set with pipeline's set slot - for (size_t i = 0; i < us.uniforms.size(); i++) + if (!render_pass_stack_.empty()) { - SRB2_ASSERT( - rhi::uniform_format(uniform_input.enabled_uniforms[slot][i]) == rhi::uniform_variant_format(us.uniforms[i]) - ); - } - - // Apply uniforms - // TODO use Uniform Buffer Objects to optimize this. - // We don't really *need* to, though, probably... - // Also, we know that any given uniform name is uniquely present in a single uniform group asserted during pipeline - // compilation. This is an RHI requirement to support backends that don't have UBOs. - for (size_t i = 0; i < us.uniforms.size(); i++) - { - auto& uniform_name = uniform_input.enabled_uniforms[slot][i]; - auto& update_data = us.uniforms[i]; - SRB2_ASSERT(pl.uniform_locations.find(uniform_name) != pl.uniform_locations.end()); - GLuint pipeline_uniform = pl.uniform_locations[uniform_name]; - + RenderPassState& state = *render_pass_stack_.rbegin(); + // We must not clear the framebuffer when restoring a previous framebuffer, + // even if the clear was previously requested. auto visitor = srb2::Overload { - [&](const float& value) - { - gl_->Uniform1f(pipeline_uniform, value); - GL_ASSERT; - }, - [&](const glm::vec2& value) - { - gl_->Uniform2f(pipeline_uniform, value.x, value.y); - GL_ASSERT; - }, - [&](const glm::vec3& value) - { - gl_->Uniform3f(pipeline_uniform, value.x, value.y, value.z); - GL_ASSERT; - }, - [&](const glm::vec4& value) - { - gl_->Uniform4f(pipeline_uniform, value.x, value.y, value.z, value.w); - GL_ASSERT; - }, - [&](const int32_t& value) - { - gl_->Uniform1i(pipeline_uniform, value); - GL_ASSERT; - }, - [&](const glm::ivec2& value) - { - gl_->Uniform2i(pipeline_uniform, value.x, value.y); - GL_ASSERT; - }, - [&](const glm::ivec3& value) - { - gl_->Uniform3i(pipeline_uniform, value.x, value.y, value.z); - GL_ASSERT; - }, - [&](const glm::ivec4& value) - { - gl_->Uniform4i(pipeline_uniform, value.x, value.y, value.z, value.w); - GL_ASSERT; - }, - [&](const glm::mat2& value) - { - gl_->UniformMatrix2fv(pipeline_uniform, 1, false, glm::value_ptr(value)); - GL_ASSERT; - }, - [&](const glm::mat3& value) - { - gl_->UniformMatrix3fv(pipeline_uniform, 1, false, glm::value_ptr(value)); - GL_ASSERT; - }, - [&](const glm::mat4& value) - { - gl_->UniformMatrix4fv(pipeline_uniform, 1, false, glm::value_ptr(value)); - GL_ASSERT; + [this](const DefaultRenderPassState& s) { + apply_default_framebuffer(false); }, + [this](const RenderPassBeginInfo& info) { + apply_framebuffer(info, false); + } }; - std::visit(visitor, update_data); + std::visit(visitor, state); } } -void Gl2Rhi::bind_binding_set(Handle ctx, Handle set) +void Gl2Rhi::bind_program(Handle program) { - SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); - SRB2_ASSERT(current_render_pass_.has_value() == true && current_pipeline_.has_value() == true); + SRB2_ASSERT(render_pass_stack_.empty() == false); + Gl2Program& prog = program_slab_[program]; - SRB2_ASSERT(pipeline_slab_.is_valid(*current_pipeline_)); - auto& pl = pipeline_slab_[*current_pipeline_]; - - SRB2_ASSERT(binding_set_slab_.is_valid(set)); - auto& bs = binding_set_slab_[set]; - - SRB2_ASSERT(bs.textures.size() == pl.desc.sampler_input.enabled_samplers.size()); - - // TODO only disable the vertex attributes of the previously bound pipeline (performance) - for (GLuint i = 0; i < kMaxVertexAttributes; i++) + gl_->UseProgram(prog.program); + current_program_ = program; + GLint max_attribs; + gl_->GetIntegerv(GL_MAX_VERTEX_ATTRIBS, &max_attribs); + GL_ASSERT; + for (GLint i = 0; i < max_attribs; i++) { gl_->DisableVertexAttribArray(i); - } - - // Update the vertex attributes with the new vertex buffer bindings. - - // OpenGL 2 does not require binding buffers to the pipeline the same way Vulkan does. - // Instead, we need to find the pipeline vertex attributes which would be affected by - // the changing set of vertex buffers, and reassign their Vertex Attribute Pointers. - for (size_t i = 0; i < pl.desc.vertex_input.attr_layouts.size(); i++) - { - auto& attr_layout = pl.desc.vertex_input.attr_layouts[i]; - uint32_t attr_buffer_index = attr_layout.buffer_index; - VertexAttributeName attr_name = attr_layout.name; - - auto& buffer_layout = pl.desc.vertex_input.buffer_layouts[attr_buffer_index]; - - SRB2_ASSERT(pl.attrib_locations.find(attr_name) != pl.attrib_locations.end()); - auto gl_attr_location = pl.attrib_locations[pl.desc.vertex_input.attr_layouts[i].name]; - - VertexAttributeFormat vert_attr_format = rhi::vertex_attribute_format(attr_name); - GLenum vertex_attr_type = map_vertex_attribute_type(vert_attr_format); - SRB2_ASSERT(vertex_attr_type != GL_ZERO); - GLint vertex_attr_size = map_vertex_attribute_format_size(vert_attr_format); - SRB2_ASSERT(vertex_attr_size != 0); - - uint32_t vertex_buffer_offset = 0; - auto& vertex_binding = bs.vertex_buffer_bindings[attr_layout.buffer_index]; - rhi::Handle vertex_buffer_handle = vertex_binding.vertex_buffer; - SRB2_ASSERT(buffer_slab_.is_valid(vertex_binding.vertex_buffer) == true); - auto& buffer = *static_cast(&buffer_slab_[vertex_buffer_handle]); - SRB2_ASSERT(buffer.desc.type == rhi::BufferType::kVertexBuffer); - - gl_->BindBuffer(GL_ARRAY_BUFFER, buffer.buffer); - gl_->EnableVertexAttribArray(gl_attr_location); - gl_->VertexAttribPointer( - gl_attr_location, - vertex_attr_size, - vertex_attr_type, - GL_FALSE, - buffer_layout.stride, - reinterpret_cast(vertex_buffer_offset + attr_layout.offset) - ); - } - - gl_->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - - // Bind the samplers to the uniforms - for (auto& texture_binding : bs.textures) - { - auto sampler_name = texture_binding.first; - GLuint texture_gl_name = texture_binding.second; - GLuint sampler_uniform_loc = pl.sampler_locations[sampler_name]; - GLenum active_texture = GL_TEXTURE0; - GLuint uniform_value = 0; - switch (sampler_name) - { - case rhi::SamplerName::kSampler0: - active_texture = GL_TEXTURE0; - uniform_value = 0; - break; - case rhi::SamplerName::kSampler1: - active_texture = GL_TEXTURE0 + 1; - uniform_value = 1; - break; - case rhi::SamplerName::kSampler2: - active_texture = GL_TEXTURE0 + 2; - uniform_value = 2; - break; - case rhi::SamplerName::kSampler3: - active_texture = GL_TEXTURE0 + 3; - uniform_value = 3; - break; - } - gl_->ActiveTexture(active_texture); - GL_ASSERT; - gl_->BindTexture(GL_TEXTURE_2D, texture_gl_name); - GL_ASSERT; - gl_->Uniform1i(sampler_uniform_loc, uniform_value); GL_ASSERT; } } -void Gl2Rhi::bind_index_buffer(Handle ctx, Handle buffer) +void Gl2Rhi::bind_vertex_attrib(const char* name, Handle buffer, VertexAttributeFormat format, uint32_t offset, uint32_t stride) { - SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); - SRB2_ASSERT(current_render_pass_.has_value() == true && current_pipeline_.has_value() == true); + SRB2_ASSERT(current_program_.has_value()); + SRB2_ASSERT(buffer_slab_.is_valid(buffer)); + + GLenum vertex_attr_type = map_vertex_attribute_type(format); + SRB2_ASSERT(vertex_attr_type != GL_ZERO); + GLint vertex_attr_size = map_vertex_attribute_format_size(format); + SRB2_ASSERT(vertex_attr_size != 0); + + Gl2Program& prog = program_slab_[*current_program_]; + Gl2Buffer& cast_buffer = buffer_slab_[buffer]; + + SRB2_ASSERT(cast_buffer.desc.type == rhi::BufferType::kVertexBuffer); + + GLint location = gl_->GetAttribLocation(prog.program, name); + GL_ASSERT; + SRB2_ASSERT(location >= 0); + gl_->BindBuffer(GL_ARRAY_BUFFER, cast_buffer.buffer); + GL_ASSERT; + gl_->EnableVertexAttribArray(location); + GL_ASSERT; + gl_->VertexAttribPointer( + location, + vertex_attr_size, + vertex_attr_type, + GL_FALSE, + stride, + reinterpret_cast(offset) + ); + GL_ASSERT; +} + +void Gl2Rhi::bind_index_buffer(Handle buffer) +{ + SRB2_ASSERT(current_program_.has_value()); SRB2_ASSERT(buffer_slab_.is_valid(buffer)); auto& ib = buffer_slab_[buffer]; @@ -1698,39 +1171,287 @@ void Gl2Rhi::bind_index_buffer(Handle ctx, Handle buffe current_index_buffer_ = buffer; gl_->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib.buffer); + GL_ASSERT; } -void Gl2Rhi::set_scissor(Handle ctx, const Rect& rect) +void Gl2Rhi::set_uniform(const char* name, float value) { - SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); - SRB2_ASSERT(current_render_pass_.has_value() == true && current_pipeline_.has_value() == true); - - gl_->Enable(GL_SCISSOR_TEST); - gl_->Scissor(rect.x, rect.y, rect.w, rect.h); + SRB2_ASSERT(current_program_.has_value()); + Gl2Program& cast = program_slab_[*current_program_]; + GLint location = gl_->GetUniformLocation(cast.program, name); + GL_ASSERT; + SRB2_ASSERT(location >= 0); + gl_->Uniform1f(location, value); + GL_ASSERT; } -void Gl2Rhi::set_viewport(Handle ctx, const Rect& rect) +void Gl2Rhi::set_uniform(const char* name, int value) { - SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); - SRB2_ASSERT(current_render_pass_.has_value() == true && current_pipeline_.has_value() == true); + SRB2_ASSERT(current_program_.has_value()); + Gl2Program& cast = program_slab_[*current_program_]; + GLint location = gl_->GetUniformLocation(cast.program, name); + GL_ASSERT; + SRB2_ASSERT(location >= 0); + gl_->Uniform1i(location, value); + GL_ASSERT; +} + +void Gl2Rhi::set_uniform(const char* name, glm::vec2 value) +{ + SRB2_ASSERT(current_program_.has_value()); + Gl2Program& cast = program_slab_[*current_program_]; + GLint location = gl_->GetUniformLocation(cast.program, name); + GL_ASSERT; + SRB2_ASSERT(location >= 0); + gl_->Uniform2f(location, value.x, value.y); + GL_ASSERT; +} + +void Gl2Rhi::set_uniform(const char* name, glm::vec3 value) +{ + SRB2_ASSERT(current_program_.has_value()); + Gl2Program& cast = program_slab_[*current_program_]; + GLint location = gl_->GetUniformLocation(cast.program, name); + GL_ASSERT; + SRB2_ASSERT(location >= 0); + gl_->Uniform3f(location, value.x, value.y, value.z); + GL_ASSERT; +} + +void Gl2Rhi::set_uniform(const char* name, glm::vec4 value) +{ + SRB2_ASSERT(current_program_.has_value()); + Gl2Program& cast = program_slab_[*current_program_]; + GLint location = gl_->GetUniformLocation(cast.program, name); + GL_ASSERT; + SRB2_ASSERT(location >= 0); + gl_->Uniform4f(location, value.x, value.y, value.z, value.w); + GL_ASSERT; +} + +void Gl2Rhi::set_uniform(const char* name, glm::ivec2 value) +{ + SRB2_ASSERT(current_program_.has_value()); + Gl2Program& cast = program_slab_[*current_program_]; + GLint location = gl_->GetUniformLocation(cast.program, name); + GL_ASSERT; + SRB2_ASSERT(location >= 0); + gl_->Uniform2i(location, value.x, value.y); + GL_ASSERT; +} + +void Gl2Rhi::set_uniform(const char* name, glm::ivec3 value) +{ + SRB2_ASSERT(current_program_.has_value()); + Gl2Program& cast = program_slab_[*current_program_]; + GLint location = gl_->GetUniformLocation(cast.program, name); + GL_ASSERT; + SRB2_ASSERT(location >= 0); + gl_->Uniform3i(location, value.x, value.y, value.z); + GL_ASSERT; +} + +void Gl2Rhi::set_uniform(const char* name, glm::ivec4 value) +{ + SRB2_ASSERT(current_program_.has_value()); + Gl2Program& cast = program_slab_[*current_program_]; + GLint location = gl_->GetUniformLocation(cast.program, name); + GL_ASSERT; + SRB2_ASSERT(location >= 0); + gl_->Uniform4i(location, value.x, value.y, value.z, value.w); + GL_ASSERT; +} + +void Gl2Rhi::set_uniform(const char* name, glm::mat2 value) +{ + SRB2_ASSERT(current_program_.has_value()); + Gl2Program& cast = program_slab_[*current_program_]; + GLint location = gl_->GetUniformLocation(cast.program, name); + GL_ASSERT; + SRB2_ASSERT(location >= 0); + gl_->UniformMatrix2fv(location, 1, false, glm::value_ptr(value)); + GL_ASSERT; +} + +void Gl2Rhi::set_uniform(const char* name, glm::mat3 value) +{ + SRB2_ASSERT(current_program_.has_value()); + Gl2Program& cast = program_slab_[*current_program_]; + GLint location = gl_->GetUniformLocation(cast.program, name); + GL_ASSERT; + SRB2_ASSERT(location >= 0); + gl_->UniformMatrix3fv(location, 1, false, glm::value_ptr(value)); + GL_ASSERT; +} + +void Gl2Rhi::set_uniform(const char* name, glm::mat4 value) +{ + SRB2_ASSERT(current_program_.has_value()); + Gl2Program& cast = program_slab_[*current_program_]; + GLint location = gl_->GetUniformLocation(cast.program, name); + GL_ASSERT; + SRB2_ASSERT(location >= 0); + gl_->UniformMatrix4fv(location, 1, false, glm::value_ptr(value)); + GL_ASSERT; +} + +void Gl2Rhi::set_sampler(const char* name, uint32_t slot, Handle texture) +{ + SRB2_ASSERT(slot >= 0 && slot < kMaxSamplers); + SRB2_ASSERT(current_program_.has_value() && render_pass_stack_.empty() == false); + + Gl2Program& prog = program_slab_[*current_program_]; + SRB2_ASSERT(texture_slab_.is_valid(texture)); + Gl2Texture& tex = texture_slab_[texture]; + GLint location = gl_->GetUniformLocation(prog.program, name); + GL_ASSERT; + SRB2_ASSERT(location >= 0); + GLenum active_texture = GL_TEXTURE0 + slot; + GLuint uniform_value = slot; + gl_->ActiveTexture(active_texture); + GL_ASSERT; + gl_->BindTexture(GL_TEXTURE_2D, tex.texture); + GL_ASSERT; + gl_->Uniform1i(location, uniform_value); + GL_ASSERT; + gl_->ActiveTexture(GL_TEXTURE0); + GL_ASSERT; +} + +void Gl2Rhi::set_rasterizer_state(const RasterizerStateDesc& desc) +{ + current_primitive_type_ = desc.primitive; + if (desc.cull == CullMode::kNone) + { + gl_->Disable(GL_CULL_FACE); + } + else + { + gl_->Enable(GL_CULL_FACE); + gl_->CullFace(map_cull_mode(desc.cull)); + } + gl_->ColorMask(desc.color_mask.r, desc.color_mask.g, desc.color_mask.b, desc.color_mask.a); + + GL_ASSERT; + gl_->FrontFace(map_winding(desc.winding)); + GL_ASSERT; + if (desc.blend_enabled) + { + gl_->Enable(GL_BLEND); + } + else + { + gl_->Disable(GL_BLEND); + } + GL_ASSERT; + gl_->BlendFuncSeparate( + map_blend_factor(desc.blend_source_factor_color), + map_blend_factor(desc.blend_dest_factor_color), + map_blend_factor(desc.blend_source_factor_alpha), + map_blend_factor(desc.blend_dest_factor_alpha) + ); + GL_ASSERT; + gl_->BlendEquationSeparate( + map_blend_function(desc.blend_color_function), + map_blend_function(desc.blend_alpha_function) + ); + GL_ASSERT; + gl_->BlendColor(desc.blend_color.r, desc.blend_color.g, desc.blend_color.b, desc.blend_color.a); + GL_ASSERT; + if (desc.depth_test) + { + gl_->Enable(GL_DEPTH_TEST); + } + else + { + gl_->Disable(GL_DEPTH_TEST); + } + GL_ASSERT; + gl_->DepthMask(desc.depth_write); + GL_ASSERT; + gl_->DepthFunc(map_compare_func(desc.depth_func)); + GL_ASSERT; + if (desc.stencil_test) + { + gl_->Enable(GL_STENCIL_TEST); + } + else + { + gl_->Disable(GL_STENCIL_TEST); + } + GL_ASSERT; + stencil_front_reference_ = 0; + stencil_back_reference_ = 0; + stencil_front_compare_mask_ = 0xFF; + stencil_back_compare_mask_ = 0xFF; + stencil_front_write_mask_ = 0xFF; + stencil_back_write_mask_ = 0xFF; + stencil_front_func_ = desc.front_stencil_compare; + stencil_back_func_ = desc.back_stencil_compare; + gl_->StencilFuncSeparate( + GL_FRONT, + map_compare_func(stencil_front_func_), + stencil_front_reference_, + stencil_front_compare_mask_ + ); + GL_ASSERT; + gl_->StencilFuncSeparate( + GL_BACK, + map_compare_func(stencil_back_func_), + stencil_back_reference_, + stencil_back_compare_mask_ + ); + GL_ASSERT; + gl_->StencilMaskSeparate(GL_FRONT, stencil_front_write_mask_); + GL_ASSERT; + gl_->StencilMaskSeparate(GL_BACK, stencil_back_write_mask_); + GL_ASSERT; + + gl_->StencilOpSeparate( + GL_FRONT, + map_stencil_op(desc.front_fail), + map_stencil_op(desc.front_depth_fail), + map_stencil_op(desc.front_pass) + ); + GL_ASSERT; + gl_->StencilOpSeparate( + GL_BACK, + map_stencil_op(desc.back_fail), + map_stencil_op(desc.back_depth_fail), + map_stencil_op(desc.back_pass) + ); + GL_ASSERT; + if (desc.scissor_test) + { + gl_->Enable(GL_SCISSOR_TEST); + GL_ASSERT; + } + else + { + gl_->Disable(GL_SCISSOR_TEST); + } + gl_->Scissor(desc.scissor.x, desc.scissor.y, desc.scissor.w, desc.scissor.h); + GL_ASSERT; +} + +void Gl2Rhi::set_viewport(const Rect& rect) +{ + SRB2_ASSERT(render_pass_stack_.empty() == false); gl_->Viewport(rect.x, rect.y, rect.w, rect.h); GL_ASSERT; } -void Gl2Rhi::draw(Handle ctx, uint32_t vertex_count, uint32_t first_vertex) +void Gl2Rhi::draw(uint32_t vertex_count, uint32_t first_vertex) { - SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); - SRB2_ASSERT(current_render_pass_.has_value() == true && current_pipeline_.has_value() == true); + SRB2_ASSERT(render_pass_stack_.empty() == false); gl_->DrawArrays(map_primitive_mode(current_primitive_type_), first_vertex, vertex_count); GL_ASSERT; } -void Gl2Rhi::draw_indexed(Handle ctx, uint32_t index_count, uint32_t first_index) +void Gl2Rhi::draw_indexed(uint32_t index_count, uint32_t first_index) { - SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); - SRB2_ASSERT(current_index_buffer_ != kNullHandle); #ifndef NDEBUG { @@ -1748,10 +1469,9 @@ void Gl2Rhi::draw_indexed(Handle ctx, uint32_t index_count, uin GL_ASSERT; } -void Gl2Rhi::read_pixels(Handle ctx, const Rect& rect, PixelFormat format, tcb::span out) +void Gl2Rhi::read_pixels(const Rect& rect, PixelFormat format, tcb::span out) { - SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); - SRB2_ASSERT(current_render_pass_.has_value()); + SRB2_ASSERT(render_pass_stack_.empty() == false); std::tuple gl_format = map_pixel_data_format(format); GLenum layout = std::get<0>(gl_format); @@ -1777,7 +1497,7 @@ void Gl2Rhi::read_pixels(Handle ctx, const Rect& rect, PixelFor src_dim = {0, 0, attach_tex.desc.width, attach_tex.desc.height}; } }; - std::visit(render_pass_visitor, *current_render_pass_); + std::visit(render_pass_visitor, *render_pass_stack_.rbegin()); SRB2_ASSERT(rect.x >= 0); SRB2_ASSERT(rect.y >= 0); @@ -1792,21 +1512,17 @@ void Gl2Rhi::read_pixels(Handle ctx, const Rect& rect, PixelFor GL_ASSERT; } -void Gl2Rhi::set_stencil_reference(Handle ctx, CullMode face, uint8_t reference) +void Gl2Rhi::set_stencil_reference(CullMode face, uint8_t reference) { SRB2_ASSERT(face != CullMode::kNone); - SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); - SRB2_ASSERT(current_render_pass_.has_value()); - SRB2_ASSERT(current_pipeline_.has_value()); - - auto& pl = pipeline_slab_[*current_pipeline_]; + SRB2_ASSERT(render_pass_stack_.empty() == false); if (face == CullMode::kFront) { stencil_front_reference_ = reference; gl_->StencilFuncSeparate( GL_FRONT, - map_compare_func(pl.desc.depth_stencil_state->front.stencil_compare), + map_compare_func(stencil_front_func_), stencil_front_reference_, stencil_front_compare_mask_ ); @@ -1816,28 +1532,24 @@ void Gl2Rhi::set_stencil_reference(Handle ctx, CullMode face, u stencil_back_reference_ = reference; gl_->StencilFuncSeparate( GL_BACK, - map_compare_func(pl.desc.depth_stencil_state->back.stencil_compare), + map_compare_func(stencil_back_func_), stencil_back_reference_, stencil_back_compare_mask_ ); } } -void Gl2Rhi::set_stencil_compare_mask(Handle ctx, CullMode face, uint8_t compare_mask) +void Gl2Rhi::set_stencil_compare_mask(CullMode face, uint8_t compare_mask) { SRB2_ASSERT(face != CullMode::kNone); - SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); - SRB2_ASSERT(current_render_pass_.has_value()); - SRB2_ASSERT(current_pipeline_.has_value()); - - auto& pl = pipeline_slab_[*current_pipeline_]; + SRB2_ASSERT(render_pass_stack_.empty() == false); if (face == CullMode::kFront) { stencil_front_compare_mask_ = compare_mask; gl_->StencilFuncSeparate( GL_FRONT, - map_compare_func(pl.desc.depth_stencil_state->front.stencil_compare), + map_compare_func(stencil_front_func_), stencil_front_reference_, stencil_front_compare_mask_ ); @@ -1847,19 +1559,17 @@ void Gl2Rhi::set_stencil_compare_mask(Handle ctx, CullMode face stencil_back_compare_mask_ = compare_mask; gl_->StencilFuncSeparate( GL_BACK, - map_compare_func(pl.desc.depth_stencil_state->back.stencil_compare), + map_compare_func(stencil_back_func_), stencil_back_reference_, stencil_back_compare_mask_ ); } } -void Gl2Rhi::set_stencil_write_mask(Handle ctx, CullMode face, uint8_t write_mask) +void Gl2Rhi::set_stencil_write_mask(CullMode face, uint8_t write_mask) { SRB2_ASSERT(face != CullMode::kNone); - SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); - SRB2_ASSERT(current_render_pass_.has_value()); - SRB2_ASSERT(current_pipeline_.has_value()); + SRB2_ASSERT(render_pass_stack_.empty() == false); if (face == CullMode::kFront) { @@ -1910,11 +1620,6 @@ uint32_t Gl2Rhi::get_buffer_size(Handle buffer) void Gl2Rhi::finish() { - SRB2_ASSERT(graphics_context_active_ == false); - - binding_set_slab_.clear(); - uniform_set_slab_.clear(); - // I sure hope creating FBOs isn't costly on the driver! for (auto& fbset : framebuffers_) { @@ -1925,14 +1630,12 @@ void Gl2Rhi::finish() } void Gl2Rhi::copy_framebuffer_to_texture( - Handle ctx, Handle dst_tex, const Rect& dst_region, const Rect& src_region ) { - SRB2_ASSERT(graphics_context_active_ == true); - SRB2_ASSERT(current_render_pass_.has_value()); + SRB2_ASSERT(render_pass_stack_.empty() == false); SRB2_ASSERT(texture_slab_.is_valid(dst_tex)); auto& tex = texture_slab_[dst_tex]; @@ -1957,7 +1660,7 @@ void Gl2Rhi::copy_framebuffer_to_texture( src_dim = {0, 0, attach_tex.desc.width, attach_tex.desc.height}; } }; - std::visit(render_pass_visitor, *current_render_pass_); + std::visit(render_pass_visitor, *render_pass_stack_.rbegin()); SRB2_ASSERT(src_region.x >= 0); SRB2_ASSERT(src_region.y >= 0); diff --git a/src/rhi/gl2/gl2_rhi.hpp b/src/rhi/gl2/gl2_rhi.hpp index 93f04446e..f8b62987b 100644 --- a/src/rhi/gl2/gl2_rhi.hpp +++ b/src/rhi/gl2/gl2_rhi.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,11 +14,11 @@ #include #include #include -#include #include -#include -#include +#include "../../core/hash_map.hpp" +#include "../../core/string.h" +#include "../../core/vector.hpp" #include "../rhi.hpp" namespace srb2::rhi @@ -71,7 +71,7 @@ struct Gl2Platform virtual ~Gl2Platform(); virtual void present() = 0; - virtual std::tuple, std::vector> find_shader_sources(PipelineProgram program) = 0; + virtual std::tuple, srb2::Vector> find_shader_sources(const char* name) = 0; virtual Rect get_default_framebuffer_dimensions() = 0; }; @@ -87,41 +87,19 @@ struct Gl2Buffer : public rhi::Buffer rhi::BufferDesc desc; }; -struct Gl2RenderPass : public rhi::RenderPass -{ - rhi::RenderPassDesc desc; -}; - struct Gl2Renderbuffer : public rhi::Renderbuffer { uint32_t renderbuffer; rhi::RenderbufferDesc desc; }; -struct Gl2UniformSet : public rhi::UniformSet -{ - std::vector uniforms; -}; - -struct Gl2BindingSet : public rhi::BindingSet -{ - std::vector vertex_buffer_bindings; - std::unordered_map textures {4}; -}; - -struct Gl2Pipeline : public rhi::Pipeline +struct Gl2Program : public rhi::Program { uint32_t vertex_shader = 0; uint32_t fragment_shader = 0; uint32_t program = 0; - std::unordered_map attrib_locations {2}; - std::unordered_map uniform_locations {2}; - std::unordered_map sampler_locations {2}; - rhi::PipelineDesc desc; -}; - -struct Gl2GraphicsContext : public rhi::GraphicsContext -{ + srb2::HashMap attrib_locations; + srb2::HashMap uniform_locations; }; struct Gl2ActiveUniform @@ -136,27 +114,23 @@ class Gl2Rhi final : public Rhi std::unique_ptr gl_; - Slab render_pass_slab_; Slab texture_slab_; Slab buffer_slab_; Slab renderbuffer_slab_; - Slab pipeline_slab_; - Slab uniform_set_slab_; - Slab binding_set_slab_; + Slab program_slab_; Handle current_index_buffer_; - std::unordered_map framebuffers_ {16}; + srb2::HashMap framebuffers_ {16}; struct DefaultRenderPassState { + bool clear = false; }; using RenderPassState = std::variant; - std::optional current_render_pass_; - std::optional> current_pipeline_; + srb2::Vector render_pass_stack_; + std::optional> current_program_; PrimitiveType current_primitive_type_ = PrimitiveType::kPoints; - bool graphics_context_active_ = false; - uint32_t graphics_context_generation_ = 1; uint32_t index_buffer_offset_ = 0; uint8_t stencil_front_reference_ = 0; @@ -165,15 +139,18 @@ class Gl2Rhi final : public Rhi uint8_t stencil_back_reference_ = 0; uint8_t stencil_back_compare_mask_ = 0xFF; uint8_t stencil_back_write_mask_ = 0xFF; + CompareFunc stencil_front_func_; + CompareFunc stencil_back_func_; + + void apply_default_framebuffer(bool clear); + void apply_framebuffer(const RenderPassBeginInfo& info, bool allow_clear); public: Gl2Rhi(std::unique_ptr&& platform, GlLoadFunc load_func); virtual ~Gl2Rhi(); - virtual Handle create_render_pass(const RenderPassDesc& desc) override; - virtual void destroy_render_pass(Handle handle) override; - virtual Handle create_pipeline(const PipelineDesc& desc) override; - virtual void destroy_pipeline(Handle handle) override; + virtual Handle create_program(const ProgramDesc& desc) override; + virtual void destroy_program(Handle handle) override; virtual Handle create_texture(const TextureDesc& desc) override; virtual void destroy_texture(Handle handle) override; @@ -187,58 +164,63 @@ public: virtual uint32_t get_buffer_size(Handle buffer) override; virtual void update_buffer( - Handle ctx, Handle buffer, uint32_t offset, tcb::span data ) override; virtual void update_texture( - Handle ctx, Handle texture, Rect region, srb2::rhi::PixelFormat data_format, tcb::span data ) override; virtual void update_texture_settings( - Handle ctx, Handle texture, TextureWrapMode u_wrap, TextureWrapMode v_wrap, TextureFilterMode min, TextureFilterMode mag ) override; - virtual Handle - create_uniform_set(Handle ctx, const CreateUniformSetInfo& info) override; - virtual Handle - create_binding_set(Handle ctx, Handle pipeline, const CreateBindingSetInfo& info) - override; - - virtual Handle begin_graphics() override; - virtual void end_graphics(Handle ctx) override; // Graphics context functions - virtual void begin_default_render_pass(Handle ctx, bool clear) override; - virtual void begin_render_pass(Handle ctx, const RenderPassBeginInfo& info) override; - virtual void end_render_pass(Handle ctx) override; - virtual void bind_pipeline(Handle ctx, Handle pipeline) override; - virtual void bind_uniform_set(Handle ctx, uint32_t slot, Handle set) override; - virtual void bind_binding_set(Handle ctx, Handle set) override; - virtual void bind_index_buffer(Handle ctx, Handle buffer) override; - virtual void set_scissor(Handle ctx, const Rect& rect) override; - virtual void set_viewport(Handle ctx, const Rect& rect) override; - virtual void draw(Handle ctx, uint32_t vertex_count, uint32_t first_vertex) override; - virtual void draw_indexed(Handle ctx, uint32_t index_count, uint32_t first_index) override; + virtual void push_default_render_pass(bool clear) override; + virtual void push_render_pass(const RenderPassBeginInfo& info) override; + virtual void pop_render_pass() override; + virtual void bind_program(Handle program) override; + virtual void bind_vertex_attrib( + const char* name, + Handle buffer, + VertexAttributeFormat format, + uint32_t offset, + uint32_t stride + ) override; + virtual void bind_index_buffer(Handle buffer) override; + virtual void set_uniform(const char* name, float value) override; + virtual void set_uniform(const char* name, int value) override; + virtual void set_uniform(const char* name, glm::vec2 value) override; + virtual void set_uniform(const char* name, glm::vec3 value) override; + virtual void set_uniform(const char* name, glm::vec4 value) override; + virtual void set_uniform(const char* name, glm::ivec2 value) override; + virtual void set_uniform(const char* name, glm::ivec3 value) override; + virtual void set_uniform(const char* name, glm::ivec4 value) override; + virtual void set_uniform(const char* name, glm::mat2 value) override; + virtual void set_uniform(const char* name, glm::mat3 value) override; + virtual void set_uniform(const char* name, glm::mat4 value) override; + virtual void set_sampler(const char* name, uint32_t slot, Handle texture) override; + virtual void set_rasterizer_state(const RasterizerStateDesc& desc) override; + virtual void set_viewport(const Rect& rect) override; + virtual void draw(uint32_t vertex_count, uint32_t first_vertex) override; + virtual void draw_indexed(uint32_t index_count, uint32_t first_index) override; virtual void - read_pixels(Handle ctx, const Rect& rect, PixelFormat format, tcb::span out) override; + read_pixels(const Rect& rect, PixelFormat format, tcb::span out) override; virtual void copy_framebuffer_to_texture( - Handle ctx, Handle dst_tex, const Rect& dst_region, const Rect& src_region ) override; - virtual void set_stencil_reference(Handle ctx, CullMode face, uint8_t reference) override; - virtual void set_stencil_compare_mask(Handle ctx, CullMode face, uint8_t mask) override; - virtual void set_stencil_write_mask(Handle ctx, CullMode face, uint8_t mask) override; + virtual void set_stencil_reference(CullMode face, uint8_t reference) override; + virtual void set_stencil_compare_mask(CullMode face, uint8_t mask) override; + virtual void set_stencil_write_mask(CullMode face, uint8_t mask) override; virtual void present() override; diff --git a/src/rhi/gles2/gles2_rhi.cpp b/src/rhi/gles2/gles2_rhi.cpp index 3bdfff0f5..d7113f2cd 100644 --- a/src/rhi/gles2/gles2_rhi.cpp +++ b/src/rhi/gles2/gles2_rhi.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -923,7 +923,7 @@ void Gles2Rhi::present() platform_->present(); } -void Gles2Rhi::begin_default_render_pass(Handle ctx) +void Gles2Rhi::push_default_render_pass(Handle ctx) { SRB2_ASSERT(platform_ != nullptr); SRB2_ASSERT(graphics_context_active_ == true); @@ -942,7 +942,7 @@ void Gles2Rhi::begin_default_render_pass(Handle ctx) current_render_pass_ = Gles2Rhi::DefaultRenderPassState {}; } -void Gles2Rhi::begin_render_pass(Handle ctx, const RenderPassBeginInfo& info) +void Gles2Rhi::push_render_pass(Handle ctx, const RenderPassBeginInfo& info) { SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); SRB2_ASSERT(current_render_pass_.has_value() == false); @@ -981,7 +981,7 @@ void Gles2Rhi::begin_render_pass(Handle ctx, const RenderPassBe current_render_pass_ = info; } -void Gles2Rhi::end_render_pass(Handle ctx) +void Gles2Rhi::pop_render_pass(Handle ctx) { SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation()); SRB2_ASSERT(current_render_pass_.has_value() == true); diff --git a/src/rhi/gles2/gles2_rhi.hpp b/src/rhi/gles2/gles2_rhi.hpp index d9e7a63cb..5c55bd56c 100644 --- a/src/rhi/gles2/gles2_rhi.hpp +++ b/src/rhi/gles2/gles2_rhi.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -133,9 +133,9 @@ public: virtual void end_graphics(Handle&& ctx) override; // Graphics context functions - virtual void begin_default_render_pass(Handle ctx) override; - virtual void begin_render_pass(Handle ctx, const RenderPassBeginInfo& info) override; - virtual void end_render_pass(Handle ctx) override; + virtual void push_default_render_pass(Handle ctx) override; + virtual void push_render_pass(Handle ctx, const RenderPassBeginInfo& info) override; + virtual void pop_render_pass(Handle ctx) override; virtual void bind_pipeline(Handle ctx, Handle pipeline) override; virtual void update_bindings(Handle ctx, const UpdateBindingsInfo& info) override; virtual void update_uniforms(Handle ctx, tcb::span uniforms) override; diff --git a/src/rhi/handle.hpp b/src/rhi/handle.hpp index 434172776..8101d9482 100644 --- a/src/rhi/handle.hpp +++ b/src/rhi/handle.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -44,20 +44,11 @@ public: Handle(NullHandleType) noexcept : Handle() {} - Handle(const Handle&) = default; - Handle(Handle&& rhs) noexcept - { - id_ = std::exchange(rhs.id_, 0); - generation_ = std::exchange(rhs.generation_, 0); - }; + Handle(const Handle&) noexcept = default; + Handle(Handle&&) noexcept = default; - Handle& operator=(const Handle&) = default; - Handle& operator=(Handle&& rhs) noexcept - { - id_ = std::exchange(rhs.id_, 0); - generation_ = std::exchange(rhs.generation_, 0); - return *this; - } + Handle& operator=(const Handle&) noexcept = default; + Handle& operator=(Handle&&) noexcept = default; // Conversions from Handles of derived type U to base type T diff --git a/src/rhi/rhi.cpp b/src/rhi/rhi.cpp index a2da54b1f..905a64f28 100644 --- a/src/rhi/rhi.cpp +++ b/src/rhi/rhi.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/rhi/rhi.hpp b/src/rhi/rhi.hpp index 1a6f68236..e689967ec 100644 --- a/src/rhi/rhi.hpp +++ b/src/rhi/rhi.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -13,9 +13,7 @@ #include #include -#include #include -#include #include #include @@ -26,6 +24,7 @@ #include #include "../core/static_vec.hpp" +#include "glm/ext/vector_float2.hpp" #include "handle.hpp" namespace srb2::rhi @@ -40,11 +39,8 @@ struct Texture { }; -struct Pipeline -{ -}; - -struct RenderPass +/// @brief A linked rendering pipeline program combining a vertex shader and fragment shader. +struct Program { }; @@ -403,16 +399,6 @@ struct ColorMask bool a; }; -struct BlendDesc -{ - BlendFactor source_factor_color; - BlendFactor dest_factor_color; - BlendFunction color_function; - BlendFactor source_factor_alpha; - BlendFactor dest_factor_alpha; - BlendFunction alpha_function; -}; - enum class StencilOp { kKeep, @@ -425,53 +411,41 @@ enum class StencilOp kDecrementWrap }; -struct PipelineStencilOpStateDesc + +struct ProgramDesc { - StencilOp fail; - StencilOp pass; - StencilOp depth_fail; - CompareFunc stencil_compare; + const char* name; + tcb::span defines; }; -struct PipelineDepthStencilStateDesc +struct RasterizerStateDesc { - bool depth_test; - bool depth_write; - CompareFunc depth_func; - bool stencil_test; - PipelineStencilOpStateDesc front; - PipelineStencilOpStateDesc back; -}; - -struct PipelineColorStateDesc -{ - std::optional blend; - ColorMask color_mask; -}; - -struct PipelineDesc -{ - PipelineProgram program; - VertexInputDesc vertex_input; - UniformInputDesc uniform_input; - SamplerInputDesc sampler_input; - std::optional depth_stencil_state; - PipelineColorStateDesc color_state; - PrimitiveType primitive; - CullMode cull; - FaceWinding winding; - glm::vec4 blend_color; -}; - -struct RenderPassDesc -{ - bool use_depth_stencil; - AttachmentLoadOp color_load_op; - AttachmentStoreOp color_store_op; - AttachmentLoadOp depth_load_op; - AttachmentStoreOp depth_store_op; - AttachmentLoadOp stencil_load_op; - AttachmentStoreOp stencil_store_op; + PrimitiveType primitive = PrimitiveType::kTriangles; + CullMode cull = CullMode::kBack; + FaceWinding winding = FaceWinding::kCounterClockwise; + ColorMask color_mask = {true, true, true, true}; + bool blend_enabled = false; + glm::vec4 blend_color = {0.0, 0.0, 0.0, 0.0}; + BlendFactor blend_source_factor_color = BlendFactor::kOne; + BlendFactor blend_dest_factor_color = BlendFactor::kZero; + BlendFunction blend_color_function = BlendFunction::kAdd; + BlendFactor blend_source_factor_alpha = BlendFactor::kOne; + BlendFactor blend_dest_factor_alpha = BlendFactor::kZero; + BlendFunction blend_alpha_function = BlendFunction::kAdd; + bool depth_test = false; + bool depth_write = true; + CompareFunc depth_func = CompareFunc::kLess; + bool stencil_test = false; + StencilOp front_fail = StencilOp::kKeep; + StencilOp front_pass = StencilOp::kKeep; + StencilOp front_depth_fail = StencilOp::kKeep; + CompareFunc front_stencil_compare = CompareFunc::kAlways; + StencilOp back_fail = StencilOp::kKeep; + StencilOp back_pass = StencilOp::kKeep; + StencilOp back_depth_fail = StencilOp::kKeep; + CompareFunc back_stencil_compare = CompareFunc::kAlways; + bool scissor_test = false; + Rect scissor = {}; }; struct RenderbufferDesc @@ -513,10 +487,15 @@ struct BufferDesc struct RenderPassBeginInfo { - Handle render_pass; Handle color_attachment; std::optional> depth_stencil_attachment; glm::vec4 clear_color; + AttachmentLoadOp color_load_op; + AttachmentStoreOp color_store_op; + AttachmentLoadOp depth_load_op; + AttachmentStoreOp depth_store_op; + AttachmentLoadOp stencil_load_op; + AttachmentStoreOp stencil_store_op; }; using UniformVariant = std::variant< @@ -577,17 +556,6 @@ struct CreateBindingSetInfo tcb::span sampler_textures; }; -struct UniformSet -{ -}; -struct BindingSet -{ -}; - -struct GraphicsContext -{ -}; - struct TextureDetails { uint32_t width; @@ -604,10 +572,8 @@ struct Rhi { virtual ~Rhi(); - virtual Handle create_render_pass(const RenderPassDesc& desc) = 0; - virtual void destroy_render_pass(Handle handle) = 0; - virtual Handle create_pipeline(const PipelineDesc& desc) = 0; - virtual void destroy_pipeline(Handle handle) = 0; + virtual Handle create_program(const ProgramDesc& desc) = 0; + virtual void destroy_program(Handle handle) = 0; virtual Handle create_texture(const TextureDesc& desc) = 0; virtual void destroy_texture(Handle handle) = 0; @@ -621,56 +587,63 @@ struct Rhi virtual uint32_t get_buffer_size(Handle buffer) = 0; virtual void update_buffer( - Handle ctx, Handle buffer, uint32_t offset, tcb::span data ) = 0; virtual void update_texture( - Handle ctx, Handle texture, Rect region, srb2::rhi::PixelFormat data_format, tcb::span data ) = 0; virtual void update_texture_settings( - Handle ctx, Handle texture, TextureWrapMode u_wrap, TextureWrapMode v_wrap, TextureFilterMode min, TextureFilterMode mag ) = 0; - virtual Handle create_uniform_set(Handle ctx, const CreateUniformSetInfo& info) = 0; - virtual Handle - create_binding_set(Handle ctx, Handle pipeline, const CreateBindingSetInfo& info) = 0; - - virtual Handle begin_graphics() = 0; - virtual void end_graphics(Handle ctx) = 0; // Graphics context functions - virtual void begin_default_render_pass(Handle ctx, bool clear) = 0; - virtual void begin_render_pass(Handle ctx, const RenderPassBeginInfo& info) = 0; - virtual void end_render_pass(Handle ctx) = 0; - virtual void bind_pipeline(Handle ctx, Handle pipeline) = 0; - virtual void bind_uniform_set(Handle ctx, uint32_t slot, Handle set) = 0; - virtual void bind_binding_set(Handle ctx, Handle set) = 0; - virtual void bind_index_buffer(Handle ctx, Handle buffer) = 0; - virtual void set_scissor(Handle ctx, const Rect& rect) = 0; - virtual void set_viewport(Handle ctx, const Rect& rect) = 0; - virtual void draw(Handle ctx, uint32_t vertex_count, uint32_t first_vertex) = 0; - virtual void draw_indexed(Handle ctx, uint32_t index_count, uint32_t first_index) = 0; + virtual void push_default_render_pass(bool clear) = 0; + virtual void push_render_pass(const RenderPassBeginInfo& info) = 0; + virtual void pop_render_pass() = 0; + virtual void bind_program(Handle program) = 0; + virtual void bind_vertex_attrib( + const char* name, + Handle buffer, + VertexAttributeFormat format, + uint32_t offset, + uint32_t stride + ) = 0; + virtual void bind_index_buffer(Handle buffer) = 0; + virtual void set_uniform(const char* name, float value) = 0; + virtual void set_uniform(const char* name, int value) = 0; + virtual void set_uniform(const char* name, glm::vec2 value) = 0; + virtual void set_uniform(const char* name, glm::vec3 value) = 0; + virtual void set_uniform(const char* name, glm::vec4 value) = 0; + virtual void set_uniform(const char* name, glm::ivec2 value) = 0; + virtual void set_uniform(const char* name, glm::ivec3 value) = 0; + virtual void set_uniform(const char* name, glm::ivec4 value) = 0; + virtual void set_uniform(const char* name, glm::mat2 value) = 0; + virtual void set_uniform(const char* name, glm::mat3 value) = 0; + virtual void set_uniform(const char* name, glm::mat4 value) = 0; + virtual void set_sampler(const char* name, uint32_t slot, Handle texture) = 0; + virtual void set_rasterizer_state(const RasterizerStateDesc& desc) = 0; + virtual void set_viewport(const Rect& rect) = 0; + virtual void draw(uint32_t vertex_count, uint32_t first_vertex) = 0; + virtual void draw_indexed(uint32_t index_count, uint32_t first_index) = 0; virtual void - read_pixels(Handle ctx, const Rect& rect, PixelFormat format, tcb::span out) = 0; + read_pixels(const Rect& rect, PixelFormat format, tcb::span out) = 0; virtual void copy_framebuffer_to_texture( - Handle ctx, Handle dst_tex, const Rect& dst_region, const Rect& src_region ) = 0; - virtual void set_stencil_reference(Handle ctx, CullMode face, uint8_t reference) = 0; - virtual void set_stencil_compare_mask(Handle ctx, CullMode face, uint8_t mask) = 0; - virtual void set_stencil_write_mask(Handle ctx, CullMode face, uint8_t mask) = 0; + virtual void set_stencil_reference(CullMode face, uint8_t reference) = 0; + virtual void set_stencil_compare_mask(CullMode face, uint8_t mask) = 0; + virtual void set_stencil_write_mask(CullMode face, uint8_t mask) = 0; virtual void present() = 0; diff --git a/src/rhi/shader_load_context.cpp b/src/rhi/shader_load_context.cpp index c07a5b0b8..ad3ffcf81 100644 --- a/src/rhi/shader_load_context.cpp +++ b/src/rhi/shader_load_context.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -12,6 +12,9 @@ #include +#include "../core/string.h" +#include "../core/vector.hpp" + using namespace srb2; using namespace rhi; @@ -19,27 +22,27 @@ ShaderLoadContext::ShaderLoadContext() = default; void ShaderLoadContext::set_version(std::string_view version) { - version_ = fmt::format("#version {}\n", version); + version_ = srb2::format("#version {}\n", version); } -void ShaderLoadContext::add_source(const std::string& source) +void ShaderLoadContext::add_source(const srb2::String& source) { sources_.push_back(source); } -void ShaderLoadContext::add_source(std::string&& source) +void ShaderLoadContext::add_source(srb2::String&& source) { sources_.push_back(std::move(source)); } void ShaderLoadContext::define(std::string_view name) { - defines_.append(fmt::format("#define {}\n", name)); + defines_.append(srb2::format("#define {}\n", name)); } -std::vector ShaderLoadContext::get_sources_array() +srb2::Vector ShaderLoadContext::get_sources_array() { - std::vector ret; + srb2::Vector ret; ret.push_back(version_.c_str()); ret.push_back(defines_.c_str()); diff --git a/src/rhi/shader_load_context.hpp b/src/rhi/shader_load_context.hpp index 83b266a99..1bc458ea8 100644 --- a/src/rhi/shader_load_context.hpp +++ b/src/rhi/shader_load_context.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -11,29 +11,30 @@ #ifndef __SRB2_RHI_SHADER_LOAD_CONTEXT_HPP__ #define __SRB2_RHI_SHADER_LOAD_CONTEXT_HPP__ -#include #include -#include + +#include "../core/string.h" +#include "../core/vector.hpp" namespace srb2::rhi { class ShaderLoadContext { - std::string version_; - std::string defines_; - std::vector sources_; + srb2::String version_; + srb2::String defines_; + srb2::Vector sources_; public: ShaderLoadContext(); void set_version(std::string_view version); - void add_source(const std::string& source); - void add_source(std::string&& source); + void add_source(const srb2::String& source); + void add_source(srb2::String&& source); void define(std::string_view name); - std::vector get_sources_array(); + srb2::Vector get_sources_array(); }; }; // namespace srb2::rhi diff --git a/src/s_sound.c b/src/s_sound.c index 4369f9250..5b68ea049 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -266,6 +266,14 @@ boolean S_SoundDisabled(void) ); } +boolean S_VoiceDisabled(void) +{ + return ( + g_voice_disabled || + (g_fast_forward > 0) + ); +} + // Stop all sounds, load level info, THEN start sounds. void S_StopSounds(void) { @@ -676,6 +684,13 @@ void S_StopSound(void *origin) static INT32 actualsfxvolume; // check for change through console static INT32 actualdigmusicvolume; static INT32 actualmastervolume; +static INT32 actualvoicevolume; + +static boolean PointIsLeft(float ax, float ay, float bx, float by) +{ + // return (b.x - a.x)*(c.y - a.y) - (b.y - a.y)*(c.x - a.x) > 0; + return ax * by - ay * bx > 0; +} void S_UpdateSounds(void) { @@ -694,6 +709,8 @@ void S_UpdateSounds(void) S_SetMusicVolume(); if (actualmastervolume != cv_mastervolume.value) S_SetMasterVolume(); + if (actualvoicevolume != cv_voicevolume.value) + S_SetVoiceVolume(); // We're done now, if we're not in a level. if (gamestate != GS_LEVEL) @@ -853,6 +870,154 @@ notinlevel: I_UpdateSound(); } +void S_UpdateVoicePositionalProperties(void) +{ + int i; + + if (gamestate != GS_LEVEL) + { + for (i = 0; i < MAXPLAYERS; i++) + { + I_SetPlayerVoiceProperties(i, 1.0f, 0.0f); + } + return; + } + + player_t *consoleplr = &players[consoleplayer]; + listener_t listener = {0}; + mobj_t *listenmobj = NULL; + + if (consoleplr) + { + if (consoleplr->awayview.tics) + { + listenmobj = consoleplr->awayview.mobj; + } + else + { + listenmobj = consoleplr->mo; + } + + if (camera[0].chase && !consoleplr->awayview.tics) + { + listener.x = camera[0].x; + listener.y = camera[0].y; + listener.z = camera[0].z; + listener.angle = camera[0].angle; + } + else if (listenmobj) + { + listener.x = listenmobj->x; + listener.y = listenmobj->y; + listener.z = listenmobj->z; + listener.angle = listenmobj->angle; + } + } + + float playerdistances[MAXPLAYERS]; + for (i = 0; i < MAXPLAYERS; i++) + { + player_t *plr = &players[i]; + mobj_t *mo = plr->mo; + if (plr->spectator || !mo) + { + playerdistances[i] = 0.f; + continue; + } + float px = FixedToFloat(mo->x - listener.x); + float py = FixedToFloat(mo->y - listener.y); + float pz = FixedToFloat(mo->z - listener.z); + playerdistances[i] = sqrtf(px * px + py * py + pz * pz); + } + + // Positional voice audio + boolean voice_proximity_enabled = cv_voice_proximity.value == 1; + float voice_distanceattenuation_distance = FixedToFloat(cv_voice_distanceattenuation_distance.value) * FixedToFloat(mapheaderinfo[gamemap-1]->mobj_scale); + float voice_distanceattenuation_factor = FixedToFloat(cv_voice_distanceattenuation_factor.value); + float voice_stereopanning_factor = FixedToFloat(cv_voice_stereopanning_factor.value); + float voice_concurrentattenuation_min = max(0, min(MAXPLAYERS, cv_voice_concurrentattenuation_min.value)); + float voice_concurrentattenuation_max = max(0, min(MAXPLAYERS, cv_voice_concurrentattenuation_max.value)); + voice_concurrentattenuation_min = min(voice_concurrentattenuation_max, voice_concurrentattenuation_min); + float voice_concurrentattenuation_factor = FixedToFloat(cv_voice_concurrentattenuation_factor.value); + + // Derive concurrent speaker attenuation + float speakingplayers = 0; + for (i = 0; i < MAXPLAYERS; i++) + { + if (S_IsPlayerVoiceActive(i)) + { + if (voice_distanceattenuation_distance > 0) + { + speakingplayers += 1.f - (playerdistances[i] / voice_distanceattenuation_distance); + } + else + { + // invalid distance attenuation + speakingplayers += 1.f; + } + } + } + speakingplayers = min(voice_concurrentattenuation_max, max(0, speakingplayers - voice_concurrentattenuation_min)); + float speakingplayerattenuation = 1.f - (speakingplayers * (voice_concurrentattenuation_factor / voice_concurrentattenuation_max)); + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || !voice_proximity_enabled) + { + I_SetPlayerVoiceProperties(i, speakingplayerattenuation, 0.0f); + continue; + } + + if (i == consoleplayer) + { + I_SetPlayerVoiceProperties(i, speakingplayerattenuation, 0.0f); + continue; + } + + player_t *plr = &players[i]; + if (plr->spectator) + { + I_SetPlayerVoiceProperties(i, speakingplayerattenuation, 0.0f); + continue; + } + + mobj_t *plrmobj = plr->mo; + + if (!plrmobj) + { + I_SetPlayerVoiceProperties(i, 1.0f, 0.0f); + continue; + } + + float lx = FixedToFloat(listener.x); + float ly = FixedToFloat(listener.y); + float lz = FixedToFloat(listener.z); + float px = FixedToFloat(plrmobj->x) - lx; + float py = FixedToFloat(plrmobj->y) - ly; + float pz = FixedToFloat(plrmobj->z) - lz; + + float ldirx = cosf(ANG2RAD(listener.angle)); + float ldiry = sinf(ANG2RAD(listener.angle)); + float pdistance = sqrtf(px * px + py * py + pz * pz); + float p2ddistance = sqrtf(px * px + py * py); + float pdirx = px / p2ddistance; + float pdiry = py / p2ddistance; + float angle = acosf(pdirx * ldirx + pdiry * ldiry); + angle = PointIsLeft(ldirx, ldiry, pdirx, pdiry) ? -angle : angle; + + float plrvolume = 1.0f; + if (voice_distanceattenuation_distance > 0 && voice_distanceattenuation_factor >= 0 && voice_distanceattenuation_factor <= 1.0f) + { + float invfactor = 1.0f - voice_distanceattenuation_factor; + float distfactor = max(0.f, min(voice_distanceattenuation_distance, pdistance)) / voice_distanceattenuation_distance; + plrvolume = max(0.0f, min(1.0f, 1.0f - (invfactor * distfactor))); + } + + float fsep = sinf(angle) * max(0.0f, min(1.0f, voice_stereopanning_factor)); + I_SetPlayerVoiceProperties(i, plrvolume * speakingplayerattenuation, fsep); + } +} + void S_UpdateClosedCaptions(void) { UINT8 i; @@ -887,6 +1052,13 @@ void S_SetSfxVolume(void) I_SetSfxVolume(actualsfxvolume); } +void S_SetVoiceVolume(void) +{ + actualvoicevolume = cv_voicevolume.value; + + I_SetVoiceVolume(actualvoicevolume); +} + void S_SetMasterVolume(void) { actualmastervolume = cv_mastervolume.value; @@ -2639,6 +2811,18 @@ void GameDigiMusic_OnChange(void) } } +void VoiceChat_OnChange(void); +void weaponPrefChange(INT32 ssplayer); +void VoiceChat_OnChange(void) +{ + if (M_CheckParm("-novoice") || M_CheckParm("-noaudio")) + return; + + g_voice_disabled = !cv_voice_chat.value; + + weaponPrefChange(0); +} + void BGAudio_OnChange(void); void BGAudio_OnChange(void) { @@ -2656,3 +2840,53 @@ void BGAudio_OnChange(void) if (window_notinfocus && !(cv_bgaudio.value & 2)) S_StopSounds(); } + + +boolean S_SoundInputIsEnabled(void) +{ + return I_SoundInputIsEnabled(); +} + +boolean S_SoundInputSetEnabled(boolean enabled) +{ + return I_SoundInputSetEnabled(enabled); +} + +UINT32 S_SoundInputDequeueSamples(void *data, UINT32 len) +{ + return I_SoundInputDequeueSamples(data, len); +} + +static INT32 g_playerlastvoiceactive[MAXPLAYERS]; + +void S_QueueVoiceFrameFromPlayer(INT32 playernum, void *data, UINT32 len, boolean terminal) +{ + if (dedicated) + { + return; + } + if (cv_voice_chat.value != 0) + { + I_QueueVoiceFrameFromPlayer(playernum, data, len, terminal); + } +} + +void S_SetPlayerVoiceActive(INT32 playernum) +{ + g_playerlastvoiceactive[playernum] = I_GetTime(); +} + +boolean S_IsPlayerVoiceActive(INT32 playernum) +{ + return I_GetTime() - g_playerlastvoiceactive[playernum] < 5; +} + +void S_ResetVoiceQueue(INT32 playernum) +{ + if (dedicated) + { + return; + } + I_ResetVoiceQueue(playernum); + g_playerlastvoiceactive[playernum] = 0; +} diff --git a/src/s_sound.h b/src/s_sound.h index 1a7b6a8c9..f2d9f882a 100644 --- a/src/s_sound.h +++ b/src/s_sound.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -35,6 +35,7 @@ extern "C" { extern consvar_t stereoreverse; extern consvar_t cv_soundvolume, cv_closedcaptioning, cv_digmusicvolume; +extern consvar_t cv_voicevolume; extern consvar_t surround; extern consvar_t cv_numChannels; @@ -47,6 +48,22 @@ extern consvar_t cv_gamesounds; extern consvar_t cv_bgaudio; extern consvar_t cv_streamersafemusic; +extern consvar_t cv_voice_chat; +extern consvar_t cv_voice_mode; +extern consvar_t cv_voice_selfmute; +extern consvar_t cv_voice_loopback; +extern consvar_t cv_voice_inputamp; +extern consvar_t cv_voice_activationthreshold; +extern consvar_t cv_voice_proximity; +extern consvar_t cv_voice_distanceattenuation_distance; +extern consvar_t cv_voice_distanceattenuation_factor; +extern consvar_t cv_voice_stereopanning_factor; +extern consvar_t cv_voice_concurrentattenuation_factor; +extern consvar_t cv_voice_concurrentattenuation_min; +extern consvar_t cv_voice_concurrentattenuation_max; +extern float g_local_voice_last_peak; +extern boolean g_local_voice_detected; + typedef enum { SF_TOTALLYSINGLE = 1, // Only play one of these sounds at a time...GLOBALLY @@ -122,6 +139,8 @@ lumpnum_t S_GetSfxLumpNum(sfxinfo_t *sfx); boolean S_SoundDisabled(void); +boolean S_VoiceDisabled(void); + // // Start sound for thing at using from sounds.h // @@ -249,6 +268,7 @@ void S_AttemptToRestoreMusic(void); // void S_UpdateSounds(void); void S_UpdateClosedCaptions(void); +void S_UpdateVoicePositionalProperties(void); FUNCMATH fixed_t S_CalculateSoundDistance(fixed_t px1, fixed_t py1, fixed_t pz1, fixed_t px2, fixed_t py2, fixed_t pz2); @@ -257,6 +277,7 @@ INT32 S_GetSoundVolume(sfxinfo_t *sfx, INT32 volume); void S_SetSfxVolume(void); void S_SetMusicVolume(void); void S_SetMasterVolume(void); +void S_SetVoiceVolume(void); INT32 S_OriginPlaying(void *origin); INT32 S_IdPlaying(sfxenum_t id); @@ -270,6 +291,15 @@ void S_StopSoundByNum(sfxenum_t sfxnum); #define S_StartAttackSound S_StartSound #define S_StartScreamSound S_StartSound +boolean S_SoundInputIsEnabled(void); +boolean S_SoundInputSetEnabled(boolean enabled); +UINT32 S_SoundInputDequeueSamples(void *data, UINT32 len); + +void S_QueueVoiceFrameFromPlayer(INT32 playernum, void *data, UINT32 len, boolean terminal); +void S_SetPlayerVoiceActive(INT32 playernum); +boolean S_IsPlayerVoiceActive(INT32 playernum); +void S_ResetVoiceQueue(INT32 playernum); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/sanitize.cpp b/src/sanitize.cpp index 4c7809187..597a27345 100644 --- a/src/sanitize.cpp +++ b/src/sanitize.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -11,9 +11,9 @@ #include #include #include -#include #include +#include "core/string.h" #include "doomtype.h" #include "sanitize.h" #include "v_draw.hpp" @@ -34,7 +34,7 @@ bool color_filter(char c) } template -std::string& filter_out(std::string& out, const std::string_view& range, F filter) +srb2::String& filter_out(srb2::String& out, const std::string_view& range, F filter) { std::remove_copy_if( range.begin(), @@ -62,9 +62,9 @@ int hexconv(int c) namespace srb2::sanitize { -std::string sanitize(std::string_view in, SanitizeMode mode) +srb2::String sanitize(std::string_view in, SanitizeMode mode) { - std::string out; + srb2::String out; return filter_out(out, in, [mode] { switch (mode) @@ -78,9 +78,9 @@ std::string sanitize(std::string_view in, SanitizeMode mode) }()); } -std::string parse_carets(std::string_view in, ParseMode mode) +srb2::String parse_carets(std::string_view in, ParseMode mode) { - std::string out; + srb2::String out; using std::size_t; for (;;) diff --git a/src/sanitize.h b/src/sanitize.h index 801c4c258..0fe04e44d 100644 --- a/src/sanitize.h +++ b/src/sanitize.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -14,9 +14,10 @@ #include "doomtype.h" #ifdef __cplusplus -#include #include +#include "core/string.h" + namespace srb2::sanitize { @@ -33,10 +34,10 @@ enum class ParseMode }; // sanitizes string of all 0x80 codes -std::string sanitize(std::string_view in, SanitizeMode mode); +srb2::String sanitize(std::string_view in, SanitizeMode mode); // sanitizes string of all 0x80 codes then parses caret codes -std::string parse_carets(std::string_view in, ParseMode mode); +srb2::String parse_carets(std::string_view in, ParseMode mode); }; // namespace srb2 diff --git a/src/screen.c b/src/screen.c index 2f0184cad..67b8f13fb 100644 --- a/src/screen.c +++ b/src/screen.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/screen.h b/src/screen.h index 39828a03a..cb4137fb0 100644 --- a/src/screen.h +++ b/src/screen.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/sdl/CMakeLists.txt b/src/sdl/CMakeLists.txt index 445f12585..8c5bde549 100644 --- a/src/sdl/CMakeLists.txt +++ b/src/sdl/CMakeLists.txt @@ -91,9 +91,6 @@ endif() target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_MIXER -DSOUND=SOUND_MIXER) target_compile_definitions(SRB2SDL2 PRIVATE -DDIRECTFULLSCREEN -DHAVE_SDL) -# NOMUMBLE till WRITE* macros are fixed for C++ or mumble integration is rewritten -target_compile_definitions(SRB2SDL2 PRIVATE -DNOMUMBLE) - #### Installation #### if("${CMAKE_SYSTEM_NAME}" MATCHES Darwin) install(TARGETS SRB2SDL2 diff --git a/src/sdl/endtxt.h b/src/sdl/endtxt.h index 715d78841..e11d7fc4d 100644 --- a/src/sdl/endtxt.h +++ b/src/sdl/endtxt.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/sdl/hwsym_sdl.c b/src/sdl/hwsym_sdl.c index d68b62411..a801c73e9 100644 --- a/src/sdl/hwsym_sdl.c +++ b/src/sdl/hwsym_sdl.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/sdl/hwsym_sdl.h b/src/sdl/hwsym_sdl.h index 41684895b..9e8461253 100644 --- a/src/sdl/hwsym_sdl.h +++ b/src/sdl/hwsym_sdl.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/sdl/i_main.cpp b/src/sdl/i_main.cpp index ebddecac9..827d6314b 100644 --- a/src/sdl/i_main.cpp +++ b/src/sdl/i_main.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -17,10 +17,10 @@ #include "../d_main.h" #include "../m_misc.h"/* path shit */ #include "../i_system.h" +#include "../core/string.h" #include #include -#include #include @@ -227,14 +227,14 @@ ChDirToExe (void) } #endif -static void walk_exception_stack(std::string& accum, bool nested) { +static void walk_exception_stack(srb2::String& accum, bool nested) { if (nested) accum.append("\n Caused by: Unknown exception"); else accum.append("Uncaught exception: Unknown exception"); } -static void walk_exception_stack(std::string& accum, const std::exception& ex, bool nested) { +static void walk_exception_stack(srb2::String& accum, const std::exception& ex, bool nested) { if (nested) accum.append("\n Caused by: "); else @@ -331,11 +331,11 @@ int main(int argc, char **argv) D_SRB2Loop(); } catch (const std::exception& ex) { - std::string exception; + srb2::String exception; walk_exception_stack(exception, ex, false); I_Error("%s", exception.c_str()); } catch (...) { - std::string exception; + srb2::String exception; walk_exception_stack(exception, false); I_Error("%s", exception.c_str()); } diff --git a/src/sdl/i_net.c b/src/sdl/i_net.c index 4fcfde5e3..ed8990559 100644 --- a/src/sdl/i_net.c +++ b/src/sdl/i_net.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/sdl/i_system.cpp b/src/sdl/i_system.cpp index 88304cdcc..ae2c35d5c 100644 --- a/src/sdl/i_system.cpp +++ b/src/sdl/i_system.cpp @@ -3,7 +3,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- // -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -1350,7 +1350,7 @@ static void I_SetupMumble(void) if(shmfd < 0) return; - mumble = mmap(NULL, sizeof(*mumble), PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0); + mumble = static_cast(mmap(NULL, sizeof(*mumble), PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0)); if (mumble == MAP_FAILED) mumble = NULL; #endif @@ -1366,7 +1366,7 @@ void I_UpdateMumble(const mobj_t *mobj, const listener_t listener) return; if(mumble->uiVersion != 2) { - wcsncpy(mumble->name, L"Dr. Robotnik's Ring Racers "VERSIONSTRINGW, 256); + wcsncpy(mumble->name, L"Dr. Robotnik's Ring Racers " VERSIONSTRINGW, 256); wcsncpy(mumble->description, L"Dr. Robotnik's Ring Racers with integrated Mumble Link support.", 2048); mumble->uiVersion = 2; } @@ -1639,6 +1639,11 @@ INT32 I_StartupSystem(void) SDLcompiled.major, SDLcompiled.minor, SDLcompiled.patch); I_OutputMsg("Linked with SDL version: %d.%d.%d\n", SDLlinked.major, SDLlinked.minor, SDLlinked.patch); + +#if (SDL_VERSION_ATLEAST(2, 0, 18)) + SDL_SetHint(SDL_HINT_APP_NAME, "Dr. Robotnik's Ring Racers"); +#endif + if (SDL_Init(0) < 0) I_Error("Dr. Robotnik's Ring Racers: SDL System Error: %s", SDL_GetError()); //Alam: Oh no.... #ifndef NOMUMBLE diff --git a/src/sdl/i_threads.c b/src/sdl/i_threads.c index 0e0558621..9cd5824f5 100644 --- a/src/sdl/i_threads.c +++ b/src/sdl/i_threads.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/sdl/i_ttf.c b/src/sdl/i_ttf.c index 43298ab93..fe8547289 100644 --- a/src/sdl/i_ttf.c +++ b/src/sdl/i_ttf.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2011 by Callum Dickinson. // diff --git a/src/sdl/i_ttf.h b/src/sdl/i_ttf.h index a8c839581..3244dd73f 100644 --- a/src/sdl/i_ttf.h +++ b/src/sdl/i_ttf.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2011 by Callum Dickinson. // diff --git a/src/sdl/i_video.cpp b/src/sdl/i_video.cpp index 6a2694938..91b223535 100644 --- a/src/sdl/i_video.cpp +++ b/src/sdl/i_video.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -12,6 +12,7 @@ /// \file /// \brief SRB2 graphics stuff for SDL +#include #include #include #include @@ -1219,8 +1220,8 @@ void I_UpdateNoVsync(void) // void I_ReadScreen(UINT8 *scr) { - if (rendermode != render_soft) - I_Error ("I_ReadScreen: called while in non-software mode"); + if (rendermode == render_opengl) + I_Error ("I_ReadScreen: called while in Legacy GL mode"); else VID_BlitLinearScreen(screens[0], scr, vid.width*vid.bpp, vid.height, diff --git a/src/sdl/macosx/mac_alert.c b/src/sdl/macosx/mac_alert.c index a130659a4..625b0235d 100644 --- a/src/sdl/macosx/mac_alert.c +++ b/src/sdl/macosx/mac_alert.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/sdl/macosx/mac_alert.h b/src/sdl/macosx/mac_alert.h index b807956ad..5d718b523 100644 --- a/src/sdl/macosx/mac_alert.h +++ b/src/sdl/macosx/mac_alert.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/sdl/macosx/mac_resources.c b/src/sdl/macosx/mac_resources.c index 35ac33864..d9856c0d4 100644 --- a/src/sdl/macosx/mac_resources.c +++ b/src/sdl/macosx/mac_resources.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/sdl/macosx/mac_resources.h b/src/sdl/macosx/mac_resources.h index 1590835a7..640e4edd8 100644 --- a/src/sdl/macosx/mac_resources.h +++ b/src/sdl/macosx/mac_resources.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/sdl/mixer_sound.c b/src/sdl/mixer_sound.c index 41b5681a2..b6b9e72f9 100644 --- a/src/sdl/mixer_sound.c +++ b/src/sdl/mixer_sound.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/sdl/new_sound.cpp b/src/sdl/new_sound.cpp index 5330b8aff..3ded71879 100644 --- a/src/sdl/new_sound.cpp +++ b/src/sdl/new_sound.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -53,6 +53,125 @@ using srb2::audio::Source; using namespace srb2; using namespace srb2::io; +namespace +{ +class SdlAudioStream final +{ + SDL_AudioStream* stream_; +public: + SdlAudioStream(const SDL_AudioFormat format, const Uint8 channels, const int src_rate, const SDL_AudioFormat dst_format, const Uint8 dst_channels, const int dst_rate) noexcept + { + stream_ = SDL_NewAudioStream(format, channels, src_rate, dst_format, dst_channels, dst_rate); + } + SdlAudioStream(const SdlAudioStream&) = delete; + SdlAudioStream(SdlAudioStream&&) = default; + SdlAudioStream& operator=(const SdlAudioStream&) = delete; + SdlAudioStream& operator=(SdlAudioStream&&) = default; + ~SdlAudioStream() + { + SDL_FreeAudioStream(stream_); + } + + void put(tcb::span buf) + { + int result = SDL_AudioStreamPut(stream_, buf.data(), buf.size_bytes()); + if (result < 0) + { + char errbuf[512]; + SDL_GetErrorMsg(errbuf, sizeof(errbuf)); + throw std::runtime_error(errbuf); + } + } + + size_t available() const + { + int result = SDL_AudioStreamAvailable(stream_); + if (result < 0) + { + char errbuf[512]; + SDL_GetErrorMsg(errbuf, sizeof(errbuf)); + throw std::runtime_error(errbuf); + } + return result; + } + + size_t get(tcb::span out) + { + int result = SDL_AudioStreamGet(stream_, out.data(), out.size_bytes()); + if (result < 0) + { + char errbuf[512]; + SDL_GetErrorMsg(errbuf, sizeof(errbuf)); + throw std::runtime_error(errbuf); + } + return result; + } + + void clear() noexcept + { + SDL_AudioStreamClear(stream_); + } +}; + +class SdlVoiceStreamPlayer : public Source<2> +{ + SdlAudioStream stream_; + float volume_ = 1.0f; + float sep_ = 0.0f; + bool terminal_ = true; + +public: + SdlVoiceStreamPlayer() : stream_(AUDIO_F32SYS, 1, 48000, AUDIO_F32SYS, 2, 44100) {} + virtual ~SdlVoiceStreamPlayer() = default; + + virtual std::size_t generate(tcb::span> buffer) override + { + size_t written = stream_.get(tcb::as_writable_bytes(buffer)) / sizeof(Sample<2>); + + for (size_t i = written; i < buffer.size(); i++) + { + buffer[i] = {0.f, 0.f}; + } + + // Apply gain de-popping if the last generation was terminal + if (terminal_) + { + for (size_t i = 0; i < std::min(16, written); i++) + { + buffer[i].amplitudes[0] *= (float)(i) / 16; + buffer[i].amplitudes[1] *= (float)(i) / 16; + } + terminal_ = false; + } + + if (written < buffer.size()) + { + terminal_ = true; + } + + for (size_t i = 0; i < written; i++) + { + float sep_pan = ((sep_ + 1.f) / 2.f) * (3.14159 / 2.f); + + float left_scale = std::cos(sep_pan); + float right_scale = std::sin(sep_pan); + buffer[i] = {buffer[i].amplitudes[0] * volume_ * left_scale, buffer[i].amplitudes[1] * volume_ * right_scale}; + } + + return buffer.size(); + }; + + SdlAudioStream& stream() noexcept { return stream_; } + + void set_properties(float volume, float sep) noexcept + { + volume_ = volume; + sep_ = sep; + } +}; + +} // namespace + // extern in i_sound.h UINT8 sound_started = false; @@ -60,13 +179,16 @@ static unique_ptr> master_gain; static shared_ptr> master; static shared_ptr> mixer_sound_effects; static shared_ptr> mixer_music; +static shared_ptr> mixer_voice; static shared_ptr music_player; static shared_ptr> resample_music_player; static shared_ptr> gain_sound_effects; static shared_ptr> gain_music_player; static shared_ptr> gain_music_channel; +static shared_ptr> gain_voice_channel; static vector> sound_effect_channels; +static vector> player_voice_channels; #ifdef SRB2_CONFIG_ENABLE_WEBM_MOVIES static shared_ptr av_recorder; @@ -74,6 +196,10 @@ static shared_ptr av_recorder; static void (*music_fade_callback)(); +static SDL_AudioDeviceID g_device_id; +static SDL_AudioDeviceID g_input_device_id; +static boolean g_input_device_paused; + void* I_GetSfx(sfxinfo_t* sfx) { if (sfx->lumpnum == LUMPERROR) @@ -120,8 +246,8 @@ namespace class SdlAudioLockHandle { public: - SdlAudioLockHandle() { SDL_LockAudio(); } - ~SdlAudioLockHandle() { SDL_UnlockAudio(); } + SdlAudioLockHandle() { SDL_LockAudioDevice(g_device_id); } + ~SdlAudioLockHandle() { SDL_UnlockAudioDevice(g_device_id); } }; #ifdef TRACY_ENABLE @@ -180,21 +306,21 @@ void initialize_sound() return; } - SDL_AudioSpec desired; + SDL_AudioSpec desired {}; desired.format = AUDIO_F32SYS; desired.channels = 2; desired.samples = cv_soundmixingbuffersize.value; desired.freq = 44100; desired.callback = audio_callback; - if (SDL_OpenAudio(&desired, NULL) < 0) + if ((g_device_id = SDL_OpenAudioDevice(NULL, SDL_FALSE, &desired, NULL, 0)) == 0) { CONS_Alert(CONS_ERROR, "Failed to open SDL Audio device: %s\n", SDL_GetError()); SDL_QuitSubSystem(SDL_INIT_AUDIO); return; } - SDL_PauseAudio(SDL_FALSE); + SDL_PauseAudioDevice(g_device_id, SDL_FALSE); { SdlAudioLockHandle _; @@ -204,16 +330,20 @@ void initialize_sound() master_gain->bind(master); mixer_sound_effects = make_shared>(); mixer_music = make_shared>(); + mixer_voice = make_shared>(); music_player = make_shared(); resample_music_player = make_shared>(music_player, 1.f); gain_sound_effects = make_shared>(); gain_music_player = make_shared>(); gain_music_channel = make_shared>(); + gain_voice_channel = make_shared>(); gain_sound_effects->bind(mixer_sound_effects); gain_music_player->bind(resample_music_player); gain_music_channel->bind(mixer_music); + gain_voice_channel->bind(mixer_voice); master->add_source(gain_sound_effects); master->add_source(gain_music_channel); + master->add_source(gain_voice_channel); mixer_music->add_source(gain_music_player); sound_effect_channels.clear(); for (size_t i = 0; i < static_cast(cv_numChannels.value); i++) @@ -222,6 +352,13 @@ void initialize_sound() sound_effect_channels.push_back(player); mixer_sound_effects->add_source(player); } + player_voice_channels.clear(); + for (size_t i = 0; i < MAXPLAYERS; i++) + { + shared_ptr player = make_shared(); + player_voice_channels.push_back(player); + mixer_voice->add_source(player); + } } sound_started = true; @@ -237,7 +374,31 @@ void I_StartupSound(void) void I_ShutdownSound(void) { - SDL_CloseAudio(); + if (g_device_id) + { + SDL_CloseAudioDevice(g_device_id); + g_device_id = 0; + } + if (g_input_device_id) + { + SDL_CloseAudioDevice(g_input_device_id); + g_input_device_id = 0; + } + + master_gain = nullptr; + master = nullptr; + mixer_sound_effects = nullptr; + mixer_music = nullptr; + mixer_voice = nullptr; + music_player = nullptr; + resample_music_player = nullptr; + gain_sound_effects = nullptr; + gain_music_player = nullptr; + gain_music_channel = nullptr; + gain_voice_channel = nullptr; + sound_effect_channels.clear(); + player_voice_channels.clear(); + SDL_QuitSubSystem(SDL_INIT_AUDIO); sound_started = false; @@ -380,6 +541,17 @@ void I_SetSfxVolume(int volume) } } +void I_SetVoiceVolume(int volume) +{ + SdlAudioLockHandle _; + float vol = static_cast(volume) / 100.f; + + if (gain_voice_channel) + { + gain_voice_channel->gain(std::clamp(vol * vol * vol, 0.f, 1.f)); + } +} + void I_SetMasterVolume(int volume) { SdlAudioLockHandle _; @@ -824,3 +996,98 @@ void I_UpdateAudioRecorder(void) av_recorder = g_av_recorder; #endif } + +boolean I_SoundInputIsEnabled(void) +{ + return g_input_device_id != 0 && !g_input_device_paused; +} + +boolean I_SoundInputSetEnabled(boolean enabled) +{ + if (g_input_device_id == 0 && enabled) + { + if (SDL_GetNumAudioDevices(true) == 0) + { + return false; + } + + SDL_AudioSpec input_desired {}; + input_desired.format = AUDIO_F32SYS; + input_desired.channels = 1; + input_desired.samples = 2048; + input_desired.freq = 48000; + SDL_AudioSpec input_obtained {}; + g_input_device_id = SDL_OpenAudioDevice(nullptr, SDL_TRUE, &input_desired, &input_obtained, 0); + if (!g_input_device_id) + { + CONS_Alert(CONS_WARNING, "Failed to open input audio device: %s\n", SDL_GetError()); + return false; + } + g_input_device_paused = true; + } + + if (enabled && g_input_device_paused) + { + SDL_PauseAudioDevice(g_input_device_id, SDL_FALSE); + g_input_device_paused = false; + } + else if (!enabled && !g_input_device_paused) + { + SDL_PauseAudioDevice(g_input_device_id, SDL_TRUE); + SDL_ClearQueuedAudio(g_input_device_id); + g_input_device_paused = true; + } + return !g_input_device_paused; +} + +UINT32 I_SoundInputDequeueSamples(void *data, UINT32 len) +{ + if (!g_input_device_id) + { + return 0; + } + UINT32 avail = SDL_GetQueuedAudioSize(g_input_device_id); + if (avail == 0) + { + return 0; + } + + UINT32 ret = SDL_DequeueAudio(g_input_device_id, data, std::min(len, avail)); + return ret; +} + +void I_QueueVoiceFrameFromPlayer(INT32 playernum, void *data, UINT32 len, boolean terminal) +{ + if (!sound_started) + { + return; + } + + SdlAudioLockHandle _; + SdlVoiceStreamPlayer* player = player_voice_channels.at(playernum).get(); + player->stream().put(tcb::span((std::byte*)data, len)); +} + +void I_SetPlayerVoiceProperties(INT32 playernum, float volume, float sep) +{ + if (!sound_started) + { + return; + } + + SdlAudioLockHandle _; + SdlVoiceStreamPlayer* player = player_voice_channels.at(playernum).get(); + player->set_properties(volume * volume * volume, sep); +} + +void I_ResetVoiceQueue(INT32 playernum) +{ + if (!sound_started) + { + return; + } + + SdlAudioLockHandle _; + SdlVoiceStreamPlayer* player = player_voice_channels.at(playernum).get(); + player->stream().clear(); +} diff --git a/src/sdl/ogl_sdl.c b/src/sdl/ogl_sdl.c index 6739cfbcd..17eb1b8b3 100644 --- a/src/sdl/ogl_sdl.c +++ b/src/sdl/ogl_sdl.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/sdl/ogl_sdl.h b/src/sdl/ogl_sdl.h index da513c490..7eb122c49 100644 --- a/src/sdl/ogl_sdl.h +++ b/src/sdl/ogl_sdl.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/sdl/rhi_gl2_platform.cpp b/src/sdl/rhi_gl2_platform.cpp index ec35c7b97..f3f10fe90 100644 --- a/src/sdl/rhi_gl2_platform.cpp +++ b/src/sdl/rhi_gl2_platform.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -11,11 +11,13 @@ #include "rhi_gl2_platform.hpp" #include +#include #include #include -#include +#include "../core/string.h" +#include "../core/vector.hpp" #include "../cxxutil.hpp" #include "../doomstat.h" // mainwads #include "../w_wad.h" @@ -34,70 +36,30 @@ void SdlGl2Platform::present() SDL_GL_SwapWindow(window); } -static constexpr const char* pipeline_lump_slug(rhi::PipelineProgram program) +static std::array glsllist_lump_names(const char* name) { - switch (program) - { - case rhi::PipelineProgram::kUnshaded: - return "unshaded"; - case rhi::PipelineProgram::kUnshadedPaletted: - return "unshadedpaletted"; - case rhi::PipelineProgram::kPostprocessWipe: - return "postprocesswipe"; - case rhi::PipelineProgram::kPostimg: - return "postimg"; - case rhi::PipelineProgram::kSharpBilinear: - return "sharpbilinear"; - case rhi::PipelineProgram::kCrt: - return "crt"; - case rhi::PipelineProgram::kCrtSharp: - return "crtsharp"; - default: - return ""; - } -} - -static std::array glsllist_lump_names(rhi::PipelineProgram program) -{ - const char* pipeline_slug = pipeline_lump_slug(program); - - std::string vertex_list_name = fmt::format("rhi_glsllist_{}_vertex", pipeline_slug); - std::string fragment_list_name = fmt::format("rhi_glsllist_{}_fragment", pipeline_slug); + srb2::String vertex_list_name = fmt::format("rhi_glsllist_{}_vertex.txt", name); + srb2::String fragment_list_name = fmt::format("rhi_glsllist_{}_fragment.txt", name); return {std::move(vertex_list_name), std::move(fragment_list_name)}; } -static std::vector get_sources_from_glsllist_lump(const char* lumpname) +static srb2::Vector get_sources_from_glsllist_lump(const char* lumpname) { - std::string shaderspk3 = "shaders.pk3"; - INT32 shaderwadnum = -1; - for (INT32 wadnum = 0; wadnum <= mainwads; wadnum++) - { - std::string wadname = std::string(wadfiles[wadnum]->filename); - if (wadname.find(shaderspk3) != std::string::npos) - { - shaderwadnum = wadnum; - break; - } - } - - if (shaderwadnum < 0) - { - throw std::runtime_error("Unable to identify the shaders.pk3 wadnum"); - } - - UINT16 glsllist_lump_num = W_CheckNumForLongNamePwad(lumpname, shaderwadnum, 0); - if (glsllist_lump_num == INT16_MAX) + size_t buffer_size; + if (!W_ReadShader(lumpname, &buffer_size, nullptr)) { throw std::runtime_error(fmt::format("Unable to find glsllist lump {}", lumpname)); } - - std::string glsllist_lump_data; - glsllist_lump_data.resize(W_LumpLengthPwad(shaderwadnum, glsllist_lump_num)); - W_ReadLumpPwad(shaderwadnum, glsllist_lump_num, glsllist_lump_data.data()); + srb2::String glsllist_lump_data; + glsllist_lump_data.resize(buffer_size); + if (!W_ReadShader(lumpname, &buffer_size, glsllist_lump_data.data())) + { + throw std::runtime_error(fmt::format("Unable to read glsllist lump {}", lumpname)); + } std::istringstream glsllist(glsllist_lump_data); - std::vector sources; + srb2::Vector sources; for (std::string line; std::getline(glsllist, line); ) { if (line.empty()) @@ -115,15 +77,24 @@ static std::vector get_sources_from_glsllist_lump(const char* lumpn line.pop_back(); } - UINT16 source_lump_num = W_CheckNumForLongNamePwad(line.c_str(), shaderwadnum, 0); - if (source_lump_num == INT16_MAX) + // Compat: entries not ending in .glsl should append, for new shader file lookup system + size_t glsl_pos = line.find(".glsl"); + if (line.size() < 5 || glsl_pos == line.npos || glsl_pos != line.size() - 5) { - throw std::runtime_error(fmt::format("Unable to find glsl source lump lump {}", lumpname)); + line.append(".glsl"); } - std::string source_lump; - source_lump.resize(W_LumpLengthPwad(shaderwadnum, source_lump_num)); - W_ReadLumpPwad(shaderwadnum, source_lump_num, source_lump.data()); + size_t source_lump_size; + if (!W_ReadShader(line.c_str(), &source_lump_size, nullptr)) + { + throw std::runtime_error(fmt::format("Unable to find glsl source lump lump {}", line)); + } + srb2::String source_lump; + source_lump.resize(source_lump_size); + if (!W_ReadShader(line.c_str(), &source_lump_size, source_lump.data())) + { + throw std::runtime_error(fmt::format("Unable to read glsl source lump lump {}", line)); + } sources.emplace_back(source_lump); } @@ -131,13 +102,13 @@ static std::vector get_sources_from_glsllist_lump(const char* lumpn return sources; } -std::tuple, std::vector> -SdlGl2Platform::find_shader_sources(rhi::PipelineProgram program) +std::tuple, srb2::Vector> +SdlGl2Platform::find_shader_sources(const char* name) { - std::array glsllist_names = glsllist_lump_names(program); + std::array glsllist_names = glsllist_lump_names(name); - std::vector vertex_sources = get_sources_from_glsllist_lump(glsllist_names[0].c_str()); - std::vector fragment_sources = get_sources_from_glsllist_lump(glsllist_names[1].c_str()); + srb2::Vector vertex_sources = get_sources_from_glsllist_lump(glsllist_names[0].c_str()); + srb2::Vector fragment_sources = get_sources_from_glsllist_lump(glsllist_names[1].c_str()); return std::make_tuple(std::move(vertex_sources), std::move(fragment_sources)); } diff --git a/src/sdl/rhi_gl2_platform.hpp b/src/sdl/rhi_gl2_platform.hpp index 93829dbf6..acaf48618 100644 --- a/src/sdl/rhi_gl2_platform.hpp +++ b/src/sdl/rhi_gl2_platform.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -11,6 +11,10 @@ #ifndef __SRB2_SDL_RHI_GL2_PLATFORM_HPP__ #define __SRB2_SDL_RHI_GL2_PLATFORM_HPP__ +#include + +#include "../core/string.h" +#include "../core/vector.hpp" #include "../rhi/gl2/gl2_rhi.hpp" #include "../rhi/rhi.hpp" @@ -26,8 +30,8 @@ struct SdlGl2Platform final : public Gl2Platform virtual ~SdlGl2Platform(); virtual void present() override; - virtual std::tuple, std::vector> - find_shader_sources(PipelineProgram program) override; + virtual std::tuple, Vector> + find_shader_sources(const char* name) override; virtual Rect get_default_framebuffer_dimensions() override; }; diff --git a/src/sdl/rhi_gles2_platform.cpp b/src/sdl/rhi_gles2_platform.cpp index c83fd6ed3..8fc70feff 100644 --- a/src/sdl/rhi_gles2_platform.cpp +++ b/src/sdl/rhi_gles2_platform.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/sdl/rhi_gles2_platform.hpp b/src/sdl/rhi_gles2_platform.hpp index 690a4f362..99c56cbe8 100644 --- a/src/sdl/rhi_gles2_platform.hpp +++ b/src/sdl/rhi_gles2_platform.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Ronald "Eidolon" Kinard -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by Ronald "Eidolon" Kinard +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/sdl/sdl_sound.c b/src/sdl/sdl_sound.c index aaf035648..2637be3a0 100644 --- a/src/sdl/sdl_sound.c +++ b/src/sdl/sdl_sound.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 1996 by id Software, Inc. // diff --git a/src/sdl/sdlmain.h b/src/sdl/sdlmain.h index f273dc198..03657e5f9 100644 --- a/src/sdl/sdlmain.h +++ b/src/sdl/sdlmain.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the diff --git a/src/sdl12/hwsym_sdl.c b/src/sdl12/hwsym_sdl.c index 7c2ef68c4..55b151aa8 100644 --- a/src/sdl12/hwsym_sdl.c +++ b/src/sdl12/hwsym_sdl.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/sdl12/i_main.c b/src/sdl12/i_main.c index 11f032ef8..8141454e3 100644 --- a/src/sdl12/i_main.c +++ b/src/sdl12/i_main.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/sdl12/i_system.c b/src/sdl12/i_system.c index 2364be17b..18d28abd4 100644 --- a/src/sdl12/i_system.c +++ b/src/sdl12/i_system.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/sdl12/i_video.c b/src/sdl12/i_video.c index 3780bd6ee..b264c596a 100644 --- a/src/sdl12/i_video.c +++ b/src/sdl12/i_video.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/sdl12/sdl_sound.c b/src/sdl12/sdl_sound.c index 6ac607eaf..c73f5c1cd 100644 --- a/src/sdl12/sdl_sound.c +++ b/src/sdl12/sdl_sound.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/slope_anchors.c b/src/slope_anchors.c index 8508dbef5..f777d5c80 100644 --- a/src/slope_anchors.c +++ b/src/slope_anchors.c @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by James Robert Roman. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/sounds.c b/src/sounds.c index 465af1aac..a14aa31b5 100644 --- a/src/sounds.c +++ b/src/sounds.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -1537,6 +1537,12 @@ sfxinfo_t S_sfx[NUMSFX] = {"die02", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, {"die03", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + // Walltransfer + {"ggfall", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + + // :apple: + {"aple", false, 64, 0, -1, NULL, 0, -1, -1, LUMPERROR, ""}, + // SRB2kart - Skin sounds {"kwin", false, 64, 96, -1, NULL, 0, SKSKWIN, -1, LUMPERROR, ""}, {"klose", false, 64, 96, -1, NULL, 0, SKSKLOSE, -1, LUMPERROR, ""}, diff --git a/src/sounds.h b/src/sounds.h index 0abcd2110..90034ba72 100644 --- a/src/sounds.h +++ b/src/sounds.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -1613,6 +1613,12 @@ typedef enum sfx_die02, sfx_die03, + // Walltransfer fuck + sfx_ggfall, + + // :apple: + sfx_aple, + // And LASTLY, Kart's skin sounds. sfx_kwin, sfx_klose, diff --git a/src/st_stuff.c b/src/st_stuff.c index aac564183..fef6481dc 100644 --- a/src/st_stuff.c +++ b/src/st_stuff.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -96,13 +96,11 @@ boolean ST_SameTeam(player_t *a, player_t *b) if (G_GametypeHasTeams() == true) { // You get team messages if you're on the same team. - return (a->ctfteam == b->ctfteam); - } - else - { - // Not that everyone's not on the same team, but team messages go to normal chat if everyone's not in the same team. - return true; + return (a->team == b->team); } + + // Not that everyone's not on the same team, but team messages go to normal chat if everyone's not in the same team. + return true; } static boolean st_stopped = true; @@ -1498,10 +1496,10 @@ void ST_DrawServerSplash(boolean timelimited) void ST_DrawSaveReplayHint(INT32 flags) { - V_DrawRightAlignedThinString( - BASEVIDWIDTH - 2, 2, - flags|V_YELLOWMAP, - (demo.willsave && demo.titlename[0]) ? "Replay will be saved. \xAB Change title" : "\xAB or \xAD Save replay" + K_DrawGameControl( + BASEVIDWIDTH - 2, 2, 0, + (demo.willsave && demo.titlename[0]) ? "Replay will be saved. Change title" : " or Save replay", + 2, 0, flags|V_YELLOWMAP ); } diff --git a/src/st_stuff.h b/src/st_stuff.h index 4fca31d5a..6dfc6b39a 100644 --- a/src/st_stuff.h +++ b/src/st_stuff.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/string.c b/src/string.c index 69806c8a5..3a4ddc6e4 100644 --- a/src/string.c +++ b/src/string.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2006 by Graue. // diff --git a/src/stun.cpp b/src/stun.cpp index ee981dd2e..a53930a50 100644 --- a/src/stun.cpp +++ b/src/stun.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -12,18 +12,19 @@ /* https://tools.ietf.org/html/rfc5389 */ -#include - #if defined (__linux__) || defined (__FreeBSD__) #include #elif defined (_WIN32) #define _CRT_RAND_S +#include #elif defined (__APPLE__) #include #else #error "Need CSPRNG." #endif +#include + #include "doomdef.h" #include "d_clisrv.h" #include "command.h" diff --git a/src/stun.h b/src/stun.h index e42694789..81f14f582 100644 --- a/src/stun.h +++ b/src/stun.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/tables.c b/src/tables.c index 69c88e96b..2a1e791e0 100644 --- a/src/tables.c +++ b/src/tables.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/tables.h b/src/tables.h index 5c842817d..5b05b5aa2 100644 --- a/src/tables.h +++ b/src/tables.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. diff --git a/src/taglist.c b/src/taglist.c index 3421326a5..8264aab4f 100644 --- a/src/taglist.c +++ b/src/taglist.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Nev3r. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. diff --git a/src/taglist.h b/src/taglist.h index b701dd745..81c210a10 100644 --- a/src/taglist.h +++ b/src/taglist.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Nev3r. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. diff --git a/src/tmap.nas b/src/tmap.nas index 8414e810b..c5bcda5f9 100644 --- a/src/tmap.nas +++ b/src/tmap.nas @@ -1,6 +1,6 @@ ;; DR. ROBOTNIK'S RING RACERS ;;----------------------------------------------------------------------------- -;; Copyright (C) 2024 by Kart Krew. +;; Copyright (C) 2025 by Kart Krew. ;; Copyright (C) 2020 by Sonic Team Junior. ;; Copyright (C) 2000 by DooM Legacy Team. ;; diff --git a/src/tmap.s b/src/tmap.s index c0966c6be..e8f7745d9 100644 --- a/src/tmap.s +++ b/src/tmap.s @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/tmap_asm.s b/src/tmap_asm.s index b6fe90e56..d23f48940 100644 --- a/src/tmap_asm.s +++ b/src/tmap_asm.s @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/tmap_mmx.nas b/src/tmap_mmx.nas index a584e3341..a7a90c72b 100644 --- a/src/tmap_mmx.nas +++ b/src/tmap_mmx.nas @@ -1,6 +1,6 @@ ;; DR. ROBOTNIK'S RING RACERS ;;----------------------------------------------------------------------------- -;; Copyright (C) 2024 by Kart Krew. +;; Copyright (C) 2025 by Kart Krew. ;; Copyright (C) 2020 by Sonic Team Junior. ;; Copyright (C) 2000 by DOSDOOM. ;; diff --git a/src/tmap_vc.nas b/src/tmap_vc.nas index 4a365843a..dbf1bd7f0 100644 --- a/src/tmap_vc.nas +++ b/src/tmap_vc.nas @@ -1,6 +1,6 @@ ;; DR. ROBOTNIK'S RING RACERS ;;----------------------------------------------------------------------------- -;; Copyright (C) 2024 by Kart Krew. +;; Copyright (C) 2025 by Kart Krew. ;; Copyright (C) 2020 by Sonic Team Junior. ;; Copyright (C) 2000 by DooM Legacy Team. ;; diff --git a/src/typedef.h b/src/typedef.h index a46b50eb1..03ecea75d 100644 --- a/src/typedef.h +++ b/src/typedef.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -83,7 +83,9 @@ TYPEDEF (challengeall_pak); TYPEDEF (responseall_pak); TYPEDEF (resultsall_pak); TYPEDEF (say_pak); +TYPEDEF (reqmapqueue_pak); TYPEDEF (netinfo_pak); +TYPEDEF (voice_pak); // d_event.h TYPEDEF (event_t); @@ -144,6 +146,7 @@ TYPEDEF (unloaded_cupheader_t); TYPEDEF (exitcondition_t); TYPEDEF (darkness_t); TYPEDEF (musicfade_t); +TYPEDEF (teaminfo_t); // font.h TYPEDEF (font_t); @@ -301,6 +304,7 @@ TYPEDEF (BasicFF_t); TYPEDEF (divline_t); TYPEDEF (intercept_t); TYPEDEF (opening_t); +TYPEDEF (fofopening_t); // p_mobj.h TYPEDEF (mobj_t); diff --git a/src/v_draw.cpp b/src/v_draw.cpp index 12a05d63c..f8fe7df9c 100644 --- a/src/v_draw.cpp +++ b/src/v_draw.cpp @@ -1,15 +1,14 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -#include - +#include "core/hash_map.hpp" #include "doomdef.h" // skincolornum_t #include "doomtype.h" #include "hu_stuff.h" @@ -21,6 +20,8 @@ #include "v_video.h" #include "w_wad.h" #include "z_zone.h" +#include "k_profiles.h" // controls +#include "p_local.h" // stplyr using srb2::Draw; using Chain = Draw::Chain; @@ -32,33 +33,41 @@ int Draw::TextElement::width() const Draw::TextElement& Draw::TextElement::parse(std::string_view raw) { - static const std::unordered_map translation = { + static const srb2::HashMap translation = { #define BUTTON(str, lower_bits) \ {str, 0xB0 | lower_bits},\ {str "_animated", 0xA0 | lower_bits},\ {str "_pressed", 0x90 | lower_bits} - BUTTON("up", 0x00), - BUTTON("down", 0x01), - BUTTON("right", 0x02), - BUTTON("left", 0x03), + BUTTON("up", sb_up), + BUTTON("down", sb_down), + BUTTON("right", sb_right), + BUTTON("left", sb_left), - BUTTON("dpad", 0x04), + BUTTON("lua1", sb_lua1), + BUTTON("lua2", sb_lua2), + BUTTON("lua3", sb_lua3), - BUTTON("r", 0x07), - BUTTON("l", 0x08), - BUTTON("start", 0x09), + BUTTON("r", sb_r), + BUTTON("l", sb_l), + BUTTON("start", sb_start), - BUTTON("a", 0x0A), - BUTTON("b", 0x0B), - BUTTON("c", 0x0C), + BUTTON("a", sb_a), + BUTTON("b", sb_b), + BUTTON("c", sb_c), - BUTTON("x", 0x0D), - BUTTON("y", 0x0E), - BUTTON("z", 0x0F), + BUTTON("x", sb_x), + BUTTON("y", sb_y), + BUTTON("z", sb_z), #undef BUTTON + {"large", 0xEB}, + + {"box", 0xEC}, + {"box_pressed", 0xED}, + {"box_animated", 0xEE}, + {"white", 0x80}, {"purple", 0x81}, {"yellow", 0x82}, @@ -77,6 +86,48 @@ Draw::TextElement& Draw::TextElement::parse(std::string_view raw) {"tan", 0x8F}, }; + // When we encounter a Saturn button, what gamecontrol does it represent? + static const srb2::HashMap inputdefinition = { + {sb_up, gc_up}, + {sb_down, gc_down}, + {sb_right, gc_right}, + {sb_left, gc_left}, + + {sb_lua1, gc_lua1}, + {sb_lua2, gc_lua2}, + {sb_lua3, gc_lua3}, + + {sb_r, gc_r}, + {sb_l, gc_l}, + {sb_start, gc_start}, + + {sb_a, gc_a}, + {sb_b, gc_b}, + {sb_c, gc_c}, + + {sb_x, gc_x}, + {sb_y, gc_y}, + {sb_z, gc_z}, + }; + + // What physical binds should appear as Saturn icons anyway? + // (We don't have generic binds for stick/dpad directions, so + // using the existing arrow graphics is the best thing here.) + static const srb2::HashMap prettyinputs = { + {KEY_UPARROW, sb_up}, + {KEY_DOWNARROW, sb_down}, + {KEY_LEFTARROW, sb_left}, + {KEY_RIGHTARROW, sb_right}, + {nc_hatup, sb_up}, + {nc_hatdown, sb_down}, + {nc_hatleft, sb_left}, + {nc_hatright, sb_right}, + {nc_lsup, sb_up}, + {nc_lsdown, sb_down}, + {nc_lsleft, sb_left}, + {nc_lsright, sb_right}, + }; + string_.clear(); string_.reserve(raw.size()); @@ -107,9 +158,155 @@ Draw::TextElement& Draw::TextElement::parse(std::string_view raw) string_view code = raw.substr(1, p - 1); - if (auto it = translation.find(code); it != translation.end()) + if (code == "dpad" || code == "dpad_pressed" || code == "dpad_animated") { - string_.push_back(it->second); // replace with character code + // SPECIAL: Generic button that we invoke explicitly, not via gamecontrol reference. + // If we ever add anything else to this category, I promise I will create a real abstraction, + // but for now, just hardcode the character replacements and pray for forgiveness. + + string_.push_back(0xEF); // Control code: "switch to descriptive input mode" + string_.push_back(0xEB); // Control code: "large button" + if (code == "dpad") + string_.push_back(0xBC); + else if (code == "dpad_pressed") + string_.push_back(0x9C); + else + string_.push_back(0xAC); + } + else if (auto it = translation.find(code); it != translation.end()) // This represents a gamecontrol, turn into Saturn button or generic button. + { + + UINT8 localplayer = 0; + UINT8 indexedplayer = as_.value_or(stplyr - players); + for (UINT8 i = 0; i < MAXSPLITSCREENPLAYERS; i++) + { + if (g_localplayers[i] == indexedplayer) + { + localplayer = i; + break; + } + } + + // This isn't how v_video.cpp checks for buttons and I don't know why. + if (cv_descriptiveinput[localplayer].value && ((it->second & 0xF0) != 0x80)) // Should we do game control translation? + { + if (auto id = inputdefinition.find(it->second & (~0xB0)); id != inputdefinition.end()) // This is a game control, do descriptive input translation! + { + // Grab our local controls - if pid set in the call to parse(), use stplyr's controls + INT32 bind = G_FindPlayerBindForGameControl(localplayer, id->second); + + // EXTRA: descriptiveinput values above 1 translate binds back to Saturn buttons, + // with various modes for various fucked up 6bt pads + srb2::HashMap padconfig = {}; + switch (cv_descriptiveinput[localplayer].value) + { + case 1: + padconfig = standardpad; + break; + case 2: + padconfig = flippedpad; + break; + case 3: + { + // Most players will map gc_L to their physical L button, + // and gc_R to their physical R button. Assuming this is + // true, try to guess their physical layout based on what + // they've chosen. + + INT32 leftbumper = G_FindPlayerBindForGameControl(localplayer, gc_l); + INT32 rightbumper = G_FindPlayerBindForGameControl(localplayer, gc_r); + + if (leftbumper == nc_lb && rightbumper == nc_lt) + { + padconfig = saturntypeA; + } + else if (leftbumper == nc_lt && rightbumper == nc_rt) + { + padconfig = saturntypeB; + } + else if (leftbumper == nc_lb && rightbumper == nc_rb) + { + padconfig = saturntypeC; + } + else if (leftbumper == nc_ls && rightbumper == nc_lb) + { + padconfig = saturntypeE; + } + else if (leftbumper == nc_rs && rightbumper == nc_lt) + { + padconfig = saturntypeE; // Not a typo! Users might bind a Hori layout pad to either bumpers or triggers + } + else + { + padconfig = saturntypeA; // :( ??? + } + break; + } + case 4: + padconfig = saturntypeA; + break; + case 5: + padconfig = saturntypeB; + break; + case 6: + padconfig = saturntypeC; + break; + case 7: + padconfig = saturntypeD; + break; + case 8: + padconfig = saturntypeE; + break; + } + + if (auto pretty = prettyinputs.find(bind); pretty != prettyinputs.end()) // Gamepad direction or keyboard arrow, use something nice-looking + { + string_.push_back((it->second & 0xF0) | pretty->second); // original invocation has the animation bits, but the glyph bits come from the table + } + else if (auto pad = padconfig.find(bind); pad != padconfig.end()) + { + // If high bits are set, this is meant to be a generic button. + if (pad->second & 0xF0) + { + string_.push_back(0xEF); // Control code: "switch to descriptive input mode" - buttons will draw as generics + string_.push_back(0xEB); // Control code: "large button" + } + + // Clear high bits so we can add animation bits back cleanly. + pad->second = pad->second & (0x0F); + + // original invocation has the animation bits, but the glyph bits come from the table + string_.push_back((it->second & 0xF0) | pad->second); + } + else + { + UINT8 fragment = (it->second & 0xB0); + UINT8 code = '\xEE'; // Control code: "toggle boxed drawing" + + if (fragment == 0xA0) + code = '\xED'; // ... but animated + else if (fragment == 0x90) + code = '\xEC'; // ... but pressed + + string_.push_back(code); + + if (bind == -1) + string_.append("N/A"); + else + string_.append((G_KeynumToShortString(bind))); + + string_.push_back(code); + } + } + else // This is a color code or some other generic glyph, treat it as is. + { + string_.push_back(it->second); // replace with character code + } + } + else // We don't care whether this is a generic glyph, because input translation isn't on. + { + string_.push_back(it->second); // replace with character code + } } else { @@ -151,6 +348,11 @@ void Chain::fill(UINT8 color) const void Chain::string(const char* str, INT32 flags, Font font) const { + if (!str) + { + return; + } + const auto _ = Clipper(*this); flags |= default_font_flags(font); @@ -192,14 +394,16 @@ patch_t** get_button_patch(Draw::Button type, int ver) X(x)[ver]; X(y)[ver]; X(z)[ver]; - X(start); - X(l); - X(r); - X(up); - X(down); - X(right); - X(left); - X(dpad); + X(start)[ver]; + X(l)[ver]; + X(r)[ver]; + X(up)[ver]; + X(down)[ver]; + X(right)[ver]; + X(left)[ver]; + X(lua1)[ver]; + X(lua2)[ver]; + X(lua3)[ver]; #undef X } @@ -223,6 +427,48 @@ void Chain::button_(Button type, int ver, std::optional press) const } } +patch_t** get_button_patch(Draw::GenericButton type, int ver) +{ + switch (type) + { +#define X(x) \ + case Draw::GenericButton::x:\ + return gen_button_ ## x + + X(a)[ver]; + X(b)[ver]; + X(x)[ver]; + X(y)[ver]; + X(lb)[ver]; + X(rb)[ver]; + X(lt)[ver]; + X(rt)[ver]; + X(start)[ver]; + X(back)[ver]; + X(ls)[ver]; + X(rs)[ver]; + X(dpad)[ver]; + +#undef X + } + + return nullptr; +}; + +void Chain::generic_button_(GenericButton type, int ver, std::optional press) const +{ + const auto _ = Clipper(*this); + + if (press) + { + K_drawButton(FloatToFixed(x_), FloatToFixed(y_), flags_, get_button_patch(type, ver), *press); + } + else + { + K_drawButtonAnim(x_, y_, flags_, get_button_patch(type, ver), I_GetTime()); + } +} + void Chain::sticker(patch_t* end_graphic, UINT8 color) const { const auto _ = Clipper(*this); @@ -334,5 +580,10 @@ INT32 Draw::default_font_flags(Font font) fixed_t Draw::font_width(Font font, INT32 flags, const char* string) { + if (!string) + { + return 0; + } + return V_StringScaledWidth(FRACUNIT, FRACUNIT, FRACUNIT, flags, font_to_fontno(font), string); } diff --git a/src/v_draw.hpp b/src/v_draw.hpp index 82c60e845..9fe9fa2c1 100644 --- a/src/v_draw.hpp +++ b/src/v_draw.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. @@ -11,18 +11,178 @@ #ifndef __V_DRAW_HPP__ #define __V_DRAW_HPP__ -#include #include #include #include #include +#include "core/hash_map.hpp" +#include "core/string.h" #include "doomdef.h" // skincolornum_t #include "doomtype.h" #include "screen.h" // BASEVIDWIDTH #include "typedef.h" #include "v_video.h" +#include "g_input.h" + +// Helpers for setting up pad napping nonsense +typedef enum +{ + gb_mask = 0xF0, + gb_a = 0xF0, + gb_b, + gb_x, + gb_y, + gb_lb, + gb_rb, + gb_lt, + gb_rt, + gb_start, + gb_back, + gb_ls, + gb_rs, + gb_dpad +} generic_buttons_e; + +typedef enum +{ + sb_up = 0x00, + sb_down, + sb_right, + sb_left, + sb_lua1, + sb_lua2, + sb_lua3, + sb_r, + sb_l, + sb_start, + sb_a, + sb_b, + sb_c, + sb_x, + sb_y, + sb_z +} saturn_buttons_e; + +// Garden-variety standard gamepad +static const srb2::HashMap standardpad = { + {nc_a, gb_a}, + {nc_b, gb_b}, + {nc_x, gb_x}, + {nc_y, gb_y}, + {nc_lb, gb_lb}, + {nc_rb, gb_rb}, + {nc_lt, gb_lt}, + {nc_rt, gb_rt}, + {nc_start, gb_start}, + {nc_back, gb_back}, + {nc_ls, gb_ls}, + {nc_rs, gb_rs}, +}; + +// Standard gamepad, but evil Nintendo layout flip was applied by your +// controller firmware or Steam Input—swap B/A and X/Y +static const srb2::HashMap flippedpad = { + {nc_a, gb_b}, + {nc_b, gb_a}, + {nc_x, gb_y}, + {nc_y, gb_x}, + {nc_lb, gb_lb}, + {nc_rb, gb_rb}, + {nc_lt, gb_lt}, + {nc_rt, gb_rt}, + {nc_start, gb_start}, + {nc_back, gb_back}, + {nc_ls, gb_ls}, + {nc_rs, gb_rs}, +}; + +// Saturn Type A - Retrobit Wired Dinput, RB RT LB LT (CZLR) +static const srb2::HashMap saturntypeA = { + {nc_a, sb_a}, + {nc_b, sb_b}, + {nc_x, sb_x}, + {nc_y, sb_y}, + {nc_rb, sb_c}, + {nc_rt, sb_z}, + {nc_lb, sb_l}, + {nc_lt, sb_r}, + {nc_start, gb_start}, + {nc_back, gb_back}, + {nc_ls, gb_ls}, + {nc_rs, gb_rs}, +}; + +// Saturn Type B - Retrobit Wireless Dinput, LB RB LT RT (CZLR) +static const srb2::HashMap saturntypeB = { + {nc_a, sb_a}, + {nc_b, sb_b}, + {nc_x, sb_x}, + {nc_y, sb_y}, + {nc_lb, sb_c}, + {nc_rb, sb_z}, + {nc_lt, sb_l}, + {nc_rt, sb_r}, + {nc_start, gb_start}, + {nc_back, gb_back}, + {nc_ls, gb_ls}, + {nc_rs, gb_rs}, +}; + +// Saturn Type C - Retrobit Xinput, RT LT LB RB (CZLR) +static const srb2::HashMap saturntypeC = { + {nc_a, sb_a}, + {nc_b, sb_b}, + {nc_x, sb_x}, + {nc_y, sb_y}, + {nc_rt, sb_c}, + {nc_lt, sb_z}, + {nc_lb, sb_l}, + {nc_rb, sb_r}, + {nc_start, gb_start}, + {nc_back, gb_back}, + {nc_ls, gb_ls}, + {nc_rs, gb_rs}, +}; + +// Saturn Type D - 8BitDo M30 / arcade, RT RB LB LT (CZLR) +// This cannot be disambiguated (shares L/R with type 1) +// but is more spatially correct w/r/t SDL expectations +// and standard arcade mapping (Z on top, C on bottom) +static const srb2::HashMap saturntypeD = { + {nc_a, sb_a}, + {nc_b, sb_b}, + {nc_x, sb_x}, + {nc_y, sb_y}, + {nc_rt, sb_c}, + {nc_rb, sb_z}, + {nc_lb, sb_l}, + {nc_lt, sb_r}, + {nc_start, gb_start}, + {nc_back, gb_back}, + {nc_ls, gb_ls}, + {nc_rs, gb_rs}, +}; + +// Saturn Type E - Hori, RT RB (CZ) with some fucked up bumpers/triggers +// The Hori layout is, to my knowledge, the only 6bt one that has fully +// unique buttons in every slot while having both bumpers and triggers, +// so there's no way to accurately portray it without using generics. +static const srb2::HashMap saturntypeE = { + {nc_a, sb_a}, + {nc_b, sb_b}, + {nc_x, sb_x}, + {nc_y, sb_y}, + {nc_rt, sb_c}, + {nc_rb, sb_z}, + {nc_lb, gb_rb}, + {nc_lt, gb_rt}, + {nc_ls, gb_lb}, + {nc_rs, gb_lt}, + {nc_start, gb_start}, + {nc_back, gb_back}, +}; namespace srb2 { @@ -76,7 +236,26 @@ public: down, right, left, - dpad, + lua1, + lua2, + lua3, + }; + + enum class GenericButton + { + a, + b, + x, + y, + lb, + rb, + lt, + rt, + start, + back, + ls, + rs, + dpad }; class TextElement @@ -84,7 +263,7 @@ public: public: explicit TextElement() {} - explicit TextElement(std::string string) : string_(string) {} + explicit TextElement(const srb2::String& string) : string_(string) {} template explicit TextElement(fmt::format_string format, Args&&... args) : @@ -92,13 +271,14 @@ public: { } - const std::string& string() const { return string_; } + const srb2::String& string() const { return string_; } std::optional font() const { return font_; } std::optional flags() const { return flags_; } + std::optional as() const { return as_; } int width() const; - TextElement& string(std::string string) + TextElement& string(srb2::String string) { string_ = string; return *this; @@ -124,10 +304,16 @@ public: return *this; } + TextElement& as(UINT8 as) + { + as_ = as; + return *this; + } private: - std::string string_; + srb2::String string_; std::optional font_; std::optional flags_; + std::optional as_; }; class Chain @@ -171,7 +357,7 @@ public: Chain& colorize(UINT16 color); void text(const char* str) const { string(str, flags_, font_); } - void text(const std::string& str) const { text(str.c_str()); } + void text(const srb2::String& str) const { text(str.c_str()); } void text(const TextElement& elm) const { string(elm.string().c_str(), elm.flags().value_or(flags_), elm.font().value_or(font_)); @@ -184,7 +370,7 @@ public: void patch(patch_t* patch) const; void patch(const char* name) const { patch(Draw::cache_patch(name)); } - void patch(const std::string& name) const { patch(name.c_str()); } + void patch(const srb2::String& name) const { patch(name.c_str()); } void thumbnail(UINT16 mapnum) const; @@ -193,6 +379,9 @@ public: void button(Button type, std::optional press = {}) const { button_(type, 0, press); } void small_button(Button type, std::optional press = {}) const { button_(type, 1, press); } + void generic_button(GenericButton type, std::optional press = {}) const { generic_button_(type, 0, press); } + void generic_small_button(GenericButton type, std::optional press = {}) const { generic_button_(type, 1, press); } + void sticker(patch_t* end_graphic, UINT8 color) const; void sticker() const { sticker(Draw::cache_patch("K_STIKEN"), 24); } void small_sticker() const { sticker(Draw::cache_patch("K_STIKE2"), 24); } @@ -234,6 +423,7 @@ public: void string(const char* str, INT32 flags, Font font) const; void button_(Button type, int ver, std::optional press = {}) const; + void generic_button_(GenericButton type, int ver, std::optional press = {}) const; friend Draw; }; @@ -283,6 +473,8 @@ public: VOID_METHOD(fill); VOID_METHOD(button); VOID_METHOD(small_button); + VOID_METHOD(generic_button); + VOID_METHOD(generic_small_button); VOID_METHOD(sticker); VOID_METHOD(small_sticker); diff --git a/src/v_draw_setter.hpp b/src/v_draw_setter.hpp index 5827558d7..43cba8c2a 100644 --- a/src/v_draw_setter.hpp +++ b/src/v_draw_setter.hpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by James Robert Roman -// Copyright (C) 2024 by Kart Krew +// Copyright (C) 2025 by James Robert Roman +// Copyright (C) 2025 by Kart Krew // // This program is free software distributed under the // terms of the GNU General Public License, version 2. diff --git a/src/v_video.cpp b/src/v_video.cpp index 8439172f6..43340aa52 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -797,7 +797,6 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca return; #ifdef HWRENDER - //if (rendermode != render_soft && !con_startup) // Why? if (rendermode == render_opengl) { HWR_DrawStretchyFixedPatch(patch, x, y, pscale, vscale, scrn, colormap); @@ -1016,7 +1015,6 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c) return; #ifdef HWRENDER - //if (rendermode != render_soft && !con_startup) // Not this again if (rendermode == render_opengl) { HWR_DrawFill(x, y, w, h, c); @@ -1600,7 +1598,7 @@ const UINT8 *V_OffsetIntoFadeMap(const lighttable_t *clm, UINT8 strength) void V_DrawCustomFadeScreen(const char *lump, UINT8 strength) { #ifdef HWRENDER - if (rendermode != render_soft && rendermode != render_none) + if (rendermode == render_opengl) { HWR_DrawCustomFadeScreen( (strcmp(lump, "FADEMAP1") != 0 @@ -1664,7 +1662,7 @@ void V_DrawFadeConsBack(INT32 plines) void V_EncoreInvertScreen(void) { #ifdef HWRENDER - if (rendermode != render_soft && rendermode != render_none) + if (rendermode == render_opengl) { HWR_EncoreInvertScreen(); return; @@ -2272,6 +2270,7 @@ typedef struct fixed_t lfh; fixed_t (*dim_fn)(fixed_t,fixed_t,INT32,INT32,fixed_t *); UINT8 button_yofs; + UINT8 right_outline; } fontspec_t; static void V_GetFontSpecification(int fontno, INT32 flags, fontspec_t *result) @@ -2285,6 +2284,8 @@ static void V_GetFontSpecification(int fontno, INT32 flags, fontspec_t *result) result->chw = 0; result->button_yofs = 0; + result->right_outline = 1; + const INT32 spacing = ( flags & V_SPACINGMASK ); switch (fontno) @@ -2482,42 +2483,94 @@ static void V_GetFontSpecification(int fontno, INT32 flags, fontspec_t *result) result->button_yofs = 1; break; } + + switch (fontno) + { + case MENU_FONT: + result->right_outline = 2; + break; + } } -static UINT8 V_GetButtonCodeWidth(UINT8 c) +static UINT8 V_GetButtonCodeWidth(UINT8 c, boolean largebutton) { - UINT8 x = 0; + UINT8 x = 14; switch (c & 0x0F) { - case 0x00: - case 0x01: - case 0x02: - case 0x03: - // arrows - x = 12; + case sb_up: + case sb_down: + case sb_left: + case sb_right: + x -= largebutton ? 2 : 4; break; - case 0x07: - case 0x08: - case 0x09: - // shoulders, start - x = 14; + case sb_l: + case sb_r: + x -= largebutton ? 1 : 4; break; - case 0x04: - // dpad - x = 14; + case sb_start: + x -= largebutton ? 0 : 4; break; - case 0x0A: - case 0x0B: - case 0x0C: - case 0x0D: - case 0x0E: - case 0x0F: - // faces - x = 10; + case sb_lua1: + case sb_lua2: + case sb_lua3: + x -= largebutton ? 0 : 4; + break; + + case sb_a: + case sb_b: + case sb_c: + case sb_x: + case sb_y: + case sb_z: + x -= largebutton ? 0 : 4; + break; + } + + return x; +} + +static UINT8 V_GetGenericButtonCodeWidth(UINT8 c, boolean largebutton) +{ + UINT8 x = 16; + + switch ((c & 0x0F) | gb_mask) + { + case gb_a: + case gb_b: + case gb_x: + case gb_y: + x -= largebutton ? 0 : 2; + break; + + case gb_lb: + case gb_rb: + x -= largebutton ? 2 : 6; + break; + + case gb_lt: + case gb_rt: + x -= largebutton ? 2 : 6; + break; + + case gb_start: + x -= largebutton ? 2 : 6; + break; + + case gb_back: + x -= largebutton ? 2 : 6; + break; + + case gb_ls: + case gb_rs: + x -= largebutton ? 1 : 4; + break; + + case gb_dpad: + x -= largebutton ? 2 : 5; break; } @@ -2547,12 +2600,25 @@ void V_DrawStringScaled( boolean uppercase; boolean notcolored; + UINT8 boxed = 0; + boolean descriptive = false; + + boolean debugalternation = false; + UINT8 debugcolor1 = 181; + UINT8 debugcolor2 = 96; boolean dance; boolean nodanceoverride; INT32 dancecounter; + INT32 boxedflags = ((flags) & (~V_HUDTRANS)) | (V_40TRANS); + + boolean largebutton = false; + fixed_t cx, cy; + fixed_t cxsave; + + const char *ssave; fixed_t cxoff, cyoff; fixed_t cw; @@ -2629,7 +2695,8 @@ void V_DrawStringScaled( right <<= FRACBITS; bot = vid.height << FRACBITS; - cx = x; + ssave = s; + cx = cxsave = x; cy = y; cyoff = 0; @@ -2638,11 +2705,76 @@ void V_DrawStringScaled( switch (c) { case '\n': + if (boxed) + continue; cy += fontspec.lfh; if (cy >= bot) return; cx = x; break; + case '\xEB': + if (fontno != TINY_FONT && fontno != HU_FONT) + largebutton = true; + break; + case '\xEF': + descriptive = true; + break; + case '\xEE': + case '\xED': + case '\xEC': + { + UINT8 anim_duration = 16; + UINT8 anim = 0; + + if (c == '\xEC') // Pressed + anim = 1; + else if (c != '\xEE') // Not lifted..? + anim = ((I_GetTime() % (anim_duration * 2)) < anim_duration) ? 1 : 0; + + // For bullshit text outlining reasons, we cannot draw this background character-by-character. + // Thinking about doing string manipulation and calling out to V_StringWidth made me drink water. + // So instead, we just draw this section of the string twice—invisibly the first time, to measure the width. + + if (boxed == 0) // Save our position and start no-op drawing + { + cy -= 2*FRACUNIT; + + Draw(FixedToFloat(cx), FixedToFloat(cy)-3).flags(flags).patch(gen_button_keyleft[anim]); + + cx += 3*FRACUNIT; + ssave = s; + cxsave = cx; + + boxed = 1; + } + else if (boxed == 1) // Draw box from saved pos to current pos and roll back + { + cx += (fontspec.right_outline)*FRACUNIT; + fixed_t working = cxsave - 1*FRACUNIT; + + Draw(FixedToFloat(working)+1, FixedToFloat(cy)-3) + .width(FixedToFloat(cx - working)-1) + .flags(flags) + .stretch(Draw::Stretch::kWidth).patch(gen_button_keycenter[anim]); + Draw(FixedToFloat(cx), FixedToFloat(cy)-3).flags(flags).patch(gen_button_keyright[anim]); + + s = ssave; + cx = cxsave; + + // This is a little gross, but this is our way of smuggling text offset to + // the standard character drawing case. boxed=3 means we're drawing a pressed button. + boxed = 2 + anim; + } + else // Meeting the ending tag the second time, space away and resume standard parsing + { + boxed = 0; + + cx += (3)*FRACUNIT; + cy += 2*FRACUNIT; + } + + break; + } default: if (( c & 0xF0 ) == 0x80) { @@ -2688,63 +2820,211 @@ void V_DrawStringScaled( if (( c & 0xB0 ) & 0x80) // button prompts { - using srb2::Draw; - - struct BtConf + if (!descriptive) { - UINT8 x, y; - Draw::Button type; - }; + using srb2::Draw; - auto bt_inst = [c]() -> std::optional - { - switch (c & 0x0F) + struct BtConf { - case 0x00: return {{0, 3, Draw::Button::up}}; - case 0x01: return {{0, 3, Draw::Button::down}}; - case 0x02: return {{0, 3, Draw::Button::right}}; - case 0x03: return {{0, 3, Draw::Button::left}}; - - case 0x04: return {{0, 4, Draw::Button::dpad}}; - - case 0x07: return {{0, 2, Draw::Button::r}}; - case 0x08: return {{0, 2, Draw::Button::l}}; - - case 0x09: return {{0, 1, Draw::Button::start}}; - - case 0x0A: return {{2, 1, Draw::Button::a}}; - case 0x0B: return {{2, 1, Draw::Button::b}}; - case 0x0C: return {{2, 1, Draw::Button::c}}; - - case 0x0D: return {{2, 1, Draw::Button::x}}; - case 0x0E: return {{2, 1, Draw::Button::y}}; - case 0x0F: return {{2, 1, Draw::Button::z}}; - - default: return {}; - } - }(); - - if (bt_inst) - { - auto bt_translate_press = [c]() -> std::optional - { - switch (c & 0xB0) - { - default: - case 0x90: return true; - case 0xA0: return {}; - case 0xB0: return false; - } + UINT8 x, y; + Draw::Button type; }; - cw = V_GetButtonCodeWidth(c) * dupx; - cxoff = (*fontspec.dim_fn)(scale, fontspec.chw, hchw, dupx, &cw); - Draw( - FixedToFloat(cx + cxoff) - (bt_inst->x * dupx), - FixedToFloat(cy + cyoff) - ((bt_inst->y + fontspec.button_yofs) * dupy)) - .flags(flags) - .small_button(bt_inst->type, bt_translate_press()); - cx += cw; + auto bt_inst = [c]() -> std::optional + { + switch (c & 0x0F) + { + case sb_up: return {{2, 2, Draw::Button::up}}; + case sb_down: return {{2, 2, Draw::Button::down}}; + case sb_right: return {{2, 2, Draw::Button::right}}; + case sb_left: return {{2, 2, Draw::Button::left}}; + + case sb_lua1: return {{2, 2, Draw::Button::lua1}}; + case sb_lua2: return {{2, 2, Draw::Button::lua2}}; + case sb_lua3: return {{2, 2, Draw::Button::lua3}}; + + case sb_r: return {{2, 2, Draw::Button::r}}; + case sb_l: return {{2, 2, Draw::Button::l}}; + + case sb_start: return {{2, 2, Draw::Button::start}}; + + case sb_a: return {{2, 2, Draw::Button::a}}; + case sb_b: return {{2, 2, Draw::Button::b}}; + case sb_c: return {{2, 2, Draw::Button::c}}; + + case sb_x: return {{2, 2, Draw::Button::x}}; + case sb_y: return {{2, 2, Draw::Button::y}}; + case sb_z: return {{2, 2, Draw::Button::z}}; + + default: return {}; + } + }(); + + if (largebutton) + { + bt_inst = [c]() -> std::optional + { + switch (c & 0x0F) + { + case sb_up: return {{2, 4, Draw::Button::up}}; + case sb_down: return {{2, 4, Draw::Button::down}}; + case sb_right: return {{2, 4, Draw::Button::right}}; + case sb_left: return {{2, 4, Draw::Button::left}}; + + case sb_lua1: return {{1, 4, Draw::Button::lua1}}; + case sb_lua2: return {{1, 4, Draw::Button::lua2}}; + case sb_lua3: return {{1, 4, Draw::Button::lua3}}; + + case sb_r: return {{1, 4, Draw::Button::r}}; + case sb_l: return {{1, 4, Draw::Button::l}}; + + case sb_start: return {{1, 4, Draw::Button::start}}; + + case sb_a: return {{1, 4, Draw::Button::a}}; + case sb_b: return {{1, 4, Draw::Button::b}}; + case sb_c: return {{1, 4, Draw::Button::c}}; + + case sb_x: return {{1, 4, Draw::Button::x}}; + case sb_y: return {{1, 4, Draw::Button::y}}; + case sb_z: return {{1, 4, Draw::Button::z}}; + + default: return {}; + } + }(); + } + + if (bt_inst) + { + auto bt_translate_press = [c]() -> std::optional + { + switch (c & 0xB0) + { + default: + case 0x90: return true; + case 0xA0: return {}; + case 0xB0: return false; + } + }; + + cw = V_GetButtonCodeWidth(c, largebutton) * dupx; + + cxoff = (*fontspec.dim_fn)(scale, fontspec.chw, hchw, dupx, &cw); + + if (cv_debugfonts.value) + { + V_DrawFill(cx/FRACUNIT, cy/FRACUNIT, cw/FRACUNIT, fontspec.lfh/FRACUNIT, debugalternation ? debugcolor1 : debugcolor2); + debugalternation = !debugalternation; + } + + Draw bt = Draw( + FixedToFloat(cx + cxoff) - (bt_inst->x * dupx), + FixedToFloat(cy + cyoff) - ((bt_inst->y + fontspec.button_yofs) * dupy)) + .flags(flags); + + if (largebutton) + bt.button(bt_inst->type, bt_translate_press()); + else + bt.small_button(bt_inst->type, bt_translate_press()); + + cx += cw; + } + descriptive = false; + largebutton = false; + break; + } + else + { + using srb2::Draw; + + struct BtConf + { + UINT8 x, y; + Draw::GenericButton type; + }; + + auto bt_inst = [c]() -> std::optional + { + switch ((c & 0x0F) | gb_mask) + { + case gb_a: return {{0, 1, Draw::GenericButton::a}}; + case gb_b: return {{0, 1, Draw::GenericButton::b}}; + case gb_x: return {{0, 1, Draw::GenericButton::x}}; + case gb_y: return {{0, 1, Draw::GenericButton::y}}; + case gb_lb: return {{2, 2, Draw::GenericButton::lb}}; + case gb_rb: return {{2, 2, Draw::GenericButton::rb}}; + case gb_lt: return {{2, 2, Draw::GenericButton::lt}}; + case gb_rt: return {{2, 2, Draw::GenericButton::rt}}; + case gb_start: return {{2, 2, Draw::GenericButton::start}}; + case gb_back: return {{2, 2, Draw::GenericButton::back}}; + case gb_ls: return {{1, 2, Draw::GenericButton::ls}}; + case gb_rs: return {{1, 2, Draw::GenericButton::rs}}; + case gb_dpad: return {{2, 2, Draw::GenericButton::dpad}}; + default: return {}; + } + }(); + + if (largebutton) + { + bt_inst = [c]() -> std::optional + { + switch ((c & 0x0F) | gb_mask) + { + case gb_a: return {{0, 3, Draw::GenericButton::a}}; + case gb_b: return {{0, 3, Draw::GenericButton::b}}; + case gb_x: return {{0, 3, Draw::GenericButton::x}}; + case gb_y: return {{0, 3, Draw::GenericButton::y}}; + case gb_lb: return {{1, 3, Draw::GenericButton::lb}}; + case gb_rb: return {{1, 3, Draw::GenericButton::rb}}; + case gb_lt: return {{1, 4, Draw::GenericButton::lt}}; + case gb_rt: return {{1, 4, Draw::GenericButton::rt}}; + case gb_start: return {{1, 6, Draw::GenericButton::start}}; + case gb_back: return {{1, 6, Draw::GenericButton::back}}; + case gb_ls: return {{1, 5, Draw::GenericButton::ls}}; + case gb_rs: return {{1, 5, Draw::GenericButton::rs}}; + case gb_dpad: return {{1, 4, Draw::GenericButton::dpad}}; + default: return {}; + } + }(); + } + + if (bt_inst) + { + auto bt_translate_press = [c]() -> std::optional + { + switch (c & 0xB0) + { + default: + case 0x90: return true; + case 0xA0: return {}; + case 0xB0: return false; + } + }; + + cw = V_GetGenericButtonCodeWidth(c, largebutton) * dupx; + + cxoff = (*fontspec.dim_fn)(scale, fontspec.chw, hchw, dupx, &cw); + + if (cv_debugfonts.value) + { + V_DrawFill(cx/FRACUNIT, cy/FRACUNIT, cw/FRACUNIT, fontspec.lfh/FRACUNIT, debugalternation ? debugcolor1 : debugcolor2); + debugalternation = !debugalternation; + } + + Draw bt = Draw( + FixedToFloat(cx + cxoff) - (bt_inst->x * dupx), + FixedToFloat(cy + cyoff) - ((bt_inst->y + fontspec.button_yofs) * dupy)) + .flags(flags); + + if (largebutton) + bt.generic_button(bt_inst->type, bt_translate_press()); + else + bt.generic_small_button(bt_inst->type, bt_translate_press()); + + cx += cw; + } + descriptive = false; + largebutton = false; + break; } break; } @@ -2756,8 +3036,19 @@ void V_DrawStringScaled( fixed_t patchxofs = SHORT (font->font[c]->leftoffset) * dupx * scale; cw = SHORT (font->font[c]->width) * dupx; cxoff = (*fontspec.dim_fn)(scale, fontspec.chw, hchw, dupx, &cw); - V_DrawFixedPatch(cx + cxoff + patchxofs, cy + cyoff, scale, - flags, font->font[c], colormap); + + if (cv_debugfonts.value) + { + V_DrawFill(cx/FRACUNIT, cy/FRACUNIT, cw/FRACUNIT, fontspec.lfh/FRACUNIT, debugalternation ? debugcolor1 : debugcolor2); + debugalternation = !debugalternation; + } + + if (boxed != 1) + { + V_DrawFixedPatch(cx + cxoff + patchxofs, cy + cyoff + (boxed == 3 ? 2*FRACUNIT : 0), scale, + boxed ? boxedflags : flags, font->font[c], boxed ? 0 : colormap); + } + cx += cw; } else @@ -2782,6 +3073,9 @@ fixed_t V_StringScaledWidth( font_t *font; boolean uppercase; + boolean boxed = false; + boolean descriptive = false; + boolean largebutton = false; fixed_t cx; fixed_t right; @@ -2840,15 +3134,43 @@ fixed_t V_StringScaledWidth( case '\n': cx = 0; break; + case '\xEB': + if (fontno != TINY_FONT && fontno != HU_FONT) + largebutton = true; + break; + case '\xEF': + descriptive = true; + break; + case '\xEE': + case '\xED': + case '\xEC': + if (boxed) + cx += 3*FRACUNIT; + else + cx += 3*FRACUNIT; + boxed = !boxed; + break; default: if (( c & 0xF0 ) == 0x80 || c == V_STRINGDANCE) continue; if (( c & 0xB0 ) & 0x80) { - cw = V_GetButtonCodeWidth(c) * dupx; - cx += cw * scale; - right = cx; + if (descriptive) + { + cw = V_GetGenericButtonCodeWidth(c, largebutton) * dupx; + cx += cw * scale; + right = cx; + } + else + { + cw = V_GetButtonCodeWidth(c, largebutton) * dupx; + cx += cw * scale; + right = cx; + } + + largebutton = false; + descriptive = false; break; } @@ -2884,6 +3206,7 @@ fixed_t V_StringScaledWidth( } else cx += fontspec.spacew; + descriptive = false; } fullwidth = std::max(right, std::max(cx, fullwidth)); @@ -2909,6 +3232,9 @@ char * V_ScaledWordWrap( font_t *font; boolean uppercase; + boolean largebutton = false; + boolean descriptive = false; + boolean boxed = false; fixed_t cx; fixed_t right; @@ -2980,14 +3306,36 @@ char * V_ScaledWordWrap( cxatstart = 0; startwriter = 0; break; + case '\xEB': + if (fontno != TINY_FONT && fontno != HU_FONT) + largebutton = true; + case '\xEF': + descriptive = true; + break; + case '\xEE': + case '\xED': + case '\xEC': + if (boxed) + cx += 3*FRACUNIT; + else + cx += 3*FRACUNIT; + boxed = !boxed; + break; default: if (( c & 0xF0 ) == 0x80 || c == V_STRINGDANCE) ; else if (( c & 0xB0 ) & 0x80) // button prompts { - cw = V_GetButtonCodeWidth(c) * dupx; + if (descriptive) + cw = V_GetGenericButtonCodeWidth(c, largebutton) * dupx; + else + cw = V_GetButtonCodeWidth(c, largebutton) * dupx; + cx += cw * scale; right = cx; + + descriptive = false; + boxed = false; } else { @@ -3416,11 +3764,10 @@ void VID_DisplaySoftwareScreen() // TODO implement // upload framebuffer, bind pipeline, draw rhi::Rhi* rhi = srb2::sys::get_rhi(srb2::sys::g_current_rhi); - rhi::Handle ctx = srb2::sys::main_graphics_context(); hwr2::HardwareState* hw_state = srb2::sys::main_hardware_state(); // Misnomer; this just uploads the screen to the software indexed screen texture - hw_state->software_screen_renderer->draw(*rhi, ctx); + hw_state->software_screen_renderer->draw(*rhi); const int screens = std::clamp(r_splitscreen + 1, 1, MAXSPLITSCREENPLAYERS); hw_state->blit_postimg_screens->set_num_screens(screens); @@ -3477,5 +3824,12 @@ void VID_DisplaySoftwareScreen() } // Post-process blit to the 'default' framebuffer - hw_state->blit_postimg_screens->draw(*rhi, ctx); + hw_state->blit_postimg_screens->draw(*rhi); +} + +char *V_ParseText(const char *rawText) +{ + using srb2::Draw; + + return Z_StrDup(srb2::Draw::TextElement().parse(rawText).string().c_str()); } diff --git a/src/v_video.h b/src/v_video.h index 018cd2c51..a41da4de5 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -442,6 +442,9 @@ void VID_BlitLinearScreen(const UINT8 *srcptr, UINT8 *destptr, INT32 width, INT3 */ void VID_DisplaySoftwareScreen(void); +char *V_ParseText(const char *rawText); // Launder srb2::draw::TextElement.parse() through C code! + + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/vid_copy.s b/src/vid_copy.s index 70a1eb70c..c5ac639a2 100644 --- a/src/vid_copy.s +++ b/src/vid_copy.s @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/w_wad.cpp b/src/w_wad.cpp index 8d31c97a1..4a2c11ed3 100644 --- a/src/w_wad.cpp +++ b/src/w_wad.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -70,6 +70,7 @@ #include "md5.h" #include "lua_script.h" #include "g_game.h" // G_SetGameModified +#include "d_main.h" #include "k_terrain.h" @@ -113,6 +114,14 @@ static UINT16 lumpnumcacheindex = 0; UINT16 numwadfiles = 0; // number of active wadfiles wadfile_t *wadfiles[MAX_WADFILES]; // 0 to numwadfiles-1 are valid +static FILE *g_shaderspk3file; +static UINT16 g_shaderspk3numlumps; +static lumpinfo_t *g_shaderspk3lumps; + +#ifndef NOMD5 +static void PrintMD5String(const UINT8 *md5, char *buf); +#endif + // W_Shutdown // Closes all of the WAD files before quitting // If not done on a Mac then open wad files @@ -138,6 +147,24 @@ void W_Shutdown(void) Z_Free(wad->lumpinfo); Z_Free(wad); } + + // Cleanup the separate shader lookup + if (g_shaderspk3file) + { + while (g_shaderspk3numlumps--) + { + lumpinfo_t *lump = &g_shaderspk3lumps[g_shaderspk3numlumps]; + Z_Free(lump->longname); + if (lump->fullname != lump->longname) + { + Z_Free(lump->fullname); + } + } + Z_Free(g_shaderspk3lumps); + g_shaderspk3lumps = NULL; + fclose(g_shaderspk3file); + g_shaderspk3file = NULL; + } } //=========================================================================== @@ -780,7 +807,7 @@ static UINT16 W_InitFileError (const char *filename, boolean exitworthy) // // Can now load dehacked files (.soc) // -UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup) +UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup, const char *md5expected) { FILE *handle; lumpinfo_t *lumpinfo = NULL; @@ -839,6 +866,53 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup) // W_MakeFileMD5(filename, md5sum); + if (md5expected) + { + // moved Graue's W_VerifyFileMD5 inline here + UINT8 realmd5[MD5_LEN]; + INT32 ix; + + I_Assert(strlen(md5expected) == 2*MD5_LEN); + + // Convert an md5 string like "7d355827fa8f981482246d6c95f9bd48" + // into a real md5. + for (ix = 0; ix < 2*MD5_LEN; ix++) + { + INT32 n, c = md5expected[ix]; + if (isdigit(c)) + n = c - '0'; + else + { + I_Assert(isxdigit(c)); + if (isupper(c)) n = c - 'A' + 10; + else n = c - 'a' + 10; + } + if (ix & 1) realmd5[ix>>1] = (UINT8)(realmd5[ix>>1]+n); + else realmd5[ix>>1] = (UINT8)(n<<4); + } + + if (memcmp(realmd5, md5sum, 16) != 0) + { + char actualmd5text[2*MD5_LEN+1]; + PrintMD5String(md5sum, actualmd5text); +#ifdef DEVELOP + CONS_Printf("File %s does not match expected md5\n", filename); +#else + if (startup) + { + I_Error(M_GetText("File is old, is corrupt or has been modified: %s (found md5: %s, wanted: %s)\n"), filename, actualmd5text, md5expected); + } + else + { + CONS_Alert(CONS_ERROR, M_GetText("Did not load file %s because it did not match expected md5sum %s\n"), filename, md5expected); + if (handle) + fclose(handle); + return W_InitFileError(filename, false); + } +#endif + } + } + for (i = 0; i < numwadfiles; i++) { if (!memcmp(wadfiles[i]->md5sum, md5sum, 16)) @@ -964,24 +1038,25 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup) * Each file is optional, but at least one file must be found or an error will * result. Lump names can appear multiple times. The name searcher looks * backwards, so a later file overrides all earlier ones. - * - * \param filenames A null-terminated list of files to use. */ -INT32 W_InitMultipleFiles(char **filenames, boolean addons) +INT32 W_InitMultipleFiles(const initmultiplefilesentry_t *entries, INT32 count, boolean addons) { + INT32 i; INT32 rc = 1; INT32 overallrc = 1; // will be realloced as lumps are added - for (; *filenames; filenames++) + for (i = 0; i < count; ++i) { - if (addons && !W_VerifyNMUSlumps(*filenames, !addons)) + const initmultiplefilesentry_t *entry = &entries[i]; + + if (addons && !W_VerifyNMUSlumps(entry->filename, !addons)) G_SetGameModified(true, false); //CONS_Debug(DBG_SETUP, "Loading %s\n", *filenames); - rc = W_InitFile(*filenames, !addons, true); + rc = W_InitFile(entry->filename, !addons, true, entry->md5sum); if (rc == INT16_MAX) - CONS_Printf(M_GetText("Errors occurred while loading %s; not added.\n"), *filenames); + CONS_Printf(M_GetText("Errors occurred while loading %s; not added.\n"), entry->filename); overallrc &= (rc != INT16_MAX) ? 1 : 0; } @@ -2001,7 +2076,7 @@ void *W_CachePatchNumPwad(UINT16 wad, UINT16 lump, INT32 tag) #ifdef HWRENDER // Software-only compile cache the data without conversion - if (rendermode == render_soft || rendermode == render_none) + if (rendermode != render_opengl) #endif return (void *)patch; @@ -2073,57 +2148,6 @@ static void PrintMD5String(const UINT8 *md5, char *buf) md5[12], md5[13], md5[14], md5[15]); } #endif -/** Verifies a file's MD5 is as it should be. - * For releases, used as cheat prevention -- if the MD5 doesn't match, a - * fatal error is thrown. In debug mode, an MD5 mismatch only triggers a - * warning. - * - * \param wadfilenum Number of the loaded wad file to check. - * \param matchmd5 The MD5 sum this wad should have, expressed as a - * textual string. - * \author Graue - */ -void W_VerifyFileMD5(UINT16 wadfilenum, const char *matchmd5) -{ -#ifdef NOMD5 - (void)wadfilenum; - (void)matchmd5; -#else - UINT8 realmd5[MD5_LEN]; - INT32 ix; - - I_Assert(strlen(matchmd5) == 2*MD5_LEN); - I_Assert(wadfilenum < numwadfiles); - // Convert an md5 string like "7d355827fa8f981482246d6c95f9bd48" - // into a real md5. - for (ix = 0; ix < 2*MD5_LEN; ix++) - { - INT32 n, c = matchmd5[ix]; - if (isdigit(c)) - n = c - '0'; - else - { - I_Assert(isxdigit(c)); - if (isupper(c)) n = c - 'A' + 10; - else n = c - 'a' + 10; - } - if (ix & 1) realmd5[ix>>1] = (UINT8)(realmd5[ix>>1]+n); - else realmd5[ix>>1] = (UINT8)(n<<4); - } - - if (memcmp(realmd5, wadfiles[wadfilenum]->md5sum, 16)) - { - char actualmd5text[2*MD5_LEN+1]; - PrintMD5String(wadfiles[wadfilenum]->md5sum, actualmd5text); -#ifdef _DEBUG - CONS_Printf -#else - I_Error -#endif - (M_GetText("File is old, is corrupt or has been modified: %s (found md5: %s, wanted: %s)\n"), wadfiles[wadfilenum]->filename, actualmd5text, matchmd5); - } -#endif -} // Verify versions for different archive // formats. checklist assumed to be valid. @@ -2406,6 +2430,8 @@ int W_VerifyNMUSlumps(const char *filename, boolean exit_on_error) {"K_", 2}, // Kart graphic changes {"MUSICDEF", 8}, // Kart song definitions + {"TLG_", 4}, // Generic button legends + #ifdef HWRENDER {"SHADERS", 7}, {"SH_", 3}, @@ -2421,6 +2447,186 @@ int W_VerifyNMUSlumps(const char *filename, boolean exit_on_error) return status; } +void W_InitShaderLookup(const char *filename) +{ + I_Assert(g_shaderspk3file == NULL); + + FILE* handle; + char filename_buf[2048]; + + g_shaderspk3file = NULL; + g_shaderspk3lumps = NULL; + g_shaderspk3numlumps = 0; + + strncpy(filename_buf, filename, 2048); + filename_buf[2048 - 1] = '\0'; + + if ((handle = fopen(filename_buf, "rb")) == NULL) + { + nameonly(filename_buf); + + if (findfile(filename_buf, NULL, true)) + { + if ((handle = fopen(filename_buf, "rb")) == NULL) + { + return; + } + } + else + { + return; + } + } + + // It is acceptable to fail opening the pk3 lookup. + // The shader pk3 lookup is only needed to build a lookup directory of the zip + // for later. We always check for the flat file shader anyway. + + UINT16 numlumps; + lumpinfo_t *shader_lumps = ResGetLumpsZip(handle, &numlumps); + if (shader_lumps == NULL) + { + return; + } + g_shaderspk3file = handle; + g_shaderspk3lumps = shader_lumps; + g_shaderspk3numlumps = numlumps; +} + +static boolean ReadShaderFlatFile(const char *filename, size_t *size, void *dest) +{ + FILE* flat_handle = NULL; + char filename_buf[2048]; + char filename_only_buf[512]; + + strncpy(filename_buf, filename, 2048); + filename_buf[2048 - 1] = '\0'; + + if ((flat_handle = fopen(filename_buf, "rb")) == NULL) + { + nameonly(filename_buf); + strncpy(filename_only_buf, filename_buf, 512); + filename_only_buf[512 - 1] = '\0'; + sprintf(filename_buf, "%s/shaders/%s", srb2path, filename_only_buf); + if ((flat_handle = fopen(filename_buf, "rb")) == NULL) + { + return false; + } + } + + // idk, pray it's not >2gb. ansi c made mistakes + fseek(flat_handle, 0, SEEK_END); + *size = ftell(flat_handle); + fseek(flat_handle, 0, SEEK_SET); + if (dest) + { + fread(dest, *size, 1, flat_handle); + } + + fclose(flat_handle); + return true; +} + +boolean W_ReadShader(const char *filename, size_t *size, void *dest) +{ + I_Assert(filename != NULL); + I_Assert(size != NULL); + + if (ReadShaderFlatFile(filename, size, dest)) + { + return true; + } + + UINT32 hash = quickncasehash(filename, 512); + + lumpinfo_t* lump = NULL; + for (int i = 0 ; i < g_shaderspk3numlumps; ++i) + { + lump = &g_shaderspk3lumps[i]; + UINT32 lumpnamehash = quickncasehash(lump->fullname, 512); + if (lumpnamehash == hash) + { + break; + } + lump = NULL; + } + + if (lump == NULL) + { + return false; + } + + size_t sizelocal = lump->size; + if (dest == NULL) + { + *size = sizelocal; + return true; + } + + if (fseek(g_shaderspk3file, lump->position, SEEK_SET) != 0) + I_Error("Failed to seek shaders pk3 to offset of file: %s", strerror(errno)); + + switch (lump->compression) + { + case CM_NOCOMPRESSION: + if (fread(dest, sizelocal, 1, g_shaderspk3file) != 0) + I_Error("Failed to read file in shaders pk3: %s", strerror(errno)); + break; +#ifdef HAVE_ZLIB + case CM_DEFLATE: + { + UINT8 *rawData; // The lump's raw data. + UINT8 *decData; // Lump's decompressed real data. + + int zErr; // Helper var. + z_stream strm; + unsigned long rawSize = lump->disksize; + unsigned long decSize = (unsigned long)size; + + rawData = static_cast(Z_Malloc(rawSize, PU_STATIC, NULL)); + decData = static_cast(dest); + + if (fread(rawData, 1, rawSize, g_shaderspk3file) < rawSize) + I_Error("Failed to read compressed file in shaders pk3: %s", strerror(errno)); + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + + strm.total_in = strm.avail_in = rawSize; + strm.total_out = strm.avail_out = decSize; + + strm.next_in = rawData; + strm.next_out = decData; + + zErr = inflateInit2(&strm, -15); + if (zErr == Z_OK) + { + zErr = inflate(&strm, Z_SYNC_FLUSH); + if (zErr != Z_OK && zErr != Z_STREAM_END) + { + zerr(zErr); + } + (void)inflateEnd(&strm); + } + else + { + size = 0; + zerr(zErr); + } + + Z_Free(rawData); + } + break; +#endif + default: + return false; + } + + *size = sizelocal; + return true; +} + /** \brief Generates a virtual resource used for level data loading. * * \param lumpnum_t reference diff --git a/src/w_wad.h b/src/w_wad.h index a928ffc44..b834e31a8 100644 --- a/src/w_wad.h +++ b/src/w_wad.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -145,12 +145,17 @@ void W_Shutdown(void); // Opens a WAD file. Returns the FILE * handle for the file, or NULL if not found or could not be opened FILE *W_OpenWadFile(const char **filename, boolean useerrors); // Load and add a wadfile to the active wad files, returns numbers of lumps, INT16_MAX on error -UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup); +UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup, const char *md5expected); +typedef struct initmultiplefilesentry_t +{ + const char *filename; + const char *md5sum; +} initmultiplefilesentry_t; // W_InitMultipleFiles returns 1 if all is okay, 0 otherwise, // so that it stops with a message if a file was not found, but not if all is okay. // W_InitMultipleFiles exits if a file was not found, but not if all is okay. -INT32 W_InitMultipleFiles(char **filenames, boolean addons); +INT32 W_InitMultipleFiles(const initmultiplefilesentry_t *entries, INT32 count, boolean addons); const char *W_CheckNameForNumPwad(UINT16 wad, UINT16 lump); const char *W_CheckNameForNum(lumpnum_t lumpnum); @@ -217,10 +222,12 @@ void *W_CacheSoftwarePatchNum(lumpnum_t lumpnum, INT32 tag); void W_UnlockCachedPatch(void *patch); -void W_VerifyFileMD5(UINT16 wadfilenum, const char *matchmd5); - int W_VerifyNMUSlumps(const char *filename, boolean exit_on_error); +/// Initialize non-legacy GL shader lookup, which lives outside the lump management system. +void W_InitShaderLookup(const char *filename); +boolean W_ReadShader(const char *filename, size_t *size, void *dest); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/win32/afxres.h b/src/win32/afxres.h index 02b634e39..288478a57 100644 --- a/src/win32/afxres.h +++ b/src/win32/afxres.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/win32/win_dbg.c b/src/win32/win_dbg.c index d23765ca3..df326cc81 100644 --- a/src/win32/win_dbg.c +++ b/src/win32/win_dbg.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/win32/win_dbg.h b/src/win32/win_dbg.h index 9e15e8135..26344afb3 100644 --- a/src/win32/win_dbg.h +++ b/src/win32/win_dbg.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/win32ce/win_dbg.c b/src/win32ce/win_dbg.c index 2cc299ccd..280fe9d34 100644 --- a/src/win32ce/win_dbg.c +++ b/src/win32ce/win_dbg.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/win32ce/win_dll.c b/src/win32ce/win_dll.c index 6e6037b1e..65f547ecd 100644 --- a/src/win32ce/win_dll.c +++ b/src/win32ce/win_dll.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/win32ce/win_dll.h b/src/win32ce/win_dll.h index eaa4390bb..4bfb53516 100644 --- a/src/win32ce/win_dll.h +++ b/src/win32ce/win_dll.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/win32ce/win_main.c b/src/win32ce/win_main.c index 044f7450f..8a1b4eca3 100644 --- a/src/win32ce/win_main.c +++ b/src/win32ce/win_main.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/win32ce/win_sys.c b/src/win32ce/win_sys.c index 5aa11b842..40f454bf4 100644 --- a/src/win32ce/win_sys.c +++ b/src/win32ce/win_sys.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // diff --git a/src/win32ce/win_vid.c b/src/win32ce/win_vid.c index 2d65c1897..fc3ddbb7a 100644 --- a/src/win32ce/win_vid.c +++ b/src/win32ce/win_vid.c @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // @@ -233,8 +233,8 @@ void I_LoadingScreen(LPCSTR msg) void I_ReadScreen(UINT8 *scr) { // DEBUGGING - if (rendermode != render_soft) - I_Error("I_ReadScreen: called while in non-software mode"); + if (rendermode == render_opengl) + I_Error("I_ReadScreen: called while in Legacy GL mode"); VID_BlitLinearScreen(screens[0], scr, vid.width*vid.bpp, vid.height, vid.width*vid.bpp, vid.rowbytes); } diff --git a/src/y_inter.cpp b/src/y_inter.cpp index 465df3e38..f4bd78c51 100644 --- a/src/y_inter.cpp +++ b/src/y_inter.cpp @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the @@ -50,12 +50,15 @@ #include "k_hud.h" // K_DrawMapThumbnail #include "k_battle.h" #include "k_boss.h" +#include "k_kart.h" #include "k_pwrlv.h" #include "k_grandprix.h" #include "k_serverstats.h" // SV_BumpMatchStats #include "m_easing.h" #include "music.h" +#include "v_draw.hpp" + #ifdef HWRENDER #include "hardware/hw_main.h" #endif @@ -101,6 +104,11 @@ static boolean Y_CanSkipIntermission(void) return false; } +boolean Y_IntermissionPlayerLock(void) +{ + return (gamestate == GS_INTERMISSION && data.rankingsmode == false); +} + static void Y_UnloadData(void); // @@ -189,8 +197,6 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) numplayersingame++; } - memset(data.color, 0, sizeof (data.color)); - memset(data.character, 0, sizeof (data.character)); memset(completed, 0, sizeof (completed)); data.numplayers = 0; data.showroundnum = false; @@ -200,9 +206,63 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) srb2::StandingsJson standings {}; bool savestandings = (!rankingsmode && demo.recording); + // Team stratification (this code only barely supports more than 2 teams) + data.winningteam = TEAM_UNASSIGNED; + data.halfway = UINT8_MAX; + + UINT8 countteam[TEAM__MAX]; + UINT8 smallestteam = UINT8_MAX; + memset(countteam, 0, sizeof(countteam)); + + if (rankingsmode == 0 && G_GametypeHasTeams()) + { + for (i = data.winningteam+1; i < TEAM__MAX; i++) + { + countteam[i] = G_CountTeam(i); + + if (g_teamscores[data.winningteam] < g_teamscores[i]) + { + data.winningteam = i; + } + + if (smallestteam > countteam[i]) + { + smallestteam = countteam[i]; + } + } + + if (countteam[data.winningteam]) + { + data.halfway = countteam[data.winningteam] - 1; + } + } + for (j = 0; j < numplayersingame; j++) { - for (i = 0; i < MAXPLAYERS; i++) + i = 0; + + if (data.winningteam != TEAM_UNASSIGNED) + { + for (; i < MAXPLAYERS; i++) + { + if (!playeringame[i] || players[i].spectator || completed[i]) + continue; + + if (players[i].team != data.winningteam) + continue; + + comparison(i); + } + + if (data.val[data.numplayers] == UINT32_MAX) + { + // Only run the un-teamed loop if everybody + // on the winning team was previously placed + i = 0; + } + } + + for (; i < MAXPLAYERS; i++) { if (!playeringame[i] || players[i].spectator || completed[i]) continue; @@ -215,9 +275,6 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) completed[i] = true; data.grade[i] = K_PlayerTallyActive(&players[i]) ? players[i].tally.rank : GRADE_INVALID; - data.color[data.numplayers] = players[i].skincolor; - data.character[data.numplayers] = players[i].skin; - if (data.numplayers && (data.val[data.numplayers] == data.val[data.numplayers-1])) { data.pos[data.numplayers] = data.pos[data.numplayers-1]; @@ -233,22 +290,42 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) if (!rankingsmode) { - if ((powertype == PWRLV_DISABLED) - && !(players[i].pflags & PF_NOCONTEST) - && (data.pos[data.numplayers] < (numplayersingame + spectateGriefed))) + // Online rank is handled further below in this file. + if (powertype == PWRLV_DISABLED) { - // Online rank is handled further below in this file. - data.increase[i] = K_CalculateGPRankPoints(data.pos[data.numplayers], numplayersingame + spectateGriefed); - players[i].score += data.increase[i]; + if (data.winningteam != TEAM_UNASSIGNED) + { + // TODO ASK TYRON + if (smallestteam != 0 + && players[i].team == data.winningteam) + { + data.increase[i] = 1; + } + } + else + { + UINT8 pointgetters = numplayersingame + spectateGriefed; + + if (data.pos[data.numplayers] < pointgetters + && !(players[i].pflags & PF_NOCONTEST)) + { + data.increase[i] = K_CalculateGPRankPoints((&players[i])->exp, data.pos[data.numplayers], pointgetters); + } + } + + if (data.increase[i] > 0) + { + players[i].score += data.increase[i]; + } } if (savestandings) { srb2::StandingJson standing {}; standing.ranking = data.pos[data.numplayers]; - standing.name = std::string(player_names[i]); - standing.demoskin = data.character[data.numplayers]; - standing.skincolor = std::string(skincolors[data.color[data.numplayers]].name); + standing.name = srb2::String(player_names[i]); + standing.demoskin = players[i].skin; + standing.skincolor = srb2::String(skincolors[players[i].skincolor].name); standing.timeorscore = data.val[data.numplayers]; standings.standings.emplace_back(std::move(standing)); } @@ -288,6 +365,14 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) data.numplayers++; } + if (data.numplayers <= 2 + || data.halfway == UINT8_MAX + || data.halfway >= 8 + || (data.numplayers - data.halfway) >= 8) + { + data.halfway = (data.numplayers-1)/2; + } + if (savestandings) { srb2::write_current_demo_end_marker(); @@ -384,7 +469,19 @@ static void Y_CalculateMatchData(UINT8 rankingsmode, void (*comparison)(INT32)) { data.mainplayer = i; - if (!(players[i].pflags & PF_NOCONTEST)) + if (data.winningteam != TEAM_UNASSIGNED + && players[i].team != TEAM_UNASSIGNED) + { + data.gotthrough = true; + + snprintf(data.headerstring, + sizeof data.headerstring, + "%s TEAM", + g_teaminfo[players[i].team].name); + + data.showroundnum = true; + } + else if (!(players[i].pflags & PF_NOCONTEST)) { data.gotthrough = true; @@ -504,11 +601,13 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) x2 -= 9; } - if (standings->numplayers > 10) + UINT8 halfway = standings->halfway; + + if (halfway > 4) { yspacing--; } - else if (standings->numplayers <= 6) + else if (halfway <= 2) { yspacing++; if (verticalresults) @@ -543,7 +642,7 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) ); i = 0; - UINT8 halfway = (standings->numplayers-1)/2; + if (doreverse) { i = standings->numplayers-1; @@ -561,13 +660,13 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) else { UINT8 *charcolormap = NULL; - if (!R_CanShowSkinInDemo(standings->character[i])) + if (!R_CanShowSkinInDemo(players[pnum].skin)) { - charcolormap = R_GetTranslationColormap(TC_BLINK, static_cast(standings->color[i]), GTC_CACHE); + charcolormap = R_GetTranslationColormap(TC_BLINK, static_cast(players[pnum].skincolor), GTC_CACHE); } - else if (standings->color[i] != SKINCOLOR_NONE) + else { - charcolormap = R_GetTranslationColormap(standings->character[i], static_cast(standings->color[i]), GTC_CACHE); + charcolormap = R_GetTranslationColormap(players[pnum].skin, static_cast(players[pnum].skincolor), GTC_CACHE); } if (standings->isduel) @@ -586,7 +685,7 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) M_DrawCharacterSprite( duelx + 40, duely + 78, - standings->character[i], + players[pnum].skin, spr2, (datarightofcolumn ? 1 : 7), 0, @@ -638,7 +737,7 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) V_DrawRightAlignedThinString(x+13, y-2, 0, va("%d", standings->pos[i])); - if (standings->color[i] != SKINCOLOR_NONE) + //if (players[pnum].skincolor != SKINCOLOR_NONE) { if ((players[pnum].pflags & PF_NOCONTEST) && players[pnum].bot) { @@ -647,15 +746,15 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) x+14, y-5, 0, static_cast(W_CachePatchName("MINIDEAD", PU_CACHE)), - R_GetTranslationColormap(TC_DEFAULT, static_cast(standings->color[i]), GTC_CACHE) + R_GetTranslationColormap(TC_DEFAULT, static_cast(players[pnum].skincolor), GTC_CACHE) ); } else { - charcolormap = R_GetTranslationColormap(standings->character[i], static_cast(standings->color[i]), GTC_CACHE); + charcolormap = R_GetTranslationColormap(players[pnum].skin, static_cast(players[pnum].skincolor), GTC_CACHE); V_DrawMappedPatch(x+14, y-5, 0, - R_CanShowSkinInDemo(standings->character[i]) ? - faceprefix[standings->character[i]][FACE_MINIMAP] : kp_unknownminimap, + R_CanShowSkinInDemo(players[pnum].skin) ? + faceprefix[players[pnum].skin][FACE_MINIMAP] : kp_unknownminimap, charcolormap); } } @@ -701,6 +800,39 @@ void Y_PlayerStandingsDrawer(y_data_t *standings, INT32 xoffset) player_names[pnum] ); + { + patch_t *voxpat; + int voxxoffs = 0; + int voxyoffs = 0; + if (players[pnum].pflags2 & (PF2_SELFDEAFEN | PF2_SERVERDEAFEN)) + { + voxpat = (patch_t*) W_CachePatchName("VOXCRD", PU_HUDGFX); + voxxoffs = 1; + voxyoffs = -5; + } + else if (players[pnum].pflags2 & (PF2_SELFMUTE | PF2_SERVERMUTE)) + { + voxpat = (patch_t*) W_CachePatchName("VOXCRM", PU_HUDGFX); + voxxoffs = 1; + voxyoffs = -6; + } + else if (S_IsPlayerVoiceActive(pnum)) + { + voxpat = (patch_t*) W_CachePatchName("VOXCRA", PU_HUDGFX); + voxyoffs = -4; + } + else + { + voxpat = NULL; + } + + if (voxpat) + { + int namewidth = V_ThinStringWidth(player_names[pnum], 0); + V_DrawFixedPatch((x + 27 + namewidth + voxxoffs) * FRACUNIT, (y + voxyoffs) * FRACUNIT, FRACUNIT, 0, voxpat, NULL); + } + } + V_DrawRightAlignedThinString( x+118, y-2, 0, @@ -1474,6 +1606,13 @@ void Y_DrawIntermissionButton(INT32 startslide, INT32 through, boolean widescree ); } + using srb2::Draw; + + Draw::TextElement text = Draw::TextElement().parse(pressed ? "" : ""); + Draw draw = Draw(FixedToFloat(2*FRACUNIT - offset), FixedToFloat((BASEVIDHEIGHT - 16)*FRACUNIT)).flags(widescreen ? (V_SNAPTOLEFT|V_SNAPTOBOTTOM) : 0); + draw.text(text.string()); + + /* K_drawButton( 2*FRACUNIT - offset, (BASEVIDHEIGHT - 16)*FRACUNIT, @@ -1484,6 +1623,7 @@ void Y_DrawIntermissionButton(INT32 startslide, INT32 through, boolean widescree kp_button_a[1], pressed ); + */ } } @@ -1810,13 +1950,15 @@ void Y_Ticker(void) // Team scramble code for team match and CTF. // Don't do this if we're going to automatically scramble teams next round. - /*if (G_GametypeHasTeams() && cv_teamscramble.value && !cv_scrambleonchange.value && server) + /* + if (G_GametypeHasTeams() && cv_teamscramble.value && !cv_scrambleonchange.value && server) { // If we run out of time in intermission, the beauty is that // the P_Ticker() team scramble code will pick it up. if ((intertic % (TICRATE/7)) == 0) P_DoTeamscrambling(); - }*/ + } + */ if ((timer < INFINITE_TIMER && --timer <= 0) || (intertic == endtic)) @@ -1882,8 +2024,7 @@ void Y_Ticker(void) { if (!data.rankingsmode && sorttic != -1 && (intertic >= sorttic + 8)) { - // Anything with post-intermission consequences here should also occur in Y_EndIntermission. - K_RetireBots(); + Y_MidIntermission(); Y_CalculateMatchData(1, Y_CompareRank); } @@ -2070,10 +2211,7 @@ static UINT32 Y_EstimatePodiumScore(player_t *const player, UINT8 numPlaying) UINT8 pos = Y_PlayersBestPossiblePosition(player); UINT32 ourScore = player->score; - if (pos < numPlaying) - { - ourScore += K_CalculateGPRankPoints(pos, numPlaying); - } + ourScore += K_CalculateGPRankPoints(player->exp, pos, numPlaying); return ourScore; } @@ -2350,6 +2488,27 @@ void Y_StartIntermission(void) // ====== +// +// Y_MidIntermission +// +void Y_MidIntermission(void) +{ + // Replacing bots that fail out of play + K_RetireBots(); + + // If tournament play is not in action... + if (roundqueue.position == 0) + { + // Unset player teams in anticipation of P_ShuffleTeams + + UINT8 i; + for (i = 0; i < MAXPLAYERS; i++) + { + players[i].team = TEAM_UNASSIGNED; + } + } +} + // // Y_EndIntermission // @@ -2357,7 +2516,7 @@ void Y_EndIntermission(void) { if (!data.rankingsmode) { - K_RetireBots(); + Y_MidIntermission(); } Y_UnloadData(); @@ -2377,7 +2536,7 @@ static void Y_UnloadData(void) { // In hardware mode, don't Z_ChangeTag a pointer returned by W_CachePatchName(). // It doesn't work and is unnecessary. - if (rendermode != render_soft) + if (rendermode == render_opengl) return; // unload the background patches diff --git a/src/y_inter.h b/src/y_inter.h index 0f3687db4..1a4647590 100644 --- a/src/y_inter.h +++ b/src/y_inter.h @@ -1,7 +1,7 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Vivian "toastergrl" Grannell. -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Vivian "toastergrl" Grannell. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // // This program is free software distributed under the @@ -25,18 +25,17 @@ typedef struct boolean showrank; // show rank-restricted queue entry at the end, if it exists boolean encore; // encore mode boolean isduel; // duel mode + UINT8 winningteam; // teamplay boolean showroundnum; // round number char headerstring[64]; // holds levelnames up to 64 characters UINT8 numplayers; // Number of players being displayed + UINT8 halfway; // Position at which column switches SINT8 num[MAXPLAYERS]; // Player # UINT8 pos[MAXPLAYERS]; // player positions. used for ties - UINT8 character[MAXPLAYERS]; // Character # - UINT16 color[MAXPLAYERS]; // Color # - UINT32 val[MAXPLAYERS]; // Gametype-specific value char strval[MAXPLAYERS][MAXPLAYERNAME+1]; @@ -59,10 +58,13 @@ void Y_RoundQueueDrawer(y_data_t *standings, INT32 offset, boolean doanimations, void Y_DrawIntermissionButton(INT32 startslide, INT32 through, boolean widescreen); void Y_StartIntermission(void); +void Y_MidIntermission(void); void Y_EndIntermission(void); void Y_PlayIntermissionMusic(void); +boolean Y_IntermissionPlayerLock(void); + typedef enum { int_none, diff --git a/src/z_zone.c b/src/z_zone.cpp similarity index 89% rename from src/z_zone.c rename to src/z_zone.cpp index 8924ed688..b5f73450b 100644 --- a/src/z_zone.c +++ b/src/z_zone.cpp @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2006 by Graue. // @@ -8,7 +8,7 @@ // terms of the GNU General Public License, version 2. // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -/// \file z_zone.c +/// \file z_zone.cpp /// \brief Zone memory allocation. /// This file does zone memory allocation. Each allocation is done with a /// tag, and this file keeps track of all the allocations made. Later, you @@ -31,6 +31,7 @@ #include +#include "core/memory.h" #include "doomdef.h" #include "doomstat.h" #include "r_patch.h" @@ -53,7 +54,6 @@ static boolean Z_calloc = false; #define ZONEID 0xa441d13d - typedef struct memblock_s { void **user; @@ -76,6 +76,16 @@ typedef struct memblock_s // both the head and tail of the zone memory block list static memblock_t head; +static constexpr size_t kLevelLargePoolBlockSize = sizeof(mobj_t); +static constexpr size_t kLevelMedPoolBlockSize = sizeof(precipmobj_t); +static constexpr size_t kLevelSmallPoolBlockSize = 128; +static constexpr size_t kLevelTinyPoolBlockSize = 64; + +static srb2::PoolAllocator g_level_large_pool { kLevelLargePoolBlockSize, 1024, PU_LEVEL }; +static srb2::PoolAllocator g_level_med_pool { kLevelMedPoolBlockSize, 32768, PU_LEVEL }; +static srb2::PoolAllocator g_level_small_pool { kLevelSmallPoolBlockSize, 4096, PU_LEVEL }; +static srb2::PoolAllocator g_level_tiny_pool { kLevelTinyPoolBlockSize, 8192, PU_LEVEL }; + // // Function prototypes // @@ -214,7 +224,7 @@ void *Z_Malloc2(size_t size, INT32 tag, void *user, INT32 alignbits, CONS_Debug(DBG_MEMORY, "Z_Malloc %s:%d\n", file, line); #endif - block = xm(sizeof (memblock_t) + ALIGNPAD + size); + block = (memblock_t*)xm(sizeof (memblock_t) + ALIGNPAD + size); TracyCAlloc(block, sizeof (memblock_t) + ALIGNPAD + size); ptr = MEMORY(block); I_Assert((intptr_t)ptr % alignof (max_align_t) == 0); @@ -243,7 +253,7 @@ void *Z_Malloc2(size_t size, INT32 tag, void *user, INT32 alignbits, if (user != NULL) { - block->user = user; + block->user = (void**)user; *(void **)user = ptr; } else if (tag >= PU_PURGELEVEL) @@ -355,6 +365,16 @@ void Z_FreeTags(INT32 lowtag, INT32 hightag) TracyCZone(__zone, true); Z_CheckHeap(420); + + // First, release all pools, since they can make allocations in zones. + if (PU_LEVEL >= lowtag && PU_LEVEL <= hightag) + { + g_level_large_pool.release(); + g_level_med_pool.release(); + g_level_small_pool.release(); + g_level_tiny_pool.release(); + } + for (block = head.next; block != &head; block = next) { next = block->next; // get link before freeing @@ -555,7 +575,7 @@ void Z_SetUser(void *ptr, void **newuser) I_Error("Internal memory management error: " "tried to make block purgable but it has no owner"); - block->user = (void*)newuser; + block->user = (void**)newuser; *newuser = ptr; } @@ -650,7 +670,7 @@ static void Command_Memdump_f(void) for (block = head.next; block != &head; block = block->next) if (block->tag >= mintag && block->tag <= maxtag) { - char *filename = strrchr(block->ownerfile, PATHSEP[0]); + const char *filename = strrchr(block->ownerfile, PATHSEP[0]); CONS_Printf("[%3d] %s (%s) bytes @ %s:%d\n", block->tag, sizeu1(block->size), sizeu2(block->realsize), filename ? filename + 1 : block->ownerfile, block->ownerline); } } @@ -662,5 +682,61 @@ static void Command_Memdump_f(void) */ char *Z_StrDup(const char *s) { - return strcpy(ZZ_Alloc(strlen(s) + 1), s); + return strcpy((char*)ZZ_Alloc(strlen(s) + 1), s); +} + +void* Z_LevelPoolMalloc(size_t size) +{ + void* p = nullptr; + if (size <= kLevelTinyPoolBlockSize) + { + p = g_level_tiny_pool.allocate(); + } + else if (size <= kLevelSmallPoolBlockSize) + { + p = g_level_small_pool.allocate(); + } + else if (size <= kLevelMedPoolBlockSize) + { + p = g_level_med_pool.allocate(); + } + else if (size <= kLevelLargePoolBlockSize) + { + p = g_level_large_pool.allocate(); + } + + if (p == nullptr) + { + p = Z_Malloc(size, PU_LEVEL, nullptr); + } + + return p; +} + +void* Z_LevelPoolCalloc(size_t size) +{ + void* p = Z_LevelPoolMalloc(size); + memset(p, 0, size); + return p; +} + +void Z_LevelPoolFree(void* p, size_t size) +{ + if (size <= kLevelTinyPoolBlockSize) + { + return g_level_tiny_pool.deallocate(p); + } + if (size <= kLevelSmallPoolBlockSize) + { + return g_level_small_pool.deallocate(p); + } + if (size <= kLevelMedPoolBlockSize) + { + return g_level_med_pool.deallocate(p); + } + if (size <= kLevelLargePoolBlockSize) + { + return g_level_large_pool.deallocate(p); + } + return Z_Free(p); } diff --git a/src/z_zone.h b/src/z_zone.h index 8bfb5f774..689756c73 100644 --- a/src/z_zone.h +++ b/src/z_zone.h @@ -1,6 +1,6 @@ // DR. ROBOTNIK'S RING RACERS //----------------------------------------------------------------------------- -// Copyright (C) 2024 by Kart Krew. +// Copyright (C) 2025 by Kart Krew. // Copyright (C) 2020 by Sonic Team Junior. // Copyright (C) 2000 by DooM Legacy Team. // Copyright (C) 1996 by id Software, Inc. @@ -148,6 +148,13 @@ size_t Z_TagsUsage(INT32 lowtag, INT32 hightag); char *Z_StrDup(const char *in); #define Z_Unlock(p) (void)p // TODO: remove this now that NDS code has been removed +// +// Specialty allocation functions +// +void *Z_LevelPoolMalloc(size_t size); +void *Z_LevelPoolCalloc(size_t size); +void Z_LevelPoolFree(void *p, size_t size); + #ifdef __cplusplus } // extern "C" #endif diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 45d8d5946..028c02910 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -15,3 +15,7 @@ add_subdirectory(tracy) add_subdirectory(libwebm) add_subdirectory(fmt) + +add_subdirectory(vulkan-headers) +add_subdirectory(volk) +add_subdirectory(vma) diff --git a/thirdparty/fmt/CMakeLists.txt b/thirdparty/fmt/CMakeLists.txt index 86e767e6a..f926d89eb 100644 --- a/thirdparty/fmt/CMakeLists.txt +++ b/thirdparty/fmt/CMakeLists.txt @@ -1,5 +1,5 @@ # Update from https://github.com/fmtlib/fmt -# fmt 10.1.1 +# fmt 11.1.3 # License: MIT with object code exception add_library(fmt INTERFACE) diff --git a/thirdparty/fmt/include/fmt/args.h b/thirdparty/fmt/include/fmt/args.h index 2d684e7cc..3ff478807 100644 --- a/thirdparty/fmt/include/fmt/args.h +++ b/thirdparty/fmt/include/fmt/args.h @@ -8,34 +8,39 @@ #ifndef FMT_ARGS_H_ #define FMT_ARGS_H_ -#include // std::reference_wrapper -#include // std::unique_ptr -#include +#ifndef FMT_MODULE +# include // std::reference_wrapper +# include // std::unique_ptr +# include +#endif -#include "core.h" +#include "format.h" // std_string_view FMT_BEGIN_NAMESPACE - namespace detail { template struct is_reference_wrapper : std::false_type {}; template struct is_reference_wrapper> : std::true_type {}; -template const T& unwrap(const T& v) { return v; } -template const T& unwrap(const std::reference_wrapper& v) { +template auto unwrap(const T& v) -> const T& { return v; } +template +auto unwrap(const std::reference_wrapper& v) -> const T& { return static_cast(v); } -class dynamic_arg_list { - // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for - // templates it doesn't complain about inability to deduce single translation - // unit for placing vtable. So storage_node_base is made a fake template. - template struct node { - virtual ~node() = default; - std::unique_ptr> next; - }; +// node is defined outside dynamic_arg_list to workaround a C2504 bug in MSVC +// 2022 (v17.10.0). +// +// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for +// templates it doesn't complain about inability to deduce single translation +// unit for placing vtable. So node is made a fake template. +template struct node { + virtual ~node() = default; + std::unique_ptr> next; +}; +class dynamic_arg_list { template struct typed_node : node<> { T value; @@ -50,7 +55,7 @@ class dynamic_arg_list { std::unique_ptr> head_; public: - template const T& push(const Arg& arg) { + template auto push(const Arg& arg) -> const T& { auto new_node = std::unique_ptr>(new typed_node(arg)); auto& value = new_node->value; new_node->next = std::move(head_); @@ -61,28 +66,18 @@ class dynamic_arg_list { } // namespace detail /** - \rst - A dynamic version of `fmt::format_arg_store`. - It's equipped with a storage to potentially temporary objects which lifetimes - could be shorter than the format arguments object. - - It can be implicitly converted into `~fmt::basic_format_args` for passing - into type-erased formatting functions such as `~fmt::vformat`. - \endrst + * A dynamic list of formatting arguments with storage. + * + * It can be implicitly converted into `fmt::basic_format_args` for passing + * into type-erased formatting functions such as `fmt::vformat`. */ -template -class dynamic_format_arg_store -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 - // Workaround a GCC template argument substitution bug. - : public basic_format_args -#endif -{ +template class dynamic_format_arg_store { private: using char_type = typename Context::char_type; template struct need_copy { static constexpr detail::type mapped_type = - detail::mapped_type_constant::value; + detail::mapped_type_constant::value; enum { value = !(detail::is_reference_wrapper::value || @@ -95,7 +90,7 @@ class dynamic_format_arg_store }; template - using stored_type = conditional_t< + using stored_t = conditional_t< std::is_convertible>::value && !detail::is_reference_wrapper::value, std::basic_string, T>; @@ -110,80 +105,72 @@ class dynamic_format_arg_store friend class basic_format_args; - unsigned long long get_types() const { - return detail::is_unpacked_bit | data_.size() | - (named_info_.empty() - ? 0ULL - : static_cast(detail::has_named_args_bit)); - } - - const basic_format_arg* data() const { + auto data() const -> const basic_format_arg* { return named_info_.empty() ? data_.data() : data_.data() + 1; } template void emplace_arg(const T& arg) { - data_.emplace_back(detail::make_arg(arg)); + data_.emplace_back(arg); } template void emplace_arg(const detail::named_arg& arg) { - if (named_info_.empty()) { - constexpr const detail::named_arg_info* zero_ptr{nullptr}; - data_.insert(data_.begin(), {zero_ptr, 0}); - } - data_.emplace_back(detail::make_arg(detail::unwrap(arg.value))); + if (named_info_.empty()) + data_.insert(data_.begin(), basic_format_arg(nullptr, 0)); + data_.emplace_back(detail::unwrap(arg.value)); auto pop_one = [](std::vector>* data) { data->pop_back(); }; std::unique_ptr>, decltype(pop_one)> guard{&data_, pop_one}; named_info_.push_back({arg.name, static_cast(data_.size() - 2u)}); - data_[0].value_.named_args = {named_info_.data(), named_info_.size()}; + data_[0] = {named_info_.data(), named_info_.size()}; guard.release(); } public: constexpr dynamic_format_arg_store() = default; + operator basic_format_args() const { + return basic_format_args(data(), static_cast(data_.size()), + !named_info_.empty()); + } + /** - \rst - Adds an argument into the dynamic store for later passing to a formatting - function. - - Note that custom types and string types (but not string views) are copied - into the store dynamically allocating memory if necessary. - - **Example**:: - - fmt::dynamic_format_arg_store store; - store.push_back(42); - store.push_back("abc"); - store.push_back(1.5f); - std::string result = fmt::vformat("{} and {} and {}", store); - \endrst - */ + * Adds an argument into the dynamic store for later passing to a formatting + * function. + * + * Note that custom types and string types (but not string views) are copied + * into the store dynamically allocating memory if necessary. + * + * **Example**: + * + * fmt::dynamic_format_arg_store store; + * store.push_back(42); + * store.push_back("abc"); + * store.push_back(1.5f); + * std::string result = fmt::vformat("{} and {} and {}", store); + */ template void push_back(const T& arg) { if (detail::const_check(need_copy::value)) - emplace_arg(dynamic_args_.push>(arg)); + emplace_arg(dynamic_args_.push>(arg)); else emplace_arg(detail::unwrap(arg)); } /** - \rst - Adds a reference to the argument into the dynamic store for later passing to - a formatting function. - - **Example**:: - - fmt::dynamic_format_arg_store store; - char band[] = "Rolling Stones"; - store.push_back(std::cref(band)); - band[9] = 'c'; // Changing str affects the output. - std::string result = fmt::vformat("{}", store); - // result == "Rolling Scones" - \endrst - */ + * Adds a reference to the argument into the dynamic store for later passing + * to a formatting function. + * + * **Example**: + * + * fmt::dynamic_format_arg_store store; + * char band[] = "Rolling Stones"; + * store.push_back(std::cref(band)); + * band[9] = 'c'; // Changing str affects the output. + * std::string result = fmt::vformat("{}", store); + * // result == "Rolling Scones" + */ template void push_back(std::reference_wrapper arg) { static_assert( need_copy::value, @@ -192,41 +179,40 @@ class dynamic_format_arg_store } /** - Adds named argument into the dynamic store for later passing to a formatting - function. ``std::reference_wrapper`` is supported to avoid copying of the - argument. The name is always copied into the store. - */ + * Adds named argument into the dynamic store for later passing to a + * formatting function. `std::reference_wrapper` is supported to avoid + * copying of the argument. The name is always copied into the store. + */ template void push_back(const detail::named_arg& arg) { const char_type* arg_name = dynamic_args_.push>(arg.name).c_str(); if (detail::const_check(need_copy::value)) { emplace_arg( - fmt::arg(arg_name, dynamic_args_.push>(arg.value))); + fmt::arg(arg_name, dynamic_args_.push>(arg.value))); } else { emplace_arg(fmt::arg(arg_name, arg.value)); } } - /** Erase all elements from the store */ + /// Erase all elements from the store. void clear() { data_.clear(); named_info_.clear(); - dynamic_args_ = detail::dynamic_arg_list(); + dynamic_args_ = {}; } - /** - \rst - Reserves space to store at least *new_cap* arguments including - *new_cap_named* named arguments. - \endrst - */ + /// Reserves space to store at least `new_cap` arguments including + /// `new_cap_named` named arguments. void reserve(size_t new_cap, size_t new_cap_named) { FMT_ASSERT(new_cap >= new_cap_named, - "Set of arguments includes set of named arguments"); + "set of arguments includes set of named arguments"); data_.reserve(new_cap); named_info_.reserve(new_cap_named); } + + /// Returns the number of elements in the store. + size_t size() const noexcept { return data_.size(); } }; FMT_END_NAMESPACE diff --git a/thirdparty/fmt/include/fmt/base.h b/thirdparty/fmt/include/fmt/base.h new file mode 100644 index 000000000..9eeeb2ce7 --- /dev/null +++ b/thirdparty/fmt/include/fmt/base.h @@ -0,0 +1,2961 @@ +// Formatting library for C++ - the base API for char/UTF-8 +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_BASE_H_ +#define FMT_BASE_H_ + +#if defined(FMT_IMPORT_STD) && !defined(FMT_MODULE) +# define FMT_MODULE +#endif + +#ifndef FMT_MODULE +# include // CHAR_BIT +# include // FILE +# include // memcmp + +# include // std::enable_if +#endif + +// The fmt library version in the form major * 10000 + minor * 100 + patch. +#define FMT_VERSION 110103 + +// Detect compiler versions. +#if defined(__clang__) && !defined(__ibmxl__) +# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) +#else +# define FMT_CLANG_VERSION 0 +#endif +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) +# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +#else +# define FMT_GCC_VERSION 0 +#endif +#if defined(__ICL) +# define FMT_ICC_VERSION __ICL +#elif defined(__INTEL_COMPILER) +# define FMT_ICC_VERSION __INTEL_COMPILER +#else +# define FMT_ICC_VERSION 0 +#endif +#if defined(_MSC_VER) +# define FMT_MSC_VERSION _MSC_VER +#else +# define FMT_MSC_VERSION 0 +#endif + +// Detect standard library versions. +#ifdef _GLIBCXX_RELEASE +# define FMT_GLIBCXX_RELEASE _GLIBCXX_RELEASE +#else +# define FMT_GLIBCXX_RELEASE 0 +#endif +#ifdef _LIBCPP_VERSION +# define FMT_LIBCPP_VERSION _LIBCPP_VERSION +#else +# define FMT_LIBCPP_VERSION 0 +#endif + +#ifdef _MSVC_LANG +# define FMT_CPLUSPLUS _MSVC_LANG +#else +# define FMT_CPLUSPLUS __cplusplus +#endif + +// Detect __has_*. +#ifdef __has_feature +# define FMT_HAS_FEATURE(x) __has_feature(x) +#else +# define FMT_HAS_FEATURE(x) 0 +#endif +#ifdef __has_include +# define FMT_HAS_INCLUDE(x) __has_include(x) +#else +# define FMT_HAS_INCLUDE(x) 0 +#endif +#ifdef __has_builtin +# define FMT_HAS_BUILTIN(x) __has_builtin(x) +#else +# define FMT_HAS_BUILTIN(x) 0 +#endif +#ifdef __has_cpp_attribute +# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +# define FMT_HAS_CPP_ATTRIBUTE(x) 0 +#endif + +#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ + (FMT_CPLUSPLUS >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + +#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ + (FMT_CPLUSPLUS >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + +// Detect C++14 relaxed constexpr. +#ifdef FMT_USE_CONSTEXPR +// Use the provided definition. +#elif FMT_GCC_VERSION >= 702 && FMT_CPLUSPLUS >= 201402L +// GCC only allows constexpr member functions in non-literal types since 7.2: +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66297. +# define FMT_USE_CONSTEXPR 1 +#elif FMT_ICC_VERSION +# define FMT_USE_CONSTEXPR 0 // https://github.com/fmtlib/fmt/issues/1628 +#elif FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VERSION >= 1912 +# define FMT_USE_CONSTEXPR 1 +#else +# define FMT_USE_CONSTEXPR 0 +#endif +#if FMT_USE_CONSTEXPR +# define FMT_CONSTEXPR constexpr +#else +# define FMT_CONSTEXPR +#endif + +// Detect consteval, C++20 constexpr extensions and std::is_constant_evaluated. +#if !defined(__cpp_lib_is_constant_evaluated) +# define FMT_USE_CONSTEVAL 0 +#elif FMT_CPLUSPLUS < 201709L +# define FMT_USE_CONSTEVAL 0 +#elif FMT_GLIBCXX_RELEASE && FMT_GLIBCXX_RELEASE < 10 +# define FMT_USE_CONSTEVAL 0 +#elif FMT_LIBCPP_VERSION && FMT_LIBCPP_VERSION < 10000 +# define FMT_USE_CONSTEVAL 0 +#elif defined(__apple_build_version__) && __apple_build_version__ < 14000029L +# define FMT_USE_CONSTEVAL 0 // consteval is broken in Apple clang < 14. +#elif FMT_MSC_VERSION && FMT_MSC_VERSION < 1929 +# define FMT_USE_CONSTEVAL 0 // consteval is broken in MSVC VS2019 < 16.10. +#elif defined(__cpp_consteval) +# define FMT_USE_CONSTEVAL 1 +#elif FMT_GCC_VERSION >= 1002 || FMT_CLANG_VERSION >= 1101 +# define FMT_USE_CONSTEVAL 1 +#else +# define FMT_USE_CONSTEVAL 0 +#endif +#if FMT_USE_CONSTEVAL +# define FMT_CONSTEVAL consteval +# define FMT_CONSTEXPR20 constexpr +#else +# define FMT_CONSTEVAL +# define FMT_CONSTEXPR20 +#endif + +// Check if exceptions are disabled. +#ifdef FMT_USE_EXCEPTIONS +// Use the provided definition. +#elif defined(__GNUC__) && !defined(__EXCEPTIONS) +# define FMT_USE_EXCEPTIONS 0 +#elif defined(__clang__) && !defined(__cpp_exceptions) +# define FMT_USE_EXCEPTIONS 0 +#elif FMT_MSC_VERSION && !_HAS_EXCEPTIONS +# define FMT_USE_EXCEPTIONS 0 +#else +# define FMT_USE_EXCEPTIONS 1 +#endif +#if FMT_USE_EXCEPTIONS +# define FMT_TRY try +# define FMT_CATCH(x) catch (x) +#else +# define FMT_TRY if (true) +# define FMT_CATCH(x) if (false) +#endif + +#ifdef FMT_NO_UNIQUE_ADDRESS +// Use the provided definition. +#elif FMT_CPLUSPLUS < 202002L +// Not supported. +#elif FMT_HAS_CPP_ATTRIBUTE(no_unique_address) +# define FMT_NO_UNIQUE_ADDRESS [[no_unique_address]] +// VS2019 v16.10 and later except clang-cl (https://reviews.llvm.org/D110485). +#elif FMT_MSC_VERSION >= 1929 && !FMT_CLANG_VERSION +# define FMT_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +#endif +#ifndef FMT_NO_UNIQUE_ADDRESS +# define FMT_NO_UNIQUE_ADDRESS +#endif + +#if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) +# define FMT_FALLTHROUGH [[fallthrough]] +#elif defined(__clang__) +# define FMT_FALLTHROUGH [[clang::fallthrough]] +#elif FMT_GCC_VERSION >= 700 && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) +# define FMT_FALLTHROUGH [[gnu::fallthrough]] +#else +# define FMT_FALLTHROUGH +#endif + +// Disable [[noreturn]] on MSVC/NVCC because of bogus unreachable code warnings. +#if FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VERSION && !defined(__NVCC__) +# define FMT_NORETURN [[noreturn]] +#else +# define FMT_NORETURN +#endif + +#ifdef FMT_NODISCARD +// Use the provided definition. +#elif FMT_HAS_CPP17_ATTRIBUTE(nodiscard) +# define FMT_NODISCARD [[nodiscard]] +#else +# define FMT_NODISCARD +#endif + +#ifdef FMT_DEPRECATED +// Use the provided definition. +#elif FMT_HAS_CPP14_ATTRIBUTE(deprecated) +# define FMT_DEPRECATED [[deprecated]] +#else +# define FMT_DEPRECATED /* deprecated */ +#endif + +#ifdef FMT_ALWAYS_INLINE +// Use the provided definition. +#elif FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_ALWAYS_INLINE inline __attribute__((always_inline)) +#else +# define FMT_ALWAYS_INLINE inline +#endif +// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode. +#ifdef NDEBUG +# define FMT_INLINE FMT_ALWAYS_INLINE +#else +# define FMT_INLINE inline +#endif + +#if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_VISIBILITY(value) __attribute__((visibility(value))) +#else +# define FMT_VISIBILITY(value) +#endif + +// Detect pragmas. +#define FMT_PRAGMA_IMPL(x) _Pragma(#x) +#if FMT_GCC_VERSION >= 504 && !defined(__NVCOMPILER) +// Workaround a _Pragma bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59884 +// and an nvhpc warning: https://github.com/fmtlib/fmt/pull/2582. +# define FMT_PRAGMA_GCC(x) FMT_PRAGMA_IMPL(GCC x) +#else +# define FMT_PRAGMA_GCC(x) +#endif +#if FMT_CLANG_VERSION +# define FMT_PRAGMA_CLANG(x) FMT_PRAGMA_IMPL(clang x) +#else +# define FMT_PRAGMA_CLANG(x) +#endif +#if FMT_MSC_VERSION +# define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__)) +#else +# define FMT_MSC_WARNING(...) +#endif + +#ifndef FMT_BEGIN_NAMESPACE +# define FMT_BEGIN_NAMESPACE \ + namespace fmt { \ + inline namespace v11 { +# define FMT_END_NAMESPACE \ + } \ + } +#endif + +#ifndef FMT_EXPORT +# define FMT_EXPORT +# define FMT_BEGIN_EXPORT +# define FMT_END_EXPORT +#endif + +#ifdef _WIN32 +# define FMT_WIN32 1 +#else +# define FMT_WIN32 0 +#endif + +#if !defined(FMT_HEADER_ONLY) && FMT_WIN32 +# if defined(FMT_LIB_EXPORT) +# define FMT_API __declspec(dllexport) +# elif defined(FMT_SHARED) +# define FMT_API __declspec(dllimport) +# endif +#elif defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) +# define FMT_API FMT_VISIBILITY("default") +#endif +#ifndef FMT_API +# define FMT_API +#endif + +#ifndef FMT_OPTIMIZE_SIZE +# define FMT_OPTIMIZE_SIZE 0 +#endif + +// FMT_BUILTIN_TYPE=0 may result in smaller library size at the cost of higher +// per-call binary size by passing built-in types through the extension API. +#ifndef FMT_BUILTIN_TYPES +# define FMT_BUILTIN_TYPES 1 +#endif + +#define FMT_APPLY_VARIADIC(expr) \ + using ignore = int[]; \ + (void)ignore { 0, (expr, 0)... } + +// Enable minimal optimizations for more compact code in debug mode. +FMT_PRAGMA_GCC(push_options) +#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE) +FMT_PRAGMA_GCC(optimize("Og")) +#endif +FMT_PRAGMA_CLANG(diagnostic push) + +FMT_BEGIN_NAMESPACE + +// Implementations of enable_if_t and other metafunctions for older systems. +template +using enable_if_t = typename std::enable_if::type; +template +using conditional_t = typename std::conditional::type; +template using bool_constant = std::integral_constant; +template +using remove_reference_t = typename std::remove_reference::type; +template +using remove_const_t = typename std::remove_const::type; +template +using remove_cvref_t = typename std::remove_cv>::type; +template +using make_unsigned_t = typename std::make_unsigned::type; +template +using underlying_t = typename std::underlying_type::type; +template using decay_t = typename std::decay::type; +using nullptr_t = decltype(nullptr); + +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 +// A workaround for gcc 4.9 to make void_t work in a SFINAE context. +template struct void_t_impl { + using type = void; +}; +template using void_t = typename void_t_impl::type; +#else +template using void_t = void; +#endif + +struct monostate { + constexpr monostate() {} +}; + +// An enable_if helper to be used in template parameters which results in much +// shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed +// to workaround a bug in MSVC 2019 (see #1140 and #1186). +#ifdef FMT_DOC +# define FMT_ENABLE_IF(...) +#else +# define FMT_ENABLE_IF(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0 +#endif + +template constexpr auto min_of(T a, T b) -> T { + return a < b ? a : b; +} +template constexpr auto max_of(T a, T b) -> T { + return a > b ? a : b; +} + +namespace detail { +// Suppresses "unused variable" warnings with the method described in +// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. +// (void)var does not work on many Intel compilers. +template FMT_CONSTEXPR void ignore_unused(const T&...) {} + +constexpr auto is_constant_evaluated(bool default_value = false) noexcept + -> bool { +// Workaround for incompatibility between clang 14 and libstdc++ consteval-based +// std::is_constant_evaluated: https://github.com/fmtlib/fmt/issues/3247. +#if FMT_CPLUSPLUS >= 202002L && FMT_GLIBCXX_RELEASE >= 12 && \ + (FMT_CLANG_VERSION >= 1400 && FMT_CLANG_VERSION < 1500) + ignore_unused(default_value); + return __builtin_is_constant_evaluated(); +#elif defined(__cpp_lib_is_constant_evaluated) + ignore_unused(default_value); + return std::is_constant_evaluated(); +#else + return default_value; +#endif +} + +// Suppresses "conditional expression is constant" warnings. +template FMT_ALWAYS_INLINE constexpr auto const_check(T val) -> T { + return val; +} + +FMT_NORETURN FMT_API void assert_fail(const char* file, int line, + const char* message); + +#if defined(FMT_ASSERT) +// Use the provided definition. +#elif defined(NDEBUG) +// FMT_ASSERT is not empty to avoid -Wempty-body. +# define FMT_ASSERT(condition, message) \ + fmt::detail::ignore_unused((condition), (message)) +#else +# define FMT_ASSERT(condition, message) \ + ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ + ? (void)0 \ + : fmt::detail::assert_fail(__FILE__, __LINE__, (message))) +#endif + +#ifdef FMT_USE_INT128 +// Use the provided definition. +#elif defined(__SIZEOF_INT128__) && !defined(__NVCC__) && \ + !(FMT_CLANG_VERSION && FMT_MSC_VERSION) +# define FMT_USE_INT128 1 +using int128_opt = __int128_t; // An optional native 128-bit integer. +using uint128_opt = __uint128_t; +inline auto map(int128_opt x) -> int128_opt { return x; } +inline auto map(uint128_opt x) -> uint128_opt { return x; } +#else +# define FMT_USE_INT128 0 +#endif +#if !FMT_USE_INT128 +enum class int128_opt {}; +enum class uint128_opt {}; +// Reduce template instantiations. +inline auto map(int128_opt) -> monostate { return {}; } +inline auto map(uint128_opt) -> monostate { return {}; } +#endif + +#ifndef FMT_USE_BITINT +# define FMT_USE_BITINT (FMT_CLANG_VERSION >= 1500) +#endif + +#if FMT_USE_BITINT +FMT_PRAGMA_CLANG(diagnostic ignored "-Wbit-int-extension") +template using bitint = _BitInt(N); +template using ubitint = unsigned _BitInt(N); +#else +template struct bitint {}; +template struct ubitint {}; +#endif // FMT_USE_BITINT + +// Casts a nonnegative integer to unsigned. +template +FMT_CONSTEXPR auto to_unsigned(Int value) -> make_unsigned_t { + FMT_ASSERT(std::is_unsigned::value || value >= 0, "negative value"); + return static_cast>(value); +} + +template +using unsigned_char = conditional_t; + +// A heuristic to detect std::string and std::[experimental::]string_view. +// It is mainly used to avoid dependency on <[experimental/]string_view>. +template +struct is_std_string_like : std::false_type {}; +template +struct is_std_string_like().find_first_of( + typename T::value_type(), 0))>> + : std::is_convertible().data()), + const typename T::value_type*> {}; + +// Check if the literal encoding is UTF-8. +enum { is_utf8_enabled = "\u00A7"[1] == '\xA7' }; +enum { use_utf8 = !FMT_WIN32 || is_utf8_enabled }; + +#ifndef FMT_UNICODE +# define FMT_UNICODE 1 +#endif + +static_assert(!FMT_UNICODE || use_utf8, + "Unicode support requires compiling with /utf-8"); + +template constexpr const char* narrow(const T*) { return nullptr; } +constexpr FMT_ALWAYS_INLINE const char* narrow(const char* s) { return s; } + +template +FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, std::size_t n) + -> int { + if (!is_constant_evaluated() && sizeof(Char) == 1) return memcmp(s1, s2, n); + for (; n != 0; ++s1, ++s2, --n) { + if (*s1 < *s2) return -1; + if (*s1 > *s2) return 1; + } + return 0; +} + +namespace adl { +using namespace std; + +template +auto invoke_back_inserter() + -> decltype(back_inserter(std::declval())); +} // namespace adl + +template +struct is_back_insert_iterator : std::false_type {}; + +template +struct is_back_insert_iterator< + It, bool_constant()), + It>::value>> : std::true_type {}; + +// Extracts a reference to the container from *insert_iterator. +template +inline FMT_CONSTEXPR20 auto get_container(OutputIt it) -> + typename OutputIt::container_type& { + struct accessor : OutputIt { + FMT_CONSTEXPR20 accessor(OutputIt base) : OutputIt(base) {} + using OutputIt::container; + }; + return *accessor(it).container; +} +} // namespace detail + +// Parsing-related public API and forward declarations. +FMT_BEGIN_EXPORT + +/** + * An implementation of `std::basic_string_view` for pre-C++17. It provides a + * subset of the API. `fmt::basic_string_view` is used for format strings even + * if `std::basic_string_view` is available to prevent issues when a library is + * compiled with a different `-std` option than the client code (which is not + * recommended). + */ +template class basic_string_view { + private: + const Char* data_; + size_t size_; + + public: + using value_type = Char; + using iterator = const Char*; + + constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {} + + /// Constructs a string reference object from a C string and a size. + constexpr basic_string_view(const Char* s, size_t count) noexcept + : data_(s), size_(count) {} + + constexpr basic_string_view(nullptr_t) = delete; + + /// Constructs a string reference object from a C string. +#if FMT_GCC_VERSION + FMT_ALWAYS_INLINE +#endif + FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) { +#if FMT_HAS_BUILTIN(__buitin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION + if (std::is_same::value) { + size_ = __builtin_strlen(detail::narrow(s)); + return; + } +#endif + size_t len = 0; + while (*s++) ++len; + size_ = len; + } + + /// Constructs a string reference from a `std::basic_string` or a + /// `std::basic_string_view` object. + template ::value&& std::is_same< + typename S::value_type, Char>::value)> + FMT_CONSTEXPR basic_string_view(const S& s) noexcept + : data_(s.data()), size_(s.size()) {} + + /// Returns a pointer to the string data. + constexpr auto data() const noexcept -> const Char* { return data_; } + + /// Returns the string size. + constexpr auto size() const noexcept -> size_t { return size_; } + + constexpr auto begin() const noexcept -> iterator { return data_; } + constexpr auto end() const noexcept -> iterator { return data_ + size_; } + + constexpr auto operator[](size_t pos) const noexcept -> const Char& { + return data_[pos]; + } + + FMT_CONSTEXPR void remove_prefix(size_t n) noexcept { + data_ += n; + size_ -= n; + } + + FMT_CONSTEXPR auto starts_with(basic_string_view sv) const noexcept + -> bool { + return size_ >= sv.size_ && detail::compare(data_, sv.data_, sv.size_) == 0; + } + FMT_CONSTEXPR auto starts_with(Char c) const noexcept -> bool { + return size_ >= 1 && *data_ == c; + } + FMT_CONSTEXPR auto starts_with(const Char* s) const -> bool { + return starts_with(basic_string_view(s)); + } + + // Lexicographically compare this string reference to other. + FMT_CONSTEXPR auto compare(basic_string_view other) const -> int { + int result = + detail::compare(data_, other.data_, min_of(size_, other.size_)); + if (result != 0) return result; + return size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); + } + + FMT_CONSTEXPR friend auto operator==(basic_string_view lhs, + basic_string_view rhs) -> bool { + return lhs.compare(rhs) == 0; + } + friend auto operator!=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) != 0; + } + friend auto operator<(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) < 0; + } + friend auto operator<=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) <= 0; + } + friend auto operator>(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) > 0; + } + friend auto operator>=(basic_string_view lhs, basic_string_view rhs) -> bool { + return lhs.compare(rhs) >= 0; + } +}; + +using string_view = basic_string_view; + +/// Specifies if `T` is an extended character type. Can be specialized by users. +template struct is_xchar : std::false_type {}; +template <> struct is_xchar : std::true_type {}; +template <> struct is_xchar : std::true_type {}; +template <> struct is_xchar : std::true_type {}; +#ifdef __cpp_char8_t +template <> struct is_xchar : std::true_type {}; +#endif + +// DEPRECATED! Will be replaced with an alias to prevent specializations. +template struct is_char : is_xchar {}; +template <> struct is_char : std::true_type {}; + +template class basic_appender; +using appender = basic_appender; + +// Checks whether T is a container with contiguous storage. +template struct is_contiguous : std::false_type {}; + +class context; +template class generic_context; +template class parse_context; + +// Longer aliases for C++20 compatibility. +template using basic_format_parse_context = parse_context; +using format_parse_context = parse_context; +template +using basic_format_context = + conditional_t::value, context, + generic_context>; +using format_context = context; + +template +using buffered_context = + conditional_t::value, context, + generic_context, Char>>; + +template class basic_format_arg; +template class basic_format_args; + +// A separate type would result in shorter symbols but break ABI compatibility +// between clang and gcc on ARM (#1919). +using format_args = basic_format_args; + +// A formatter for objects of type T. +template +struct formatter { + // A deleted default constructor indicates a disabled formatter. + formatter() = delete; +}; + +/// Reports a format error at compile time or, via a `format_error` exception, +/// at runtime. +// This function is intentionally not constexpr to give a compile-time error. +FMT_NORETURN FMT_API void report_error(const char* message); + +enum class presentation_type : unsigned char { + // Common specifiers: + none = 0, + debug = 1, // '?' + string = 2, // 's' (string, bool) + + // Integral, bool and character specifiers: + dec = 3, // 'd' + hex, // 'x' or 'X' + oct, // 'o' + bin, // 'b' or 'B' + chr, // 'c' + + // String and pointer specifiers: + pointer = 3, // 'p' + + // Floating-point specifiers: + exp = 1, // 'e' or 'E' (1 since there is no FP debug presentation) + fixed, // 'f' or 'F' + general, // 'g' or 'G' + hexfloat // 'a' or 'A' +}; + +enum class align { none, left, right, center, numeric }; +enum class sign { none, minus, plus, space }; +enum class arg_id_kind { none, index, name }; + +// Basic format specifiers for built-in and string types. +class basic_specs { + private: + // Data is arranged as follows: + // + // 0 1 2 3 + // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + // +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + // |type |align| w | p | s |u|#|L| f | unused | + // +-----+-----+---+---+---+-+-+-+-----+---------------------------+ + // + // w - dynamic width info + // p - dynamic precision info + // s - sign + // u - uppercase (e.g. 'X' for 'x') + // # - alternate form ('#') + // L - localized + // f - fill size + // + // Bitfields are not used because of compiler bugs such as gcc bug 61414. + enum : unsigned { + type_mask = 0x00007, + align_mask = 0x00038, + width_mask = 0x000C0, + precision_mask = 0x00300, + sign_mask = 0x00C00, + uppercase_mask = 0x01000, + alternate_mask = 0x02000, + localized_mask = 0x04000, + fill_size_mask = 0x38000, + + align_shift = 3, + width_shift = 6, + precision_shift = 8, + sign_shift = 10, + fill_size_shift = 15, + + max_fill_size = 4 + }; + + unsigned data_ = 1 << fill_size_shift; + static_assert(sizeof(data_) * CHAR_BIT >= 18, ""); + + // Character (code unit) type is erased to prevent template bloat. + char fill_data_[max_fill_size] = {' '}; + + FMT_CONSTEXPR void set_fill_size(size_t size) { + data_ = (data_ & ~fill_size_mask) | + (static_cast(size) << fill_size_shift); + } + + public: + constexpr auto type() const -> presentation_type { + return static_cast(data_ & type_mask); + } + FMT_CONSTEXPR void set_type(presentation_type t) { + data_ = (data_ & ~type_mask) | static_cast(t); + } + + constexpr auto align() const -> align { + return static_cast((data_ & align_mask) >> align_shift); + } + FMT_CONSTEXPR void set_align(fmt::align a) { + data_ = (data_ & ~align_mask) | (static_cast(a) << align_shift); + } + + constexpr auto dynamic_width() const -> arg_id_kind { + return static_cast((data_ & width_mask) >> width_shift); + } + FMT_CONSTEXPR void set_dynamic_width(arg_id_kind w) { + data_ = (data_ & ~width_mask) | (static_cast(w) << width_shift); + } + + FMT_CONSTEXPR auto dynamic_precision() const -> arg_id_kind { + return static_cast((data_ & precision_mask) >> + precision_shift); + } + FMT_CONSTEXPR void set_dynamic_precision(arg_id_kind p) { + data_ = (data_ & ~precision_mask) | + (static_cast(p) << precision_shift); + } + + constexpr bool dynamic() const { + return (data_ & (width_mask | precision_mask)) != 0; + } + + constexpr auto sign() const -> sign { + return static_cast((data_ & sign_mask) >> sign_shift); + } + FMT_CONSTEXPR void set_sign(fmt::sign s) { + data_ = (data_ & ~sign_mask) | (static_cast(s) << sign_shift); + } + + constexpr auto upper() const -> bool { return (data_ & uppercase_mask) != 0; } + FMT_CONSTEXPR void set_upper() { data_ |= uppercase_mask; } + + constexpr auto alt() const -> bool { return (data_ & alternate_mask) != 0; } + FMT_CONSTEXPR void set_alt() { data_ |= alternate_mask; } + FMT_CONSTEXPR void clear_alt() { data_ &= ~alternate_mask; } + + constexpr auto localized() const -> bool { + return (data_ & localized_mask) != 0; + } + FMT_CONSTEXPR void set_localized() { data_ |= localized_mask; } + + constexpr auto fill_size() const -> size_t { + return (data_ & fill_size_mask) >> fill_size_shift; + } + + template ::value)> + constexpr auto fill() const -> const Char* { + return fill_data_; + } + template ::value)> + constexpr auto fill() const -> const Char* { + return nullptr; + } + + template constexpr auto fill_unit() const -> Char { + using uchar = unsigned char; + return static_cast(static_cast(fill_data_[0]) | + (static_cast(fill_data_[1]) << 8) | + (static_cast(fill_data_[2]) << 16)); + } + + FMT_CONSTEXPR void set_fill(char c) { + fill_data_[0] = c; + set_fill_size(1); + } + + template + FMT_CONSTEXPR void set_fill(basic_string_view s) { + auto size = s.size(); + set_fill_size(size); + if (size == 1) { + unsigned uchar = static_cast>(s[0]); + fill_data_[0] = static_cast(uchar); + fill_data_[1] = static_cast(uchar >> 8); + fill_data_[2] = static_cast(uchar >> 16); + return; + } + FMT_ASSERT(size <= max_fill_size, "invalid fill"); + for (size_t i = 0; i < size; ++i) + fill_data_[i & 3] = static_cast(s[i]); + } + + FMT_CONSTEXPR void copy_fill_from(const basic_specs& specs) { + set_fill_size(specs.fill_size()); + for (size_t i = 0; i < max_fill_size; ++i) + fill_data_[i] = specs.fill_data_[i]; + } +}; + +// Format specifiers for built-in and string types. +struct format_specs : basic_specs { + int width; + int precision; + + constexpr format_specs() : width(0), precision(-1) {} +}; + +/** + * Parsing context consisting of a format string range being parsed and an + * argument counter for automatic indexing. + */ +template class parse_context { + private: + basic_string_view fmt_; + int next_arg_id_; + + enum { use_constexpr_cast = !FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200 }; + + FMT_CONSTEXPR void do_check_arg_id(int arg_id); + + public: + using char_type = Char; + using iterator = const Char*; + + constexpr explicit parse_context(basic_string_view fmt, + int next_arg_id = 0) + : fmt_(fmt), next_arg_id_(next_arg_id) {} + + /// Returns an iterator to the beginning of the format string range being + /// parsed. + constexpr auto begin() const noexcept -> iterator { return fmt_.begin(); } + + /// Returns an iterator past the end of the format string range being parsed. + constexpr auto end() const noexcept -> iterator { return fmt_.end(); } + + /// Advances the begin iterator to `it`. + FMT_CONSTEXPR void advance_to(iterator it) { + fmt_.remove_prefix(detail::to_unsigned(it - begin())); + } + + /// Reports an error if using the manual argument indexing; otherwise returns + /// the next argument index and switches to the automatic indexing. + FMT_CONSTEXPR auto next_arg_id() -> int { + if (next_arg_id_ < 0) { + report_error("cannot switch from manual to automatic argument indexing"); + return 0; + } + int id = next_arg_id_++; + do_check_arg_id(id); + return id; + } + + /// Reports an error if using the automatic argument indexing; otherwise + /// switches to the manual indexing. + FMT_CONSTEXPR void check_arg_id(int id) { + if (next_arg_id_ > 0) { + report_error("cannot switch from automatic to manual argument indexing"); + return; + } + next_arg_id_ = -1; + do_check_arg_id(id); + } + FMT_CONSTEXPR void check_arg_id(basic_string_view) { + next_arg_id_ = -1; + } + FMT_CONSTEXPR void check_dynamic_spec(int arg_id); +}; + +FMT_END_EXPORT + +namespace detail { + +// Constructs fmt::basic_string_view from types implicitly convertible +// to it, deducing Char. Explicitly convertible types such as the ones returned +// from FMT_STRING are intentionally excluded. +template ::value)> +constexpr auto to_string_view(const Char* s) -> basic_string_view { + return s; +} +template ::value)> +constexpr auto to_string_view(const T& s) + -> basic_string_view { + return s; +} +template +constexpr auto to_string_view(basic_string_view s) + -> basic_string_view { + return s; +} + +template +struct has_to_string_view : std::false_type {}; +// detail:: is intentional since to_string_view is not an extension point. +template +struct has_to_string_view< + T, void_t()))>> + : std::true_type {}; + +/// String's character (code unit) type. detail:: is intentional to prevent ADL. +template ()))> +using char_t = typename V::value_type; + +enum class type { + none_type, + // Integer types should go first, + int_type, + uint_type, + long_long_type, + ulong_long_type, + int128_type, + uint128_type, + bool_type, + char_type, + last_integer_type = char_type, + // followed by floating-point types. + float_type, + double_type, + long_double_type, + last_numeric_type = long_double_type, + cstring_type, + string_type, + pointer_type, + custom_type +}; + +// Maps core type T to the corresponding type enum constant. +template +struct type_constant : std::integral_constant {}; + +#define FMT_TYPE_CONSTANT(Type, constant) \ + template \ + struct type_constant \ + : std::integral_constant {} + +FMT_TYPE_CONSTANT(int, int_type); +FMT_TYPE_CONSTANT(unsigned, uint_type); +FMT_TYPE_CONSTANT(long long, long_long_type); +FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type); +FMT_TYPE_CONSTANT(int128_opt, int128_type); +FMT_TYPE_CONSTANT(uint128_opt, uint128_type); +FMT_TYPE_CONSTANT(bool, bool_type); +FMT_TYPE_CONSTANT(Char, char_type); +FMT_TYPE_CONSTANT(float, float_type); +FMT_TYPE_CONSTANT(double, double_type); +FMT_TYPE_CONSTANT(long double, long_double_type); +FMT_TYPE_CONSTANT(const Char*, cstring_type); +FMT_TYPE_CONSTANT(basic_string_view, string_type); +FMT_TYPE_CONSTANT(const void*, pointer_type); + +constexpr auto is_integral_type(type t) -> bool { + return t > type::none_type && t <= type::last_integer_type; +} +constexpr auto is_arithmetic_type(type t) -> bool { + return t > type::none_type && t <= type::last_numeric_type; +} + +constexpr auto set(type rhs) -> int { return 1 << static_cast(rhs); } +constexpr auto in(type t, int set) -> bool { + return ((set >> static_cast(t)) & 1) != 0; +} + +// Bitsets of types. +enum { + sint_set = + set(type::int_type) | set(type::long_long_type) | set(type::int128_type), + uint_set = set(type::uint_type) | set(type::ulong_long_type) | + set(type::uint128_type), + bool_set = set(type::bool_type), + char_set = set(type::char_type), + float_set = set(type::float_type) | set(type::double_type) | + set(type::long_double_type), + string_set = set(type::string_type), + cstring_set = set(type::cstring_type), + pointer_set = set(type::pointer_type) +}; + +struct view {}; + +template struct named_arg; +template struct is_named_arg : std::false_type {}; +template struct is_static_named_arg : std::false_type {}; + +template +struct is_named_arg> : std::true_type {}; + +template struct named_arg : view { + const Char* name; + const T& value; + + named_arg(const Char* n, const T& v) : name(n), value(v) {} + static_assert(!is_named_arg::value, "nested named arguments"); +}; + +template constexpr auto count() -> int { return B ? 1 : 0; } +template constexpr auto count() -> int { + return (B1 ? 1 : 0) + count(); +} + +template constexpr auto count_named_args() -> int { + return count::value...>(); +} +template constexpr auto count_static_named_args() -> int { + return count::value...>(); +} + +template struct named_arg_info { + const Char* name; + int id; +}; + +template ::value)> +void init_named_arg(named_arg_info*, int& arg_index, int&, const T&) { + ++arg_index; +} +template ::value)> +void init_named_arg(named_arg_info* named_args, int& arg_index, + int& named_arg_index, const T& arg) { + named_args[named_arg_index++] = {arg.name, arg_index++}; +} + +template ::value)> +FMT_CONSTEXPR void init_static_named_arg(named_arg_info*, int& arg_index, + int&) { + ++arg_index; +} +template ::value)> +FMT_CONSTEXPR void init_static_named_arg(named_arg_info* named_args, + int& arg_index, int& named_arg_index) { + named_args[named_arg_index++] = {T::name, arg_index++}; +} + +// To minimize the number of types we need to deal with, long is translated +// either to int or to long long depending on its size. +enum { long_short = sizeof(long) == sizeof(int) }; +using long_type = conditional_t; +using ulong_type = conditional_t; + +template +using format_as_result = + remove_cvref_t()))>; +template +using format_as_member_result = + remove_cvref_t::format_as(std::declval()))>; + +template +struct use_format_as : std::false_type {}; +// format_as member is only used to avoid injection into the std namespace. +template +struct use_format_as_member : std::false_type {}; + +// Only map owning types because mapping views can be unsafe. +template +struct use_format_as< + T, bool_constant>::value>> + : std::true_type {}; +template +struct use_format_as_member< + T, bool_constant>::value>> + : std::true_type {}; + +template > +using use_formatter = + bool_constant<(std::is_class::value || std::is_enum::value || + std::is_union::value || std::is_array::value) && + !has_to_string_view::value && !is_named_arg::value && + !use_format_as::value && !use_format_as_member::value>; + +template > +auto has_formatter_impl(T* p, buffered_context* ctx = nullptr) + -> decltype(formatter().format(*p, *ctx), std::true_type()); +template auto has_formatter_impl(...) -> std::false_type; + +// T can be const-qualified to check if it is const-formattable. +template constexpr auto has_formatter() -> bool { + return decltype(has_formatter_impl(static_cast(nullptr)))::value; +} + +// Maps formatting argument types to natively supported types or user-defined +// types with formatters. Returns void on errors to be SFINAE-friendly. +template struct type_mapper { + static auto map(signed char) -> int; + static auto map(unsigned char) -> unsigned; + static auto map(short) -> int; + static auto map(unsigned short) -> unsigned; + static auto map(int) -> int; + static auto map(unsigned) -> unsigned; + static auto map(long) -> long_type; + static auto map(unsigned long) -> ulong_type; + static auto map(long long) -> long long; + static auto map(unsigned long long) -> unsigned long long; + static auto map(int128_opt) -> int128_opt; + static auto map(uint128_opt) -> uint128_opt; + static auto map(bool) -> bool; + + template + static auto map(bitint) -> conditional_t; + template + static auto map(ubitint) + -> conditional_t; + + template ::value)> + static auto map(T) -> conditional_t< + std::is_same::value || std::is_same::value, Char, void>; + + static auto map(float) -> float; + static auto map(double) -> double; + static auto map(long double) -> long double; + + static auto map(Char*) -> const Char*; + static auto map(const Char*) -> const Char*; + template , + FMT_ENABLE_IF(!std::is_pointer::value)> + static auto map(const T&) -> conditional_t::value, + basic_string_view, void>; + + static auto map(void*) -> const void*; + static auto map(const void*) -> const void*; + static auto map(volatile void*) -> const void*; + static auto map(const volatile void*) -> const void*; + static auto map(nullptr_t) -> const void*; + template ::value || + std::is_member_pointer::value)> + static auto map(const T&) -> void; + + template ::value)> + static auto map(const T& x) -> decltype(map(format_as(x))); + template ::value)> + static auto map(const T& x) -> decltype(map(formatter::format_as(x))); + + template ::value)> + static auto map(T&) -> conditional_t(), T&, void>; + + template ::value)> + static auto map(const T& named_arg) -> decltype(map(named_arg.value)); +}; + +// detail:: is used to workaround a bug in MSVC 2017. +template +using mapped_t = decltype(detail::type_mapper::map(std::declval())); + +// A type constant after applying type_mapper. +template +using mapped_type_constant = type_constant, Char>; + +template ::value> +using stored_type_constant = std::integral_constant< + type, Context::builtin_types || TYPE == type::int_type ? TYPE + : type::custom_type>; +// A parse context with extra data used only in compile-time checks. +template +class compile_parse_context : public parse_context { + private: + int num_args_; + const type* types_; + using base = parse_context; + + public: + FMT_CONSTEXPR explicit compile_parse_context(basic_string_view fmt, + int num_args, const type* types, + int next_arg_id = 0) + : base(fmt, next_arg_id), num_args_(num_args), types_(types) {} + + constexpr auto num_args() const -> int { return num_args_; } + constexpr auto arg_type(int id) const -> type { return types_[id]; } + + FMT_CONSTEXPR auto next_arg_id() -> int { + int id = base::next_arg_id(); + if (id >= num_args_) report_error("argument not found"); + return id; + } + + FMT_CONSTEXPR void check_arg_id(int id) { + base::check_arg_id(id); + if (id >= num_args_) report_error("argument not found"); + } + using base::check_arg_id; + + FMT_CONSTEXPR void check_dynamic_spec(int arg_id) { + ignore_unused(arg_id); + if (arg_id < num_args_ && types_ && !is_integral_type(types_[arg_id])) + report_error("width/precision is not integer"); + } +}; + +// An argument reference. +template union arg_ref { + FMT_CONSTEXPR arg_ref(int idx = 0) : index(idx) {} + FMT_CONSTEXPR arg_ref(basic_string_view n) : name(n) {} + + int index; + basic_string_view name; +}; + +// Format specifiers with width and precision resolved at formatting rather +// than parsing time to allow reusing the same parsed specifiers with +// different sets of arguments (precompilation of format strings). +template struct dynamic_format_specs : format_specs { + arg_ref width_ref; + arg_ref precision_ref; +}; + +// Converts a character to ASCII. Returns '\0' on conversion failure. +template ::value)> +constexpr auto to_ascii(Char c) -> char { + return c <= 0xff ? static_cast(c) : '\0'; +} + +// Returns the number of code units in a code point or 1 on error. +template +FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { + if (const_check(sizeof(Char) != 1)) return 1; + auto c = static_cast(*begin); + return static_cast((0x3a55000000000000ull >> (2 * (c >> 3))) & 3) + 1; +} + +// Parses the range [begin, end) as an unsigned integer. This function assumes +// that the range is non-empty and the first character is a digit. +template +FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end, + int error_value) noexcept -> int { + FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); + unsigned value = 0, prev = 0; + auto p = begin; + do { + prev = value; + value = value * 10 + unsigned(*p - '0'); + ++p; + } while (p != end && '0' <= *p && *p <= '9'); + auto num_digits = p - begin; + begin = p; + int digits10 = static_cast(sizeof(int) * CHAR_BIT * 3 / 10); + if (num_digits <= digits10) return static_cast(value); + // Check for overflow. + unsigned max = INT_MAX; + return num_digits == digits10 + 1 && + prev * 10ull + unsigned(p[-1] - '0') <= max + ? static_cast(value) + : error_value; +} + +FMT_CONSTEXPR inline auto parse_align(char c) -> align { + switch (c) { + case '<': return align::left; + case '>': return align::right; + case '^': return align::center; + } + return align::none; +} + +template constexpr auto is_name_start(Char c) -> bool { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_'; +} + +template +FMT_CONSTEXPR auto parse_arg_id(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + Char c = *begin; + if (c >= '0' && c <= '9') { + int index = 0; + if (c != '0') + index = parse_nonnegative_int(begin, end, INT_MAX); + else + ++begin; + if (begin == end || (*begin != '}' && *begin != ':')) + report_error("invalid format string"); + else + handler.on_index(index); + return begin; + } + if (FMT_OPTIMIZE_SIZE > 1 || !is_name_start(c)) { + report_error("invalid format string"); + return begin; + } + auto it = begin; + do { + ++it; + } while (it != end && (is_name_start(*it) || ('0' <= *it && *it <= '9'))); + handler.on_name({begin, to_unsigned(it - begin)}); + return it; +} + +template struct dynamic_spec_handler { + parse_context& ctx; + arg_ref& ref; + arg_id_kind& kind; + + FMT_CONSTEXPR void on_index(int id) { + ref = id; + kind = arg_id_kind::index; + ctx.check_arg_id(id); + ctx.check_dynamic_spec(id); + } + FMT_CONSTEXPR void on_name(basic_string_view id) { + ref = id; + kind = arg_id_kind::name; + ctx.check_arg_id(id); + } +}; + +template struct parse_dynamic_spec_result { + const Char* end; + arg_id_kind kind; +}; + +// Parses integer | "{" [arg_id] "}". +template +FMT_CONSTEXPR auto parse_dynamic_spec(const Char* begin, const Char* end, + int& value, arg_ref& ref, + parse_context& ctx) + -> parse_dynamic_spec_result { + FMT_ASSERT(begin != end, ""); + auto kind = arg_id_kind::none; + if ('0' <= *begin && *begin <= '9') { + int val = parse_nonnegative_int(begin, end, -1); + if (val == -1) report_error("number is too big"); + value = val; + } else { + if (*begin == '{') { + ++begin; + if (begin != end) { + Char c = *begin; + if (c == '}' || c == ':') { + int id = ctx.next_arg_id(); + ref = id; + kind = arg_id_kind::index; + ctx.check_dynamic_spec(id); + } else { + begin = parse_arg_id(begin, end, + dynamic_spec_handler{ctx, ref, kind}); + } + } + if (begin != end && *begin == '}') return {++begin, kind}; + } + report_error("invalid format string"); + } + return {begin, kind}; +} + +template +FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end, + format_specs& specs, arg_ref& width_ref, + parse_context& ctx) -> const Char* { + auto result = parse_dynamic_spec(begin, end, specs.width, width_ref, ctx); + specs.set_dynamic_width(result.kind); + return result.end; +} + +template +FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, + format_specs& specs, + arg_ref& precision_ref, + parse_context& ctx) -> const Char* { + ++begin; + if (begin == end) { + report_error("invalid precision"); + return begin; + } + auto result = + parse_dynamic_spec(begin, end, specs.precision, precision_ref, ctx); + specs.set_dynamic_precision(result.kind); + return result.end; +} + +enum class state { start, align, sign, hash, zero, width, precision, locale }; + +// Parses standard format specifiers. +template +FMT_CONSTEXPR auto parse_format_specs(const Char* begin, const Char* end, + dynamic_format_specs& specs, + parse_context& ctx, type arg_type) + -> const Char* { + auto c = '\0'; + if (end - begin > 1) { + auto next = to_ascii(begin[1]); + c = parse_align(next) == align::none ? to_ascii(*begin) : '\0'; + } else { + if (begin == end) return begin; + c = to_ascii(*begin); + } + + struct { + state current_state = state::start; + FMT_CONSTEXPR void operator()(state s, bool valid = true) { + if (current_state >= s || !valid) + report_error("invalid format specifier"); + current_state = s; + } + } enter_state; + + using pres = presentation_type; + constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; + struct { + const Char*& begin; + format_specs& specs; + type arg_type; + + FMT_CONSTEXPR auto operator()(pres pres_type, int set) -> const Char* { + if (!in(arg_type, set)) report_error("invalid format specifier"); + specs.set_type(pres_type); + return begin + 1; + } + } parse_presentation_type{begin, specs, arg_type}; + + for (;;) { + switch (c) { + case '<': + case '>': + case '^': + enter_state(state::align); + specs.set_align(parse_align(c)); + ++begin; + break; + case '+': + case ' ': + specs.set_sign(c == ' ' ? sign::space : sign::plus); + FMT_FALLTHROUGH; + case '-': + enter_state(state::sign, in(arg_type, sint_set | float_set)); + ++begin; + break; + case '#': + enter_state(state::hash, is_arithmetic_type(arg_type)); + specs.set_alt(); + ++begin; + break; + case '0': + enter_state(state::zero); + if (!is_arithmetic_type(arg_type)) + report_error("format specifier requires numeric argument"); + if (specs.align() == align::none) { + // Ignore 0 if align is specified for compatibility with std::format. + specs.set_align(align::numeric); + specs.set_fill('0'); + } + ++begin; + break; + // clang-format off + case '1': case '2': case '3': case '4': case '5': + case '6': case '7': case '8': case '9': case '{': + // clang-format on + enter_state(state::width); + begin = parse_width(begin, end, specs, specs.width_ref, ctx); + break; + case '.': + enter_state(state::precision, + in(arg_type, float_set | string_set | cstring_set)); + begin = parse_precision(begin, end, specs, specs.precision_ref, ctx); + break; + case 'L': + enter_state(state::locale, is_arithmetic_type(arg_type)); + specs.set_localized(); + ++begin; + break; + case 'd': return parse_presentation_type(pres::dec, integral_set); + case 'X': specs.set_upper(); FMT_FALLTHROUGH; + case 'x': return parse_presentation_type(pres::hex, integral_set); + case 'o': return parse_presentation_type(pres::oct, integral_set); + case 'B': specs.set_upper(); FMT_FALLTHROUGH; + case 'b': return parse_presentation_type(pres::bin, integral_set); + case 'E': specs.set_upper(); FMT_FALLTHROUGH; + case 'e': return parse_presentation_type(pres::exp, float_set); + case 'F': specs.set_upper(); FMT_FALLTHROUGH; + case 'f': return parse_presentation_type(pres::fixed, float_set); + case 'G': specs.set_upper(); FMT_FALLTHROUGH; + case 'g': return parse_presentation_type(pres::general, float_set); + case 'A': specs.set_upper(); FMT_FALLTHROUGH; + case 'a': return parse_presentation_type(pres::hexfloat, float_set); + case 'c': + if (arg_type == type::bool_type) report_error("invalid format specifier"); + return parse_presentation_type(pres::chr, integral_set); + case 's': + return parse_presentation_type(pres::string, + bool_set | string_set | cstring_set); + case 'p': + return parse_presentation_type(pres::pointer, pointer_set | cstring_set); + case '?': + return parse_presentation_type(pres::debug, + char_set | string_set | cstring_set); + case '}': return begin; + default: { + if (*begin == '}') return begin; + // Parse fill and alignment. + auto fill_end = begin + code_point_length(begin); + if (end - fill_end <= 0) { + report_error("invalid format specifier"); + return begin; + } + if (*begin == '{') { + report_error("invalid fill character '{'"); + return begin; + } + auto alignment = parse_align(to_ascii(*fill_end)); + enter_state(state::align, alignment != align::none); + specs.set_fill( + basic_string_view(begin, to_unsigned(fill_end - begin))); + specs.set_align(alignment); + begin = fill_end + 1; + } + } + if (begin == end) return begin; + c = to_ascii(*begin); + } +} + +template +FMT_CONSTEXPR FMT_INLINE auto parse_replacement_field(const Char* begin, + const Char* end, + Handler&& handler) + -> const Char* { + ++begin; + if (begin == end) { + handler.on_error("invalid format string"); + return end; + } + int arg_id = 0; + switch (*begin) { + case '}': + handler.on_replacement_field(handler.on_arg_id(), begin); + return begin + 1; + case '{': handler.on_text(begin, begin + 1); return begin + 1; + case ':': arg_id = handler.on_arg_id(); break; + default: { + struct id_adapter { + Handler& handler; + int arg_id; + + FMT_CONSTEXPR void on_index(int id) { arg_id = handler.on_arg_id(id); } + FMT_CONSTEXPR void on_name(basic_string_view id) { + arg_id = handler.on_arg_id(id); + } + } adapter = {handler, 0}; + begin = parse_arg_id(begin, end, adapter); + arg_id = adapter.arg_id; + Char c = begin != end ? *begin : Char(); + if (c == '}') { + handler.on_replacement_field(arg_id, begin); + return begin + 1; + } + if (c != ':') { + handler.on_error("missing '}' in format string"); + return end; + } + break; + } + } + begin = handler.on_format_specs(arg_id, begin + 1, end); + if (begin == end || *begin != '}') + return handler.on_error("unknown format specifier"), end; + return begin + 1; +} + +template +FMT_CONSTEXPR void parse_format_string(basic_string_view fmt, + Handler&& handler) { + auto begin = fmt.data(), end = begin + fmt.size(); + auto p = begin; + while (p != end) { + auto c = *p++; + if (c == '{') { + handler.on_text(begin, p - 1); + begin = p = parse_replacement_field(p - 1, end, handler); + } else if (c == '}') { + if (p == end || *p != '}') + return handler.on_error("unmatched '}' in format string"); + handler.on_text(begin, p); + begin = ++p; + } + } + handler.on_text(begin, end); +} + +// Checks char specs and returns true iff the presentation type is char-like. +FMT_CONSTEXPR inline auto check_char_specs(const format_specs& specs) -> bool { + auto type = specs.type(); + if (type != presentation_type::none && type != presentation_type::chr && + type != presentation_type::debug) { + return false; + } + if (specs.align() == align::numeric || specs.sign() != sign::none || + specs.alt()) { + report_error("invalid format specifier for char"); + } + return true; +} + +// A base class for compile-time strings. +struct compile_string {}; + +template +FMT_VISIBILITY("hidden") // Suppress an ld warning on macOS (#3769). +FMT_CONSTEXPR auto invoke_parse(parse_context& ctx) -> const Char* { + using mapped_type = remove_cvref_t>; + constexpr bool formattable = + std::is_constructible>::value; + if (!formattable) return ctx.begin(); // Error is reported in the value ctor. + using formatted_type = conditional_t; + return formatter().parse(ctx); +} + +template struct arg_pack {}; + +template +class format_string_checker { + private: + type types_[max_of(1, NUM_ARGS)]; + named_arg_info named_args_[max_of(1, NUM_NAMED_ARGS)]; + compile_parse_context context_; + + using parse_func = auto (*)(parse_context&) -> const Char*; + parse_func parse_funcs_[max_of(1, NUM_ARGS)]; + + public: + template + FMT_CONSTEXPR explicit format_string_checker(basic_string_view fmt, + arg_pack) + : types_{mapped_type_constant::value...}, + named_args_{}, + context_(fmt, NUM_ARGS, types_), + parse_funcs_{&invoke_parse...} { + int arg_index = 0, named_arg_index = 0; + FMT_APPLY_VARIADIC( + init_static_named_arg(named_args_, arg_index, named_arg_index)); + ignore_unused(arg_index, named_arg_index); + } + + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + + FMT_CONSTEXPR auto on_arg_id() -> int { return context_.next_arg_id(); } + FMT_CONSTEXPR auto on_arg_id(int id) -> int { + context_.check_arg_id(id); + return id; + } + FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { + for (int i = 0; i < NUM_NAMED_ARGS; ++i) { + if (named_args_[i].name == id) return named_args_[i].id; + } + if (!DYNAMIC_NAMES) on_error("argument not found"); + return -1; + } + + FMT_CONSTEXPR void on_replacement_field(int id, const Char* begin) { + on_format_specs(id, begin, begin); // Call parse() on empty specs. + } + + FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char* end) + -> const Char* { + context_.advance_to(begin); + if (id >= 0 && id < NUM_ARGS) return parse_funcs_[id](context_); + while (begin != end && *begin != '}') ++begin; + return begin; + } + + FMT_NORETURN FMT_CONSTEXPR void on_error(const char* message) { + report_error(message); + } +}; + +/// A contiguous memory buffer with an optional growing ability. It is an +/// internal class and shouldn't be used directly, only via `memory_buffer`. +template class buffer { + private: + T* ptr_; + size_t size_; + size_t capacity_; + + using grow_fun = void (*)(buffer& buf, size_t capacity); + grow_fun grow_; + + protected: + // Don't initialize ptr_ since it is not accessed to save a few cycles. + FMT_MSC_WARNING(suppress : 26495) + FMT_CONSTEXPR buffer(grow_fun grow, size_t sz) noexcept + : size_(sz), capacity_(sz), grow_(grow) {} + + constexpr buffer(grow_fun grow, T* p = nullptr, size_t sz = 0, + size_t cap = 0) noexcept + : ptr_(p), size_(sz), capacity_(cap), grow_(grow) {} + + FMT_CONSTEXPR20 ~buffer() = default; + buffer(buffer&&) = default; + + /// Sets the buffer data and capacity. + FMT_CONSTEXPR void set(T* buf_data, size_t buf_capacity) noexcept { + ptr_ = buf_data; + capacity_ = buf_capacity; + } + + public: + using value_type = T; + using const_reference = const T&; + + buffer(const buffer&) = delete; + void operator=(const buffer&) = delete; + + auto begin() noexcept -> T* { return ptr_; } + auto end() noexcept -> T* { return ptr_ + size_; } + + auto begin() const noexcept -> const T* { return ptr_; } + auto end() const noexcept -> const T* { return ptr_ + size_; } + + /// Returns the size of this buffer. + constexpr auto size() const noexcept -> size_t { return size_; } + + /// Returns the capacity of this buffer. + constexpr auto capacity() const noexcept -> size_t { return capacity_; } + + /// Returns a pointer to the buffer data (not null-terminated). + FMT_CONSTEXPR auto data() noexcept -> T* { return ptr_; } + FMT_CONSTEXPR auto data() const noexcept -> const T* { return ptr_; } + + /// Clears this buffer. + FMT_CONSTEXPR void clear() { size_ = 0; } + + // Tries resizing the buffer to contain `count` elements. If T is a POD type + // the new elements may not be initialized. + FMT_CONSTEXPR void try_resize(size_t count) { + try_reserve(count); + size_ = min_of(count, capacity_); + } + + // Tries increasing the buffer capacity to `new_capacity`. It can increase the + // capacity by a smaller amount than requested but guarantees there is space + // for at least one additional element either by increasing the capacity or by + // flushing the buffer if it is full. + FMT_CONSTEXPR void try_reserve(size_t new_capacity) { + if (new_capacity > capacity_) grow_(*this, new_capacity); + } + + FMT_CONSTEXPR void push_back(const T& value) { + try_reserve(size_ + 1); + ptr_[size_++] = value; + } + + /// Appends data to the end of the buffer. + template +// Workaround for MSVC2019 to fix error C2893: Failed to specialize function +// template 'void fmt::v11::detail::buffer::append(const U *,const U *)'. +#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940 + FMT_CONSTEXPR20 +#endif + void + append(const U* begin, const U* end) { + while (begin != end) { + auto count = to_unsigned(end - begin); + try_reserve(size_ + count); + auto free_cap = capacity_ - size_; + if (free_cap < count) count = free_cap; + // A loop is faster than memcpy on small sizes. + T* out = ptr_ + size_; + for (size_t i = 0; i < count; ++i) out[i] = begin[i]; + size_ += count; + begin += count; + } + } + + template FMT_CONSTEXPR auto operator[](Idx index) -> T& { + return ptr_[index]; + } + template + FMT_CONSTEXPR auto operator[](Idx index) const -> const T& { + return ptr_[index]; + } +}; + +struct buffer_traits { + constexpr explicit buffer_traits(size_t) {} + constexpr auto count() const -> size_t { return 0; } + constexpr auto limit(size_t size) const -> size_t { return size; } +}; + +class fixed_buffer_traits { + private: + size_t count_ = 0; + size_t limit_; + + public: + constexpr explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} + constexpr auto count() const -> size_t { return count_; } + FMT_CONSTEXPR auto limit(size_t size) -> size_t { + size_t n = limit_ > count_ ? limit_ - count_ : 0; + count_ += size; + return min_of(size, n); + } +}; + +// A buffer that writes to an output iterator when flushed. +template +class iterator_buffer : public Traits, public buffer { + private: + OutputIt out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t) { + if (buf.size() == buffer_size) static_cast(buf).flush(); + } + + void flush() { + auto size = this->size(); + this->clear(); + const T* begin = data_; + const T* end = begin + this->limit(size); + while (begin != end) *out_++ = *begin++; + } + + public: + explicit iterator_buffer(OutputIt out, size_t n = buffer_size) + : Traits(n), buffer(grow, data_, 0, buffer_size), out_(out) {} + iterator_buffer(iterator_buffer&& other) noexcept + : Traits(other), + buffer(grow, data_, 0, buffer_size), + out_(other.out_) {} + ~iterator_buffer() { + // Don't crash if flush fails during unwinding. + FMT_TRY { flush(); } + FMT_CATCH(...) {} + } + + auto out() -> OutputIt { + flush(); + return out_; + } + auto count() const -> size_t { return Traits::count() + this->size(); } +}; + +template +class iterator_buffer : public fixed_buffer_traits, + public buffer { + private: + T* out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t) { + if (buf.size() == buf.capacity()) + static_cast(buf).flush(); + } + + void flush() { + size_t n = this->limit(this->size()); + if (this->data() == out_) { + out_ += n; + this->set(data_, buffer_size); + } + this->clear(); + } + + public: + explicit iterator_buffer(T* out, size_t n = buffer_size) + : fixed_buffer_traits(n), buffer(grow, out, 0, n), out_(out) {} + iterator_buffer(iterator_buffer&& other) noexcept + : fixed_buffer_traits(other), + buffer(static_cast(other)), + out_(other.out_) { + if (this->data() != out_) { + this->set(data_, buffer_size); + this->clear(); + } + } + ~iterator_buffer() { flush(); } + + auto out() -> T* { + flush(); + return out_; + } + auto count() const -> size_t { + return fixed_buffer_traits::count() + this->size(); + } +}; + +template class iterator_buffer : public buffer { + public: + explicit iterator_buffer(T* out, size_t = 0) + : buffer([](buffer&, size_t) {}, out, 0, ~size_t()) {} + + auto out() -> T* { return &*this->end(); } +}; + +template +class container_buffer : public buffer { + private: + using value_type = typename Container::value_type; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t capacity) { + auto& self = static_cast(buf); + self.container.resize(capacity); + self.set(&self.container[0], capacity); + } + + public: + Container& container; + + explicit container_buffer(Container& c) + : buffer(grow, c.size()), container(c) {} +}; + +// A buffer that writes to a container with the contiguous storage. +template +class iterator_buffer< + OutputIt, + enable_if_t::value && + is_contiguous::value, + typename OutputIt::container_type::value_type>> + : public container_buffer { + private: + using base = container_buffer; + + public: + explicit iterator_buffer(typename OutputIt::container_type& c) : base(c) {} + explicit iterator_buffer(OutputIt out, size_t = 0) + : base(get_container(out)) {} + + auto out() -> OutputIt { return OutputIt(this->container); } +}; + +// A buffer that counts the number of code units written discarding the output. +template class counting_buffer : public buffer { + private: + enum { buffer_size = 256 }; + T data_[buffer_size]; + size_t count_ = 0; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t) { + if (buf.size() != buffer_size) return; + static_cast(buf).count_ += buf.size(); + buf.clear(); + } + + public: + FMT_CONSTEXPR counting_buffer() : buffer(grow, data_, 0, buffer_size) {} + + constexpr auto count() const noexcept -> size_t { + return count_ + this->size(); + } +}; + +template +struct is_back_insert_iterator> : std::true_type {}; + +template +struct has_back_insert_iterator_container_append : std::false_type {}; +template +struct has_back_insert_iterator_container_append< + OutputIt, InputIt, + void_t()) + .append(std::declval(), + std::declval()))>> : std::true_type {}; + +// An optimized version of std::copy with the output value type (T). +template ::value&& + has_back_insert_iterator_container_append< + OutputIt, InputIt>::value)> +FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) + -> OutputIt { + get_container(out).append(begin, end); + return out; +} + +template ::value && + !has_back_insert_iterator_container_append< + OutputIt, InputIt>::value)> +FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) + -> OutputIt { + auto& c = get_container(out); + c.insert(c.end(), begin, end); + return out; +} + +template ::value)> +FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { + while (begin != end) *out++ = static_cast(*begin++); + return out; +} + +template +FMT_CONSTEXPR auto copy(basic_string_view s, OutputIt out) -> OutputIt { + return copy(s.begin(), s.end(), out); +} + +template +struct is_buffer_appender : std::false_type {}; +template +struct is_buffer_appender< + It, bool_constant< + is_back_insert_iterator::value && + std::is_base_of, + typename It::container_type>::value>> + : std::true_type {}; + +// Maps an output iterator to a buffer. +template ::value)> +auto get_buffer(OutputIt out) -> iterator_buffer { + return iterator_buffer(out); +} +template ::value)> +auto get_buffer(OutputIt out) -> buffer& { + return get_container(out); +} + +template +auto get_iterator(Buf& buf, OutputIt) -> decltype(buf.out()) { + return buf.out(); +} +template +auto get_iterator(buffer&, OutputIt out) -> OutputIt { + return out; +} + +// This type is intentionally undefined, only used for errors. +template struct type_is_unformattable_for; + +template struct string_value { + const Char* data; + size_t size; + auto str() const -> basic_string_view { return {data, size}; } +}; + +template struct custom_value { + using char_type = typename Context::char_type; + void* value; + void (*format)(void* arg, parse_context& parse_ctx, Context& ctx); +}; + +template struct named_arg_value { + const named_arg_info* data; + size_t size; +}; + +struct custom_tag {}; + +#if !FMT_BUILTIN_TYPES +# define FMT_BUILTIN , monostate +#else +# define FMT_BUILTIN +#endif + +// A formatting argument value. +template class value { + public: + using char_type = typename Context::char_type; + + union { + monostate no_value; + int int_value; + unsigned uint_value; + long long long_long_value; + unsigned long long ulong_long_value; + int128_opt int128_value; + uint128_opt uint128_value; + bool bool_value; + char_type char_value; + float float_value; + double double_value; + long double long_double_value; + const void* pointer; + string_value string; + custom_value custom; + named_arg_value named_args; + }; + + constexpr FMT_INLINE value() : no_value() {} + constexpr FMT_INLINE value(signed char x) : int_value(x) {} + constexpr FMT_INLINE value(unsigned char x FMT_BUILTIN) : uint_value(x) {} + constexpr FMT_INLINE value(signed short x) : int_value(x) {} + constexpr FMT_INLINE value(unsigned short x FMT_BUILTIN) : uint_value(x) {} + constexpr FMT_INLINE value(int x) : int_value(x) {} + constexpr FMT_INLINE value(unsigned x FMT_BUILTIN) : uint_value(x) {} + FMT_CONSTEXPR FMT_INLINE value(long x FMT_BUILTIN) : value(long_type(x)) {} + FMT_CONSTEXPR FMT_INLINE value(unsigned long x FMT_BUILTIN) + : value(ulong_type(x)) {} + constexpr FMT_INLINE value(long long x FMT_BUILTIN) : long_long_value(x) {} + constexpr FMT_INLINE value(unsigned long long x FMT_BUILTIN) + : ulong_long_value(x) {} + FMT_INLINE value(int128_opt x FMT_BUILTIN) : int128_value(x) {} + FMT_INLINE value(uint128_opt x FMT_BUILTIN) : uint128_value(x) {} + constexpr FMT_INLINE value(bool x FMT_BUILTIN) : bool_value(x) {} + + template + constexpr FMT_INLINE value(bitint x FMT_BUILTIN) : long_long_value(x) { + static_assert(N <= 64, "unsupported _BitInt"); + } + template + constexpr FMT_INLINE value(ubitint x FMT_BUILTIN) : ulong_long_value(x) { + static_assert(N <= 64, "unsupported _BitInt"); + } + + template ::value)> + constexpr FMT_INLINE value(T x FMT_BUILTIN) : char_value(x) { + static_assert( + std::is_same::value || std::is_same::value, + "mixing character types is disallowed"); + } + + constexpr FMT_INLINE value(float x FMT_BUILTIN) : float_value(x) {} + constexpr FMT_INLINE value(double x FMT_BUILTIN) : double_value(x) {} + FMT_INLINE value(long double x FMT_BUILTIN) : long_double_value(x) {} + + FMT_CONSTEXPR FMT_INLINE value(char_type* x FMT_BUILTIN) { + string.data = x; + if (is_constant_evaluated()) string.size = 0; + } + FMT_CONSTEXPR FMT_INLINE value(const char_type* x FMT_BUILTIN) { + string.data = x; + if (is_constant_evaluated()) string.size = 0; + } + template , + FMT_ENABLE_IF(!std::is_pointer::value)> + FMT_CONSTEXPR value(const T& x FMT_BUILTIN) { + static_assert(std::is_same::value, + "mixing character types is disallowed"); + auto sv = to_string_view(x); + string.data = sv.data(); + string.size = sv.size(); + } + FMT_INLINE value(void* x FMT_BUILTIN) : pointer(x) {} + FMT_INLINE value(const void* x FMT_BUILTIN) : pointer(x) {} + FMT_INLINE value(volatile void* x FMT_BUILTIN) + : pointer(const_cast(x)) {} + FMT_INLINE value(const volatile void* x FMT_BUILTIN) + : pointer(const_cast(x)) {} + FMT_INLINE value(nullptr_t) : pointer(nullptr) {} + + template ::value || + std::is_member_pointer::value)> + value(const T&) { + // Formatting of arbitrary pointers is disallowed. If you want to format a + // pointer cast it to `void*` or `const void*`. In particular, this forbids + // formatting of `[const] volatile char*` printed as bool by iostreams. + static_assert(sizeof(T) == 0, + "formatting of non-void pointers is disallowed"); + } + + template ::value)> + value(const T& x) : value(format_as(x)) {} + template ::value)> + value(const T& x) : value(formatter::format_as(x)) {} + + template ::value)> + value(const T& named_arg) : value(named_arg.value) {} + + template ::value || !FMT_BUILTIN_TYPES)> + FMT_CONSTEXPR20 FMT_INLINE value(T& x) : value(x, custom_tag()) {} + + FMT_ALWAYS_INLINE value(const named_arg_info* args, size_t size) + : named_args{args, size} {} + + private: + template ())> + FMT_CONSTEXPR value(T& x, custom_tag) { + using value_type = remove_const_t; + // T may overload operator& e.g. std::vector::reference in libc++. + if (!is_constant_evaluated()) { + custom.value = + const_cast(&reinterpret_cast(x)); + } else { + custom.value = nullptr; +#if defined(__cpp_if_constexpr) + if constexpr (std::is_same*>::value) + custom.value = const_cast(&x); +#endif + } + custom.format = format_custom>; + } + + template ())> + FMT_CONSTEXPR value(const T&, custom_tag) { + // Cannot format an argument; to make type T formattable provide a + // formatter specialization: https://fmt.dev/latest/api.html#udt. + type_is_unformattable_for _; + } + + // Formats an argument of a custom type, such as a user-defined class. + template + static void format_custom(void* arg, parse_context& parse_ctx, + Context& ctx) { + auto f = Formatter(); + parse_ctx.advance_to(f.parse(parse_ctx)); + using qualified_type = + conditional_t(), const T, T>; + // format must be const for compatibility with std::format and compilation. + const auto& cf = f; + ctx.advance_to(cf.format(*static_cast(arg), ctx)); + } +}; + +enum { packed_arg_bits = 4 }; +// Maximum number of arguments with packed types. +enum { max_packed_args = 62 / packed_arg_bits }; +enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; +enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; + +template +struct is_output_iterator : std::false_type {}; + +template <> struct is_output_iterator : std::true_type {}; + +template +struct is_output_iterator< + It, T, + void_t&>()++ = std::declval())>> + : std::true_type {}; + +#ifndef FMT_USE_LOCALE +# define FMT_USE_LOCALE (FMT_OPTIMIZE_SIZE <= 1) +#endif + +// A type-erased reference to an std::locale to avoid a heavy include. +struct locale_ref { +#if FMT_USE_LOCALE + private: + const void* locale_; // A type-erased pointer to std::locale. + + public: + constexpr locale_ref() : locale_(nullptr) {} + template locale_ref(const Locale& loc); + + inline explicit operator bool() const noexcept { return locale_ != nullptr; } +#endif // FMT_USE_LOCALE + + template auto get() const -> Locale; +}; + +template constexpr auto encode_types() -> unsigned long long { + return 0; +} + +template +constexpr auto encode_types() -> unsigned long long { + return static_cast(stored_type_constant::value) | + (encode_types() << packed_arg_bits); +} + +template +constexpr auto make_descriptor() -> unsigned long long { + return NUM_ARGS <= max_packed_args ? encode_types() + : is_unpacked_bit | NUM_ARGS; +} + +template +using arg_t = conditional_t, + basic_format_arg>; + +template +struct named_arg_store { + // args_[0].named_args points to named_args to avoid bloating format_args. + arg_t args[1 + NUM_ARGS]; + named_arg_info named_args[NUM_NAMED_ARGS]; + + template + FMT_CONSTEXPR FMT_ALWAYS_INLINE named_arg_store(T&... values) + : args{{named_args, NUM_NAMED_ARGS}, values...} { + int arg_index = 0, named_arg_index = 0; + FMT_APPLY_VARIADIC( + init_named_arg(named_args, arg_index, named_arg_index, values)); + } + + named_arg_store(named_arg_store&& rhs) { + args[0] = {named_args, NUM_NAMED_ARGS}; + for (size_t i = 1; i < sizeof(args) / sizeof(*args); ++i) + args[i] = rhs.args[i]; + for (size_t i = 0; i < NUM_NAMED_ARGS; ++i) + named_args[i] = rhs.named_args[i]; + } + + named_arg_store(const named_arg_store& rhs) = delete; + named_arg_store& operator=(const named_arg_store& rhs) = delete; + named_arg_store& operator=(named_arg_store&& rhs) = delete; + operator const arg_t*() const { return args + 1; } +}; + +// An array of references to arguments. It can be implicitly converted to +// `basic_format_args` for passing into type-erased formatting functions +// such as `vformat`. It is a plain struct to reduce binary size in debug mode. +template +struct format_arg_store { + // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. + using type = + conditional_t[max_of(1, NUM_ARGS)], + named_arg_store>; + type args; +}; + +// TYPE can be different from type_constant, e.g. for __float128. +template struct native_formatter { + private: + dynamic_format_specs specs_; + + public: + using nonlocking = void; + + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin(); + auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, TYPE); + if (const_check(TYPE == type::char_type)) check_char_specs(specs_); + return end; + } + + template + FMT_CONSTEXPR void set_debug_format(bool set = true) { + specs_.set_type(set ? presentation_type::debug : presentation_type::none); + } + + FMT_PRAGMA_CLANG(diagnostic ignored "-Wundefined-inline") + template + FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const + -> decltype(ctx.out()); +}; + +template +struct locking + : bool_constant::value == type::custom_type> {}; +template +struct locking>::nonlocking>> + : std::false_type {}; + +template FMT_CONSTEXPR inline auto is_locking() -> bool { + return locking::value; +} +template +FMT_CONSTEXPR inline auto is_locking() -> bool { + return locking::value || is_locking(); +} + +FMT_API void vformat_to(buffer& buf, string_view fmt, format_args args, + locale_ref loc = {}); + +#if FMT_WIN32 +FMT_API void vprint_mojibake(FILE*, string_view, format_args, bool); +#else // format_args is passed by reference since it is defined later. +inline void vprint_mojibake(FILE*, string_view, const format_args&, bool) {} +#endif +} // namespace detail + +// The main public API. + +template +FMT_CONSTEXPR void parse_context::do_check_arg_id(int arg_id) { + // Argument id is only checked at compile time during parsing because + // formatting has its own validation. + if (detail::is_constant_evaluated() && use_constexpr_cast) { + auto ctx = static_cast*>(this); + if (arg_id >= ctx->num_args()) report_error("argument not found"); + } +} + +template +FMT_CONSTEXPR void parse_context::check_dynamic_spec(int arg_id) { + using detail::compile_parse_context; + if (detail::is_constant_evaluated() && use_constexpr_cast) + static_cast*>(this)->check_dynamic_spec(arg_id); +} + +FMT_BEGIN_EXPORT + +// An output iterator that appends to a buffer. It is used instead of +// back_insert_iterator to reduce symbol sizes and avoid dependency. +template class basic_appender { + protected: + detail::buffer* container; + + public: + using container_type = detail::buffer; + + FMT_CONSTEXPR basic_appender(detail::buffer& buf) : container(&buf) {} + + FMT_CONSTEXPR20 auto operator=(T c) -> basic_appender& { + container->push_back(c); + return *this; + } + FMT_CONSTEXPR20 auto operator*() -> basic_appender& { return *this; } + FMT_CONSTEXPR20 auto operator++() -> basic_appender& { return *this; } + FMT_CONSTEXPR20 auto operator++(int) -> basic_appender { return *this; } +}; + +// A formatting argument. Context is a template parameter for the compiled API +// where output can be unbuffered. +template class basic_format_arg { + private: + detail::value value_; + detail::type type_; + + friend class basic_format_args; + + using char_type = typename Context::char_type; + + public: + class handle { + private: + detail::custom_value custom_; + + public: + explicit handle(detail::custom_value custom) : custom_(custom) {} + + void format(parse_context& parse_ctx, Context& ctx) const { + custom_.format(custom_.value, parse_ctx, ctx); + } + }; + + constexpr basic_format_arg() : type_(detail::type::none_type) {} + basic_format_arg(const detail::named_arg_info* args, size_t size) + : value_(args, size) {} + template + basic_format_arg(T&& val) + : value_(val), type_(detail::stored_type_constant::value) {} + + constexpr explicit operator bool() const noexcept { + return type_ != detail::type::none_type; + } + auto type() const -> detail::type { return type_; } + + /** + * Visits an argument dispatching to the appropriate visit method based on + * the argument type. For example, if the argument type is `double` then + * `vis(value)` will be called with the value of type `double`. + */ + template + FMT_CONSTEXPR FMT_INLINE auto visit(Visitor&& vis) const -> decltype(vis(0)) { + using detail::map; + switch (type_) { + case detail::type::none_type: break; + case detail::type::int_type: return vis(value_.int_value); + case detail::type::uint_type: return vis(value_.uint_value); + case detail::type::long_long_type: return vis(value_.long_long_value); + case detail::type::ulong_long_type: return vis(value_.ulong_long_value); + case detail::type::int128_type: return vis(map(value_.int128_value)); + case detail::type::uint128_type: return vis(map(value_.uint128_value)); + case detail::type::bool_type: return vis(value_.bool_value); + case detail::type::char_type: return vis(value_.char_value); + case detail::type::float_type: return vis(value_.float_value); + case detail::type::double_type: return vis(value_.double_value); + case detail::type::long_double_type: return vis(value_.long_double_value); + case detail::type::cstring_type: return vis(value_.string.data); + case detail::type::string_type: return vis(value_.string.str()); + case detail::type::pointer_type: return vis(value_.pointer); + case detail::type::custom_type: return vis(handle(value_.custom)); + } + return vis(monostate()); + } + + auto format_custom(const char_type* parse_begin, + parse_context& parse_ctx, Context& ctx) + -> bool { + if (type_ != detail::type::custom_type) return false; + parse_ctx.advance_to(parse_begin); + value_.custom.format(value_.custom.value, parse_ctx, ctx); + return true; + } +}; + +/** + * A view of a collection of formatting arguments. To avoid lifetime issues it + * should only be used as a parameter type in type-erased functions such as + * `vformat`: + * + * void vlog(fmt::string_view fmt, fmt::format_args args); // OK + * fmt::format_args args = fmt::make_format_args(); // Dangling reference + */ +template class basic_format_args { + private: + // A descriptor that contains information about formatting arguments. + // If the number of arguments is less or equal to max_packed_args then + // argument types are passed in the descriptor. This reduces binary code size + // per formatting function call. + unsigned long long desc_; + union { + // If is_packed() returns true then argument values are stored in values_; + // otherwise they are stored in args_. This is done to improve cache + // locality and reduce compiled code size since storing larger objects + // may require more code (at least on x86-64) even if the same amount of + // data is actually copied to stack. It saves ~10% on the bloat test. + const detail::value* values_; + const basic_format_arg* args_; + }; + + constexpr auto is_packed() const -> bool { + return (desc_ & detail::is_unpacked_bit) == 0; + } + constexpr auto has_named_args() const -> bool { + return (desc_ & detail::has_named_args_bit) != 0; + } + + FMT_CONSTEXPR auto type(int index) const -> detail::type { + int shift = index * detail::packed_arg_bits; + unsigned mask = (1 << detail::packed_arg_bits) - 1; + return static_cast((desc_ >> shift) & mask); + } + + template + using store = + detail::format_arg_store; + + public: + using format_arg = basic_format_arg; + + constexpr basic_format_args() : desc_(0), args_(nullptr) {} + + /// Constructs a `basic_format_args` object from `format_arg_store`. + template + constexpr FMT_ALWAYS_INLINE basic_format_args( + const store& s) + : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)), + values_(s.args) {} + + template detail::max_packed_args)> + constexpr basic_format_args(const store& s) + : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)), + args_(s.args) {} + + /// Constructs a `basic_format_args` object from a dynamic list of arguments. + constexpr basic_format_args(const format_arg* args, int count, + bool has_named = false) + : desc_(detail::is_unpacked_bit | detail::to_unsigned(count) | + (has_named ? +detail::has_named_args_bit : 0)), + args_(args) {} + + /// Returns the argument with the specified id. + FMT_CONSTEXPR auto get(int id) const -> format_arg { + auto arg = format_arg(); + if (!is_packed()) { + if (id < max_size()) arg = args_[id]; + return arg; + } + if (static_cast(id) >= detail::max_packed_args) return arg; + arg.type_ = type(id); + if (arg.type_ != detail::type::none_type) arg.value_ = values_[id]; + return arg; + } + + template + auto get(basic_string_view name) const -> format_arg { + int id = get_id(name); + return id >= 0 ? get(id) : format_arg(); + } + + template + FMT_CONSTEXPR auto get_id(basic_string_view name) const -> int { + if (!has_named_args()) return -1; + const auto& named_args = + (is_packed() ? values_[-1] : args_[-1].value_).named_args; + for (size_t i = 0; i < named_args.size; ++i) { + if (named_args.data[i].name == name) return named_args.data[i].id; + } + return -1; + } + + auto max_size() const -> int { + unsigned long long max_packed = detail::max_packed_args; + return static_cast(is_packed() ? max_packed + : desc_ & ~detail::is_unpacked_bit); + } +}; + +// A formatting context. +class context { + private: + appender out_; + format_args args_; + FMT_NO_UNIQUE_ADDRESS detail::locale_ref loc_; + + public: + /// The character type for the output. + using char_type = char; + + using iterator = appender; + using format_arg = basic_format_arg; + using parse_context_type FMT_DEPRECATED = parse_context<>; + template using formatter_type FMT_DEPRECATED = formatter; + enum { builtin_types = FMT_BUILTIN_TYPES }; + + /// Constructs a `context` object. References to the arguments are stored + /// in the object so make sure they have appropriate lifetimes. + FMT_CONSTEXPR context(iterator out, format_args args, + detail::locale_ref loc = {}) + : out_(out), args_(args), loc_(loc) {} + context(context&&) = default; + context(const context&) = delete; + void operator=(const context&) = delete; + + FMT_CONSTEXPR auto arg(int id) const -> format_arg { return args_.get(id); } + inline auto arg(string_view name) const -> format_arg { + return args_.get(name); + } + FMT_CONSTEXPR auto arg_id(string_view name) const -> int { + return args_.get_id(name); + } + auto args() const -> const format_args& { return args_; } + + // Returns an iterator to the beginning of the output range. + FMT_CONSTEXPR auto out() const -> iterator { return out_; } + + // Advances the begin iterator to `it`. + FMT_CONSTEXPR void advance_to(iterator) {} + + FMT_CONSTEXPR auto locale() const -> detail::locale_ref { return loc_; } +}; + +template struct runtime_format_string { + basic_string_view str; +}; + +/** + * Creates a runtime format string. + * + * **Example**: + * + * // Check format string at runtime instead of compile-time. + * fmt::print(fmt::runtime("{:d}"), "I am not a number"); + */ +inline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; } + +/// A compile-time format string. Use `format_string` in the public API to +/// prevent type deduction. +template struct fstring { + private: + static constexpr int num_static_named_args = + detail::count_static_named_args(); + + using checker = detail::format_string_checker< + char, static_cast(sizeof...(T)), num_static_named_args, + num_static_named_args != detail::count_named_args()>; + + using arg_pack = detail::arg_pack; + + public: + string_view str; + using t = fstring; + + // Reports a compile-time error if S is not a valid format string for T. + template + FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const char (&s)[N]) : str(s, N - 1) { + using namespace detail; + static_assert(count<(std::is_base_of>::value && + std::is_reference::value)...>() == 0, + "passing views as lvalues is disallowed"); + if (FMT_USE_CONSTEVAL) parse_format_string(s, checker(s, arg_pack())); +#ifdef FMT_ENFORCE_COMPILE_STRING + static_assert( + FMT_USE_CONSTEVAL && sizeof(s) != 0, + "FMT_ENFORCE_COMPILE_STRING requires format strings to use FMT_STRING"); +#endif + } + template ::value)> + FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const S& s) : str(s) { + auto sv = string_view(str); + if (FMT_USE_CONSTEVAL) + detail::parse_format_string(sv, checker(sv, arg_pack())); +#ifdef FMT_ENFORCE_COMPILE_STRING + static_assert( + FMT_USE_CONSTEVAL && sizeof(s) != 0, + "FMT_ENFORCE_COMPILE_STRING requires format strings to use FMT_STRING"); +#endif + } + template ::value&& + std::is_same::value)> + FMT_ALWAYS_INLINE fstring(const S&) : str(S()) { + FMT_CONSTEXPR auto sv = string_view(S()); + FMT_CONSTEXPR int ignore = + (parse_format_string(sv, checker(sv, arg_pack())), 0); + detail::ignore_unused(ignore); + } + fstring(runtime_format_string<> fmt) : str(fmt.str) {} + + // Returning by reference generates better code in debug mode. + FMT_ALWAYS_INLINE operator const string_view&() const { return str; } + auto get() const -> string_view { return str; } +}; + +template using format_string = typename fstring::t; + +template +using is_formattable = bool_constant::value, int*, T>, Char>, + void>::value>; +#ifdef __cpp_concepts +template +concept formattable = is_formattable, Char>::value; +#endif + +template +using has_formatter FMT_DEPRECATED = std::is_constructible>; + +// A formatter specialization for natively supported types. +template +struct formatter::value != + detail::type::custom_type>> + : detail::native_formatter::value> { +}; + +/** + * Constructs an object that stores references to arguments and can be + * implicitly converted to `format_args`. `Context` can be omitted in which case + * it defaults to `context`. See `arg` for lifetime considerations. + */ +// Take arguments by lvalue references to avoid some lifetime issues, e.g. +// auto args = make_format_args(std::string()); +template (), + unsigned long long DESC = detail::make_descriptor()> +constexpr FMT_ALWAYS_INLINE auto make_format_args(T&... args) + -> detail::format_arg_store { + // Suppress warnings for pathological types convertible to detail::value. + FMT_PRAGMA_GCC(diagnostic ignored "-Wconversion") + return {{args...}}; +} + +template +using vargs = + detail::format_arg_store(), + detail::make_descriptor()>; + +/** + * Returns a named argument to be used in a formatting function. + * It should only be used in a call to a formatting function. + * + * **Example**: + * + * fmt::print("The answer is {answer}.", fmt::arg("answer", 42)); + */ +template +inline auto arg(const Char* name, const T& arg) -> detail::named_arg { + return {name, arg}; +} + +/// Formats a string and writes the output to `out`. +template , + char>::value)> +auto vformat_to(OutputIt&& out, string_view fmt, format_args args) + -> remove_cvref_t { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, fmt, args, {}); + return detail::get_iterator(buf, out); +} + +/** + * Formats `args` according to specifications in `fmt`, writes the result to + * the output iterator `out` and returns the iterator past the end of the output + * range. `format_to` does not append a terminating null character. + * + * **Example**: + * + * auto out = std::vector(); + * fmt::format_to(std::back_inserter(out), "{}", 42); + */ +template , + char>::value)> +FMT_INLINE auto format_to(OutputIt&& out, format_string fmt, T&&... args) + -> remove_cvref_t { + return vformat_to(out, fmt.str, vargs{{args...}}); +} + +template struct format_to_n_result { + /// Iterator past the end of the output range. + OutputIt out; + /// Total (not truncated) output size. + size_t size; +}; + +template ::value)> +auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args) + -> format_to_n_result { + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer(out, n); + detail::vformat_to(buf, fmt, args, {}); + return {buf.out(), buf.count()}; +} + +/** + * Formats `args` according to specifications in `fmt`, writes up to `n` + * characters of the result to the output iterator `out` and returns the total + * (not truncated) output size and the iterator past the end of the output + * range. `format_to_n` does not append a terminating null character. + */ +template ::value)> +FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string fmt, + T&&... args) -> format_to_n_result { + return vformat_to_n(out, n, fmt.str, vargs{{args...}}); +} + +struct format_to_result { + /// Pointer to just after the last successful write in the array. + char* out; + /// Specifies if the output was truncated. + bool truncated; + + FMT_CONSTEXPR operator char*() const { + // Report truncation to prevent silent data loss. + if (truncated) report_error("output is truncated"); + return out; + } +}; + +template +auto vformat_to(char (&out)[N], string_view fmt, format_args args) + -> format_to_result { + auto result = vformat_to_n(out, N, fmt, args); + return {result.out, result.size > N}; +} + +template +FMT_INLINE auto format_to(char (&out)[N], format_string fmt, T&&... args) + -> format_to_result { + auto result = vformat_to_n(out, N, fmt.str, vargs{{args...}}); + return {result.out, result.size > N}; +} + +/// Returns the number of chars in the output of `format(fmt, args...)`. +template +FMT_NODISCARD FMT_INLINE auto formatted_size(format_string fmt, + T&&... args) -> size_t { + auto buf = detail::counting_buffer<>(); + detail::vformat_to(buf, fmt.str, vargs{{args...}}, {}); + return buf.count(); +} + +FMT_API void vprint(string_view fmt, format_args args); +FMT_API void vprint(FILE* f, string_view fmt, format_args args); +FMT_API void vprintln(FILE* f, string_view fmt, format_args args); +FMT_API void vprint_buffered(FILE* f, string_view fmt, format_args args); + +/** + * Formats `args` according to specifications in `fmt` and writes the output + * to `stdout`. + * + * **Example**: + * + * fmt::print("The answer is {}.", 42); + */ +template +FMT_INLINE void print(format_string fmt, T&&... args) { + vargs va = {{args...}}; + if (detail::const_check(!detail::use_utf8)) + return detail::vprint_mojibake(stdout, fmt.str, va, false); + return detail::is_locking() ? vprint_buffered(stdout, fmt.str, va) + : vprint(fmt.str, va); +} + +/** + * Formats `args` according to specifications in `fmt` and writes the + * output to the file `f`. + * + * **Example**: + * + * fmt::print(stderr, "Don't {}!", "panic"); + */ +template +FMT_INLINE void print(FILE* f, format_string fmt, T&&... args) { + vargs va = {{args...}}; + if (detail::const_check(!detail::use_utf8)) + return detail::vprint_mojibake(f, fmt.str, va, false); + return detail::is_locking() ? vprint_buffered(f, fmt.str, va) + : vprint(f, fmt.str, va); +} + +/// Formats `args` according to specifications in `fmt` and writes the output +/// to the file `f` followed by a newline. +template +FMT_INLINE void println(FILE* f, format_string fmt, T&&... args) { + vargs va = {{args...}}; + return detail::const_check(detail::use_utf8) + ? vprintln(f, fmt.str, va) + : detail::vprint_mojibake(f, fmt.str, va, true); +} + +/// Formats `args` according to specifications in `fmt` and writes the output +/// to `stdout` followed by a newline. +template +FMT_INLINE void println(format_string fmt, T&&... args) { + return fmt::println(stdout, fmt, static_cast(args)...); +} + +FMT_END_EXPORT +FMT_PRAGMA_CLANG(diagnostic pop) +FMT_PRAGMA_GCC(pop_options) +FMT_END_NAMESPACE + +#ifdef FMT_HEADER_ONLY +# include "format.h" +#endif +#endif // FMT_BASE_H_ diff --git a/thirdparty/fmt/include/fmt/chrono.h b/thirdparty/fmt/include/fmt/chrono.h index ff3e1445b..50c777c84 100644 --- a/thirdparty/fmt/include/fmt/chrono.h +++ b/thirdparty/fmt/include/fmt/chrono.h @@ -8,52 +8,37 @@ #ifndef FMT_CHRONO_H_ #define FMT_CHRONO_H_ -#include -#include -#include // std::isfinite -#include // std::memcpy -#include -#include -#include -#include -#include +#ifndef FMT_MODULE +# include +# include +# include // std::isfinite +# include // std::memcpy +# include +# include +# include +# include +# include +#endif #include "format.h" +namespace fmt_detail { +struct time_zone { + template + auto to_sys(T) + -> std::chrono::time_point { + return {}; + } +}; +template inline auto current_zone(T...) -> time_zone* { + return nullptr; +} + +template inline void _tzset(T...) {} +} // namespace fmt_detail + FMT_BEGIN_NAMESPACE -// Check if std::chrono::local_t is available. -#ifndef FMT_USE_LOCAL_TIME -# ifdef __cpp_lib_chrono -# define FMT_USE_LOCAL_TIME (__cpp_lib_chrono >= 201907L) -# else -# define FMT_USE_LOCAL_TIME 0 -# endif -#endif - -// Check if std::chrono::utc_timestamp is available. -#ifndef FMT_USE_UTC_TIME -# ifdef __cpp_lib_chrono -# define FMT_USE_UTC_TIME (__cpp_lib_chrono >= 201907L) -# else -# define FMT_USE_UTC_TIME 0 -# endif -#endif - -// Enable tzset. -#ifndef FMT_USE_TZSET -// UWP doesn't provide _tzset. -# if FMT_HAS_INCLUDE("winapifamily.h") -# include -# endif -# if defined(_WIN32) && (!defined(WINAPI_FAMILY) || \ - (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) -# define FMT_USE_TZSET 1 -# else -# define FMT_USE_TZSET 0 -# endif -#endif - // Enable safe chrono durations, unless explicitly disabled. #ifndef FMT_SAFE_DURATION_CAST # define FMT_SAFE_DURATION_CAST 1 @@ -72,7 +57,8 @@ template ::value && std::numeric_limits::is_signed == std::numeric_limits::is_signed)> -FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { ec = 0; using F = std::numeric_limits; using T = std::numeric_limits; @@ -93,15 +79,14 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { return static_cast(from); } -/** - * converts From to To, without loss. If the dynamic value of from - * can't be converted to To without loss, ec is set. - */ +/// Converts From to To, without loss. If the dynamic value of from +/// can't be converted to To without loss, ec is set. template ::value && std::numeric_limits::is_signed != std::numeric_limits::is_signed)> -FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { ec = 0; using F = std::numeric_limits; using T = std::numeric_limits; @@ -133,7 +118,8 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { template ::value)> -FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto lossless_integral_conversion(const From from, int& ec) + -> To { ec = 0; return from; } // function @@ -154,7 +140,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { // clang-format on template ::value)> -FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To { ec = 0; using T = std::numeric_limits; static_assert(std::is_floating_point::value, "From must be floating"); @@ -176,72 +162,18 @@ FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { template ::value)> -FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { +FMT_CONSTEXPR auto safe_float_conversion(const From from, int& ec) -> To { ec = 0; static_assert(std::is_floating_point::value, "From must be floating"); return from; } -/** - * safe duration cast between integral durations - */ -template ::value), - FMT_ENABLE_IF(std::is_integral::value)> -To safe_duration_cast(std::chrono::duration from, - int& ec) { - using From = std::chrono::duration; - ec = 0; - // the basic idea is that we need to convert from count() in the from type - // to count() in the To type, by multiplying it with this: - struct Factor - : std::ratio_divide {}; - - static_assert(Factor::num > 0, "num must be positive"); - static_assert(Factor::den > 0, "den must be positive"); - - // the conversion is like this: multiply from.count() with Factor::num - // /Factor::den and convert it to To::rep, all this without - // overflow/underflow. let's start by finding a suitable type that can hold - // both To, From and Factor::num - using IntermediateRep = - typename std::common_type::type; - - // safe conversion to IntermediateRep - IntermediateRep count = - lossless_integral_conversion(from.count(), ec); - if (ec) return {}; - // multiply with Factor::num without overflow or underflow - if (detail::const_check(Factor::num != 1)) { - const auto max1 = detail::max_value() / Factor::num; - if (count > max1) { - ec = 1; - return {}; - } - const auto min1 = - (std::numeric_limits::min)() / Factor::num; - if (detail::const_check(!std::is_unsigned::value) && - count < min1) { - ec = 1; - return {}; - } - count *= Factor::num; - } - - if (detail::const_check(Factor::den != 1)) count /= Factor::den; - auto tocount = lossless_integral_conversion(count, ec); - return ec ? To() : To(tocount); -} - -/** - * safe duration_cast between floating point durations - */ +/// Safe duration_cast between floating point durations template ::value), FMT_ENABLE_IF(std::is_floating_point::value)> -To safe_duration_cast(std::chrono::duration from, - int& ec) { +auto safe_duration_cast(std::chrono::duration from, + int& ec) -> To { using From = std::chrono::duration; ec = 0; if (std::isnan(from.count())) { @@ -315,18 +247,95 @@ To safe_duration_cast(std::chrono::duration from, } // namespace safe_duration_cast #endif +namespace detail { + +// Check if std::chrono::utc_time is available. +#ifdef FMT_USE_UTC_TIME +// Use the provided definition. +#elif defined(__cpp_lib_chrono) +# define FMT_USE_UTC_TIME (__cpp_lib_chrono >= 201907L) +#else +# define FMT_USE_UTC_TIME 0 +#endif +#if FMT_USE_UTC_TIME +using utc_clock = std::chrono::utc_clock; +#else +struct utc_clock { + template void to_sys(T); +}; +#endif + +// Check if std::chrono::local_time is available. +#ifdef FMT_USE_LOCAL_TIME +// Use the provided definition. +#elif defined(__cpp_lib_chrono) +# define FMT_USE_LOCAL_TIME (__cpp_lib_chrono >= 201907L) +#else +# define FMT_USE_LOCAL_TIME 0 +#endif +#if FMT_USE_LOCAL_TIME +using local_t = std::chrono::local_t; +#else +struct local_t {}; +#endif + +} // namespace detail + +template +using sys_time = std::chrono::time_point; + +template +using utc_time = std::chrono::time_point; + +template +using local_time = std::chrono::time_point; + +namespace detail { + // Prevents expansion of a preceding token as a function-style macro. // Usage: f FMT_NOMACRO() #define FMT_NOMACRO -namespace detail { template struct null {}; -inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); } -inline null<> localtime_s(...) { return null<>(); } -inline null<> gmtime_r(...) { return null<>(); } -inline null<> gmtime_s(...) { return null<>(); } +inline auto localtime_r FMT_NOMACRO(...) -> null<> { return null<>(); } +inline auto localtime_s(...) -> null<> { return null<>(); } +inline auto gmtime_r(...) -> null<> { return null<>(); } +inline auto gmtime_s(...) -> null<> { return null<>(); } -inline const std::locale& get_classic_locale() { +// It is defined here and not in ostream.h because the latter has expensive +// includes. +template class formatbuf : public StreamBuf { + private: + using char_type = typename StreamBuf::char_type; + using streamsize = decltype(std::declval().sputn(nullptr, 0)); + using int_type = typename StreamBuf::int_type; + using traits_type = typename StreamBuf::traits_type; + + buffer& buffer_; + + public: + explicit formatbuf(buffer& buf) : buffer_(buf) {} + + protected: + // The put area is always empty. This makes the implementation simpler and has + // the advantage that the streambuf and the buffer are always in sync and + // sputc never writes into uninitialized memory. A disadvantage is that each + // call to sputc always results in a (virtual) call to overflow. There is no + // disadvantage here for sputn since this always results in a call to xsputn. + + auto overflow(int_type ch) -> int_type override { + if (!traits_type::eq_int_type(ch, traits_type::eof())) + buffer_.push_back(static_cast(ch)); + return ch; + } + + auto xsputn(const char_type* s, streamsize count) -> streamsize override { + buffer_.append(s, s + count); + return count; + } +}; + +inline auto get_classic_locale() -> const std::locale& { static const auto& locale = std::locale::classic(); return locale; } @@ -336,24 +345,18 @@ template struct codecvt_result { CodeUnit buf[max_size]; CodeUnit* end; }; -template -constexpr const size_t codecvt_result::max_size; template -void write_codecvt(codecvt_result& out, string_view in_buf, +void write_codecvt(codecvt_result& out, string_view in, const std::locale& loc) { -#if FMT_CLANG_VERSION -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdeprecated" + FMT_PRAGMA_CLANG(diagnostic push) + FMT_PRAGMA_CLANG(diagnostic ignored "-Wdeprecated") auto& f = std::use_facet>(loc); -# pragma clang diagnostic pop -#else - auto& f = std::use_facet>(loc); -#endif + FMT_PRAGMA_CLANG(diagnostic pop) auto mb = std::mbstate_t(); const char* from_next = nullptr; - auto result = f.in(mb, in_buf.begin(), in_buf.end(), from_next, - std::begin(out.buf), std::end(out.buf), out.end); + auto result = f.in(mb, in.begin(), in.end(), from_next, std::begin(out.buf), + std::end(out.buf), out.end); if (result != std::codecvt_base::ok) FMT_THROW(format_error("failed to format time")); } @@ -361,11 +364,12 @@ void write_codecvt(codecvt_result& out, string_view in_buf, template auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc) -> OutputIt { - if (detail::is_utf8() && loc != get_classic_locale()) { + if (const_check(detail::use_utf8) && loc != get_classic_locale()) { // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and // gcc-4. -#if FMT_MSC_VERSION != 0 || \ - (defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI)) +#if FMT_MSC_VERSION != 0 || \ + (defined(__GLIBCXX__) && \ + (!defined(_GLIBCXX_USE_DUAL_ABI) || _GLIBCXX_USE_DUAL_ABI == 0)) // The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5 // and newer. using code_unit = wchar_t; @@ -381,9 +385,9 @@ auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc) to_utf8>(); if (!u.convert({unit.buf, to_unsigned(unit.end - unit.buf)})) FMT_THROW(format_error("failed to format time")); - return copy_str(u.c_str(), u.c_str() + u.size(), out); + return copy(u.c_str(), u.c_str() + u.size(), out); } - return copy_str(in.data(), in.data() + in.size(), out); + return copy(in.data(), in.data() + in.size(), out); } template OutputIt { codecvt_result unit; write_codecvt(unit, sv, loc); - return copy_str(unit.buf, unit.end, out); + return copy(unit.buf, unit.end, out); } template & buf, const std::tm& time, auto&& format_buf = formatbuf>(buf); auto&& os = std::basic_ostream(&format_buf); os.imbue(loc); - using iterator = std::ostreambuf_iterator; - const auto& facet = std::use_facet>(loc); + const auto& facet = std::use_facet>(loc); auto end = facet.put(os, os, Char(' '), &time, format, modifier); if (end.failed()) FMT_THROW(format_error("failed to format time")); } @@ -432,38 +435,129 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc, return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc); } +template +struct is_same_arithmetic_type + : public std::integral_constant::value && + std::is_integral::value) || + (std::is_floating_point::value && + std::is_floating_point::value)> { +}; + +FMT_NORETURN inline void throw_duration_error() { + FMT_THROW(format_error("cannot format duration")); +} + +// Cast one integral duration to another with an overflow check. +template ::value&& + std::is_integral::value)> +auto duration_cast(std::chrono::duration from) -> To { +#if !FMT_SAFE_DURATION_CAST + return std::chrono::duration_cast(from); +#else + // The conversion factor: to.count() == factor * from.count(). + using factor = std::ratio_divide; + + using common_rep = typename std::common_type::type; + + int ec = 0; + auto count = safe_duration_cast::lossless_integral_conversion( + from.count(), ec); + if (ec) throw_duration_error(); + + // Multiply from.count() by factor and check for overflow. + if (const_check(factor::num != 1)) { + if (count > max_value() / factor::num) throw_duration_error(); + const auto min = (std::numeric_limits::min)() / factor::num; + if (const_check(!std::is_unsigned::value) && count < min) + throw_duration_error(); + count *= factor::num; + } + if (const_check(factor::den != 1)) count /= factor::den; + auto to = + To(safe_duration_cast::lossless_integral_conversion( + count, ec)); + if (ec) throw_duration_error(); + return to; +#endif +} + +template ::value&& + std::is_floating_point::value)> +auto duration_cast(std::chrono::duration from) -> To { +#if FMT_SAFE_DURATION_CAST + // Throwing version of safe_duration_cast is only available for + // integer to integer or float to float casts. + int ec; + To to = safe_duration_cast::safe_duration_cast(from, ec); + if (ec) throw_duration_error(); + return to; +#else + // Standard duration cast, may overflow. + return std::chrono::duration_cast(from); +#endif +} + +template < + typename To, typename FromRep, typename FromPeriod, + FMT_ENABLE_IF(!is_same_arithmetic_type::value)> +auto duration_cast(std::chrono::duration from) -> To { + // Mixed integer <-> float cast is not supported by safe_duration_cast. + return std::chrono::duration_cast(from); +} + +template +auto to_time_t(sys_time time_point) -> std::time_t { + // Cannot use std::chrono::system_clock::to_time_t since this would first + // require a cast to std::chrono::system_clock::time_point, which could + // overflow. + return detail::duration_cast>( + time_point.time_since_epoch()) + .count(); +} + +// Workaround a bug in libstdc++ which sets __cpp_lib_chrono to 201907 without +// providing current_zone(): https://github.com/fmtlib/fmt/issues/4160. +template FMT_CONSTEXPR auto has_current_zone() -> bool { + using namespace std::chrono; + using namespace fmt_detail; + return !std::is_same::value; +} } // namespace detail FMT_BEGIN_EXPORT /** - Converts given time since epoch as ``std::time_t`` value into calendar time, - expressed in local time. Unlike ``std::localtime``, this function is - thread-safe on most platforms. + * Converts given time since epoch as `std::time_t` value into calendar time, + * expressed in local time. Unlike `std::localtime`, this function is + * thread-safe on most platforms. */ -inline std::tm localtime(std::time_t time) { +inline auto localtime(std::time_t time) -> std::tm { struct dispatcher { std::time_t time_; std::tm tm_; - dispatcher(std::time_t t) : time_(t) {} + inline dispatcher(std::time_t t) : time_(t) {} - bool run() { + inline auto run() -> bool { using namespace fmt::detail; return handle(localtime_r(&time_, &tm_)); } - bool handle(std::tm* tm) { return tm != nullptr; } + inline auto handle(std::tm* tm) -> bool { return tm != nullptr; } - bool handle(detail::null<>) { + inline auto handle(detail::null<>) -> bool { using namespace fmt::detail; return fallback(localtime_s(&tm_, &time_)); } - bool fallback(int res) { return res == 0; } + inline auto fallback(int res) -> bool { return res == 0; } #if !FMT_MSC_VERSION - bool fallback(detail::null<>) { + inline auto fallback(detail::null<>) -> bool { using namespace fmt::detail; std::tm* tm = std::localtime(&time_); if (tm) tm_ = *tm; @@ -478,41 +572,43 @@ inline std::tm localtime(std::time_t time) { } #if FMT_USE_LOCAL_TIME -template +template ())> inline auto localtime(std::chrono::local_time time) -> std::tm { - return localtime(std::chrono::system_clock::to_time_t( - std::chrono::current_zone()->to_sys(time))); + using namespace std::chrono; + using namespace fmt_detail; + return localtime(detail::to_time_t(current_zone()->to_sys(time))); } #endif /** - Converts given time since epoch as ``std::time_t`` value into calendar time, - expressed in Coordinated Universal Time (UTC). Unlike ``std::gmtime``, this - function is thread-safe on most platforms. + * Converts given time since epoch as `std::time_t` value into calendar time, + * expressed in Coordinated Universal Time (UTC). Unlike `std::gmtime`, this + * function is thread-safe on most platforms. */ -inline std::tm gmtime(std::time_t time) { +inline auto gmtime(std::time_t time) -> std::tm { struct dispatcher { std::time_t time_; std::tm tm_; - dispatcher(std::time_t t) : time_(t) {} + inline dispatcher(std::time_t t) : time_(t) {} - bool run() { + inline auto run() -> bool { using namespace fmt::detail; return handle(gmtime_r(&time_, &tm_)); } - bool handle(std::tm* tm) { return tm != nullptr; } + inline auto handle(std::tm* tm) -> bool { return tm != nullptr; } - bool handle(detail::null<>) { + inline auto handle(detail::null<>) -> bool { using namespace fmt::detail; return fallback(gmtime_s(&tm_, &time_)); } - bool fallback(int res) { return res == 0; } + inline auto fallback(int res) -> bool { return res == 0; } #if !FMT_MSC_VERSION - bool fallback(detail::null<>) { + inline auto fallback(detail::null<>) -> bool { std::tm* tm = std::gmtime(&time_); if (tm) tm_ = *tm; return tm != nullptr; @@ -525,9 +621,9 @@ inline std::tm gmtime(std::time_t time) { return gt.tm_; } -inline std::tm gmtime( - std::chrono::time_point time_point) { - return gmtime(std::chrono::system_clock::to_time_t(time_point)); +template +inline auto gmtime(sys_time time_point) -> std::tm { + return gmtime(detail::to_time_t(time_point)); } namespace detail { @@ -566,12 +662,14 @@ inline void write_digit2_separated(char* buf, unsigned a, unsigned b, } } -template FMT_CONSTEXPR inline const char* get_units() { +template +FMT_CONSTEXPR inline auto get_units() -> const char* { if (std::is_same::value) return "as"; if (std::is_same::value) return "fs"; if (std::is_same::value) return "ps"; if (std::is_same::value) return "ns"; - if (std::is_same::value) return "µs"; + if (std::is_same::value) + return detail::use_utf8 ? "µs" : "us"; if (std::is_same::value) return "ms"; if (std::is_same::value) return "cs"; if (std::is_same::value) return "ds"; @@ -584,8 +682,9 @@ template FMT_CONSTEXPR inline const char* get_units() { if (std::is_same::value) return "Ts"; if (std::is_same::value) return "Ps"; if (std::is_same::value) return "Es"; - if (std::is_same>::value) return "m"; + if (std::is_same>::value) return "min"; if (std::is_same>::value) return "h"; + if (std::is_same>::value) return "d"; return nullptr; } @@ -597,12 +696,10 @@ enum class numeric_system { // Glibc extensions for formatting numeric values. enum class pad_type { - unspecified, + // Pad a numeric result string with zeros (the default). + zero, // Do not pad a numeric result string. none, - // Pad a numeric result string with zeros even if the conversion specifier - // character uses space-padding by default. - zero, // Pad a numeric result string with spaces. space, }; @@ -610,7 +707,7 @@ enum class pad_type { template auto write_padding(OutputIt out, pad_type pad, int width) -> OutputIt { if (pad == pad_type::none) return out; - return std::fill_n(out, width, pad == pad_type::space ? ' ' : '0'); + return detail::fill_n(out, width, pad == pad_type::space ? ' ' : '0'); } template @@ -621,14 +718,13 @@ auto write_padding(OutputIt out, pad_type pad) -> OutputIt { // Parses a put_time-like format string and invokes handler actions. template -FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, - const Char* end, - Handler&& handler) { +FMT_CONSTEXPR auto parse_chrono_format(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { if (begin == end || *begin == '}') return begin; if (*begin != '%') FMT_THROW(format_error("invalid format")); auto ptr = begin; - pad_type pad = pad_type::unspecified; while (ptr != end) { + pad_type pad = pad_type::zero; auto c = *ptr; if (c == '}') break; if (c != '%') { @@ -648,17 +744,11 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, pad = pad_type::none; ++ptr; break; - case '0': - pad = pad_type::zero; - ++ptr; - break; } if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { - case '%': - handler.on_text(ptr - 1, ptr); - break; + case '%': handler.on_text(ptr - 1, ptr); break; case 'n': { const Char newline[] = {'\n'}; handler.on_text(newline, newline + 1); @@ -670,145 +760,66 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, break; } // Year: - case 'Y': - handler.on_year(numeric_system::standard); - break; - case 'y': - handler.on_short_year(numeric_system::standard); - break; - case 'C': - handler.on_century(numeric_system::standard); - break; - case 'G': - handler.on_iso_week_based_year(); - break; - case 'g': - handler.on_iso_week_based_short_year(); - break; + case 'Y': handler.on_year(numeric_system::standard, pad); break; + case 'y': handler.on_short_year(numeric_system::standard); break; + case 'C': handler.on_century(numeric_system::standard); break; + case 'G': handler.on_iso_week_based_year(); break; + case 'g': handler.on_iso_week_based_short_year(); break; // Day of the week: - case 'a': - handler.on_abbr_weekday(); - break; - case 'A': - handler.on_full_weekday(); - break; - case 'w': - handler.on_dec0_weekday(numeric_system::standard); - break; - case 'u': - handler.on_dec1_weekday(numeric_system::standard); - break; + case 'a': handler.on_abbr_weekday(); break; + case 'A': handler.on_full_weekday(); break; + case 'w': handler.on_dec0_weekday(numeric_system::standard); break; + case 'u': handler.on_dec1_weekday(numeric_system::standard); break; // Month: case 'b': - case 'h': - handler.on_abbr_month(); - break; - case 'B': - handler.on_full_month(); - break; - case 'm': - handler.on_dec_month(numeric_system::standard); - break; + case 'h': handler.on_abbr_month(); break; + case 'B': handler.on_full_month(); break; + case 'm': handler.on_dec_month(numeric_system::standard, pad); break; // Day of the year/month: case 'U': - handler.on_dec0_week_of_year(numeric_system::standard); + handler.on_dec0_week_of_year(numeric_system::standard, pad); break; case 'W': - handler.on_dec1_week_of_year(numeric_system::standard); - break; - case 'V': - handler.on_iso_week_of_year(numeric_system::standard); - break; - case 'j': - handler.on_day_of_year(); - break; - case 'd': - handler.on_day_of_month(numeric_system::standard); + handler.on_dec1_week_of_year(numeric_system::standard, pad); break; + case 'V': handler.on_iso_week_of_year(numeric_system::standard, pad); break; + case 'j': handler.on_day_of_year(pad); break; + case 'd': handler.on_day_of_month(numeric_system::standard, pad); break; case 'e': - handler.on_day_of_month_space(numeric_system::standard); + handler.on_day_of_month(numeric_system::standard, pad_type::space); break; // Hour, minute, second: - case 'H': - handler.on_24_hour(numeric_system::standard, pad); - break; - case 'I': - handler.on_12_hour(numeric_system::standard, pad); - break; - case 'M': - handler.on_minute(numeric_system::standard, pad); - break; - case 'S': - handler.on_second(numeric_system::standard, pad); - break; + case 'H': handler.on_24_hour(numeric_system::standard, pad); break; + case 'I': handler.on_12_hour(numeric_system::standard, pad); break; + case 'M': handler.on_minute(numeric_system::standard, pad); break; + case 'S': handler.on_second(numeric_system::standard, pad); break; // Other: - case 'c': - handler.on_datetime(numeric_system::standard); - break; - case 'x': - handler.on_loc_date(numeric_system::standard); - break; - case 'X': - handler.on_loc_time(numeric_system::standard); - break; - case 'D': - handler.on_us_date(); - break; - case 'F': - handler.on_iso_date(); - break; - case 'r': - handler.on_12_hour_time(); - break; - case 'R': - handler.on_24_hour_time(); - break; - case 'T': - handler.on_iso_time(); - break; - case 'p': - handler.on_am_pm(); - break; - case 'Q': - handler.on_duration_value(); - break; - case 'q': - handler.on_duration_unit(); - break; - case 'z': - handler.on_utc_offset(numeric_system::standard); - break; - case 'Z': - handler.on_tz_name(); - break; + case 'c': handler.on_datetime(numeric_system::standard); break; + case 'x': handler.on_loc_date(numeric_system::standard); break; + case 'X': handler.on_loc_time(numeric_system::standard); break; + case 'D': handler.on_us_date(); break; + case 'F': handler.on_iso_date(); break; + case 'r': handler.on_12_hour_time(); break; + case 'R': handler.on_24_hour_time(); break; + case 'T': handler.on_iso_time(); break; + case 'p': handler.on_am_pm(); break; + case 'Q': handler.on_duration_value(); break; + case 'q': handler.on_duration_unit(); break; + case 'z': handler.on_utc_offset(numeric_system::standard); break; + case 'Z': handler.on_tz_name(); break; // Alternative representation: case 'E': { if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { - case 'Y': - handler.on_year(numeric_system::alternative); - break; - case 'y': - handler.on_offset_year(); - break; - case 'C': - handler.on_century(numeric_system::alternative); - break; - case 'c': - handler.on_datetime(numeric_system::alternative); - break; - case 'x': - handler.on_loc_date(numeric_system::alternative); - break; - case 'X': - handler.on_loc_time(numeric_system::alternative); - break; - case 'z': - handler.on_utc_offset(numeric_system::alternative); - break; - default: - FMT_THROW(format_error("invalid format")); + case 'Y': handler.on_year(numeric_system::alternative, pad); break; + case 'y': handler.on_offset_year(); break; + case 'C': handler.on_century(numeric_system::alternative); break; + case 'c': handler.on_datetime(numeric_system::alternative); break; + case 'x': handler.on_loc_date(numeric_system::alternative); break; + case 'X': handler.on_loc_time(numeric_system::alternative); break; + case 'z': handler.on_utc_offset(numeric_system::alternative); break; + default: FMT_THROW(format_error("invalid format")); } break; } @@ -816,54 +827,34 @@ FMT_CONSTEXPR const Char* parse_chrono_format(const Char* begin, if (ptr == end) FMT_THROW(format_error("invalid format")); c = *ptr++; switch (c) { - case 'y': - handler.on_short_year(numeric_system::alternative); - break; - case 'm': - handler.on_dec_month(numeric_system::alternative); - break; + case 'y': handler.on_short_year(numeric_system::alternative); break; + case 'm': handler.on_dec_month(numeric_system::alternative, pad); break; case 'U': - handler.on_dec0_week_of_year(numeric_system::alternative); + handler.on_dec0_week_of_year(numeric_system::alternative, pad); break; case 'W': - handler.on_dec1_week_of_year(numeric_system::alternative); + handler.on_dec1_week_of_year(numeric_system::alternative, pad); break; case 'V': - handler.on_iso_week_of_year(numeric_system::alternative); + handler.on_iso_week_of_year(numeric_system::alternative, pad); break; case 'd': - handler.on_day_of_month(numeric_system::alternative); + handler.on_day_of_month(numeric_system::alternative, pad); break; case 'e': - handler.on_day_of_month_space(numeric_system::alternative); + handler.on_day_of_month(numeric_system::alternative, pad_type::space); break; - case 'w': - handler.on_dec0_weekday(numeric_system::alternative); - break; - case 'u': - handler.on_dec1_weekday(numeric_system::alternative); - break; - case 'H': - handler.on_24_hour(numeric_system::alternative, pad); - break; - case 'I': - handler.on_12_hour(numeric_system::alternative, pad); - break; - case 'M': - handler.on_minute(numeric_system::alternative, pad); - break; - case 'S': - handler.on_second(numeric_system::alternative, pad); - break; - case 'z': - handler.on_utc_offset(numeric_system::alternative); - break; - default: - FMT_THROW(format_error("invalid format")); + case 'w': handler.on_dec0_weekday(numeric_system::alternative); break; + case 'u': handler.on_dec1_weekday(numeric_system::alternative); break; + case 'H': handler.on_24_hour(numeric_system::alternative, pad); break; + case 'I': handler.on_12_hour(numeric_system::alternative, pad); break; + case 'M': handler.on_minute(numeric_system::alternative, pad); break; + case 'S': handler.on_second(numeric_system::alternative, pad); break; + case 'z': handler.on_utc_offset(numeric_system::alternative); break; + default: FMT_THROW(format_error("invalid format")); } break; - default: - FMT_THROW(format_error("invalid format")); + default: FMT_THROW(format_error("invalid format")); } begin = ptr; } @@ -875,7 +866,7 @@ template struct null_chrono_spec_handler { FMT_CONSTEXPR void unsupported() { static_cast(this)->unsupported(); } - FMT_CONSTEXPR void on_year(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_year(numeric_system, pad_type) { unsupported(); } FMT_CONSTEXPR void on_short_year(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_offset_year() { unsupported(); } FMT_CONSTEXPR void on_century(numeric_system) { unsupported(); } @@ -887,13 +878,20 @@ template struct null_chrono_spec_handler { FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_abbr_month() { unsupported(); } FMT_CONSTEXPR void on_full_month() { unsupported(); } - FMT_CONSTEXPR void on_dec_month(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_iso_week_of_year(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_day_of_year() { unsupported(); } - FMT_CONSTEXPR void on_day_of_month(numeric_system) { unsupported(); } - FMT_CONSTEXPR void on_day_of_month_space(numeric_system) { unsupported(); } + FMT_CONSTEXPR void on_dec_month(numeric_system, pad_type) { unsupported(); } + FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_iso_week_of_year(numeric_system, pad_type) { + unsupported(); + } + FMT_CONSTEXPR void on_day_of_year(pad_type) { unsupported(); } + FMT_CONSTEXPR void on_day_of_month(numeric_system, pad_type) { + unsupported(); + } FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); } FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); } @@ -914,11 +912,13 @@ template struct null_chrono_spec_handler { }; struct tm_format_checker : null_chrono_spec_handler { - FMT_NORETURN void unsupported() { FMT_THROW(format_error("no format")); } + FMT_NORETURN inline void unsupported() { + FMT_THROW(format_error("no format")); + } template FMT_CONSTEXPR void on_text(const Char*, const Char*) {} - FMT_CONSTEXPR void on_year(numeric_system) {} + FMT_CONSTEXPR void on_year(numeric_system, pad_type) {} FMT_CONSTEXPR void on_short_year(numeric_system) {} FMT_CONSTEXPR void on_offset_year() {} FMT_CONSTEXPR void on_century(numeric_system) {} @@ -930,13 +930,12 @@ struct tm_format_checker : null_chrono_spec_handler { FMT_CONSTEXPR void on_dec1_weekday(numeric_system) {} FMT_CONSTEXPR void on_abbr_month() {} FMT_CONSTEXPR void on_full_month() {} - FMT_CONSTEXPR void on_dec_month(numeric_system) {} - FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system) {} - FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system) {} - FMT_CONSTEXPR void on_iso_week_of_year(numeric_system) {} - FMT_CONSTEXPR void on_day_of_year() {} - FMT_CONSTEXPR void on_day_of_month(numeric_system) {} - FMT_CONSTEXPR void on_day_of_month_space(numeric_system) {} + FMT_CONSTEXPR void on_dec_month(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_iso_week_of_year(numeric_system, pad_type) {} + FMT_CONSTEXPR void on_day_of_year(pad_type) {} + FMT_CONSTEXPR void on_day_of_month(numeric_system, pad_type) {} FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} @@ -954,25 +953,25 @@ struct tm_format_checker : null_chrono_spec_handler { FMT_CONSTEXPR void on_tz_name() {} }; -inline const char* tm_wday_full_name(int wday) { +inline auto tm_wday_full_name(int wday) -> const char* { static constexpr const char* full_name_list[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; return wday >= 0 && wday <= 6 ? full_name_list[wday] : "?"; } -inline const char* tm_wday_short_name(int wday) { +inline auto tm_wday_short_name(int wday) -> const char* { static constexpr const char* short_name_list[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; return wday >= 0 && wday <= 6 ? short_name_list[wday] : "???"; } -inline const char* tm_mon_full_name(int mon) { +inline auto tm_mon_full_name(int mon) -> const char* { static constexpr const char* full_name_list[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; return mon >= 0 && mon <= 11 ? full_name_list[mon] : "?"; } -inline const char* tm_mon_short_name(int mon) { +inline auto tm_mon_short_name(int mon) -> const char* { static constexpr const char* short_name_list[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", @@ -992,33 +991,33 @@ template struct has_member_data_tm_zone> : std::true_type {}; -#if FMT_USE_TZSET inline void tzset_once() { - static bool init = []() -> bool { + static bool init = []() { + using namespace fmt_detail; _tzset(); - return true; + return false; }(); ignore_unused(init); } -#endif // Converts value to Int and checks that it's in the range [0, upper). template ::value)> -inline Int to_nonnegative_int(T value, Int upper) { - FMT_ASSERT(std::is_unsigned::value || - (value >= 0 && to_unsigned(value) <= to_unsigned(upper)), - "invalid value"); - (void)upper; +inline auto to_nonnegative_int(T value, Int upper) -> Int { + if (!std::is_unsigned::value && + (value < 0 || to_unsigned(value) > to_unsigned(upper))) { + FMT_THROW(fmt::format_error("chrono value is out of range")); + } return static_cast(value); } template ::value)> -inline Int to_nonnegative_int(T value, Int upper) { - if (value < 0 || value > static_cast(upper)) +inline auto to_nonnegative_int(T value, Int upper) -> Int { + auto int_value = static_cast(value); + if (int_value < 0 || value > static_cast(upper)) FMT_THROW(format_error("invalid value")); - return static_cast(value); + return int_value; } -constexpr long long pow10(std::uint32_t n) { +constexpr auto pow10(std::uint32_t n) -> long long { return n == 0 ? 1 : 10 * pow10(n - 1); } @@ -1050,17 +1049,16 @@ void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) { using subsecond_precision = std::chrono::duration< typename std::common_type::type, - std::ratio<1, detail::pow10(num_fractional_digits)>>; + std::ratio<1, pow10(num_fractional_digits)>>; - const auto fractional = - d - std::chrono::duration_cast(d); + const auto fractional = d - detail::duration_cast(d); const auto subseconds = std::chrono::treat_as_floating_point< typename subsecond_precision::rep>::value ? fractional.count() - : std::chrono::duration_cast(fractional).count(); + : detail::duration_cast(fractional).count(); auto n = static_cast>(subseconds); - const int num_digits = detail::count_digits(n); + const int num_digits = count_digits(n); int leading_zeroes = (std::max)(0, num_fractional_digits - num_digits); if (precision < 0) { @@ -1068,22 +1066,25 @@ void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) { if (std::ratio_less::value) { *out++ = '.'; - out = std::fill_n(out, leading_zeroes, '0'); - out = format_decimal(out, n, num_digits).end; + out = detail::fill_n(out, leading_zeroes, '0'); + out = format_decimal(out, n, num_digits); } - } else { + } else if (precision > 0) { *out++ = '.'; - leading_zeroes = (std::min)(leading_zeroes, precision); - out = std::fill_n(out, leading_zeroes, '0'); + leading_zeroes = min_of(leading_zeroes, precision); int remaining = precision - leading_zeroes; - if (remaining != 0 && remaining < num_digits) { - n /= to_unsigned(detail::pow10(to_unsigned(num_digits - remaining))); - out = format_decimal(out, n, remaining).end; + out = detail::fill_n(out, leading_zeroes, '0'); + if (remaining < num_digits) { + int num_truncated_digits = num_digits - remaining; + n /= to_unsigned(pow10(to_unsigned(num_truncated_digits))); + if (n != 0) out = format_decimal(out, n, remaining); return; } - out = format_decimal(out, n, num_digits).end; - remaining -= num_digits; - out = std::fill_n(out, remaining, '0'); + if (n != 0) { + out = format_decimal(out, n, num_digits); + remaining -= num_digits; + } + out = detail::fill_n(out, remaining, '0'); } } @@ -1109,11 +1110,11 @@ void write_floating_seconds(memory_buffer& buf, Duration duration, num_fractional_digits = 6; } - format_to(std::back_inserter(buf), FMT_STRING("{:.{}f}"), - std::fmod(val * static_cast(Duration::period::num) / - static_cast(Duration::period::den), - static_cast(60)), - num_fractional_digits); + fmt::format_to(std::back_inserter(buf), FMT_STRING("{:.{}f}"), + std::fmod(val * static_cast(Duration::period::num) / + static_cast(Duration::period::den), + static_cast(60)), + num_fractional_digits); } template (l); } - // Algorithm: - // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_the_week_number_from_a_month_and_day_of_the_month_or_ordinal_date + // Algorithm: https://en.wikipedia.org/wiki/ISO_week_date. auto iso_year_weeks(long long curr_year) const noexcept -> int { const auto prev_year = curr_year - 1; const auto curr_p = @@ -1225,29 +1225,28 @@ class tm_writer { } } - void write_year_extended(long long year) { + void write_year_extended(long long year, pad_type pad) { // At least 4 characters. int width = 4; - if (year < 0) { - *out_++ = '-'; + bool negative = year < 0; + if (negative) { year = 0 - year; --width; } uint32_or_64_or_128_t n = to_unsigned(year); const int num_digits = count_digits(n); - if (width > num_digits) out_ = std::fill_n(out_, width - num_digits, '0'); - out_ = format_decimal(out_, n, num_digits).end; - } - void write_year(long long year) { - if (year >= 0 && year < 10000) { - write2(static_cast(year / 100)); - write2(static_cast(year % 100)); - } else { - write_year_extended(year); + if (negative && pad == pad_type::zero) *out_++ = '-'; + if (width > num_digits) { + out_ = detail::write_padding(out_, pad, width - num_digits); } + if (negative && pad != pad_type::zero) *out_++ = '-'; + out_ = format_decimal(out_, n, num_digits); + } + void write_year(long long year, pad_type pad) { + write_year_extended(year, pad); } - void write_utc_offset(long offset, numeric_system ns) { + void write_utc_offset(long long offset, numeric_system ns) { if (offset < 0) { *out_++ = '-'; offset = -offset; @@ -1259,6 +1258,7 @@ class tm_writer { if (ns != numeric_system::standard) *out_++ = ':'; write2(static_cast(offset % 60)); } + template ::value)> void format_utc_offset_impl(const T& tm, numeric_system ns) { write_utc_offset(tm.tm_gmtoff, ns); @@ -1266,9 +1266,7 @@ class tm_writer { template ::value)> void format_utc_offset_impl(const T& tm, numeric_system ns) { #if defined(_WIN32) && defined(_UCRT) -# if FMT_USE_TZSET tzset_once(); -# endif long offset = 0; _get_timezone(&offset); if (tm.tm_isdst) { @@ -1285,7 +1283,7 @@ class tm_writer { std::time_t gt = std::mktime(>m); std::tm ltm = gmtime(gt); std::time_t lt = std::mktime(<m); - long offset = gt - lt; + long long offset = gt - lt; write_utc_offset(offset, ns); #endif } @@ -1315,10 +1313,10 @@ class tm_writer { subsecs_(subsecs), tm_(tm) {} - OutputIt out() const { return out_; } + auto out() const -> OutputIt { return out_; } FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) { - out_ = copy_str(begin, end, out_); + out_ = copy(begin, end, out_); } void on_abbr_weekday() { @@ -1365,11 +1363,11 @@ class tm_writer { *out_++ = ' '; on_abbr_month(); *out_++ = ' '; - on_day_of_month_space(numeric_system::standard); + on_day_of_month(numeric_system::standard, pad_type::space); *out_++ = ' '; on_iso_time(); *out_++ = ' '; - on_year(numeric_system::standard); + on_year(numeric_system::standard, pad_type::space); } else { format_localized('c', ns == numeric_system::standard ? '\0' : 'E'); } @@ -1391,31 +1389,31 @@ class tm_writer { write_digit2_separated(buf, to_unsigned(tm_mon() + 1), to_unsigned(tm_mday()), to_unsigned(split_year_lower(tm_year())), '/'); - out_ = copy_str(std::begin(buf), std::end(buf), out_); + out_ = copy(std::begin(buf), std::end(buf), out_); } void on_iso_date() { auto year = tm_year(); char buf[10]; size_t offset = 0; if (year >= 0 && year < 10000) { - copy2(buf, digits2(static_cast(year / 100))); + write2digits(buf, static_cast(year / 100)); } else { offset = 4; - write_year_extended(year); + write_year_extended(year, pad_type::zero); year = 0; } write_digit2_separated(buf + 2, static_cast(year % 100), to_unsigned(tm_mon() + 1), to_unsigned(tm_mday()), '-'); - out_ = copy_str(std::begin(buf) + offset, std::end(buf), out_); + out_ = copy(std::begin(buf) + offset, std::end(buf), out_); } void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); } void on_tz_name() { format_tz_name_impl(tm_); } - void on_year(numeric_system ns) { + void on_year(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) - return write_year(tm_year()); + return write_year(tm_year(), pad); format_localized('Y', 'E'); } void on_short_year(numeric_system ns) { @@ -1446,56 +1444,57 @@ class tm_writer { } } - void on_dec_month(numeric_system ns) { + void on_dec_month(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) - return write2(tm_mon() + 1); + return write2(tm_mon() + 1, pad); format_localized('m', 'O'); } - void on_dec0_week_of_year(numeric_system ns) { + void on_dec0_week_of_year(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) - return write2((tm_yday() + days_per_week - tm_wday()) / days_per_week); + return write2((tm_yday() + days_per_week - tm_wday()) / days_per_week, + pad); format_localized('U', 'O'); } - void on_dec1_week_of_year(numeric_system ns) { + void on_dec1_week_of_year(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) { auto wday = tm_wday(); write2((tm_yday() + days_per_week - (wday == 0 ? (days_per_week - 1) : (wday - 1))) / - days_per_week); + days_per_week, + pad); } else { format_localized('W', 'O'); } } - void on_iso_week_of_year(numeric_system ns) { + void on_iso_week_of_year(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) - return write2(tm_iso_week_of_year()); + return write2(tm_iso_week_of_year(), pad); format_localized('V', 'O'); } - void on_iso_week_based_year() { write_year(tm_iso_week_year()); } + void on_iso_week_based_year() { + write_year(tm_iso_week_year(), pad_type::zero); + } void on_iso_week_based_short_year() { write2(split_year_lower(tm_iso_week_year())); } - void on_day_of_year() { + void on_day_of_year(pad_type pad) { auto yday = tm_yday() + 1; - write1(yday / 100); - write2(yday % 100); - } - void on_day_of_month(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) return write2(tm_mday()); - format_localized('d', 'O'); - } - void on_day_of_month_space(numeric_system ns) { - if (is_classic_ || ns == numeric_system::standard) { - auto mday = to_unsigned(tm_mday()) % 100; - const char* d2 = digits2(mday); - *out_++ = mday < 10 ? ' ' : d2[0]; - *out_++ = d2[1]; + auto digit1 = yday / 100; + if (digit1 != 0) { + write1(digit1); } else { - format_localized('e', 'O'); + out_ = detail::write_padding(out_, pad); } + write2(yday % 100, pad); + } + + void on_day_of_month(numeric_system ns, pad_type pad) { + if (is_classic_ || ns == numeric_system::standard) + return write2(tm_mday(), pad); + format_localized('d', 'O'); } void on_24_hour(numeric_system ns, pad_type pad) { @@ -1523,7 +1522,7 @@ class tm_writer { write_floating_seconds(buf, *subsecs_); if (buf.size() > 1) { // Remove the leading "0", write something like ".123". - out_ = std::copy(buf.begin() + 1, buf.end(), out_); + out_ = copy(buf.begin() + 1, buf.end(), out_); } } else { write_fractional_seconds(out_, *subsecs_); @@ -1540,7 +1539,7 @@ class tm_writer { char buf[8]; write_digit2_separated(buf, to_unsigned(tm_hour12()), to_unsigned(tm_min()), to_unsigned(tm_sec()), ':'); - out_ = copy_str(std::begin(buf), std::end(buf), out_); + out_ = copy(std::begin(buf), std::end(buf), out_); *out_++ = ' '; on_am_pm(); } else { @@ -1555,7 +1554,7 @@ class tm_writer { void on_iso_time() { on_24_hour_time(); *out_++ = ':'; - on_second(numeric_system::standard, pad_type::unspecified); + on_second(numeric_system::standard, pad_type::zero); } void on_am_pm() { @@ -1575,10 +1574,11 @@ class tm_writer { struct chrono_format_checker : null_chrono_spec_handler { bool has_precision_integral = false; - FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); } + FMT_NORETURN inline void unsupported() { FMT_THROW(format_error("no date")); } template FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + FMT_CONSTEXPR void on_day_of_year(pad_type) {} FMT_CONSTEXPR void on_24_hour(numeric_system, pad_type) {} FMT_CONSTEXPR void on_12_hour(numeric_system, pad_type) {} FMT_CONSTEXPR void on_minute(numeric_system, pad_type) {} @@ -1588,25 +1588,24 @@ struct chrono_format_checker : null_chrono_spec_handler { FMT_CONSTEXPR void on_iso_time() {} FMT_CONSTEXPR void on_am_pm() {} FMT_CONSTEXPR void on_duration_value() const { - if (has_precision_integral) { + if (has_precision_integral) FMT_THROW(format_error("precision not allowed for this argument type")); - } } FMT_CONSTEXPR void on_duration_unit() {} }; template ::value&& has_isfinite::value)> -inline bool isfinite(T) { +inline auto isfinite(T) -> bool { return true; } template ::value)> -inline T mod(T x, int y) { +inline auto mod(T x, int y) -> T { return x % static_cast(y); } template ::value)> -inline T mod(T x, int y) { +inline auto mod(T x, int y) -> T { return std::fmod(x, static_cast(y)); } @@ -1621,71 +1620,60 @@ template struct make_unsigned_or_unchanged { using type = typename std::make_unsigned::type; }; -#if FMT_SAFE_DURATION_CAST -// throwing version of safe_duration_cast -template -To fmt_safe_duration_cast(std::chrono::duration from) { - int ec; - To to = safe_duration_cast::safe_duration_cast(from, ec); - if (ec) FMT_THROW(format_error("cannot format duration")); - return to; -} -#endif - template ::value)> -inline std::chrono::duration get_milliseconds( - std::chrono::duration d) { +inline auto get_milliseconds(std::chrono::duration d) + -> std::chrono::duration { // this may overflow and/or the result may not fit in the // target type. #if FMT_SAFE_DURATION_CAST using CommonSecondsType = typename std::common_type::type; - const auto d_as_common = fmt_safe_duration_cast(d); + const auto d_as_common = detail::duration_cast(d); const auto d_as_whole_seconds = - fmt_safe_duration_cast(d_as_common); + detail::duration_cast(d_as_common); // this conversion should be nonproblematic const auto diff = d_as_common - d_as_whole_seconds; const auto ms = - fmt_safe_duration_cast>(diff); + detail::duration_cast>(diff); return ms; #else - auto s = std::chrono::duration_cast(d); - return std::chrono::duration_cast(d - s); + auto s = detail::duration_cast(d); + return detail::duration_cast(d - s); #endif } template ::value)> -OutputIt format_duration_value(OutputIt out, Rep val, int) { +auto format_duration_value(OutputIt out, Rep val, int) -> OutputIt { return write(out, val); } template ::value)> -OutputIt format_duration_value(OutputIt out, Rep val, int precision) { - auto specs = format_specs(); +auto format_duration_value(OutputIt out, Rep val, int precision) -> OutputIt { + auto specs = format_specs(); specs.precision = precision; - specs.type = precision >= 0 ? presentation_type::fixed_lower - : presentation_type::general_lower; + specs.set_type(precision >= 0 ? presentation_type::fixed + : presentation_type::general); return write(out, val, specs); } template -OutputIt copy_unit(string_view unit, OutputIt out, Char) { - return std::copy(unit.begin(), unit.end(), out); +auto copy_unit(string_view unit, OutputIt out, Char) -> OutputIt { + return copy(unit.begin(), unit.end(), out); } template -OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) { +auto copy_unit(string_view unit, OutputIt out, wchar_t) -> OutputIt { // This works when wchar_t is UTF-32 because units only contain characters // that have the same representation in UTF-16 and UTF-32. utf8_to_utf16 u(unit); - return std::copy(u.c_str(), u.c_str() + u.size(), out); + return copy(u.c_str(), u.c_str() + u.size(), out); } template -OutputIt format_duration_unit(OutputIt out) { +auto format_duration_unit(OutputIt out) -> OutputIt { if (const char* unit = get_units()) return copy_unit(string_view(unit), out, Char()); *out++ = '['; @@ -1707,14 +1695,14 @@ class get_locale { bool has_locale_ = false; public: - get_locale(bool localized, locale_ref loc) : has_locale_(localized) { + inline get_locale(bool localized, locale_ref loc) : has_locale_(localized) { if (localized) ::new (&locale_) std::locale(loc.template get()); } - ~get_locale() { + inline ~get_locale() { if (has_locale_) locale_.~locale(); } - operator const std::locale&() const { + inline operator const std::locale&() const { return has_locale_ ? locale_ : get_classic_locale(); } }; @@ -1752,18 +1740,12 @@ struct chrono_formatter { // this may overflow and/or the result may not fit in the // target type. -#if FMT_SAFE_DURATION_CAST // might need checked conversion (rep!=Rep) - auto tmpval = std::chrono::duration(val); - s = fmt_safe_duration_cast(tmpval); -#else - s = std::chrono::duration_cast( - std::chrono::duration(val)); -#endif + s = detail::duration_cast(std::chrono::duration(val)); } // returns true if nan or inf, writes to out. - bool handle_nan_inf() { + auto handle_nan_inf() -> bool { if (isfinite(val)) { return false; } @@ -1780,17 +1762,22 @@ struct chrono_formatter { return true; } - Rep hour() const { return static_cast(mod((s.count() / 3600), 24)); } + auto days() const -> Rep { return static_cast(s.count() / 86400); } + auto hour() const -> Rep { + return static_cast(mod((s.count() / 3600), 24)); + } - Rep hour12() const { + auto hour12() const -> Rep { Rep hour = static_cast(mod((s.count() / 3600), 12)); return hour <= 0 ? 12 : hour; } - Rep minute() const { return static_cast(mod((s.count() / 60), 60)); } - Rep second() const { return static_cast(mod(s.count(), 60)); } + auto minute() const -> Rep { + return static_cast(mod((s.count() / 60), 60)); + } + auto second() const -> Rep { return static_cast(mod(s.count(), 60)); } - std::tm time() const { + auto time() const -> std::tm { auto time = std::tm(); time.tm_hour = to_nonnegative_int(hour(), 24); time.tm_min = to_nonnegative_int(minute(), 60); @@ -1805,7 +1792,7 @@ struct chrono_formatter { } } - void write(Rep value, int width, pad_type pad = pad_type::unspecified) { + void write(Rep value, int width, pad_type pad = pad_type::zero) { write_sign(); if (isnan(value)) return write_nan(); uint32_or_64_or_128_t n = @@ -1814,7 +1801,7 @@ struct chrono_formatter { if (width > num_digits) { out = detail::write_padding(out, pad, width - num_digits); } - out = format_decimal(out, n, num_digits).end; + out = format_decimal(out, n, num_digits); } void write_nan() { std::copy_n("nan", 3, out); } @@ -1831,7 +1818,7 @@ struct chrono_formatter { } void on_text(const char_type* begin, const char_type* end) { - std::copy(begin, end, out); + copy(begin, end, out); } // These are not implemented because durations don't have date information. @@ -1848,19 +1835,22 @@ struct chrono_formatter { void on_iso_date() {} void on_utc_offset(numeric_system) {} void on_tz_name() {} - void on_year(numeric_system) {} + void on_year(numeric_system, pad_type) {} void on_short_year(numeric_system) {} void on_offset_year() {} void on_century(numeric_system) {} void on_iso_week_based_year() {} void on_iso_week_based_short_year() {} - void on_dec_month(numeric_system) {} - void on_dec0_week_of_year(numeric_system) {} - void on_dec1_week_of_year(numeric_system) {} - void on_iso_week_of_year(numeric_system) {} - void on_day_of_year() {} - void on_day_of_month(numeric_system) {} - void on_day_of_month_space(numeric_system) {} + void on_dec_month(numeric_system, pad_type) {} + void on_dec0_week_of_year(numeric_system, pad_type) {} + void on_dec1_week_of_year(numeric_system, pad_type) {} + void on_iso_week_of_year(numeric_system, pad_type) {} + void on_day_of_month(numeric_system, pad_type) {} + + void on_day_of_year(pad_type) { + if (handle_nan_inf()) return; + write(days(), 0); + } void on_24_hour(numeric_system ns, pad_type pad) { if (handle_nan_inf()) return; @@ -1901,7 +1891,7 @@ struct chrono_formatter { if (buf.size() < 2 || buf[1] == '.') { out = detail::write_padding(out, pad); } - out = std::copy(buf.begin(), buf.end(), out); + out = copy(buf.begin(), buf.end(), out); } else { write(second(), 2, pad); write_fractional_seconds( @@ -1935,7 +1925,7 @@ struct chrono_formatter { on_24_hour_time(); *out++ = ':'; if (handle_nan_inf()) return; - on_second(numeric_system::standard, pad_type::unspecified); + on_second(numeric_system::standard, pad_type::zero); } void on_am_pm() { @@ -1958,82 +1948,240 @@ struct chrono_formatter { #if defined(__cpp_lib_chrono) && __cpp_lib_chrono >= 201907 using weekday = std::chrono::weekday; +using day = std::chrono::day; +using month = std::chrono::month; +using year = std::chrono::year; +using year_month_day = std::chrono::year_month_day; #else // A fallback version of weekday. class weekday { private: - unsigned char value; + unsigned char value_; public: weekday() = default; - explicit constexpr weekday(unsigned wd) noexcept - : value(static_cast(wd != 7 ? wd : 0)) {} - constexpr unsigned c_encoding() const noexcept { return value; } + constexpr explicit weekday(unsigned wd) noexcept + : value_(static_cast(wd != 7 ? wd : 0)) {} + constexpr auto c_encoding() const noexcept -> unsigned { return value_; } }; -class year_month_day {}; -#endif - -// A rudimentary weekday formatter. -template struct formatter { +class day { private: - bool localized = false; + unsigned char value_; public: - FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) - -> decltype(ctx.begin()) { - auto begin = ctx.begin(), end = ctx.end(); - if (begin != end && *begin == 'L') { - ++begin; - localized = true; + day() = default; + constexpr explicit day(unsigned d) noexcept + : value_(static_cast(d)) {} + constexpr explicit operator unsigned() const noexcept { return value_; } +}; + +class month { + private: + unsigned char value_; + + public: + month() = default; + constexpr explicit month(unsigned m) noexcept + : value_(static_cast(m)) {} + constexpr explicit operator unsigned() const noexcept { return value_; } +}; + +class year { + private: + int value_; + + public: + year() = default; + constexpr explicit year(int y) noexcept : value_(y) {} + constexpr explicit operator int() const noexcept { return value_; } +}; + +class year_month_day { + private: + fmt::year year_; + fmt::month month_; + fmt::day day_; + + public: + year_month_day() = default; + constexpr year_month_day(const year& y, const month& m, const day& d) noexcept + : year_(y), month_(m), day_(d) {} + constexpr auto year() const noexcept -> fmt::year { return year_; } + constexpr auto month() const noexcept -> fmt::month { return month_; } + constexpr auto day() const noexcept -> fmt::day { return day_; } +}; +#endif + +template +struct formatter : private formatter { + private: + bool localized_ = false; + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + if (it != end && *it == 'L') { + ++it; + localized_ = true; + return it; } - return begin; + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; } template auto format(weekday wd, FormatContext& ctx) const -> decltype(ctx.out()) { auto time = std::tm(); time.tm_wday = static_cast(wd.c_encoding()); - detail::get_locale loc(localized, ctx.locale()); + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(localized_, ctx.locale()); auto w = detail::tm_writer(loc, ctx.out(), time); w.on_abbr_weekday(); return w.out(); } }; +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(day d, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_mday = static_cast(static_cast(d)); + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(false, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_day_of_month(detail::numeric_system::standard, detail::pad_type::zero); + return w.out(); + } +}; + +template +struct formatter : private formatter { + private: + bool localized_ = false; + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + if (it != end && *it == 'L') { + ++it; + localized_ = true; + return it; + } + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(month m, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_mon = static_cast(static_cast(m)) - 1; + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(localized_, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_abbr_month(); + return w.out(); + } +}; + +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(year y, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_year = static_cast(y) - 1900; + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(false, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_year(detail::numeric_system::standard, detail::pad_type::zero); + return w.out(); + } +}; + +template +struct formatter : private formatter { + private: + bool use_tm_formatter_ = false; + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + auto it = ctx.begin(), end = ctx.end(); + use_tm_formatter_ = it != end && *it != '}'; + return use_tm_formatter_ ? formatter::parse(ctx) : it; + } + + template + auto format(year_month_day val, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto time = std::tm(); + time.tm_year = static_cast(val.year()) - 1900; + time.tm_mon = static_cast(static_cast(val.month())) - 1; + time.tm_mday = static_cast(static_cast(val.day())); + if (use_tm_formatter_) return formatter::format(time, ctx); + detail::get_locale loc(true, ctx.locale()); + auto w = detail::tm_writer(loc, ctx.out(), time); + w.on_iso_date(); + return w.out(); + } +}; + template struct formatter, Char> { private: - format_specs specs_; + format_specs specs_; detail::arg_ref width_ref_; detail::arg_ref precision_ref_; bool localized_ = false; - basic_string_view format_str_; + basic_string_view fmt_; public: - FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) - -> decltype(ctx.begin()) { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { auto it = ctx.begin(), end = ctx.end(); if (it == end || *it == '}') return it; it = detail::parse_align(it, end, specs_); if (it == end) return it; - it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx); - if (it == end) return it; + Char c = *it; + if ((c >= '0' && c <= '9') || c == '{') { + it = detail::parse_width(it, end, specs_, width_ref_, ctx); + if (it == end) return it; + } auto checker = detail::chrono_format_checker(); if (*it == '.') { checker.has_precision_integral = !std::is_floating_point::value; - it = detail::parse_precision(it, end, specs_.precision, precision_ref_, - ctx); + it = detail::parse_precision(it, end, specs_, precision_ref_, ctx); } if (it != end && *it == 'L') { localized_ = true; ++it; } end = detail::parse_chrono_format(it, end, checker); - format_str_ = {it, detail::to_unsigned(end - it)}; + fmt_ = {it, detail::to_unsigned(end - it)}; return end; } @@ -2043,15 +2191,15 @@ struct formatter, Char> { auto specs = specs_; auto precision = specs.precision; specs.precision = -1; - auto begin = format_str_.begin(), end = format_str_.end(); + auto begin = fmt_.begin(), end = fmt_.end(); // As a possible future optimization, we could avoid extra copying if width // is not specified. auto buf = basic_memory_buffer(); - auto out = std::back_inserter(buf); - detail::handle_dynamic_spec(specs.width, width_ref_, - ctx); - detail::handle_dynamic_spec(precision, - precision_ref_, ctx); + auto out = basic_appender(buf); + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, + ctx); + detail::handle_dynamic_spec(specs.dynamic_precision(), precision, + precision_ref_, ctx); if (begin == end || *begin == '}') { out = detail::format_duration_value(out, d.count(), precision); detail::format_duration_unit(out); @@ -2068,137 +2216,119 @@ struct formatter, Char> { } }; -template -struct formatter, - Char> : formatter { - FMT_CONSTEXPR formatter() { - this->format_str_ = detail::string_literal{}; - } - - template - auto format(std::chrono::time_point val, - FormatContext& ctx) const -> decltype(ctx.out()) { - using period = typename Duration::period; - if (detail::const_check( - period::num != 1 || period::den != 1 || - std::is_floating_point::value)) { - const auto epoch = val.time_since_epoch(); - auto subsecs = std::chrono::duration_cast( - epoch - std::chrono::duration_cast(epoch)); - - if (subsecs.count() < 0) { - auto second = - std::chrono::duration_cast(std::chrono::seconds(1)); - if (epoch.count() < ((Duration::min)() + second).count()) - FMT_THROW(format_error("duration is too small")); - subsecs += second; - val -= second; - } - - return formatter::do_format( - gmtime(std::chrono::time_point_cast(val)), ctx, - &subsecs); - } - - return formatter::format( - gmtime(std::chrono::time_point_cast(val)), ctx); - } -}; - -#if FMT_USE_LOCAL_TIME -template -struct formatter, Char> - : formatter { - FMT_CONSTEXPR formatter() { - this->format_str_ = detail::string_literal{}; - } - - template - auto format(std::chrono::local_time val, FormatContext& ctx) const - -> decltype(ctx.out()) { - using period = typename Duration::period; - if (period::num != 1 || period::den != 1 || - std::is_floating_point::value) { - const auto epoch = val.time_since_epoch(); - const auto subsecs = std::chrono::duration_cast( - epoch - std::chrono::duration_cast(epoch)); - - return formatter::do_format( - localtime(std::chrono::time_point_cast(val)), - ctx, &subsecs); - } - - return formatter::format( - localtime(std::chrono::time_point_cast(val)), - ctx); - } -}; -#endif - -#if FMT_USE_UTC_TIME -template -struct formatter, - Char> - : formatter, - Char> { - template - auto format(std::chrono::time_point val, - FormatContext& ctx) const -> decltype(ctx.out()) { - return formatter< - std::chrono::time_point, - Char>::format(std::chrono::utc_clock::to_sys(val), ctx); - } -}; -#endif - template struct formatter { private: - format_specs specs_; + format_specs specs_; detail::arg_ref width_ref_; protected: - basic_string_view format_str_; + basic_string_view fmt_; - template + template auto do_format(const std::tm& tm, FormatContext& ctx, const Duration* subsecs) const -> decltype(ctx.out()) { auto specs = specs_; auto buf = basic_memory_buffer(); - auto out = std::back_inserter(buf); - detail::handle_dynamic_spec(specs.width, width_ref_, - ctx); + auto out = basic_appender(buf); + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, + ctx); auto loc_ref = ctx.locale(); detail::get_locale loc(static_cast(loc_ref), loc_ref); auto w = detail::tm_writer(loc, out, tm, subsecs); - detail::parse_chrono_format(format_str_.begin(), format_str_.end(), w); + detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w); return detail::write( ctx.out(), basic_string_view(buf.data(), buf.size()), specs); } public: - FMT_CONSTEXPR auto parse(basic_format_parse_context& ctx) - -> decltype(ctx.begin()) { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { auto it = ctx.begin(), end = ctx.end(); if (it == end || *it == '}') return it; it = detail::parse_align(it, end, specs_); if (it == end) return it; - it = detail::parse_dynamic_spec(it, end, specs_.width, width_ref_, ctx); - if (it == end) return it; + Char c = *it; + if ((c >= '0' && c <= '9') || c == '{') { + it = detail::parse_width(it, end, specs_, width_ref_, ctx); + if (it == end) return it; + } end = detail::parse_chrono_format(it, end, detail::tm_format_checker()); - // Replace the default format_str only if the new spec is not empty. - if (end != it) format_str_ = {it, detail::to_unsigned(end - it)}; + // Replace the default format string only if the new spec is not empty. + if (end != it) fmt_ = {it, detail::to_unsigned(end - it)}; return end; } template auto format(const std::tm& tm, FormatContext& ctx) const -> decltype(ctx.out()) { - return do_format(tm, ctx, nullptr); + return do_format(tm, ctx, nullptr); + } +}; + +template +struct formatter, Char> : formatter { + FMT_CONSTEXPR formatter() { + this->fmt_ = detail::string_literal(); + } + + template + auto format(sys_time val, FormatContext& ctx) const + -> decltype(ctx.out()) { + std::tm tm = gmtime(val); + using period = typename Duration::period; + if (detail::const_check( + period::num == 1 && period::den == 1 && + !std::is_floating_point::value)) { + return formatter::format(tm, ctx); + } + Duration epoch = val.time_since_epoch(); + Duration subsecs = detail::duration_cast( + epoch - detail::duration_cast(epoch)); + if (subsecs.count() < 0) { + auto second = detail::duration_cast(std::chrono::seconds(1)); + if (tm.tm_sec != 0) + --tm.tm_sec; + else + tm = gmtime(val - second); + subsecs += detail::duration_cast(std::chrono::seconds(1)); + } + return formatter::do_format(tm, ctx, &subsecs); + } +}; + +template +struct formatter, Char> + : formatter, Char> { + template + auto format(utc_time val, FormatContext& ctx) const + -> decltype(ctx.out()) { + return formatter, Char>::format( + detail::utc_clock::to_sys(val), ctx); + } +}; + +template +struct formatter, Char> : formatter { + FMT_CONSTEXPR formatter() { + this->fmt_ = detail::string_literal(); + } + + template + auto format(local_time val, FormatContext& ctx) const + -> decltype(ctx.out()) { + using period = typename Duration::period; + if (period::num == 1 && period::den == 1 && + !std::is_floating_point::value) { + return formatter::format(localtime(val), ctx); + } + auto epoch = val.time_since_epoch(); + auto subsecs = detail::duration_cast( + epoch - detail::duration_cast(epoch)); + return formatter::do_format(localtime(val), ctx, &subsecs); } }; diff --git a/thirdparty/fmt/include/fmt/color.h b/thirdparty/fmt/include/fmt/color.h index 8697e1ca0..2faaf3a06 100644 --- a/thirdparty/fmt/include/fmt/color.h +++ b/thirdparty/fmt/include/fmt/color.h @@ -227,19 +227,19 @@ struct color_type { }; } // namespace detail -/** A text style consisting of foreground and background colors and emphasis. */ +/// A text style consisting of foreground and background colors and emphasis. class text_style { public: FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept : set_foreground_color(), set_background_color(), ems(em) {} - FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) { + FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& { if (!set_foreground_color) { set_foreground_color = rhs.set_foreground_color; foreground_color = rhs.foreground_color; } else if (rhs.set_foreground_color) { if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) - FMT_THROW(format_error("can't OR a terminal color")); + report_error("can't OR a terminal color"); foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color; } @@ -248,7 +248,7 @@ class text_style { background_color = rhs.background_color; } else if (rhs.set_background_color) { if (!background_color.is_rgb || !rhs.background_color.is_rgb) - FMT_THROW(format_error("can't OR a terminal color")); + report_error("can't OR a terminal color"); background_color.value.rgb_color |= rhs.background_color.value.rgb_color; } @@ -257,29 +257,29 @@ class text_style { return *this; } - friend FMT_CONSTEXPR text_style operator|(text_style lhs, - const text_style& rhs) { + friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs) + -> text_style { return lhs |= rhs; } - FMT_CONSTEXPR bool has_foreground() const noexcept { + FMT_CONSTEXPR auto has_foreground() const noexcept -> bool { return set_foreground_color; } - FMT_CONSTEXPR bool has_background() const noexcept { + FMT_CONSTEXPR auto has_background() const noexcept -> bool { return set_background_color; } - FMT_CONSTEXPR bool has_emphasis() const noexcept { + FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool { return static_cast(ems) != 0; } - FMT_CONSTEXPR detail::color_type get_foreground() const noexcept { + FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type { FMT_ASSERT(has_foreground(), "no foreground specified for this style"); return foreground_color; } - FMT_CONSTEXPR detail::color_type get_background() const noexcept { + FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type { FMT_ASSERT(has_background(), "no background specified for this style"); return background_color; } - FMT_CONSTEXPR emphasis get_emphasis() const noexcept { + FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis { FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); return ems; } @@ -297,9 +297,11 @@ class text_style { } } - friend FMT_CONSTEXPR text_style fg(detail::color_type foreground) noexcept; + friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept + -> text_style; - friend FMT_CONSTEXPR text_style bg(detail::color_type background) noexcept; + friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept + -> text_style; detail::color_type foreground_color; detail::color_type background_color; @@ -308,24 +310,27 @@ class text_style { emphasis ems; }; -/** Creates a text style from the foreground (text) color. */ -FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) noexcept { +/// Creates a text style from the foreground (text) color. +FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept + -> text_style { return text_style(true, foreground); } -/** Creates a text style from the background color. */ -FMT_CONSTEXPR inline text_style bg(detail::color_type background) noexcept { +/// Creates a text style from the background color. +FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept + -> text_style { return text_style(false, background); } -FMT_CONSTEXPR inline text_style operator|(emphasis lhs, emphasis rhs) noexcept { +FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept + -> text_style { return text_style(lhs) | rhs; } namespace detail { template struct ansi_color_escape { - FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color, + FMT_CONSTEXPR ansi_color_escape(color_type text_color, const char* esc) noexcept { // If we have a terminal color, we need to output another escape code // sequence. @@ -384,9 +389,9 @@ template struct ansi_color_escape { } FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; } - FMT_CONSTEXPR const Char* begin() const noexcept { return buffer; } - FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const noexcept { - return buffer + std::char_traits::length(buffer); + FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; } + FMT_CONSTEXPR20 auto end() const noexcept -> const Char* { + return buffer + basic_string_view(buffer).size(); } private: @@ -400,25 +405,27 @@ template struct ansi_color_escape { out[2] = static_cast('0' + c % 10); out[3] = static_cast(delimiter); } - static FMT_CONSTEXPR bool has_emphasis(emphasis em, emphasis mask) noexcept { + static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept + -> bool { return static_cast(em) & static_cast(mask); } }; template -FMT_CONSTEXPR ansi_color_escape make_foreground_color( - detail::color_type foreground) noexcept { +FMT_CONSTEXPR auto make_foreground_color(color_type foreground) noexcept + -> ansi_color_escape { return ansi_color_escape(foreground, "\x1b[38;2;"); } template -FMT_CONSTEXPR ansi_color_escape make_background_color( - detail::color_type background) noexcept { +FMT_CONSTEXPR auto make_background_color(color_type background) noexcept + -> ansi_color_escape { return ansi_color_escape(background, "\x1b[48;2;"); } template -FMT_CONSTEXPR ansi_color_escape make_emphasis(emphasis em) noexcept { +FMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept + -> ansi_color_escape { return ansi_color_escape(em); } @@ -427,149 +434,123 @@ template inline void reset_color(buffer& buffer) { buffer.append(reset_color.begin(), reset_color.end()); } -template struct styled_arg { +template struct styled_arg : view { const T& value; text_style style; + styled_arg(const T& v, text_style s) : value(v), style(s) {} }; template void vformat_to(buffer& buf, const text_style& ts, - basic_string_view format_str, - basic_format_args>> args) { + basic_string_view fmt, + basic_format_args> args) { bool has_style = false; if (ts.has_emphasis()) { has_style = true; - auto emphasis = detail::make_emphasis(ts.get_emphasis()); + auto emphasis = make_emphasis(ts.get_emphasis()); buf.append(emphasis.begin(), emphasis.end()); } if (ts.has_foreground()) { has_style = true; - auto foreground = detail::make_foreground_color(ts.get_foreground()); + auto foreground = make_foreground_color(ts.get_foreground()); buf.append(foreground.begin(), foreground.end()); } if (ts.has_background()) { has_style = true; - auto background = detail::make_background_color(ts.get_background()); + auto background = make_background_color(ts.get_background()); buf.append(background.begin(), background.end()); } - detail::vformat_to(buf, format_str, args, {}); - if (has_style) detail::reset_color(buf); + vformat_to(buf, fmt, args); + if (has_style) reset_color(buf); } - } // namespace detail -inline void vprint(std::FILE* f, const text_style& ts, string_view fmt, +inline void vprint(FILE* f, const text_style& ts, string_view fmt, format_args args) { - // Legacy wide streams are not supported. auto buf = memory_buffer(); detail::vformat_to(buf, ts, fmt, args); - if (detail::is_utf8()) { - detail::print(f, string_view(buf.begin(), buf.size())); - return; - } - buf.push_back('\0'); - int result = std::fputs(buf.data(), f); - if (result < 0) - FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); + print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size())); } /** - \rst - Formats a string and prints it to the specified file stream using ANSI - escape sequences to specify text formatting. - - **Example**:: - - fmt::print(fmt::emphasis::bold | fg(fmt::color::red), - "Elapsed time: {0:.2f} seconds", 1.23); - \endrst + * Formats a string and prints it to the specified file stream using ANSI + * escape sequences to specify text formatting. + * + * **Example**: + * + * fmt::print(fmt::emphasis::bold | fg(fmt::color::red), + * "Elapsed time: {0:.2f} seconds", 1.23); */ -template ::value)> -void print(std::FILE* f, const text_style& ts, const S& format_str, - const Args&... args) { - vprint(f, ts, format_str, - fmt::make_format_args>>(args...)); +template +void print(FILE* f, const text_style& ts, format_string fmt, + T&&... args) { + vprint(f, ts, fmt.str, vargs{{args...}}); } /** - \rst - Formats a string and prints it to stdout using ANSI escape sequences to - specify text formatting. - - **Example**:: - - fmt::print(fmt::emphasis::bold | fg(fmt::color::red), - "Elapsed time: {0:.2f} seconds", 1.23); - \endrst + * Formats a string and prints it to stdout using ANSI escape sequences to + * specify text formatting. + * + * **Example**: + * + * fmt::print(fmt::emphasis::bold | fg(fmt::color::red), + * "Elapsed time: {0:.2f} seconds", 1.23); */ -template ::value)> -void print(const text_style& ts, const S& format_str, const Args&... args) { - return print(stdout, ts, format_str, args...); +template +void print(const text_style& ts, format_string fmt, T&&... args) { + return print(stdout, ts, fmt, std::forward(args)...); } -template > -inline std::basic_string vformat( - const text_style& ts, const S& format_str, - basic_format_args>> args) { - basic_memory_buffer buf; - detail::vformat_to(buf, ts, detail::to_string_view(format_str), args); +inline auto vformat(const text_style& ts, string_view fmt, format_args args) + -> std::string { + auto buf = memory_buffer(); + detail::vformat_to(buf, ts, fmt, args); return fmt::to_string(buf); } /** - \rst - Formats arguments and returns the result as a string using ANSI - escape sequences to specify text formatting. - - **Example**:: - - #include - std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), - "The answer is {}", 42); - \endrst -*/ -template > -inline std::basic_string format(const text_style& ts, const S& format_str, - const Args&... args) { - return fmt::vformat(ts, detail::to_string_view(format_str), - fmt::make_format_args>(args...)); + * Formats arguments and returns the result as a string using ANSI escape + * sequences to specify text formatting. + * + * **Example**: + * + * ``` + * #include + * std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), + * "The answer is {}", 42); + * ``` + */ +template +inline auto format(const text_style& ts, format_string fmt, T&&... args) + -> std::string { + return fmt::vformat(ts, fmt.str, vargs{{args...}}); } -/** - Formats a string with the given text_style and writes the output to ``out``. - */ -template ::value)> -OutputIt vformat_to( - OutputIt out, const text_style& ts, basic_string_view format_str, - basic_format_args>> args) { - auto&& buf = detail::get_buffer(out); - detail::vformat_to(buf, ts, format_str, args); +/// Formats a string with the given text_style and writes the output to `out`. +template ::value)> +auto vformat_to(OutputIt out, const text_style& ts, string_view fmt, + format_args args) -> OutputIt { + auto&& buf = detail::get_buffer(out); + detail::vformat_to(buf, ts, fmt, args); return detail::get_iterator(buf, out); } /** - \rst - Formats arguments with the given text_style, writes the result to the output - iterator ``out`` and returns the iterator past the end of the output range. - - **Example**:: - - std::vector out; - fmt::format_to(std::back_inserter(out), - fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); - \endrst -*/ -template >::value&& - detail::is_string::value> -inline auto format_to(OutputIt out, const text_style& ts, const S& format_str, - Args&&... args) -> - typename std::enable_if::type { - return vformat_to(out, ts, detail::to_string_view(format_str), - fmt::make_format_args>>(args...)); + * Formats arguments with the given text style, writes the result to the output + * iterator `out` and returns the iterator past the end of the output range. + * + * **Example**: + * + * std::vector out; + * fmt::format_to(std::back_inserter(out), + * fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); + */ +template ::value)> +inline auto format_to(OutputIt out, const text_style& ts, + format_string fmt, T&&... args) -> OutputIt { + return vformat_to(out, ts, fmt.str, vargs{{args...}}); } template @@ -578,47 +559,44 @@ struct formatter, Char> : formatter { auto format(const detail::styled_arg& arg, FormatContext& ctx) const -> decltype(ctx.out()) { const auto& ts = arg.style; - const auto& value = arg.value; auto out = ctx.out(); bool has_style = false; if (ts.has_emphasis()) { has_style = true; auto emphasis = detail::make_emphasis(ts.get_emphasis()); - out = std::copy(emphasis.begin(), emphasis.end(), out); + out = detail::copy(emphasis.begin(), emphasis.end(), out); } if (ts.has_foreground()) { has_style = true; auto foreground = detail::make_foreground_color(ts.get_foreground()); - out = std::copy(foreground.begin(), foreground.end(), out); + out = detail::copy(foreground.begin(), foreground.end(), out); } if (ts.has_background()) { has_style = true; auto background = detail::make_background_color(ts.get_background()); - out = std::copy(background.begin(), background.end(), out); + out = detail::copy(background.begin(), background.end(), out); } - out = formatter::format(value, ctx); + out = formatter::format(arg.value, ctx); if (has_style) { auto reset_color = string_view("\x1b[0m"); - out = std::copy(reset_color.begin(), reset_color.end(), out); + out = detail::copy(reset_color.begin(), reset_color.end(), out); } return out; } }; /** - \rst - Returns an argument that will be formatted using ANSI escape sequences, - to be used in a formatting function. - - **Example**:: - - fmt::print("Elapsed time: {0:.2f} seconds", - fmt::styled(1.23, fmt::fg(fmt::color::green) | - fmt::bg(fmt::color::blue))); - \endrst + * Returns an argument that will be formatted using ANSI escape sequences, + * to be used in a formatting function. + * + * **Example**: + * + * fmt::print("Elapsed time: {0:.2f} seconds", + * fmt::styled(1.23, fmt::fg(fmt::color::green) | + * fmt::bg(fmt::color::blue))); */ template FMT_CONSTEXPR auto styled(const T& value, text_style ts) diff --git a/thirdparty/fmt/include/fmt/compile.h b/thirdparty/fmt/include/fmt/compile.h index a4c7e4956..68b451c71 100644 --- a/thirdparty/fmt/include/fmt/compile.h +++ b/thirdparty/fmt/include/fmt/compile.h @@ -8,56 +8,51 @@ #ifndef FMT_COMPILE_H_ #define FMT_COMPILE_H_ +#ifndef FMT_MODULE +# include // std::back_inserter +#endif + #include "format.h" FMT_BEGIN_NAMESPACE -namespace detail { - -template -FMT_CONSTEXPR inline counting_iterator copy_str(InputIt begin, InputIt end, - counting_iterator it) { - return it + (end - begin); -} // A compile-time string which is compiled into fast formatting code. -class compiled_string {}; +FMT_EXPORT class compiled_string {}; + +namespace detail { template struct is_compiled_string : std::is_base_of {}; /** - \rst - Converts a string literal *s* into a format string that will be parsed at - compile time and converted into efficient formatting code. Requires C++17 - ``constexpr if`` compiler support. - - **Example**:: - - // Converts 42 into std::string using the most efficient method and no - // runtime format string processing. - std::string s = fmt::format(FMT_COMPILE("{}"), 42); - \endrst + * Converts a string literal `s` into a format string that will be parsed at + * compile time and converted into efficient formatting code. Requires C++17 + * `constexpr if` compiler support. + * + * **Example**: + * + * // Converts 42 into std::string using the most efficient method and no + * // runtime format string processing. + * std::string s = fmt::format(FMT_COMPILE("{}"), 42); */ #if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction) -# define FMT_COMPILE(s) \ - FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit) +# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string) #else # define FMT_COMPILE(s) FMT_STRING(s) #endif #if FMT_USE_NONTYPE_TEMPLATE_ARGS -template Str> +template Str> struct udl_compiled_string : compiled_string { using char_type = Char; - explicit constexpr operator basic_string_view() const { + constexpr explicit operator basic_string_view() const { return {Str.data, N - 1}; } }; #endif template -const T& first(const T& value, const Tail&...) { +auto first(const T& value, const Tail&...) -> const T& { return value; } @@ -75,6 +70,29 @@ constexpr const auto& get([[maybe_unused]] const T& first, return detail::get(rest...); } +# if FMT_USE_NONTYPE_TEMPLATE_ARGS +template +constexpr auto get_arg_index_by_name(basic_string_view name) -> int { + if constexpr (is_static_named_arg()) { + if (name == T::name) return N; + } + if constexpr (sizeof...(Args) > 0) + return get_arg_index_by_name(name); + (void)name; // Workaround an MSVC bug about "unused" parameter. + return -1; +} +# endif + +template +FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view name) -> int { +# if FMT_USE_NONTYPE_TEMPLATE_ARGS + if constexpr (sizeof...(Args) > 0) + return get_arg_index_by_name<0, Args...>(name); +# endif + (void)name; + return -1; +} + template constexpr int get_arg_index_by_name(basic_string_view name, type_list) { @@ -144,11 +162,12 @@ template struct field { template constexpr OutputIt format(OutputIt out, const Args&... args) const { const T& arg = get_arg_checked(args...); - if constexpr (std::is_convertible_v>) { + if constexpr (std::is_convertible>::value) { auto s = basic_string_view(arg); - return copy_str(s.begin(), s.end(), out); + return copy(s.begin(), s.end(), out); + } else { + return write(out, arg); } - return write(out, arg); } }; @@ -236,13 +255,12 @@ constexpr size_t parse_text(basic_string_view str, size_t pos) { } template -constexpr auto compile_format_string(S format_str); +constexpr auto compile_format_string(S fmt); template -constexpr auto parse_tail(T head, S format_str) { - if constexpr (POS != - basic_string_view(format_str).size()) { - constexpr auto tail = compile_format_string(format_str); +constexpr auto parse_tail(T head, S fmt) { + if constexpr (POS != basic_string_view(fmt).size()) { + constexpr auto tail = compile_format_string(fmt); if constexpr (std::is_same, unknown_format>()) return tail; @@ -274,6 +292,7 @@ constexpr parse_specs_result parse_specs(basic_string_view str, } template struct arg_id_handler { + arg_id_kind kind; arg_ref arg_id; constexpr int on_auto() { @@ -281,25 +300,28 @@ template struct arg_id_handler { return 0; } constexpr int on_index(int id) { + kind = arg_id_kind::index; arg_id = arg_ref(id); return 0; } constexpr int on_name(basic_string_view id) { + kind = arg_id_kind::name; arg_id = arg_ref(id); return 0; } }; template struct parse_arg_id_result { + arg_id_kind kind; arg_ref arg_id; const Char* arg_id_end; }; template constexpr auto parse_arg_id(const Char* begin, const Char* end) { - auto handler = arg_id_handler{arg_ref{}}; + auto handler = arg_id_handler{arg_id_kind::none, arg_ref{}}; auto arg_id_end = parse_arg_id(begin, end, handler); - return parse_arg_id_result{handler.arg_id, arg_id_end}; + return parse_arg_id_result{handler.kind, handler.arg_id, arg_id_end}; } template struct field_type { @@ -313,14 +335,13 @@ struct field_type::value>> { template -constexpr auto parse_replacement_field_then_tail(S format_str) { +constexpr auto parse_replacement_field_then_tail(S fmt) { using char_type = typename S::char_type; - constexpr auto str = basic_string_view(format_str); + constexpr auto str = basic_string_view(fmt); constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type(); if constexpr (c == '}') { return parse_tail( - field::type, ARG_INDEX>(), - format_str); + field::type, ARG_INDEX>(), fmt); } else if constexpr (c != ':') { FMT_THROW(format_error("expected ':'")); } else { @@ -333,7 +354,7 @@ constexpr auto parse_replacement_field_then_tail(S format_str) { return parse_tail( spec_field::type, ARG_INDEX>{ result.fmt}, - format_str); + fmt); } } } @@ -341,22 +362,21 @@ constexpr auto parse_replacement_field_then_tail(S format_str) { // Compiles a non-empty format string and returns the compiled representation // or unknown_format() on unrecognized input. template -constexpr auto compile_format_string(S format_str) { +constexpr auto compile_format_string(S fmt) { using char_type = typename S::char_type; - constexpr auto str = basic_string_view(format_str); + constexpr auto str = basic_string_view(fmt); if constexpr (str[POS] == '{') { if constexpr (POS + 1 == str.size()) FMT_THROW(format_error("unmatched '{' in format string")); if constexpr (str[POS + 1] == '{') { - return parse_tail(make_text(str, POS, 1), format_str); + return parse_tail(make_text(str, POS, 1), fmt); } else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') { static_assert(ID != manual_indexing_id, "cannot switch from manual to automatic argument indexing"); constexpr auto next_id = ID != manual_indexing_id ? ID + 1 : manual_indexing_id; return parse_replacement_field_then_tail, Args, - POS + 1, ID, next_id>( - format_str); + POS + 1, ID, next_id>(fmt); } else { constexpr auto arg_id_result = parse_arg_id(str.data() + POS + 1, str.data() + str.size()); @@ -364,28 +384,27 @@ constexpr auto compile_format_string(S format_str) { constexpr char_type c = arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type(); static_assert(c == '}' || c == ':', "missing '}' in format string"); - if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) { + if constexpr (arg_id_result.kind == arg_id_kind::index) { static_assert( ID == manual_indexing_id || ID == 0, "cannot switch from automatic to manual argument indexing"); - constexpr auto arg_index = arg_id_result.arg_id.val.index; + constexpr auto arg_index = arg_id_result.arg_id.index; return parse_replacement_field_then_tail, Args, arg_id_end_pos, arg_index, manual_indexing_id>( - format_str); - } else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) { + fmt); + } else if constexpr (arg_id_result.kind == arg_id_kind::name) { constexpr auto arg_index = - get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{}); + get_arg_index_by_name(arg_id_result.arg_id.name, Args{}); if constexpr (arg_index >= 0) { constexpr auto next_id = ID != manual_indexing_id ? ID + 1 : manual_indexing_id; return parse_replacement_field_then_tail< decltype(get_type::value), Args, arg_id_end_pos, - arg_index, next_id>(format_str); + arg_index, next_id>(fmt); } else if constexpr (c == '}') { return parse_tail( - runtime_named_field{arg_id_result.arg_id.val.name}, - format_str); + runtime_named_field{arg_id_result.arg_id.name}, fmt); } else if constexpr (c == ':') { return unknown_format(); // no type info for specs parsing } @@ -394,29 +413,26 @@ constexpr auto compile_format_string(S format_str) { } else if constexpr (str[POS] == '}') { if constexpr (POS + 1 == str.size()) FMT_THROW(format_error("unmatched '}' in format string")); - return parse_tail(make_text(str, POS, 1), format_str); + return parse_tail(make_text(str, POS, 1), fmt); } else { constexpr auto end = parse_text(str, POS + 1); if constexpr (end - POS > 1) { - return parse_tail(make_text(str, POS, end - POS), - format_str); + return parse_tail(make_text(str, POS, end - POS), fmt); } else { - return parse_tail(code_unit{str[POS]}, - format_str); + return parse_tail(code_unit{str[POS]}, fmt); } } } template ::value)> -constexpr auto compile(S format_str) { - constexpr auto str = basic_string_view(format_str); +constexpr auto compile(S fmt) { + constexpr auto str = basic_string_view(fmt); if constexpr (str.size() == 0) { return detail::make_text(str, 0, 0); } else { constexpr auto result = - detail::compile_format_string, 0, 0>( - format_str); + detail::compile_format_string, 0, 0>(fmt); return result; } } @@ -488,39 +504,40 @@ FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { template ::value)> -format_to_n_result format_to_n(OutputIt out, size_t n, - const S& format_str, Args&&... args) { +auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args) + -> format_to_n_result { using traits = detail::fixed_buffer_traits; auto buf = detail::iterator_buffer(out, n); - format_to(std::back_inserter(buf), format_str, std::forward(args)...); + fmt::format_to(std::back_inserter(buf), fmt, std::forward(args)...); return {buf.out(), buf.count()}; } template ::value)> -FMT_CONSTEXPR20 size_t formatted_size(const S& format_str, - const Args&... args) { - return fmt::format_to(detail::counting_iterator(), format_str, args...) - .count(); +FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args) + -> size_t { + auto buf = detail::counting_buffer<>(); + fmt::format_to(appender(buf), fmt, args...); + return buf.count(); } template ::value)> -void print(std::FILE* f, const S& format_str, const Args&... args) { - memory_buffer buffer; - fmt::format_to(std::back_inserter(buffer), format_str, args...); - detail::print(f, {buffer.data(), buffer.size()}); +void print(std::FILE* f, const S& fmt, const Args&... args) { + auto buf = memory_buffer(); + fmt::format_to(appender(buf), fmt, args...); + detail::print(f, {buf.data(), buf.size()}); } template ::value)> -void print(const S& format_str, const Args&... args) { - print(stdout, format_str, args...); +void print(const S& fmt, const Args&... args) { + print(stdout, fmt, args...); } #if FMT_USE_NONTYPE_TEMPLATE_ARGS inline namespace literals { -template constexpr auto operator""_cf() { +template constexpr auto operator""_cf() { using char_t = remove_cvref_t; return detail::udl_compiled_string(); diff --git a/thirdparty/fmt/include/fmt/core.h b/thirdparty/fmt/include/fmt/core.h index f9e3b7d6d..8ca735f0c 100644 --- a/thirdparty/fmt/include/fmt/core.h +++ b/thirdparty/fmt/include/fmt/core.h @@ -1,2922 +1,5 @@ -// Formatting library for C++ - the core API for char/UTF-8 -// -// Copyright (c) 2012 - present, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. +// This file is only provided for compatibility and may be removed in future +// versions. Use fmt/base.h if you don't need fmt::format and fmt/format.h +// otherwise. -#ifndef FMT_CORE_H_ -#define FMT_CORE_H_ - -#include // std::byte -#include // std::FILE -#include // std::strlen -#include -#include -#include // std::addressof -#include -#include - -// The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 100101 - -#if defined(__clang__) && !defined(__ibmxl__) -# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) -#else -# define FMT_CLANG_VERSION 0 -#endif - -#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && \ - !defined(__NVCOMPILER) -# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) -#else -# define FMT_GCC_VERSION 0 -#endif - -#ifndef FMT_GCC_PRAGMA -// Workaround _Pragma bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59884. -# if FMT_GCC_VERSION >= 504 -# define FMT_GCC_PRAGMA(arg) _Pragma(arg) -# else -# define FMT_GCC_PRAGMA(arg) -# endif -#endif - -#ifdef __ICL -# define FMT_ICC_VERSION __ICL -#elif defined(__INTEL_COMPILER) -# define FMT_ICC_VERSION __INTEL_COMPILER -#else -# define FMT_ICC_VERSION 0 -#endif - -#ifdef _MSC_VER -# define FMT_MSC_VERSION _MSC_VER -# define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__)) -#else -# define FMT_MSC_VERSION 0 -# define FMT_MSC_WARNING(...) -#endif - -#ifdef _MSVC_LANG -# define FMT_CPLUSPLUS _MSVC_LANG -#else -# define FMT_CPLUSPLUS __cplusplus -#endif - -#ifdef __has_feature -# define FMT_HAS_FEATURE(x) __has_feature(x) -#else -# define FMT_HAS_FEATURE(x) 0 -#endif - -#if defined(__has_include) || FMT_ICC_VERSION >= 1600 || FMT_MSC_VERSION > 1900 -# define FMT_HAS_INCLUDE(x) __has_include(x) -#else -# define FMT_HAS_INCLUDE(x) 0 -#endif - -#ifdef __has_cpp_attribute -# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) -#else -# define FMT_HAS_CPP_ATTRIBUTE(x) 0 -#endif - -#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ - (FMT_CPLUSPLUS >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) - -#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ - (FMT_CPLUSPLUS >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) - -// Check if relaxed C++14 constexpr is supported. -// GCC doesn't allow throw in constexpr until version 6 (bug 67371). -#ifndef FMT_USE_CONSTEXPR -# if (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VERSION >= 1912 || \ - (FMT_GCC_VERSION >= 600 && FMT_CPLUSPLUS >= 201402L)) && \ - !FMT_ICC_VERSION && (!defined(__NVCC__) || FMT_CPLUSPLUS >= 202002L) -# define FMT_USE_CONSTEXPR 1 -# else -# define FMT_USE_CONSTEXPR 0 -# endif -#endif -#if FMT_USE_CONSTEXPR -# define FMT_CONSTEXPR constexpr -#else -# define FMT_CONSTEXPR -#endif - -#if ((FMT_CPLUSPLUS >= 202002L) && \ - (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9)) || \ - (FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002) -# define FMT_CONSTEXPR20 constexpr -#else -# define FMT_CONSTEXPR20 -#endif - -// Check if constexpr std::char_traits<>::{compare,length} are supported. -#if defined(__GLIBCXX__) -# if FMT_CPLUSPLUS >= 201703L && defined(_GLIBCXX_RELEASE) && \ - _GLIBCXX_RELEASE >= 7 // GCC 7+ libstdc++ has _GLIBCXX_RELEASE. -# define FMT_CONSTEXPR_CHAR_TRAITS constexpr -# endif -#elif defined(_LIBCPP_VERSION) && FMT_CPLUSPLUS >= 201703L && \ - _LIBCPP_VERSION >= 4000 -# define FMT_CONSTEXPR_CHAR_TRAITS constexpr -#elif FMT_MSC_VERSION >= 1914 && FMT_CPLUSPLUS >= 201703L -# define FMT_CONSTEXPR_CHAR_TRAITS constexpr -#endif -#ifndef FMT_CONSTEXPR_CHAR_TRAITS -# define FMT_CONSTEXPR_CHAR_TRAITS -#endif - -// Check if exceptions are disabled. -#ifndef FMT_EXCEPTIONS -# if (defined(__GNUC__) && !defined(__EXCEPTIONS)) || \ - (FMT_MSC_VERSION && !_HAS_EXCEPTIONS) -# define FMT_EXCEPTIONS 0 -# else -# define FMT_EXCEPTIONS 1 -# endif -#endif - -// Disable [[noreturn]] on MSVC/NVCC because of bogus unreachable code warnings. -#if FMT_EXCEPTIONS && FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VERSION && \ - !defined(__NVCC__) -# define FMT_NORETURN [[noreturn]] -#else -# define FMT_NORETURN -#endif - -#ifndef FMT_NODISCARD -# if FMT_HAS_CPP17_ATTRIBUTE(nodiscard) -# define FMT_NODISCARD [[nodiscard]] -# else -# define FMT_NODISCARD -# endif -#endif - -#ifndef FMT_INLINE -# if FMT_GCC_VERSION || FMT_CLANG_VERSION -# define FMT_INLINE inline __attribute__((always_inline)) -# else -# define FMT_INLINE inline -# endif -#endif - -#ifdef _MSC_VER -# define FMT_UNCHECKED_ITERATOR(It) \ - using _Unchecked_type = It // Mark iterator as checked. -#else -# define FMT_UNCHECKED_ITERATOR(It) using unchecked_type = It -#endif - -#ifndef FMT_BEGIN_NAMESPACE -# define FMT_BEGIN_NAMESPACE \ - namespace fmt { \ - inline namespace v10 { -# define FMT_END_NAMESPACE \ - } \ - } -#endif - -#ifndef FMT_EXPORT -# define FMT_EXPORT -# define FMT_BEGIN_EXPORT -# define FMT_END_EXPORT -#endif - -#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) -# ifdef FMT_LIB_EXPORT -# define FMT_API __declspec(dllexport) -# elif defined(FMT_SHARED) -# define FMT_API __declspec(dllimport) -# endif -#else -# if defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) -# if defined(__GNUC__) || defined(__clang__) -# define FMT_API __attribute__((visibility("default"))) -# endif -# endif -#endif -#ifndef FMT_API -# define FMT_API -#endif - -// libc++ supports string_view in pre-c++17. -#if FMT_HAS_INCLUDE() && \ - (FMT_CPLUSPLUS >= 201703L || defined(_LIBCPP_VERSION)) -# include -# define FMT_USE_STRING_VIEW -#elif FMT_HAS_INCLUDE("experimental/string_view") && FMT_CPLUSPLUS >= 201402L -# include -# define FMT_USE_EXPERIMENTAL_STRING_VIEW -#endif - -#ifndef FMT_UNICODE -# define FMT_UNICODE !FMT_MSC_VERSION -#endif - -#ifndef FMT_CONSTEVAL -# if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \ - (!defined(__apple_build_version__) || \ - __apple_build_version__ >= 14000029L) && \ - FMT_CPLUSPLUS >= 202002L) || \ - (defined(__cpp_consteval) && \ - (!FMT_MSC_VERSION || _MSC_FULL_VER >= 193030704)) -// consteval is broken in MSVC before VS2022 and Apple clang before 14. -# define FMT_CONSTEVAL consteval -# define FMT_HAS_CONSTEVAL -# else -# define FMT_CONSTEVAL -# endif -#endif - -#ifndef FMT_USE_NONTYPE_TEMPLATE_ARGS -# if defined(__cpp_nontype_template_args) && \ - ((FMT_GCC_VERSION >= 903 && FMT_CPLUSPLUS >= 201709L) || \ - __cpp_nontype_template_args >= 201911L) && \ - !defined(__NVCOMPILER) && !defined(__LCC__) -# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 -# else -# define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 -# endif -#endif - -// Enable minimal optimizations for more compact code in debug mode. -FMT_GCC_PRAGMA("GCC push_options") -#if !defined(__OPTIMIZE__) && !defined(__NVCOMPILER) && !defined(__LCC__) && \ - !defined(__CUDACC__) -FMT_GCC_PRAGMA("GCC optimize(\"Og\")") -#endif - -FMT_BEGIN_NAMESPACE - -// Implementations of enable_if_t and other metafunctions for older systems. -template -using enable_if_t = typename std::enable_if::type; -template -using conditional_t = typename std::conditional::type; -template using bool_constant = std::integral_constant; -template -using remove_reference_t = typename std::remove_reference::type; -template -using remove_const_t = typename std::remove_const::type; -template -using remove_cvref_t = typename std::remove_cv>::type; -template struct type_identity { using type = T; }; -template using type_identity_t = typename type_identity::type; -template -using underlying_t = typename std::underlying_type::type; - -// Checks whether T is a container with contiguous storage. -template struct is_contiguous : std::false_type {}; -template -struct is_contiguous> : std::true_type {}; - -struct monostate { - constexpr monostate() {} -}; - -// An enable_if helper to be used in template parameters which results in much -// shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed -// to workaround a bug in MSVC 2019 (see #1140 and #1186). -#ifdef FMT_DOC -# define FMT_ENABLE_IF(...) -#else -# define FMT_ENABLE_IF(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0 -#endif - -// This is defined in core.h instead of format.h to avoid injecting in std. -// It is a template to avoid undesirable implicit conversions to std::byte. -#ifdef __cpp_lib_byte -template ::value)> -inline auto format_as(T b) -> unsigned char { - return static_cast(b); -} -#endif - -namespace detail { -// Suppresses "unused variable" warnings with the method described in -// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. -// (void)var does not work on many Intel compilers. -template FMT_CONSTEXPR void ignore_unused(const T&...) {} - -constexpr FMT_INLINE auto is_constant_evaluated( - bool default_value = false) noexcept -> bool { -// Workaround for incompatibility between libstdc++ consteval-based -// std::is_constant_evaluated() implementation and clang-14. -// https://github.com/fmtlib/fmt/issues/3247 -#if FMT_CPLUSPLUS >= 202002L && defined(_GLIBCXX_RELEASE) && \ - _GLIBCXX_RELEASE >= 12 && \ - (FMT_CLANG_VERSION >= 1400 && FMT_CLANG_VERSION < 1500) - ignore_unused(default_value); - return __builtin_is_constant_evaluated(); -#elif defined(__cpp_lib_is_constant_evaluated) - ignore_unused(default_value); - return std::is_constant_evaluated(); -#else - return default_value; -#endif -} - -// Suppresses "conditional expression is constant" warnings. -template constexpr FMT_INLINE auto const_check(T value) -> T { - return value; -} - -FMT_NORETURN FMT_API void assert_fail(const char* file, int line, - const char* message); - -#ifndef FMT_ASSERT -# ifdef NDEBUG -// FMT_ASSERT is not empty to avoid -Wempty-body. -# define FMT_ASSERT(condition, message) \ - fmt::detail::ignore_unused((condition), (message)) -# else -# define FMT_ASSERT(condition, message) \ - ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ - ? (void)0 \ - : fmt::detail::assert_fail(__FILE__, __LINE__, (message))) -# endif -#endif - -#if defined(FMT_USE_STRING_VIEW) -template using std_string_view = std::basic_string_view; -#elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW) -template -using std_string_view = std::experimental::basic_string_view; -#else -template struct std_string_view {}; -#endif - -#ifdef FMT_USE_INT128 -// Do nothing. -#elif defined(__SIZEOF_INT128__) && !defined(__NVCC__) && \ - !(FMT_CLANG_VERSION && FMT_MSC_VERSION) -# define FMT_USE_INT128 1 -using int128_opt = __int128_t; // An optional native 128-bit integer. -using uint128_opt = __uint128_t; -template inline auto convert_for_visit(T value) -> T { - return value; -} -#else -# define FMT_USE_INT128 0 -#endif -#if !FMT_USE_INT128 -enum class int128_opt {}; -enum class uint128_opt {}; -// Reduce template instantiations. -template auto convert_for_visit(T) -> monostate { return {}; } -#endif - -// Casts a nonnegative integer to unsigned. -template -FMT_CONSTEXPR auto to_unsigned(Int value) -> - typename std::make_unsigned::type { - FMT_ASSERT(std::is_unsigned::value || value >= 0, "negative value"); - return static_cast::type>(value); -} - -FMT_CONSTEXPR inline auto is_utf8() -> bool { - FMT_MSC_WARNING(suppress : 4566) constexpr unsigned char section[] = "\u00A7"; - - // Avoid buggy sign extensions in MSVC's constant evaluation mode (#2297). - using uchar = unsigned char; - return FMT_UNICODE || (sizeof(section) == 3 && uchar(section[0]) == 0xC2 && - uchar(section[1]) == 0xA7); -} -} // namespace detail - -/** - An implementation of ``std::basic_string_view`` for pre-C++17. It provides a - subset of the API. ``fmt::basic_string_view`` is used for format strings even - if ``std::string_view`` is available to prevent issues when a library is - compiled with a different ``-std`` option than the client code (which is not - recommended). - */ -FMT_EXPORT -template class basic_string_view { - private: - const Char* data_; - size_t size_; - - public: - using value_type = Char; - using iterator = const Char*; - - constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {} - - /** Constructs a string reference object from a C string and a size. */ - constexpr basic_string_view(const Char* s, size_t count) noexcept - : data_(s), size_(count) {} - - /** - \rst - Constructs a string reference object from a C string computing - the size with ``std::char_traits::length``. - \endrst - */ - FMT_CONSTEXPR_CHAR_TRAITS - FMT_INLINE - basic_string_view(const Char* s) - : data_(s), - size_(detail::const_check(std::is_same::value && - !detail::is_constant_evaluated(true)) - ? std::strlen(reinterpret_cast(s)) - : std::char_traits::length(s)) {} - - /** Constructs a string reference from a ``std::basic_string`` object. */ - template - FMT_CONSTEXPR basic_string_view( - const std::basic_string& s) noexcept - : data_(s.data()), size_(s.size()) {} - - template >::value)> - FMT_CONSTEXPR basic_string_view(S s) noexcept - : data_(s.data()), size_(s.size()) {} - - /** Returns a pointer to the string data. */ - constexpr auto data() const noexcept -> const Char* { return data_; } - - /** Returns the string size. */ - constexpr auto size() const noexcept -> size_t { return size_; } - - constexpr auto begin() const noexcept -> iterator { return data_; } - constexpr auto end() const noexcept -> iterator { return data_ + size_; } - - constexpr auto operator[](size_t pos) const noexcept -> const Char& { - return data_[pos]; - } - - FMT_CONSTEXPR void remove_prefix(size_t n) noexcept { - data_ += n; - size_ -= n; - } - - FMT_CONSTEXPR_CHAR_TRAITS bool starts_with( - basic_string_view sv) const noexcept { - return size_ >= sv.size_ && - std::char_traits::compare(data_, sv.data_, sv.size_) == 0; - } - FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(Char c) const noexcept { - return size_ >= 1 && std::char_traits::eq(*data_, c); - } - FMT_CONSTEXPR_CHAR_TRAITS bool starts_with(const Char* s) const { - return starts_with(basic_string_view(s)); - } - - // Lexicographically compare this string reference to other. - FMT_CONSTEXPR_CHAR_TRAITS auto compare(basic_string_view other) const -> int { - size_t str_size = size_ < other.size_ ? size_ : other.size_; - int result = std::char_traits::compare(data_, other.data_, str_size); - if (result == 0) - result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); - return result; - } - - FMT_CONSTEXPR_CHAR_TRAITS friend auto operator==(basic_string_view lhs, - basic_string_view rhs) - -> bool { - return lhs.compare(rhs) == 0; - } - friend auto operator!=(basic_string_view lhs, basic_string_view rhs) -> bool { - return lhs.compare(rhs) != 0; - } - friend auto operator<(basic_string_view lhs, basic_string_view rhs) -> bool { - return lhs.compare(rhs) < 0; - } - friend auto operator<=(basic_string_view lhs, basic_string_view rhs) -> bool { - return lhs.compare(rhs) <= 0; - } - friend auto operator>(basic_string_view lhs, basic_string_view rhs) -> bool { - return lhs.compare(rhs) > 0; - } - friend auto operator>=(basic_string_view lhs, basic_string_view rhs) -> bool { - return lhs.compare(rhs) >= 0; - } -}; - -FMT_EXPORT -using string_view = basic_string_view; - -/** Specifies if ``T`` is a character type. Can be specialized by users. */ -FMT_EXPORT -template struct is_char : std::false_type {}; -template <> struct is_char : std::true_type {}; - -namespace detail { - -// A base class for compile-time strings. -struct compile_string {}; - -template -struct is_compile_string : std::is_base_of {}; - -template ::value)> -FMT_INLINE auto to_string_view(const Char* s) -> basic_string_view { - return s; -} -template -inline auto to_string_view(const std::basic_string& s) - -> basic_string_view { - return s; -} -template -constexpr auto to_string_view(basic_string_view s) - -> basic_string_view { - return s; -} -template >::value)> -inline auto to_string_view(std_string_view s) -> basic_string_view { - return s; -} -template ::value)> -constexpr auto to_string_view(const S& s) - -> basic_string_view { - return basic_string_view(s); -} -void to_string_view(...); - -// Specifies whether S is a string type convertible to fmt::basic_string_view. -// It should be a constexpr function but MSVC 2017 fails to compile it in -// enable_if and MSVC 2015 fails to compile it as an alias template. -// ADL is intentionally disabled as to_string_view is not an extension point. -template -struct is_string - : std::is_class()))> {}; - -template struct char_t_impl {}; -template struct char_t_impl::value>> { - using result = decltype(to_string_view(std::declval())); - using type = typename result::value_type; -}; - -enum class type { - none_type, - // Integer types should go first, - int_type, - uint_type, - long_long_type, - ulong_long_type, - int128_type, - uint128_type, - bool_type, - char_type, - last_integer_type = char_type, - // followed by floating-point types. - float_type, - double_type, - long_double_type, - last_numeric_type = long_double_type, - cstring_type, - string_type, - pointer_type, - custom_type -}; - -// Maps core type T to the corresponding type enum constant. -template -struct type_constant : std::integral_constant {}; - -#define FMT_TYPE_CONSTANT(Type, constant) \ - template \ - struct type_constant \ - : std::integral_constant {} - -FMT_TYPE_CONSTANT(int, int_type); -FMT_TYPE_CONSTANT(unsigned, uint_type); -FMT_TYPE_CONSTANT(long long, long_long_type); -FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type); -FMT_TYPE_CONSTANT(int128_opt, int128_type); -FMT_TYPE_CONSTANT(uint128_opt, uint128_type); -FMT_TYPE_CONSTANT(bool, bool_type); -FMT_TYPE_CONSTANT(Char, char_type); -FMT_TYPE_CONSTANT(float, float_type); -FMT_TYPE_CONSTANT(double, double_type); -FMT_TYPE_CONSTANT(long double, long_double_type); -FMT_TYPE_CONSTANT(const Char*, cstring_type); -FMT_TYPE_CONSTANT(basic_string_view, string_type); -FMT_TYPE_CONSTANT(const void*, pointer_type); - -constexpr bool is_integral_type(type t) { - return t > type::none_type && t <= type::last_integer_type; -} -constexpr bool is_arithmetic_type(type t) { - return t > type::none_type && t <= type::last_numeric_type; -} - -constexpr auto set(type rhs) -> int { return 1 << static_cast(rhs); } -constexpr auto in(type t, int set) -> bool { - return ((set >> static_cast(t)) & 1) != 0; -} - -// Bitsets of types. -enum { - sint_set = - set(type::int_type) | set(type::long_long_type) | set(type::int128_type), - uint_set = set(type::uint_type) | set(type::ulong_long_type) | - set(type::uint128_type), - bool_set = set(type::bool_type), - char_set = set(type::char_type), - float_set = set(type::float_type) | set(type::double_type) | - set(type::long_double_type), - string_set = set(type::string_type), - cstring_set = set(type::cstring_type), - pointer_set = set(type::pointer_type) -}; - -FMT_NORETURN FMT_API void throw_format_error(const char* message); - -struct error_handler { - constexpr error_handler() = default; - - // This function is intentionally not constexpr to give a compile-time error. - FMT_NORETURN void on_error(const char* message) { - throw_format_error(message); - } -}; -} // namespace detail - -/** Throws ``format_error`` with a given message. */ -using detail::throw_format_error; - -/** String's character type. */ -template using char_t = typename detail::char_t_impl::type; - -/** - \rst - Parsing context consisting of a format string range being parsed and an - argument counter for automatic indexing. - You can use the ``format_parse_context`` type alias for ``char`` instead. - \endrst - */ -FMT_EXPORT -template class basic_format_parse_context { - private: - basic_string_view format_str_; - int next_arg_id_; - - FMT_CONSTEXPR void do_check_arg_id(int id); - - public: - using char_type = Char; - using iterator = const Char*; - - explicit constexpr basic_format_parse_context( - basic_string_view format_str, int next_arg_id = 0) - : format_str_(format_str), next_arg_id_(next_arg_id) {} - - /** - Returns an iterator to the beginning of the format string range being - parsed. - */ - constexpr auto begin() const noexcept -> iterator { - return format_str_.begin(); - } - - /** - Returns an iterator past the end of the format string range being parsed. - */ - constexpr auto end() const noexcept -> iterator { return format_str_.end(); } - - /** Advances the begin iterator to ``it``. */ - FMT_CONSTEXPR void advance_to(iterator it) { - format_str_.remove_prefix(detail::to_unsigned(it - begin())); - } - - /** - Reports an error if using the manual argument indexing; otherwise returns - the next argument index and switches to the automatic indexing. - */ - FMT_CONSTEXPR auto next_arg_id() -> int { - if (next_arg_id_ < 0) { - detail::throw_format_error( - "cannot switch from manual to automatic argument indexing"); - return 0; - } - int id = next_arg_id_++; - do_check_arg_id(id); - return id; - } - - /** - Reports an error if using the automatic argument indexing; otherwise - switches to the manual indexing. - */ - FMT_CONSTEXPR void check_arg_id(int id) { - if (next_arg_id_ > 0) { - detail::throw_format_error( - "cannot switch from automatic to manual argument indexing"); - return; - } - next_arg_id_ = -1; - do_check_arg_id(id); - } - FMT_CONSTEXPR void check_arg_id(basic_string_view) {} - FMT_CONSTEXPR void check_dynamic_spec(int arg_id); -}; - -FMT_EXPORT -using format_parse_context = basic_format_parse_context; - -namespace detail { -// A parse context with extra data used only in compile-time checks. -template -class compile_parse_context : public basic_format_parse_context { - private: - int num_args_; - const type* types_; - using base = basic_format_parse_context; - - public: - explicit FMT_CONSTEXPR compile_parse_context( - basic_string_view format_str, int num_args, const type* types, - int next_arg_id = 0) - : base(format_str, next_arg_id), num_args_(num_args), types_(types) {} - - constexpr auto num_args() const -> int { return num_args_; } - constexpr auto arg_type(int id) const -> type { return types_[id]; } - - FMT_CONSTEXPR auto next_arg_id() -> int { - int id = base::next_arg_id(); - if (id >= num_args_) throw_format_error("argument not found"); - return id; - } - - FMT_CONSTEXPR void check_arg_id(int id) { - base::check_arg_id(id); - if (id >= num_args_) throw_format_error("argument not found"); - } - using base::check_arg_id; - - FMT_CONSTEXPR void check_dynamic_spec(int arg_id) { - detail::ignore_unused(arg_id); -#if !defined(__LCC__) - if (arg_id < num_args_ && types_ && !is_integral_type(types_[arg_id])) - throw_format_error("width/precision is not integer"); -#endif - } -}; - -// Extracts a reference to the container from back_insert_iterator. -template -inline auto get_container(std::back_insert_iterator it) - -> Container& { - using base = std::back_insert_iterator; - struct accessor : base { - accessor(base b) : base(b) {} - using base::container; - }; - return *accessor(it).container; -} - -template -FMT_CONSTEXPR auto copy_str(InputIt begin, InputIt end, OutputIt out) - -> OutputIt { - while (begin != end) *out++ = static_cast(*begin++); - return out; -} - -template , U>::value&& is_char::value)> -FMT_CONSTEXPR auto copy_str(T* begin, T* end, U* out) -> U* { - if (is_constant_evaluated()) return copy_str(begin, end, out); - auto size = to_unsigned(end - begin); - if (size > 0) memcpy(out, begin, size * sizeof(U)); - return out + size; -} - -/** - \rst - A contiguous memory buffer with an optional growing ability. It is an internal - class and shouldn't be used directly, only via `~fmt::basic_memory_buffer`. - \endrst - */ -template class buffer { - private: - T* ptr_; - size_t size_; - size_t capacity_; - - protected: - // Don't initialize ptr_ since it is not accessed to save a few cycles. - FMT_MSC_WARNING(suppress : 26495) - buffer(size_t sz) noexcept : size_(sz), capacity_(sz) {} - - FMT_CONSTEXPR20 buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) noexcept - : ptr_(p), size_(sz), capacity_(cap) {} - - FMT_CONSTEXPR20 ~buffer() = default; - buffer(buffer&&) = default; - - /** Sets the buffer data and capacity. */ - FMT_CONSTEXPR void set(T* buf_data, size_t buf_capacity) noexcept { - ptr_ = buf_data; - capacity_ = buf_capacity; - } - - /** Increases the buffer capacity to hold at least *capacity* elements. */ - virtual FMT_CONSTEXPR20 void grow(size_t capacity) = 0; - - public: - using value_type = T; - using const_reference = const T&; - - buffer(const buffer&) = delete; - void operator=(const buffer&) = delete; - - FMT_INLINE auto begin() noexcept -> T* { return ptr_; } - FMT_INLINE auto end() noexcept -> T* { return ptr_ + size_; } - - FMT_INLINE auto begin() const noexcept -> const T* { return ptr_; } - FMT_INLINE auto end() const noexcept -> const T* { return ptr_ + size_; } - - /** Returns the size of this buffer. */ - constexpr auto size() const noexcept -> size_t { return size_; } - - /** Returns the capacity of this buffer. */ - constexpr auto capacity() const noexcept -> size_t { return capacity_; } - - /** Returns a pointer to the buffer data (not null-terminated). */ - FMT_CONSTEXPR auto data() noexcept -> T* { return ptr_; } - FMT_CONSTEXPR auto data() const noexcept -> const T* { return ptr_; } - - /** Clears this buffer. */ - void clear() { size_ = 0; } - - // Tries resizing the buffer to contain *count* elements. If T is a POD type - // the new elements may not be initialized. - FMT_CONSTEXPR20 void try_resize(size_t count) { - try_reserve(count); - size_ = count <= capacity_ ? count : capacity_; - } - - // Tries increasing the buffer capacity to *new_capacity*. It can increase the - // capacity by a smaller amount than requested but guarantees there is space - // for at least one additional element either by increasing the capacity or by - // flushing the buffer if it is full. - FMT_CONSTEXPR20 void try_reserve(size_t new_capacity) { - if (new_capacity > capacity_) grow(new_capacity); - } - - FMT_CONSTEXPR20 void push_back(const T& value) { - try_reserve(size_ + 1); - ptr_[size_++] = value; - } - - /** Appends data to the end of the buffer. */ - template void append(const U* begin, const U* end); - - template FMT_CONSTEXPR auto operator[](Idx index) -> T& { - return ptr_[index]; - } - template - FMT_CONSTEXPR auto operator[](Idx index) const -> const T& { - return ptr_[index]; - } -}; - -struct buffer_traits { - explicit buffer_traits(size_t) {} - auto count() const -> size_t { return 0; } - auto limit(size_t size) -> size_t { return size; } -}; - -class fixed_buffer_traits { - private: - size_t count_ = 0; - size_t limit_; - - public: - explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} - auto count() const -> size_t { return count_; } - auto limit(size_t size) -> size_t { - size_t n = limit_ > count_ ? limit_ - count_ : 0; - count_ += size; - return size < n ? size : n; - } -}; - -// A buffer that writes to an output iterator when flushed. -template -class iterator_buffer final : public Traits, public buffer { - private: - OutputIt out_; - enum { buffer_size = 256 }; - T data_[buffer_size]; - - protected: - FMT_CONSTEXPR20 void grow(size_t) override { - if (this->size() == buffer_size) flush(); - } - - void flush() { - auto size = this->size(); - this->clear(); - out_ = copy_str(data_, data_ + this->limit(size), out_); - } - - public: - explicit iterator_buffer(OutputIt out, size_t n = buffer_size) - : Traits(n), buffer(data_, 0, buffer_size), out_(out) {} - iterator_buffer(iterator_buffer&& other) - : Traits(other), buffer(data_, 0, buffer_size), out_(other.out_) {} - ~iterator_buffer() { flush(); } - - auto out() -> OutputIt { - flush(); - return out_; - } - auto count() const -> size_t { return Traits::count() + this->size(); } -}; - -template -class iterator_buffer final - : public fixed_buffer_traits, - public buffer { - private: - T* out_; - enum { buffer_size = 256 }; - T data_[buffer_size]; - - protected: - FMT_CONSTEXPR20 void grow(size_t) override { - if (this->size() == this->capacity()) flush(); - } - - void flush() { - size_t n = this->limit(this->size()); - if (this->data() == out_) { - out_ += n; - this->set(data_, buffer_size); - } - this->clear(); - } - - public: - explicit iterator_buffer(T* out, size_t n = buffer_size) - : fixed_buffer_traits(n), buffer(out, 0, n), out_(out) {} - iterator_buffer(iterator_buffer&& other) - : fixed_buffer_traits(other), - buffer(std::move(other)), - out_(other.out_) { - if (this->data() != out_) { - this->set(data_, buffer_size); - this->clear(); - } - } - ~iterator_buffer() { flush(); } - - auto out() -> T* { - flush(); - return out_; - } - auto count() const -> size_t { - return fixed_buffer_traits::count() + this->size(); - } -}; - -template class iterator_buffer final : public buffer { - protected: - FMT_CONSTEXPR20 void grow(size_t) override {} - - public: - explicit iterator_buffer(T* out, size_t = 0) : buffer(out, 0, ~size_t()) {} - - auto out() -> T* { return &*this->end(); } -}; - -// A buffer that writes to a container with the contiguous storage. -template -class iterator_buffer, - enable_if_t::value, - typename Container::value_type>> - final : public buffer { - private: - Container& container_; - - protected: - FMT_CONSTEXPR20 void grow(size_t capacity) override { - container_.resize(capacity); - this->set(&container_[0], capacity); - } - - public: - explicit iterator_buffer(Container& c) - : buffer(c.size()), container_(c) {} - explicit iterator_buffer(std::back_insert_iterator out, size_t = 0) - : iterator_buffer(get_container(out)) {} - - auto out() -> std::back_insert_iterator { - return std::back_inserter(container_); - } -}; - -// A buffer that counts the number of code units written discarding the output. -template class counting_buffer final : public buffer { - private: - enum { buffer_size = 256 }; - T data_[buffer_size]; - size_t count_ = 0; - - protected: - FMT_CONSTEXPR20 void grow(size_t) override { - if (this->size() != buffer_size) return; - count_ += this->size(); - this->clear(); - } - - public: - counting_buffer() : buffer(data_, 0, buffer_size) {} - - auto count() -> size_t { return count_ + this->size(); } -}; -} // namespace detail - -template -FMT_CONSTEXPR void basic_format_parse_context::do_check_arg_id(int id) { - // Argument id is only checked at compile-time during parsing because - // formatting has its own validation. - if (detail::is_constant_evaluated() && - (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200)) { - using context = detail::compile_parse_context; - if (id >= static_cast(this)->num_args()) - detail::throw_format_error("argument not found"); - } -} - -template -FMT_CONSTEXPR void basic_format_parse_context::check_dynamic_spec( - int arg_id) { - if (detail::is_constant_evaluated() && - (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200)) { - using context = detail::compile_parse_context; - static_cast(this)->check_dynamic_spec(arg_id); - } -} - -FMT_EXPORT template class basic_format_arg; -FMT_EXPORT template class basic_format_args; -FMT_EXPORT template class dynamic_format_arg_store; - -// A formatter for objects of type T. -FMT_EXPORT -template -struct formatter { - // A deleted default constructor indicates a disabled formatter. - formatter() = delete; -}; - -// Specifies if T has an enabled formatter specialization. A type can be -// formattable even if it doesn't have a formatter e.g. via a conversion. -template -using has_formatter = - std::is_constructible>; - -// An output iterator that appends to a buffer. -// It is used to reduce symbol sizes for the common case. -class appender : public std::back_insert_iterator> { - using base = std::back_insert_iterator>; - - public: - using std::back_insert_iterator>::back_insert_iterator; - appender(base it) noexcept : base(it) {} - FMT_UNCHECKED_ITERATOR(appender); - - auto operator++() noexcept -> appender& { return *this; } - auto operator++(int) noexcept -> appender { return *this; } -}; - -namespace detail { - -template -constexpr auto has_const_formatter_impl(T*) - -> decltype(typename Context::template formatter_type().format( - std::declval(), std::declval()), - true) { - return true; -} -template -constexpr auto has_const_formatter_impl(...) -> bool { - return false; -} -template -constexpr auto has_const_formatter() -> bool { - return has_const_formatter_impl(static_cast(nullptr)); -} - -template -using buffer_appender = conditional_t::value, appender, - std::back_insert_iterator>>; - -// Maps an output iterator to a buffer. -template -auto get_buffer(OutputIt out) -> iterator_buffer { - return iterator_buffer(out); -} -template , Buf>::value)> -auto get_buffer(std::back_insert_iterator out) -> buffer& { - return get_container(out); -} - -template -FMT_INLINE auto get_iterator(Buf& buf, OutputIt) -> decltype(buf.out()) { - return buf.out(); -} -template -auto get_iterator(buffer&, OutputIt out) -> OutputIt { - return out; -} - -struct view {}; - -template struct named_arg : view { - const Char* name; - const T& value; - named_arg(const Char* n, const T& v) : name(n), value(v) {} -}; - -template struct named_arg_info { - const Char* name; - int id; -}; - -template -struct arg_data { - // args_[0].named_args points to named_args_ to avoid bloating format_args. - // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. - T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : +1)]; - named_arg_info named_args_[NUM_NAMED_ARGS]; - - template - arg_data(const U&... init) : args_{T(named_args_, NUM_NAMED_ARGS), init...} {} - arg_data(const arg_data& other) = delete; - auto args() const -> const T* { return args_ + 1; } - auto named_args() -> named_arg_info* { return named_args_; } -}; - -template -struct arg_data { - // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. - T args_[NUM_ARGS != 0 ? NUM_ARGS : +1]; - - template - FMT_CONSTEXPR FMT_INLINE arg_data(const U&... init) : args_{init...} {} - FMT_CONSTEXPR FMT_INLINE auto args() const -> const T* { return args_; } - FMT_CONSTEXPR FMT_INLINE auto named_args() -> std::nullptr_t { - return nullptr; - } -}; - -template -inline void init_named_args(named_arg_info*, int, int) {} - -template struct is_named_arg : std::false_type {}; -template struct is_statically_named_arg : std::false_type {}; - -template -struct is_named_arg> : std::true_type {}; - -template ::value)> -void init_named_args(named_arg_info* named_args, int arg_count, - int named_arg_count, const T&, const Tail&... args) { - init_named_args(named_args, arg_count + 1, named_arg_count, args...); -} - -template ::value)> -void init_named_args(named_arg_info* named_args, int arg_count, - int named_arg_count, const T& arg, const Tail&... args) { - named_args[named_arg_count++] = {arg.name, arg_count}; - init_named_args(named_args, arg_count + 1, named_arg_count, args...); -} - -template -FMT_CONSTEXPR FMT_INLINE void init_named_args(std::nullptr_t, int, int, - const Args&...) {} - -template constexpr auto count() -> size_t { return B ? 1 : 0; } -template constexpr auto count() -> size_t { - return (B1 ? 1 : 0) + count(); -} - -template constexpr auto count_named_args() -> size_t { - return count::value...>(); -} - -template -constexpr auto count_statically_named_args() -> size_t { - return count::value...>(); -} - -struct unformattable {}; -struct unformattable_char : unformattable {}; -struct unformattable_pointer : unformattable {}; - -template struct string_value { - const Char* data; - size_t size; -}; - -template struct named_arg_value { - const named_arg_info* data; - size_t size; -}; - -template struct custom_value { - using parse_context = typename Context::parse_context_type; - void* value; - void (*format)(void* arg, parse_context& parse_ctx, Context& ctx); -}; - -// A formatting argument value. -template class value { - public: - using char_type = typename Context::char_type; - - union { - monostate no_value; - int int_value; - unsigned uint_value; - long long long_long_value; - unsigned long long ulong_long_value; - int128_opt int128_value; - uint128_opt uint128_value; - bool bool_value; - char_type char_value; - float float_value; - double double_value; - long double long_double_value; - const void* pointer; - string_value string; - custom_value custom; - named_arg_value named_args; - }; - - constexpr FMT_INLINE value() : no_value() {} - constexpr FMT_INLINE value(int val) : int_value(val) {} - constexpr FMT_INLINE value(unsigned val) : uint_value(val) {} - constexpr FMT_INLINE value(long long val) : long_long_value(val) {} - constexpr FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {} - FMT_INLINE value(int128_opt val) : int128_value(val) {} - FMT_INLINE value(uint128_opt val) : uint128_value(val) {} - constexpr FMT_INLINE value(float val) : float_value(val) {} - constexpr FMT_INLINE value(double val) : double_value(val) {} - FMT_INLINE value(long double val) : long_double_value(val) {} - constexpr FMT_INLINE value(bool val) : bool_value(val) {} - constexpr FMT_INLINE value(char_type val) : char_value(val) {} - FMT_CONSTEXPR FMT_INLINE value(const char_type* val) { - string.data = val; - if (is_constant_evaluated()) string.size = {}; - } - FMT_CONSTEXPR FMT_INLINE value(basic_string_view val) { - string.data = val.data(); - string.size = val.size(); - } - FMT_INLINE value(const void* val) : pointer(val) {} - FMT_INLINE value(const named_arg_info* args, size_t size) - : named_args{args, size} {} - - template FMT_CONSTEXPR20 FMT_INLINE value(T& val) { - using value_type = remove_const_t; - custom.value = const_cast(std::addressof(val)); - // Get the formatter type through the context to allow different contexts - // have different extension points, e.g. `formatter` for `format` and - // `printf_formatter` for `printf`. - custom.format = format_custom_arg< - value_type, typename Context::template formatter_type>; - } - value(unformattable); - value(unformattable_char); - value(unformattable_pointer); - - private: - // Formats an argument of a custom type, such as a user-defined class. - template - static void format_custom_arg(void* arg, - typename Context::parse_context_type& parse_ctx, - Context& ctx) { - auto f = Formatter(); - parse_ctx.advance_to(f.parse(parse_ctx)); - using qualified_type = - conditional_t(), const T, T>; - ctx.advance_to(f.format(*static_cast(arg), ctx)); - } -}; - -// To minimize the number of types we need to deal with, long is translated -// either to int or to long long depending on its size. -enum { long_short = sizeof(long) == sizeof(int) }; -using long_type = conditional_t; -using ulong_type = conditional_t; - -template struct format_as_result { - template ::value || std::is_class::value)> - static auto map(U*) -> decltype(format_as(std::declval())); - static auto map(...) -> void; - - using type = decltype(map(static_cast(nullptr))); -}; -template using format_as_t = typename format_as_result::type; - -template -struct has_format_as - : bool_constant, void>::value> {}; - -// Maps formatting arguments to core types. -// arg_mapper reports errors by returning unformattable instead of using -// static_assert because it's used in the is_formattable trait. -template struct arg_mapper { - using char_type = typename Context::char_type; - - FMT_CONSTEXPR FMT_INLINE auto map(signed char val) -> int { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned char val) -> unsigned { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(short val) -> int { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned short val) -> unsigned { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(int val) -> int { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned val) -> unsigned { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(long val) -> long_type { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned long val) -> ulong_type { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(long long val) -> long long { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(unsigned long long val) - -> unsigned long long { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(int128_opt val) -> int128_opt { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(uint128_opt val) -> uint128_opt { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(bool val) -> bool { return val; } - - template ::value || - std::is_same::value)> - FMT_CONSTEXPR FMT_INLINE auto map(T val) -> char_type { - return val; - } - template ::value || -#ifdef __cpp_char8_t - std::is_same::value || -#endif - std::is_same::value || - std::is_same::value) && - !std::is_same::value, - int> = 0> - FMT_CONSTEXPR FMT_INLINE auto map(T) -> unformattable_char { - return {}; - } - - FMT_CONSTEXPR FMT_INLINE auto map(float val) -> float { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(double val) -> double { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(long double val) -> long double { - return val; - } - - FMT_CONSTEXPR FMT_INLINE auto map(char_type* val) -> const char_type* { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(const char_type* val) -> const char_type* { - return val; - } - template ::value && !std::is_pointer::value && - std::is_same>::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) - -> basic_string_view { - return to_string_view(val); - } - template ::value && !std::is_pointer::value && - !std::is_same>::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T&) -> unformattable_char { - return {}; - } - - FMT_CONSTEXPR FMT_INLINE auto map(void* val) -> const void* { return val; } - FMT_CONSTEXPR FMT_INLINE auto map(const void* val) -> const void* { - return val; - } - FMT_CONSTEXPR FMT_INLINE auto map(std::nullptr_t val) -> const void* { - return val; - } - - // Use SFINAE instead of a const T* parameter to avoid a conflict with the - // array overload. - template < - typename T, - FMT_ENABLE_IF( - std::is_pointer::value || std::is_member_pointer::value || - std::is_function::type>::value || - (std::is_array::value && - !std::is_convertible::value))> - FMT_CONSTEXPR auto map(const T&) -> unformattable_pointer { - return {}; - } - - template ::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T (&values)[N]) -> const T (&)[N] { - return values; - } - - // Only map owning types because mapping views can be unsafe. - template , - FMT_ENABLE_IF(std::is_arithmetic::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> decltype(this->map(U())) { - return map(format_as(val)); - } - - template > - struct formattable : bool_constant() || - (has_formatter::value && - !std::is_const::value)> {}; - - template ::value)> - FMT_CONSTEXPR FMT_INLINE auto do_map(T& val) -> T& { - return val; - } - template ::value)> - FMT_CONSTEXPR FMT_INLINE auto do_map(T&) -> unformattable { - return {}; - } - - template , - FMT_ENABLE_IF((std::is_class::value || std::is_enum::value || - std::is_union::value) && - !is_string::value && !is_char::value && - !is_named_arg::value && - !std::is_arithmetic>::value)> - FMT_CONSTEXPR FMT_INLINE auto map(T& val) -> decltype(this->do_map(val)) { - return do_map(val); - } - - template ::value)> - FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg) - -> decltype(this->map(named_arg.value)) { - return map(named_arg.value); - } - - auto map(...) -> unformattable { return {}; } -}; - -// A type constant after applying arg_mapper. -template -using mapped_type_constant = - type_constant().map(std::declval())), - typename Context::char_type>; - -enum { packed_arg_bits = 4 }; -// Maximum number of arguments with packed types. -enum { max_packed_args = 62 / packed_arg_bits }; -enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; -enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; - -template -auto copy_str(InputIt begin, InputIt end, appender out) -> appender { - get_container(out).append(begin, end); - return out; -} -template -auto copy_str(InputIt begin, InputIt end, - std::back_insert_iterator out) - -> std::back_insert_iterator { - get_container(out).append(begin, end); - return out; -} - -template -FMT_CONSTEXPR auto copy_str(R&& rng, OutputIt out) -> OutputIt { - return detail::copy_str(rng.begin(), rng.end(), out); -} - -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 -// A workaround for gcc 4.8 to make void_t work in a SFINAE context. -template struct void_t_impl { using type = void; }; -template using void_t = typename void_t_impl::type; -#else -template using void_t = void; -#endif - -template -struct is_output_iterator : std::false_type {}; - -template -struct is_output_iterator< - It, T, - void_t::iterator_category, - decltype(*std::declval() = std::declval())>> - : std::true_type {}; - -template struct is_back_insert_iterator : std::false_type {}; -template -struct is_back_insert_iterator> - : std::true_type {}; - -// A type-erased reference to an std::locale to avoid a heavy include. -class locale_ref { - private: - const void* locale_; // A type-erased pointer to std::locale. - - public: - constexpr FMT_INLINE locale_ref() : locale_(nullptr) {} - template explicit locale_ref(const Locale& loc); - - explicit operator bool() const noexcept { return locale_ != nullptr; } - - template auto get() const -> Locale; -}; - -template constexpr auto encode_types() -> unsigned long long { - return 0; -} - -template -constexpr auto encode_types() -> unsigned long long { - return static_cast(mapped_type_constant::value) | - (encode_types() << packed_arg_bits); -} - -#if defined(__cpp_if_constexpr) -// This type is intentionally undefined, only used for errors -template struct type_is_unformattable_for; -#endif - -template -FMT_CONSTEXPR FMT_INLINE auto make_arg(T& val) -> value { - using arg_type = remove_cvref_t().map(val))>; - - constexpr bool formattable_char = - !std::is_same::value; - static_assert(formattable_char, "Mixing character types is disallowed."); - - // Formatting of arbitrary pointers is disallowed. If you want to format a - // pointer cast it to `void*` or `const void*`. In particular, this forbids - // formatting of `[const] volatile char*` printed as bool by iostreams. - constexpr bool formattable_pointer = - !std::is_same::value; - static_assert(formattable_pointer, - "Formatting of non-void pointers is disallowed."); - - constexpr bool formattable = !std::is_same::value; -#if defined(__cpp_if_constexpr) - if constexpr (!formattable) { - type_is_unformattable_for _; - } -#endif - static_assert( - formattable, - "Cannot format an argument. To make type T formattable provide a " - "formatter specialization: https://fmt.dev/latest/api.html#udt"); - return {arg_mapper().map(val)}; -} - -template -FMT_CONSTEXPR auto make_arg(T& val) -> basic_format_arg { - auto arg = basic_format_arg(); - arg.type_ = mapped_type_constant::value; - arg.value_ = make_arg(val); - return arg; -} - -template -FMT_CONSTEXPR inline auto make_arg(T& val) -> basic_format_arg { - return make_arg(val); -} -} // namespace detail -FMT_BEGIN_EXPORT - -// A formatting argument. It is a trivially copyable/constructible type to -// allow storage in basic_memory_buffer. -template class basic_format_arg { - private: - detail::value value_; - detail::type type_; - - template - friend FMT_CONSTEXPR auto detail::make_arg(T& value) - -> basic_format_arg; - - template - friend FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis, - const basic_format_arg& arg) - -> decltype(vis(0)); - - friend class basic_format_args; - friend class dynamic_format_arg_store; - - using char_type = typename Context::char_type; - - template - friend struct detail::arg_data; - - basic_format_arg(const detail::named_arg_info* args, size_t size) - : value_(args, size) {} - - public: - class handle { - public: - explicit handle(detail::custom_value custom) : custom_(custom) {} - - void format(typename Context::parse_context_type& parse_ctx, - Context& ctx) const { - custom_.format(custom_.value, parse_ctx, ctx); - } - - private: - detail::custom_value custom_; - }; - - constexpr basic_format_arg() : type_(detail::type::none_type) {} - - constexpr explicit operator bool() const noexcept { - return type_ != detail::type::none_type; - } - - auto type() const -> detail::type { return type_; } - - auto is_integral() const -> bool { return detail::is_integral_type(type_); } - auto is_arithmetic() const -> bool { - return detail::is_arithmetic_type(type_); - } -}; - -/** - \rst - Visits an argument dispatching to the appropriate visit method based on - the argument type. For example, if the argument type is ``double`` then - ``vis(value)`` will be called with the value of type ``double``. - \endrst - */ -// DEPRECATED! -template -FMT_CONSTEXPR FMT_INLINE auto visit_format_arg( - Visitor&& vis, const basic_format_arg& arg) -> decltype(vis(0)) { - switch (arg.type_) { - case detail::type::none_type: - break; - case detail::type::int_type: - return vis(arg.value_.int_value); - case detail::type::uint_type: - return vis(arg.value_.uint_value); - case detail::type::long_long_type: - return vis(arg.value_.long_long_value); - case detail::type::ulong_long_type: - return vis(arg.value_.ulong_long_value); - case detail::type::int128_type: - return vis(detail::convert_for_visit(arg.value_.int128_value)); - case detail::type::uint128_type: - return vis(detail::convert_for_visit(arg.value_.uint128_value)); - case detail::type::bool_type: - return vis(arg.value_.bool_value); - case detail::type::char_type: - return vis(arg.value_.char_value); - case detail::type::float_type: - return vis(arg.value_.float_value); - case detail::type::double_type: - return vis(arg.value_.double_value); - case detail::type::long_double_type: - return vis(arg.value_.long_double_value); - case detail::type::cstring_type: - return vis(arg.value_.string.data); - case detail::type::string_type: - using sv = basic_string_view; - return vis(sv(arg.value_.string.data, arg.value_.string.size)); - case detail::type::pointer_type: - return vis(arg.value_.pointer); - case detail::type::custom_type: - return vis(typename basic_format_arg::handle(arg.value_.custom)); - } - return vis(monostate()); -} - -// Formatting context. -template class basic_format_context { - private: - OutputIt out_; - basic_format_args args_; - detail::locale_ref loc_; - - public: - using iterator = OutputIt; - using format_arg = basic_format_arg; - using format_args = basic_format_args; - using parse_context_type = basic_format_parse_context; - template using formatter_type = formatter; - - /** The character type for the output. */ - using char_type = Char; - - basic_format_context(basic_format_context&&) = default; - basic_format_context(const basic_format_context&) = delete; - void operator=(const basic_format_context&) = delete; - /** - Constructs a ``basic_format_context`` object. References to the arguments - are stored in the object so make sure they have appropriate lifetimes. - */ - constexpr basic_format_context(OutputIt out, format_args ctx_args, - detail::locale_ref loc = {}) - : out_(out), args_(ctx_args), loc_(loc) {} - - constexpr auto arg(int id) const -> format_arg { return args_.get(id); } - FMT_CONSTEXPR auto arg(basic_string_view name) -> format_arg { - return args_.get(name); - } - FMT_CONSTEXPR auto arg_id(basic_string_view name) -> int { - return args_.get_id(name); - } - auto args() const -> const format_args& { return args_; } - - FMT_CONSTEXPR auto error_handler() -> detail::error_handler { return {}; } - void on_error(const char* message) { error_handler().on_error(message); } - - // Returns an iterator to the beginning of the output range. - FMT_CONSTEXPR auto out() -> iterator { return out_; } - - // Advances the begin iterator to ``it``. - void advance_to(iterator it) { - if (!detail::is_back_insert_iterator()) out_ = it; - } - - FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; } -}; - -template -using buffer_context = - basic_format_context, Char>; -using format_context = buffer_context; - -template -using is_formattable = bool_constant>() - .map(std::declval()))>::value>; - -/** - \rst - An array of references to arguments. It can be implicitly converted into - `~fmt::basic_format_args` for passing into type-erased formatting functions - such as `~fmt::vformat`. - \endrst - */ -template -class format_arg_store -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 - // Workaround a GCC template argument substitution bug. - : public basic_format_args -#endif -{ - private: - static const size_t num_args = sizeof...(Args); - static constexpr size_t num_named_args = detail::count_named_args(); - static const bool is_packed = num_args <= detail::max_packed_args; - - using value_type = conditional_t, - basic_format_arg>; - - detail::arg_data - data_; - - friend class basic_format_args; - - static constexpr unsigned long long desc = - (is_packed ? detail::encode_types() - : detail::is_unpacked_bit | num_args) | - (num_named_args != 0 - ? static_cast(detail::has_named_args_bit) - : 0); - - public: - template - FMT_CONSTEXPR FMT_INLINE format_arg_store(T&... args) - : -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 - basic_format_args(*this), -#endif - data_{detail::make_arg(args)...} { - if (detail::const_check(num_named_args != 0)) - detail::init_named_args(data_.named_args(), 0, 0, args...); - } -}; - -/** - \rst - Constructs a `~fmt::format_arg_store` object that contains references to - arguments and can be implicitly converted to `~fmt::format_args`. `Context` - can be omitted in which case it defaults to `~fmt::format_context`. - See `~fmt::arg` for lifetime considerations. - \endrst - */ -// Arguments are taken by lvalue references to avoid some lifetime issues. -template -constexpr auto make_format_args(T&... args) - -> format_arg_store...> { - return {args...}; -} - -/** - \rst - Returns a named argument to be used in a formatting function. - It should only be used in a call to a formatting function or - `dynamic_format_arg_store::push_back`. - - **Example**:: - - fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); - \endrst - */ -template -inline auto arg(const Char* name, const T& arg) -> detail::named_arg { - static_assert(!detail::is_named_arg(), "nested named arguments"); - return {name, arg}; -} -FMT_END_EXPORT - -/** - \rst - A view of a collection of formatting arguments. To avoid lifetime issues it - should only be used as a parameter type in type-erased functions such as - ``vformat``:: - - void vlog(string_view format_str, format_args args); // OK - format_args args = make_format_args(); // Error: dangling reference - \endrst - */ -template class basic_format_args { - public: - using size_type = int; - using format_arg = basic_format_arg; - - private: - // A descriptor that contains information about formatting arguments. - // If the number of arguments is less or equal to max_packed_args then - // argument types are passed in the descriptor. This reduces binary code size - // per formatting function call. - unsigned long long desc_; - union { - // If is_packed() returns true then argument values are stored in values_; - // otherwise they are stored in args_. This is done to improve cache - // locality and reduce compiled code size since storing larger objects - // may require more code (at least on x86-64) even if the same amount of - // data is actually copied to stack. It saves ~10% on the bloat test. - const detail::value* values_; - const format_arg* args_; - }; - - constexpr auto is_packed() const -> bool { - return (desc_ & detail::is_unpacked_bit) == 0; - } - auto has_named_args() const -> bool { - return (desc_ & detail::has_named_args_bit) != 0; - } - - FMT_CONSTEXPR auto type(int index) const -> detail::type { - int shift = index * detail::packed_arg_bits; - unsigned int mask = (1 << detail::packed_arg_bits) - 1; - return static_cast((desc_ >> shift) & mask); - } - - constexpr FMT_INLINE basic_format_args(unsigned long long desc, - const detail::value* values) - : desc_(desc), values_(values) {} - constexpr basic_format_args(unsigned long long desc, const format_arg* args) - : desc_(desc), args_(args) {} - - public: - constexpr basic_format_args() : desc_(0), args_(nullptr) {} - - /** - \rst - Constructs a `basic_format_args` object from `~fmt::format_arg_store`. - \endrst - */ - template - constexpr FMT_INLINE basic_format_args( - const format_arg_store& store) - : basic_format_args(format_arg_store::desc, - store.data_.args()) {} - - /** - \rst - Constructs a `basic_format_args` object from - `~fmt::dynamic_format_arg_store`. - \endrst - */ - constexpr FMT_INLINE basic_format_args( - const dynamic_format_arg_store& store) - : basic_format_args(store.get_types(), store.data()) {} - - /** - \rst - Constructs a `basic_format_args` object from a dynamic set of arguments. - \endrst - */ - constexpr basic_format_args(const format_arg* args, int count) - : basic_format_args(detail::is_unpacked_bit | detail::to_unsigned(count), - args) {} - - /** Returns the argument with the specified id. */ - FMT_CONSTEXPR auto get(int id) const -> format_arg { - format_arg arg; - if (!is_packed()) { - if (id < max_size()) arg = args_[id]; - return arg; - } - if (id >= detail::max_packed_args) return arg; - arg.type_ = type(id); - if (arg.type_ == detail::type::none_type) return arg; - arg.value_ = values_[id]; - return arg; - } - - template - auto get(basic_string_view name) const -> format_arg { - int id = get_id(name); - return id >= 0 ? get(id) : format_arg(); - } - - template - auto get_id(basic_string_view name) const -> int { - if (!has_named_args()) return -1; - const auto& named_args = - (is_packed() ? values_[-1] : args_[-1].value_).named_args; - for (size_t i = 0; i < named_args.size; ++i) { - if (named_args.data[i].name == name) return named_args.data[i].id; - } - return -1; - } - - auto max_size() const -> int { - unsigned long long max_packed = detail::max_packed_args; - return static_cast(is_packed() ? max_packed - : desc_ & ~detail::is_unpacked_bit); - } -}; - -/** An alias to ``basic_format_args``. */ -// A separate type would result in shorter symbols but break ABI compatibility -// between clang and gcc on ARM (#1919). -FMT_EXPORT using format_args = basic_format_args; - -// We cannot use enum classes as bit fields because of a gcc bug, so we put them -// in namespaces instead (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414). -// Additionally, if an underlying type is specified, older gcc incorrectly warns -// that the type is too small. Both bugs are fixed in gcc 9.3. -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 903 -# define FMT_ENUM_UNDERLYING_TYPE(type) -#else -# define FMT_ENUM_UNDERLYING_TYPE(type) : type -#endif -namespace align { -enum type FMT_ENUM_UNDERLYING_TYPE(unsigned char){none, left, right, center, - numeric}; -} -using align_t = align::type; -namespace sign { -enum type FMT_ENUM_UNDERLYING_TYPE(unsigned char){none, minus, plus, space}; -} -using sign_t = sign::type; - -namespace detail { - -// Workaround an array initialization issue in gcc 4.8. -template struct fill_t { - private: - enum { max_size = 4 }; - Char data_[max_size] = {Char(' '), Char(0), Char(0), Char(0)}; - unsigned char size_ = 1; - - public: - FMT_CONSTEXPR void operator=(basic_string_view s) { - auto size = s.size(); - FMT_ASSERT(size <= max_size, "invalid fill"); - for (size_t i = 0; i < size; ++i) data_[i] = s[i]; - size_ = static_cast(size); - } - - constexpr auto size() const -> size_t { return size_; } - constexpr auto data() const -> const Char* { return data_; } - - FMT_CONSTEXPR auto operator[](size_t index) -> Char& { return data_[index]; } - FMT_CONSTEXPR auto operator[](size_t index) const -> const Char& { - return data_[index]; - } -}; -} // namespace detail - -enum class presentation_type : unsigned char { - none, - dec, // 'd' - oct, // 'o' - hex_lower, // 'x' - hex_upper, // 'X' - bin_lower, // 'b' - bin_upper, // 'B' - hexfloat_lower, // 'a' - hexfloat_upper, // 'A' - exp_lower, // 'e' - exp_upper, // 'E' - fixed_lower, // 'f' - fixed_upper, // 'F' - general_lower, // 'g' - general_upper, // 'G' - chr, // 'c' - string, // 's' - pointer, // 'p' - debug // '?' -}; - -// Format specifiers for built-in and string types. -template struct format_specs { - int width; - int precision; - presentation_type type; - align_t align : 4; - sign_t sign : 3; - bool alt : 1; // Alternate form ('#'). - bool localized : 1; - detail::fill_t fill; - - constexpr format_specs() - : width(0), - precision(-1), - type(presentation_type::none), - align(align::none), - sign(sign::none), - alt(false), - localized(false) {} -}; - -namespace detail { - -enum class arg_id_kind { none, index, name }; - -// An argument reference. -template struct arg_ref { - FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {} - - FMT_CONSTEXPR explicit arg_ref(int index) - : kind(arg_id_kind::index), val(index) {} - FMT_CONSTEXPR explicit arg_ref(basic_string_view name) - : kind(arg_id_kind::name), val(name) {} - - FMT_CONSTEXPR auto operator=(int idx) -> arg_ref& { - kind = arg_id_kind::index; - val.index = idx; - return *this; - } - - arg_id_kind kind; - union value { - FMT_CONSTEXPR value(int idx = 0) : index(idx) {} - FMT_CONSTEXPR value(basic_string_view n) : name(n) {} - - int index; - basic_string_view name; - } val; -}; - -// Format specifiers with width and precision resolved at formatting rather -// than parsing time to allow reusing the same parsed specifiers with -// different sets of arguments (precompilation of format strings). -template -struct dynamic_format_specs : format_specs { - arg_ref width_ref; - arg_ref precision_ref; -}; - -// Converts a character to ASCII. Returns '\0' on conversion failure. -template ::value)> -constexpr auto to_ascii(Char c) -> char { - return c <= 0xff ? static_cast(c) : '\0'; -} -template ::value)> -constexpr auto to_ascii(Char c) -> char { - return c <= 0xff ? static_cast(c) : '\0'; -} - -// Returns the number of code units in a code point or 1 on error. -template -FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { - if (const_check(sizeof(Char) != 1)) return 1; - auto c = static_cast(*begin); - return static_cast((0x3a55000000000000ull >> (2 * (c >> 3))) & 0x3) + 1; -} - -// Return the result via the out param to workaround gcc bug 77539. -template -FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool { - for (out = first; out != last; ++out) { - if (*out == value) return true; - } - return false; -} - -template <> -inline auto find(const char* first, const char* last, char value, - const char*& out) -> bool { - out = static_cast( - std::memchr(first, value, to_unsigned(last - first))); - return out != nullptr; -} - -// Parses the range [begin, end) as an unsigned integer. This function assumes -// that the range is non-empty and the first character is a digit. -template -FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end, - int error_value) noexcept -> int { - FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); - unsigned value = 0, prev = 0; - auto p = begin; - do { - prev = value; - value = value * 10 + unsigned(*p - '0'); - ++p; - } while (p != end && '0' <= *p && *p <= '9'); - auto num_digits = p - begin; - begin = p; - if (num_digits <= std::numeric_limits::digits10) - return static_cast(value); - // Check for overflow. - const unsigned max = to_unsigned((std::numeric_limits::max)()); - return num_digits == std::numeric_limits::digits10 + 1 && - prev * 10ull + unsigned(p[-1] - '0') <= max - ? static_cast(value) - : error_value; -} - -FMT_CONSTEXPR inline auto parse_align(char c) -> align_t { - switch (c) { - case '<': - return align::left; - case '>': - return align::right; - case '^': - return align::center; - } - return align::none; -} - -template constexpr auto is_name_start(Char c) -> bool { - return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '_'; -} - -template -FMT_CONSTEXPR auto do_parse_arg_id(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { - Char c = *begin; - if (c >= '0' && c <= '9') { - int index = 0; - constexpr int max = (std::numeric_limits::max)(); - if (c != '0') - index = parse_nonnegative_int(begin, end, max); - else - ++begin; - if (begin == end || (*begin != '}' && *begin != ':')) - throw_format_error("invalid format string"); - else - handler.on_index(index); - return begin; - } - if (!is_name_start(c)) { - throw_format_error("invalid format string"); - return begin; - } - auto it = begin; - do { - ++it; - } while (it != end && (is_name_start(*it) || ('0' <= *it && *it <= '9'))); - handler.on_name({begin, to_unsigned(it - begin)}); - return it; -} - -template -FMT_CONSTEXPR FMT_INLINE auto parse_arg_id(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { - FMT_ASSERT(begin != end, ""); - Char c = *begin; - if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler); - handler.on_auto(); - return begin; -} - -template struct dynamic_spec_id_handler { - basic_format_parse_context& ctx; - arg_ref& ref; - - FMT_CONSTEXPR void on_auto() { - int id = ctx.next_arg_id(); - ref = arg_ref(id); - ctx.check_dynamic_spec(id); - } - FMT_CONSTEXPR void on_index(int id) { - ref = arg_ref(id); - ctx.check_arg_id(id); - ctx.check_dynamic_spec(id); - } - FMT_CONSTEXPR void on_name(basic_string_view id) { - ref = arg_ref(id); - ctx.check_arg_id(id); - } -}; - -// Parses [integer | "{" [arg_id] "}"]. -template -FMT_CONSTEXPR auto parse_dynamic_spec(const Char* begin, const Char* end, - int& value, arg_ref& ref, - basic_format_parse_context& ctx) - -> const Char* { - FMT_ASSERT(begin != end, ""); - if ('0' <= *begin && *begin <= '9') { - int val = parse_nonnegative_int(begin, end, -1); - if (val != -1) - value = val; - else - throw_format_error("number is too big"); - } else if (*begin == '{') { - ++begin; - auto handler = dynamic_spec_id_handler{ctx, ref}; - if (begin != end) begin = parse_arg_id(begin, end, handler); - if (begin != end && *begin == '}') return ++begin; - throw_format_error("invalid format string"); - } - return begin; -} - -template -FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, - int& value, arg_ref& ref, - basic_format_parse_context& ctx) - -> const Char* { - ++begin; - if (begin == end || *begin == '}') { - throw_format_error("invalid precision"); - return begin; - } - return parse_dynamic_spec(begin, end, value, ref, ctx); -} - -enum class state { start, align, sign, hash, zero, width, precision, locale }; - -// Parses standard format specifiers. -template -FMT_CONSTEXPR FMT_INLINE auto parse_format_specs( - const Char* begin, const Char* end, dynamic_format_specs& specs, - basic_format_parse_context& ctx, type arg_type) -> const Char* { - auto c = '\0'; - if (end - begin > 1) { - auto next = to_ascii(begin[1]); - c = parse_align(next) == align::none ? to_ascii(*begin) : '\0'; - } else { - if (begin == end) return begin; - c = to_ascii(*begin); - } - - struct { - state current_state = state::start; - FMT_CONSTEXPR void operator()(state s, bool valid = true) { - if (current_state >= s || !valid) - throw_format_error("invalid format specifier"); - current_state = s; - } - } enter_state; - - using pres = presentation_type; - constexpr auto integral_set = sint_set | uint_set | bool_set | char_set; - struct { - const Char*& begin; - dynamic_format_specs& specs; - type arg_type; - - FMT_CONSTEXPR auto operator()(pres type, int set) -> const Char* { - if (!in(arg_type, set)) throw_format_error("invalid format specifier"); - specs.type = type; - return begin + 1; - } - } parse_presentation_type{begin, specs, arg_type}; - - for (;;) { - switch (c) { - case '<': - case '>': - case '^': - enter_state(state::align); - specs.align = parse_align(c); - ++begin; - break; - case '+': - case '-': - case ' ': - enter_state(state::sign, in(arg_type, sint_set | float_set)); - switch (c) { - case '+': - specs.sign = sign::plus; - break; - case '-': - specs.sign = sign::minus; - break; - case ' ': - specs.sign = sign::space; - break; - } - ++begin; - break; - case '#': - enter_state(state::hash, is_arithmetic_type(arg_type)); - specs.alt = true; - ++begin; - break; - case '0': - enter_state(state::zero); - if (!is_arithmetic_type(arg_type)) - throw_format_error("format specifier requires numeric argument"); - if (specs.align == align::none) { - // Ignore 0 if align is specified for compatibility with std::format. - specs.align = align::numeric; - specs.fill[0] = Char('0'); - } - ++begin; - break; - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '{': - enter_state(state::width); - begin = parse_dynamic_spec(begin, end, specs.width, specs.width_ref, ctx); - break; - case '.': - enter_state(state::precision, - in(arg_type, float_set | string_set | cstring_set)); - begin = parse_precision(begin, end, specs.precision, specs.precision_ref, - ctx); - break; - case 'L': - enter_state(state::locale, is_arithmetic_type(arg_type)); - specs.localized = true; - ++begin; - break; - case 'd': - return parse_presentation_type(pres::dec, integral_set); - case 'o': - return parse_presentation_type(pres::oct, integral_set); - case 'x': - return parse_presentation_type(pres::hex_lower, integral_set); - case 'X': - return parse_presentation_type(pres::hex_upper, integral_set); - case 'b': - return parse_presentation_type(pres::bin_lower, integral_set); - case 'B': - return parse_presentation_type(pres::bin_upper, integral_set); - case 'a': - return parse_presentation_type(pres::hexfloat_lower, float_set); - case 'A': - return parse_presentation_type(pres::hexfloat_upper, float_set); - case 'e': - return parse_presentation_type(pres::exp_lower, float_set); - case 'E': - return parse_presentation_type(pres::exp_upper, float_set); - case 'f': - return parse_presentation_type(pres::fixed_lower, float_set); - case 'F': - return parse_presentation_type(pres::fixed_upper, float_set); - case 'g': - return parse_presentation_type(pres::general_lower, float_set); - case 'G': - return parse_presentation_type(pres::general_upper, float_set); - case 'c': - return parse_presentation_type(pres::chr, integral_set); - case 's': - return parse_presentation_type(pres::string, - bool_set | string_set | cstring_set); - case 'p': - return parse_presentation_type(pres::pointer, pointer_set | cstring_set); - case '?': - return parse_presentation_type(pres::debug, - char_set | string_set | cstring_set); - case '}': - return begin; - default: { - if (*begin == '}') return begin; - // Parse fill and alignment. - auto fill_end = begin + code_point_length(begin); - if (end - fill_end <= 0) { - throw_format_error("invalid format specifier"); - return begin; - } - if (*begin == '{') { - throw_format_error("invalid fill character '{'"); - return begin; - } - auto align = parse_align(to_ascii(*fill_end)); - enter_state(state::align, align != align::none); - specs.fill = {begin, to_unsigned(fill_end - begin)}; - specs.align = align; - begin = fill_end + 1; - } - } - if (begin == end) return begin; - c = to_ascii(*begin); - } -} - -template -FMT_CONSTEXPR auto parse_replacement_field(const Char* begin, const Char* end, - Handler&& handler) -> const Char* { - struct id_adapter { - Handler& handler; - int arg_id; - - FMT_CONSTEXPR void on_auto() { arg_id = handler.on_arg_id(); } - FMT_CONSTEXPR void on_index(int id) { arg_id = handler.on_arg_id(id); } - FMT_CONSTEXPR void on_name(basic_string_view id) { - arg_id = handler.on_arg_id(id); - } - }; - - ++begin; - if (begin == end) return handler.on_error("invalid format string"), end; - if (*begin == '}') { - handler.on_replacement_field(handler.on_arg_id(), begin); - } else if (*begin == '{') { - handler.on_text(begin, begin + 1); - } else { - auto adapter = id_adapter{handler, 0}; - begin = parse_arg_id(begin, end, adapter); - Char c = begin != end ? *begin : Char(); - if (c == '}') { - handler.on_replacement_field(adapter.arg_id, begin); - } else if (c == ':') { - begin = handler.on_format_specs(adapter.arg_id, begin + 1, end); - if (begin == end || *begin != '}') - return handler.on_error("unknown format specifier"), end; - } else { - return handler.on_error("missing '}' in format string"), end; - } - } - return begin + 1; -} - -template -FMT_CONSTEXPR FMT_INLINE void parse_format_string( - basic_string_view format_str, Handler&& handler) { - auto begin = format_str.data(); - auto end = begin + format_str.size(); - if (end - begin < 32) { - // Use a simple loop instead of memchr for small strings. - const Char* p = begin; - while (p != end) { - auto c = *p++; - if (c == '{') { - handler.on_text(begin, p - 1); - begin = p = parse_replacement_field(p - 1, end, handler); - } else if (c == '}') { - if (p == end || *p != '}') - return handler.on_error("unmatched '}' in format string"); - handler.on_text(begin, p); - begin = ++p; - } - } - handler.on_text(begin, end); - return; - } - struct writer { - FMT_CONSTEXPR void operator()(const Char* from, const Char* to) { - if (from == to) return; - for (;;) { - const Char* p = nullptr; - if (!find(from, to, Char('}'), p)) - return handler_.on_text(from, to); - ++p; - if (p == to || *p != '}') - return handler_.on_error("unmatched '}' in format string"); - handler_.on_text(from, p); - from = p + 1; - } - } - Handler& handler_; - } write = {handler}; - while (begin != end) { - // Doing two passes with memchr (one for '{' and another for '}') is up to - // 2.5x faster than the naive one-pass implementation on big format strings. - const Char* p = begin; - if (*begin != '{' && !find(begin + 1, end, Char('{'), p)) - return write(begin, end); - write(begin, p); - begin = parse_replacement_field(p, end, handler); - } -} - -template ::value> struct strip_named_arg { - using type = T; -}; -template struct strip_named_arg { - using type = remove_cvref_t; -}; - -template -FMT_CONSTEXPR auto parse_format_specs(ParseContext& ctx) - -> decltype(ctx.begin()) { - using char_type = typename ParseContext::char_type; - using context = buffer_context; - using mapped_type = conditional_t< - mapped_type_constant::value != type::custom_type, - decltype(arg_mapper().map(std::declval())), - typename strip_named_arg::type>; -#if defined(__cpp_if_constexpr) - if constexpr (std::is_default_constructible_v< - formatter>) { - return formatter().parse(ctx); - } else { - type_is_unformattable_for _; - return ctx.begin(); - } -#else - return formatter().parse(ctx); -#endif -} - -// Checks char specs and returns true iff the presentation type is char-like. -template -FMT_CONSTEXPR auto check_char_specs(const format_specs& specs) -> bool { - if (specs.type != presentation_type::none && - specs.type != presentation_type::chr && - specs.type != presentation_type::debug) { - return false; - } - if (specs.align == align::numeric || specs.sign != sign::none || specs.alt) - throw_format_error("invalid format specifier for char"); - return true; -} - -#if FMT_USE_NONTYPE_TEMPLATE_ARGS -template -constexpr auto get_arg_index_by_name(basic_string_view name) -> int { - if constexpr (is_statically_named_arg()) { - if (name == T::name) return N; - } - if constexpr (sizeof...(Args) > 0) - return get_arg_index_by_name(name); - (void)name; // Workaround an MSVC bug about "unused" parameter. - return -1; -} -#endif - -template -FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view name) -> int { -#if FMT_USE_NONTYPE_TEMPLATE_ARGS - if constexpr (sizeof...(Args) > 0) - return get_arg_index_by_name<0, Args...>(name); -#endif - (void)name; - return -1; -} - -template class format_string_checker { - private: - using parse_context_type = compile_parse_context; - static constexpr int num_args = sizeof...(Args); - - // Format specifier parsing function. - // In the future basic_format_parse_context will replace compile_parse_context - // here and will use is_constant_evaluated and downcasting to access the data - // needed for compile-time checks: https://godbolt.org/z/GvWzcTjh1. - using parse_func = const Char* (*)(parse_context_type&); - - type types_[num_args > 0 ? static_cast(num_args) : 1]; - parse_context_type context_; - parse_func parse_funcs_[num_args > 0 ? static_cast(num_args) : 1]; - - public: - explicit FMT_CONSTEXPR format_string_checker(basic_string_view fmt) - : types_{mapped_type_constant>::value...}, - context_(fmt, num_args, types_), - parse_funcs_{&parse_format_specs...} {} - - FMT_CONSTEXPR void on_text(const Char*, const Char*) {} - - FMT_CONSTEXPR auto on_arg_id() -> int { return context_.next_arg_id(); } - FMT_CONSTEXPR auto on_arg_id(int id) -> int { - return context_.check_arg_id(id), id; - } - FMT_CONSTEXPR auto on_arg_id(basic_string_view id) -> int { -#if FMT_USE_NONTYPE_TEMPLATE_ARGS - auto index = get_arg_index_by_name(id); - if (index < 0) on_error("named argument is not found"); - return index; -#else - (void)id; - on_error("compile-time checks for named arguments require C++20 support"); - return 0; -#endif - } - - FMT_CONSTEXPR void on_replacement_field(int id, const Char* begin) { - on_format_specs(id, begin, begin); // Call parse() on empty specs. - } - - FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char*) - -> const Char* { - context_.advance_to(begin); - // id >= 0 check is a workaround for gcc 10 bug (#2065). - return id >= 0 && id < num_args ? parse_funcs_[id](context_) : begin; - } - - FMT_CONSTEXPR void on_error(const char* message) { - throw_format_error(message); - } -}; - -// Reports a compile-time error if S is not a valid format string. -template ::value)> -FMT_INLINE void check_format_string(const S&) { -#ifdef FMT_ENFORCE_COMPILE_STRING - static_assert(is_compile_string::value, - "FMT_ENFORCE_COMPILE_STRING requires all format strings to use " - "FMT_STRING."); -#endif -} -template ::value)> -void check_format_string(S format_str) { - using char_t = typename S::char_type; - FMT_CONSTEXPR auto s = basic_string_view(format_str); - using checker = format_string_checker...>; - FMT_CONSTEXPR bool error = (parse_format_string(s, checker(s)), true); - ignore_unused(error); -} - -template struct vformat_args { - using type = basic_format_args< - basic_format_context>, Char>>; -}; -template <> struct vformat_args { using type = format_args; }; - -// Use vformat_args and avoid type_identity to keep symbols short. -template -void vformat_to(buffer& buf, basic_string_view fmt, - typename vformat_args::type args, locale_ref loc = {}); - -FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); -#ifndef _WIN32 -inline void vprint_mojibake(std::FILE*, string_view, format_args) {} -#endif -} // namespace detail - -FMT_BEGIN_EXPORT - -// A formatter specialization for natively supported types. -template -struct formatter::value != - detail::type::custom_type>> { - private: - detail::dynamic_format_specs specs_; - - public: - template - FMT_CONSTEXPR auto parse(ParseContext& ctx) -> const Char* { - auto type = detail::type_constant::value; - auto end = - detail::parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx, type); - if (type == detail::type::char_type) detail::check_char_specs(specs_); - return end; - } - - template ::value, - FMT_ENABLE_IF(U == detail::type::string_type || - U == detail::type::cstring_type || - U == detail::type::char_type)> - FMT_CONSTEXPR void set_debug_format(bool set = true) { - specs_.type = set ? presentation_type::debug : presentation_type::none; - } - - template - FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const - -> decltype(ctx.out()); -}; - -template struct runtime_format_string { - basic_string_view str; -}; - -/** A compile-time format string. */ -template class basic_format_string { - private: - basic_string_view str_; - - public: - template >::value)> - FMT_CONSTEVAL FMT_INLINE basic_format_string(const S& s) : str_(s) { - static_assert( - detail::count< - (std::is_base_of>::value && - std::is_reference::value)...>() == 0, - "passing views as lvalues is disallowed"); -#ifdef FMT_HAS_CONSTEVAL - if constexpr (detail::count_named_args() == - detail::count_statically_named_args()) { - using checker = - detail::format_string_checker...>; - detail::parse_format_string(str_, checker(s)); - } -#else - detail::check_format_string(s); -#endif - } - basic_format_string(runtime_format_string fmt) : str_(fmt.str) {} - - FMT_INLINE operator basic_string_view() const { return str_; } - FMT_INLINE auto get() const -> basic_string_view { return str_; } -}; - -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 -// Workaround broken conversion on older gcc. -template using format_string = string_view; -inline auto runtime(string_view s) -> string_view { return s; } -#else -template -using format_string = basic_format_string...>; -/** - \rst - Creates a runtime format string. - - **Example**:: - - // Check format string at runtime instead of compile-time. - fmt::print(fmt::runtime("{:d}"), "I am not a number"); - \endrst - */ -inline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; } -#endif - -FMT_API auto vformat(string_view fmt, format_args args) -> std::string; - -/** - \rst - Formats ``args`` according to specifications in ``fmt`` and returns the result - as a string. - - **Example**:: - - #include - std::string message = fmt::format("The answer is {}.", 42); - \endrst -*/ -template -FMT_NODISCARD FMT_INLINE auto format(format_string fmt, T&&... args) - -> std::string { - return vformat(fmt, fmt::make_format_args(args...)); -} - -/** Formats a string and writes the output to ``out``. */ -template ::value)> -auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt { - auto&& buf = detail::get_buffer(out); - detail::vformat_to(buf, fmt, args, {}); - return detail::get_iterator(buf, out); -} - -/** - \rst - Formats ``args`` according to specifications in ``fmt``, writes the result to - the output iterator ``out`` and returns the iterator past the end of the output - range. `format_to` does not append a terminating null character. - - **Example**:: - - auto out = std::vector(); - fmt::format_to(std::back_inserter(out), "{}", 42); - \endrst - */ -template ::value)> -FMT_INLINE auto format_to(OutputIt out, format_string fmt, T&&... args) - -> OutputIt { - return vformat_to(out, fmt, fmt::make_format_args(args...)); -} - -template struct format_to_n_result { - /** Iterator past the end of the output range. */ - OutputIt out; - /** Total (not truncated) output size. */ - size_t size; -}; - -template ::value)> -auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args) - -> format_to_n_result { - using traits = detail::fixed_buffer_traits; - auto buf = detail::iterator_buffer(out, n); - detail::vformat_to(buf, fmt, args, {}); - return {buf.out(), buf.count()}; -} - -/** - \rst - Formats ``args`` according to specifications in ``fmt``, writes up to ``n`` - characters of the result to the output iterator ``out`` and returns the total - (not truncated) output size and the iterator past the end of the output range. - `format_to_n` does not append a terminating null character. - \endrst - */ -template ::value)> -FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string fmt, - T&&... args) -> format_to_n_result { - return vformat_to_n(out, n, fmt, fmt::make_format_args(args...)); -} - -/** Returns the number of chars in the output of ``format(fmt, args...)``. */ -template -FMT_NODISCARD FMT_INLINE auto formatted_size(format_string fmt, - T&&... args) -> size_t { - auto buf = detail::counting_buffer<>(); - detail::vformat_to(buf, fmt, fmt::make_format_args(args...), {}); - return buf.count(); -} - -FMT_API void vprint(string_view fmt, format_args args); -FMT_API void vprint(std::FILE* f, string_view fmt, format_args args); - -/** - \rst - Formats ``args`` according to specifications in ``fmt`` and writes the output - to ``stdout``. - - **Example**:: - - fmt::print("Elapsed time: {0:.2f} seconds", 1.23); - \endrst - */ -template -FMT_INLINE void print(format_string fmt, T&&... args) { - const auto& vargs = fmt::make_format_args(args...); - return detail::is_utf8() ? vprint(fmt, vargs) - : detail::vprint_mojibake(stdout, fmt, vargs); -} - -/** - \rst - Formats ``args`` according to specifications in ``fmt`` and writes the - output to the file ``f``. - - **Example**:: - - fmt::print(stderr, "Don't {}!", "panic"); - \endrst - */ -template -FMT_INLINE void print(std::FILE* f, format_string fmt, T&&... args) { - const auto& vargs = fmt::make_format_args(args...); - return detail::is_utf8() ? vprint(f, fmt, vargs) - : detail::vprint_mojibake(f, fmt, vargs); -} - -/** - Formats ``args`` according to specifications in ``fmt`` and writes the - output to the file ``f`` followed by a newline. - */ -template -FMT_INLINE void println(std::FILE* f, format_string fmt, T&&... args) { - return fmt::print(f, "{}\n", fmt::format(fmt, std::forward(args)...)); -} - -/** - Formats ``args`` according to specifications in ``fmt`` and writes the output - to ``stdout`` followed by a newline. - */ -template -FMT_INLINE void println(format_string fmt, T&&... args) { - return fmt::println(stdout, fmt, std::forward(args)...); -} - -FMT_END_EXPORT -FMT_GCC_PRAGMA("GCC pop_options") -FMT_END_NAMESPACE - -#ifdef FMT_HEADER_ONLY -# include "format.h" -#endif -#endif // FMT_CORE_H_ +#include "format.h" diff --git a/thirdparty/fmt/include/fmt/format-inl.h b/thirdparty/fmt/include/fmt/format-inl.h index dac2d437a..a5b79dbe4 100644 --- a/thirdparty/fmt/include/fmt/format-inl.h +++ b/thirdparty/fmt/include/fmt/format-inl.h @@ -8,36 +8,36 @@ #ifndef FMT_FORMAT_INL_H_ #define FMT_FORMAT_INL_H_ -#include -#include // errno -#include -#include -#include - -#ifndef FMT_STATIC_THOUSANDS_SEPARATOR -# include +#ifndef FMT_MODULE +# include +# include // errno +# include +# include +# include #endif -#ifdef _WIN32 +#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) # include // _isatty #endif #include "format.h" +#if FMT_USE_LOCALE +# include +#endif + +#ifndef FMT_FUNC +# define FMT_FUNC +#endif + FMT_BEGIN_NAMESPACE namespace detail { FMT_FUNC void assert_fail(const char* file, int line, const char* message) { // Use unchecked std::fprintf to avoid triggering another assertion when - // writing to stderr fails - std::fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message); - // Chosen instead of std::abort to satisfy Clang in CUDA mode during device - // code pass. - std::terminate(); -} - -FMT_FUNC void throw_format_error(const char* message) { - FMT_THROW(format_error(message)); + // writing to stderr fails. + fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message); + abort(); } FMT_FUNC void format_error_code(detail::buffer& out, int error_code, @@ -56,112 +56,129 @@ FMT_FUNC void format_error_code(detail::buffer& out, int error_code, ++error_code_size; } error_code_size += detail::to_unsigned(detail::count_digits(abs_value)); - auto it = buffer_appender(out); + auto it = appender(out); if (message.size() <= inline_buffer_size - error_code_size) - format_to(it, FMT_STRING("{}{}"), message, SEP); - format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code); + fmt::format_to(it, FMT_STRING("{}{}"), message, SEP); + fmt::format_to(it, FMT_STRING("{}{}"), ERROR_STR, error_code); FMT_ASSERT(out.size() <= inline_buffer_size, ""); } -FMT_FUNC void report_error(format_func func, int error_code, - const char* message) noexcept { +FMT_FUNC void do_report_error(format_func func, int error_code, + const char* message) noexcept { memory_buffer full_message; func(full_message, error_code, message); - // Don't use fwrite_fully because the latter may throw. + // Don't use fwrite_all because the latter may throw. if (std::fwrite(full_message.data(), full_message.size(), 1, stderr) > 0) std::fputc('\n', stderr); } // A wrapper around fwrite that throws on error. -inline void fwrite_fully(const void* ptr, size_t size, size_t count, - FILE* stream) { - size_t written = std::fwrite(ptr, size, count, stream); +inline void fwrite_all(const void* ptr, size_t count, FILE* stream) { + size_t written = std::fwrite(ptr, 1, count, stream); if (written < count) FMT_THROW(system_error(errno, FMT_STRING("cannot write to file"))); } -#ifndef FMT_STATIC_THOUSANDS_SEPARATOR +#if FMT_USE_LOCALE +using std::locale; +using std::numpunct; +using std::use_facet; + template locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { - static_assert(std::is_same::value, ""); + static_assert(std::is_same::value, ""); } +#else +struct locale {}; +template struct numpunct { + auto grouping() const -> std::string { return "\03"; } + auto thousands_sep() const -> Char { return ','; } + auto decimal_point() const -> Char { return '.'; } +}; +template Facet use_facet(locale) { return {}; } +#endif // FMT_USE_LOCALE -template Locale locale_ref::get() const { - static_assert(std::is_same::value, ""); - return locale_ ? *static_cast(locale_) : std::locale(); +template auto locale_ref::get() const -> Locale { + static_assert(std::is_same::value, ""); +#if FMT_USE_LOCALE + if (locale_) return *static_cast(locale_); +#endif + return locale(); } template FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result { - auto& facet = std::use_facet>(loc.get()); + auto&& facet = use_facet>(loc.get()); auto grouping = facet.grouping(); auto thousands_sep = grouping.empty() ? Char() : facet.thousands_sep(); return {std::move(grouping), thousands_sep}; } -template FMT_FUNC Char decimal_point_impl(locale_ref loc) { - return std::use_facet>(loc.get()) - .decimal_point(); -} -#else template -FMT_FUNC auto thousands_sep_impl(locale_ref) -> thousands_sep_result { - return {"\03", FMT_STATIC_THOUSANDS_SEPARATOR}; +FMT_FUNC auto decimal_point_impl(locale_ref loc) -> Char { + return use_facet>(loc.get()).decimal_point(); } -template FMT_FUNC Char decimal_point_impl(locale_ref) { - return '.'; -} -#endif +#if FMT_USE_LOCALE FMT_FUNC auto write_loc(appender out, loc_value value, - const format_specs<>& specs, locale_ref loc) -> bool { -#ifndef FMT_STATIC_THOUSANDS_SEPARATOR + const format_specs& specs, locale_ref loc) -> bool { auto locale = loc.get(); // We cannot use the num_put facet because it may produce output in // a wrong encoding. using facet = format_facet; if (std::has_facet(locale)) - return std::use_facet(locale).put(out, value, specs); + return use_facet(locale).put(out, value, specs); return facet(locale).put(out, value, specs); -#endif - return false; } +#endif } // namespace detail +FMT_FUNC void report_error(const char* message) { +#if FMT_USE_EXCEPTIONS + // Use FMT_THROW instead of throw to avoid bogus unreachable code warnings + // from MSVC. + FMT_THROW(format_error(message)); +#else + fputs(message, stderr); + abort(); +#endif +} + template typename Locale::id format_facet::id; -#ifndef FMT_STATIC_THOUSANDS_SEPARATOR template format_facet::format_facet(Locale& loc) { - auto& numpunct = std::use_facet>(loc); - grouping_ = numpunct.grouping(); - if (!grouping_.empty()) separator_ = std::string(1, numpunct.thousands_sep()); + auto& np = detail::use_facet>(loc); + grouping_ = np.grouping(); + if (!grouping_.empty()) separator_ = std::string(1, np.thousands_sep()); } +#if FMT_USE_LOCALE template <> FMT_API FMT_FUNC auto format_facet::do_put( - appender out, loc_value val, const format_specs<>& specs) const -> bool { + appender out, loc_value val, const format_specs& specs) const -> bool { return val.visit( detail::loc_writer<>{out, specs, separator_, grouping_, decimal_point_}); } #endif -FMT_FUNC std::system_error vsystem_error(int error_code, string_view fmt, - format_args args) { +FMT_FUNC auto vsystem_error(int error_code, string_view fmt, format_args args) + -> std::system_error { auto ec = std::error_code(error_code, std::generic_category()); return std::system_error(ec, vformat(fmt, args)); } namespace detail { -template inline bool operator==(basic_fp x, basic_fp y) { +template +inline auto operator==(basic_fp x, basic_fp y) -> bool { return x.f == y.f && x.e == y.e; } // Compilers should be able to optimize this into the ror instruction. -FMT_CONSTEXPR inline uint32_t rotr(uint32_t n, uint32_t r) noexcept { +FMT_CONSTEXPR inline auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t { r &= 31; return (n >> r) | (n << (32 - r)); } -FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept { +FMT_CONSTEXPR inline auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t { r &= 63; return (n >> r) | (n << (64 - r)); } @@ -170,14 +187,14 @@ FMT_CONSTEXPR inline uint64_t rotr(uint64_t n, uint32_t r) noexcept { namespace dragonbox { // Computes upper 64 bits of multiplication of a 32-bit unsigned integer and a // 64-bit unsigned integer. -inline uint64_t umul96_upper64(uint32_t x, uint64_t y) noexcept { +inline auto umul96_upper64(uint32_t x, uint64_t y) noexcept -> uint64_t { return umul128_upper64(static_cast(x) << 32, y); } // Computes lower 128 bits of multiplication of a 64-bit unsigned integer and a // 128-bit unsigned integer. -inline uint128_fallback umul192_lower128(uint64_t x, - uint128_fallback y) noexcept { +inline auto umul192_lower128(uint64_t x, uint128_fallback y) noexcept + -> uint128_fallback { uint64_t high = x * y.high(); uint128_fallback high_low = umul128(x, y.low()); return {high + high_low.high(), high_low.low()}; @@ -185,12 +202,12 @@ inline uint128_fallback umul192_lower128(uint64_t x, // Computes lower 64 bits of multiplication of a 32-bit unsigned integer and a // 64-bit unsigned integer. -inline uint64_t umul96_lower64(uint32_t x, uint64_t y) noexcept { +inline auto umul96_lower64(uint32_t x, uint64_t y) noexcept -> uint64_t { return x * y; } // Various fast log computations. -inline int floor_log10_pow2_minus_log10_4_over_3(int e) noexcept { +inline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int { FMT_ASSERT(e <= 2936 && e >= -2985, "too large exponent"); return (e * 631305 - 261663) >> 21; } @@ -204,7 +221,7 @@ FMT_INLINE_VARIABLE constexpr struct { // divisible by pow(10, N). // Precondition: n <= pow(10, N + 1). template -bool check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept { +auto check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept -> bool { // The numbers below are chosen such that: // 1. floor(n/d) = floor(nm / 2^k) where d=10 or d=100, // 2. nm mod 2^k < m if and only if n is divisible by d, @@ -229,7 +246,7 @@ bool check_divisibility_and_divide_by_pow10(uint32_t& n) noexcept { // Computes floor(n / pow(10, N)) for small n and N. // Precondition: n <= pow(10, N + 1). -template uint32_t small_division_by_pow10(uint32_t n) noexcept { +template auto small_division_by_pow10(uint32_t n) noexcept -> uint32_t { constexpr auto info = div_small_pow10_infos[N - 1]; FMT_ASSERT(n <= info.divisor * 10, "n is too large"); constexpr uint32_t magic_number = @@ -238,12 +255,12 @@ template uint32_t small_division_by_pow10(uint32_t n) noexcept { } // Computes floor(n / 10^(kappa + 1)) (float) -inline uint32_t divide_by_10_to_kappa_plus_1(uint32_t n) noexcept { +inline auto divide_by_10_to_kappa_plus_1(uint32_t n) noexcept -> uint32_t { // 1374389535 = ceil(2^37/100) return static_cast((static_cast(n) * 1374389535) >> 37); } // Computes floor(n / 10^(kappa + 1)) (double) -inline uint64_t divide_by_10_to_kappa_plus_1(uint64_t n) noexcept { +inline auto divide_by_10_to_kappa_plus_1(uint64_t n) noexcept -> uint64_t { // 2361183241434822607 = ceil(2^(64+7)/1000) return umul128_upper64(n, 2361183241434822607ull) >> 7; } @@ -255,7 +272,7 @@ template <> struct cache_accessor { using carrier_uint = float_info::carrier_uint; using cache_entry_type = uint64_t; - static uint64_t get_cached_power(int k) noexcept { + static auto get_cached_power(int k) noexcept -> uint64_t { FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, "k is out of range"); static constexpr const uint64_t pow10_significands[] = { @@ -297,20 +314,23 @@ template <> struct cache_accessor { bool is_integer; }; - static compute_mul_result compute_mul( - carrier_uint u, const cache_entry_type& cache) noexcept { + static auto compute_mul(carrier_uint u, + const cache_entry_type& cache) noexcept + -> compute_mul_result { auto r = umul96_upper64(u, cache); return {static_cast(r >> 32), static_cast(r) == 0}; } - static uint32_t compute_delta(const cache_entry_type& cache, - int beta) noexcept { + static auto compute_delta(const cache_entry_type& cache, int beta) noexcept + -> uint32_t { return static_cast(cache >> (64 - 1 - beta)); } - static compute_mul_parity_result compute_mul_parity( - carrier_uint two_f, const cache_entry_type& cache, int beta) noexcept { + static auto compute_mul_parity(carrier_uint two_f, + const cache_entry_type& cache, + int beta) noexcept + -> compute_mul_parity_result { FMT_ASSERT(beta >= 1, ""); FMT_ASSERT(beta < 64, ""); @@ -319,22 +339,22 @@ template <> struct cache_accessor { static_cast(r >> (32 - beta)) == 0}; } - static carrier_uint compute_left_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { + static auto compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return static_cast( (cache - (cache >> (num_significand_bits() + 2))) >> (64 - num_significand_bits() - 1 - beta)); } - static carrier_uint compute_right_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { + static auto compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return static_cast( (cache + (cache >> (num_significand_bits() + 1))) >> (64 - num_significand_bits() - 1 - beta)); } - static carrier_uint compute_round_up_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { + static auto compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return (static_cast( cache >> (64 - num_significand_bits() - 2 - beta)) + 1) / @@ -346,7 +366,7 @@ template <> struct cache_accessor { using carrier_uint = float_info::carrier_uint; using cache_entry_type = uint128_fallback; - static uint128_fallback get_cached_power(int k) noexcept { + static auto get_cached_power(int k) noexcept -> uint128_fallback { FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, "k is out of range"); @@ -985,8 +1005,7 @@ template <> struct cache_accessor { {0xe0accfa875af45a7, 0x93eb1b80a33b8606}, {0x8c6c01c9498d8b88, 0xbc72f130660533c4}, {0xaf87023b9bf0ee6a, 0xeb8fad7c7f8680b5}, - { 0xdb68c2ca82ed2a05, - 0xa67398db9f6820e2 } + {0xdb68c2ca82ed2a05, 0xa67398db9f6820e2}, #else {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, {0xce5d73ff402d98e3, 0xfb0a3d212dc81290}, @@ -1071,19 +1090,22 @@ template <> struct cache_accessor { bool is_integer; }; - static compute_mul_result compute_mul( - carrier_uint u, const cache_entry_type& cache) noexcept { + static auto compute_mul(carrier_uint u, + const cache_entry_type& cache) noexcept + -> compute_mul_result { auto r = umul192_upper128(u, cache); return {r.high(), r.low() == 0}; } - static uint32_t compute_delta(cache_entry_type const& cache, - int beta) noexcept { + static auto compute_delta(cache_entry_type const& cache, int beta) noexcept + -> uint32_t { return static_cast(cache.high() >> (64 - 1 - beta)); } - static compute_mul_parity_result compute_mul_parity( - carrier_uint two_f, const cache_entry_type& cache, int beta) noexcept { + static auto compute_mul_parity(carrier_uint two_f, + const cache_entry_type& cache, + int beta) noexcept + -> compute_mul_parity_result { FMT_ASSERT(beta >= 1, ""); FMT_ASSERT(beta < 64, ""); @@ -1092,35 +1114,35 @@ template <> struct cache_accessor { ((r.high() << beta) | (r.low() >> (64 - beta))) == 0}; } - static carrier_uint compute_left_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { + static auto compute_left_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return (cache.high() - (cache.high() >> (num_significand_bits() + 2))) >> (64 - num_significand_bits() - 1 - beta); } - static carrier_uint compute_right_endpoint_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { + static auto compute_right_endpoint_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return (cache.high() + (cache.high() >> (num_significand_bits() + 1))) >> (64 - num_significand_bits() - 1 - beta); } - static carrier_uint compute_round_up_for_shorter_interval_case( - const cache_entry_type& cache, int beta) noexcept { + static auto compute_round_up_for_shorter_interval_case( + const cache_entry_type& cache, int beta) noexcept -> carrier_uint { return ((cache.high() >> (64 - num_significand_bits() - 2 - beta)) + 1) / 2; } }; -FMT_FUNC uint128_fallback get_cached_power(int k) noexcept { +FMT_FUNC auto get_cached_power(int k) noexcept -> uint128_fallback { return cache_accessor::get_cached_power(k); } // Various integer checks template -bool is_left_endpoint_integer_shorter_interval(int exponent) noexcept { +auto is_left_endpoint_integer_shorter_interval(int exponent) noexcept -> bool { const int case_shorter_interval_left_endpoint_lower_threshold = 2; const int case_shorter_interval_left_endpoint_upper_threshold = 3; return exponent >= case_shorter_interval_left_endpoint_lower_threshold && @@ -1132,7 +1154,7 @@ FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept { FMT_ASSERT(n != 0, ""); // Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1. constexpr uint32_t mod_inv_5 = 0xcccccccd; - constexpr uint32_t mod_inv_25 = 0xc28f5c29; // = mod_inv_5 * mod_inv_5 + constexpr uint32_t mod_inv_25 = 0xc28f5c29; // = mod_inv_5 * mod_inv_5 while (true) { auto q = rotr(n * mod_inv_25, 2); @@ -1168,7 +1190,7 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept { // If n is not divisible by 10^8, work with n itself. constexpr uint64_t mod_inv_5 = 0xcccccccccccccccd; - constexpr uint64_t mod_inv_25 = 0x8f5c28f5c28f5c29; // = mod_inv_5 * mod_inv_5 + constexpr uint64_t mod_inv_25 = 0x8f5c28f5c28f5c29; // mod_inv_5 * mod_inv_5 int s = 0; while (true) { @@ -1234,7 +1256,7 @@ FMT_INLINE decimal_fp shorter_interval_case(int exponent) noexcept { return ret_value; } -template decimal_fp to_decimal(T x) noexcept { +template auto to_decimal(T x) noexcept -> decimal_fp { // Step 1: integer promotion & Schubfach multiplier calculation. using carrier_uint = typename float_info::carrier_uint; @@ -1373,15 +1395,15 @@ template <> struct formatter { for (auto i = n.bigits_.size(); i > 0; --i) { auto value = n.bigits_[i - 1u]; if (first) { - out = format_to(out, FMT_STRING("{:x}"), value); + out = fmt::format_to(out, FMT_STRING("{:x}"), value); first = false; continue; } - out = format_to(out, FMT_STRING("{:08x}"), value); + out = fmt::format_to(out, FMT_STRING("{:08x}"), value); } if (n.exp_ > 0) - out = format_to(out, FMT_STRING("p{}"), - n.exp_ * detail::bigint::bigit_bits); + out = fmt::format_to(out, FMT_STRING("p{}"), + n.exp_ * detail::bigint::bigit_bits); return out; } }; @@ -1405,7 +1427,7 @@ FMT_FUNC void format_system_error(detail::buffer& out, int error_code, const char* message) noexcept { FMT_TRY { auto ec = std::error_code(error_code, std::generic_category()); - write(std::back_inserter(out), std::system_error(ec, message).what()); + detail::write(appender(out), std::system_error(ec, message).what()); return; } FMT_CATCH(...) {} @@ -1414,10 +1436,10 @@ FMT_FUNC void format_system_error(detail::buffer& out, int error_code, FMT_FUNC void report_system_error(int error_code, const char* message) noexcept { - report_error(format_system_error, error_code, message); + do_report_error(format_system_error, error_code, message); } -FMT_FUNC std::string vformat(string_view fmt, format_args args) { +FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string { // Don't optimize the "{}" case to keep the binary size small and because it // can be better optimized in fmt::format anyway. auto buffer = memory_buffer(); @@ -1426,42 +1448,307 @@ FMT_FUNC std::string vformat(string_view fmt, format_args args) { } namespace detail { -#ifndef _WIN32 -FMT_FUNC bool write_console(std::FILE*, string_view) { return false; } + +FMT_FUNC void vformat_to(buffer& buf, string_view fmt, format_args args, + locale_ref loc) { + auto out = appender(buf); + if (fmt.size() == 2 && equal2(fmt.data(), "{}")) + return args.get(0).visit(default_arg_formatter{out}); + parse_format_string( + fmt, format_handler{parse_context(fmt), {out, args, loc}}); +} + +template struct span { + T* data; + size_t size; +}; + +template auto flockfile(F* f) -> decltype(_lock_file(f)) { + _lock_file(f); +} +template auto funlockfile(F* f) -> decltype(_unlock_file(f)) { + _unlock_file(f); +} + +#ifndef getc_unlocked +template auto getc_unlocked(F* f) -> decltype(_fgetc_nolock(f)) { + return _fgetc_nolock(f); +} +#endif + +template +struct has_flockfile : std::false_type {}; + +template +struct has_flockfile()))>> + : std::true_type {}; + +// A FILE wrapper. F is FILE defined as a template parameter to make system API +// detection work. +template class file_base { + public: + F* file_; + + public: + file_base(F* file) : file_(file) {} + operator F*() const { return file_; } + + // Reads a code unit from the stream. + auto get() -> int { + int result = getc_unlocked(file_); + if (result == EOF && ferror(file_) != 0) + FMT_THROW(system_error(errno, FMT_STRING("getc failed"))); + return result; + } + + // Puts the code unit back into the stream buffer. + void unget(char c) { + if (ungetc(c, file_) == EOF) + FMT_THROW(system_error(errno, FMT_STRING("ungetc failed"))); + } + + void flush() { fflush(this->file_); } +}; + +// A FILE wrapper for glibc. +template class glibc_file : public file_base { + private: + enum { + line_buffered = 0x200, // _IO_LINE_BUF + unbuffered = 2 // _IO_UNBUFFERED + }; + + public: + using file_base::file_base; + + auto is_buffered() const -> bool { + return (this->file_->_flags & unbuffered) == 0; + } + + void init_buffer() { + if (this->file_->_IO_write_ptr) return; + // Force buffer initialization by placing and removing a char in a buffer. + assume(this->file_->_IO_write_ptr >= this->file_->_IO_write_end); + putc_unlocked(0, this->file_); + --this->file_->_IO_write_ptr; + } + + // Returns the file's read buffer. + auto get_read_buffer() const -> span { + auto ptr = this->file_->_IO_read_ptr; + return {ptr, to_unsigned(this->file_->_IO_read_end - ptr)}; + } + + // Returns the file's write buffer. + auto get_write_buffer() const -> span { + auto ptr = this->file_->_IO_write_ptr; + return {ptr, to_unsigned(this->file_->_IO_buf_end - ptr)}; + } + + void advance_write_buffer(size_t size) { this->file_->_IO_write_ptr += size; } + + bool needs_flush() const { + if ((this->file_->_flags & line_buffered) == 0) return false; + char* end = this->file_->_IO_write_end; + return memchr(end, '\n', to_unsigned(this->file_->_IO_write_ptr - end)); + } + + void flush() { fflush_unlocked(this->file_); } +}; + +// A FILE wrapper for Apple's libc. +template class apple_file : public file_base { + private: + enum { + line_buffered = 1, // __SNBF + unbuffered = 2 // __SLBF + }; + + public: + using file_base::file_base; + + auto is_buffered() const -> bool { + return (this->file_->_flags & unbuffered) == 0; + } + + void init_buffer() { + if (this->file_->_p) return; + // Force buffer initialization by placing and removing a char in a buffer. + putc_unlocked(0, this->file_); + --this->file_->_p; + ++this->file_->_w; + } + + auto get_read_buffer() const -> span { + return {reinterpret_cast(this->file_->_p), + to_unsigned(this->file_->_r)}; + } + + auto get_write_buffer() const -> span { + return {reinterpret_cast(this->file_->_p), + to_unsigned(this->file_->_bf._base + this->file_->_bf._size - + this->file_->_p)}; + } + + void advance_write_buffer(size_t size) { + this->file_->_p += size; + this->file_->_w -= size; + } + + bool needs_flush() const { + if ((this->file_->_flags & line_buffered) == 0) return false; + return memchr(this->file_->_p + this->file_->_w, '\n', + to_unsigned(-this->file_->_w)); + } +}; + +// A fallback FILE wrapper. +template class fallback_file : public file_base { + private: + char next_; // The next unconsumed character in the buffer. + bool has_next_ = false; + + public: + using file_base::file_base; + + auto is_buffered() const -> bool { return false; } + auto needs_flush() const -> bool { return false; } + void init_buffer() {} + + auto get_read_buffer() const -> span { + return {&next_, has_next_ ? 1u : 0u}; + } + + auto get_write_buffer() const -> span { return {nullptr, 0}; } + + void advance_write_buffer(size_t) {} + + auto get() -> int { + has_next_ = false; + return file_base::get(); + } + + void unget(char c) { + file_base::unget(c); + next_ = c; + has_next_ = true; + } +}; + +#ifndef FMT_USE_FALLBACK_FILE +# define FMT_USE_FALLBACK_FILE 0 +#endif + +template +auto get_file(F* f, int) -> apple_file { + return f; +} +template +inline auto get_file(F* f, int) -> glibc_file { + return f; +} + +inline auto get_file(FILE* f, ...) -> fallback_file { return f; } + +using file_ref = decltype(get_file(static_cast(nullptr), 0)); + +template +class file_print_buffer : public buffer { + public: + explicit file_print_buffer(F*) : buffer(nullptr, size_t()) {} +}; + +template +class file_print_buffer::value>> + : public buffer { + private: + file_ref file_; + + static void grow(buffer& base, size_t) { + auto& self = static_cast(base); + self.file_.advance_write_buffer(self.size()); + if (self.file_.get_write_buffer().size == 0) self.file_.flush(); + auto buf = self.file_.get_write_buffer(); + FMT_ASSERT(buf.size > 0, ""); + self.set(buf.data, buf.size); + self.clear(); + } + + public: + explicit file_print_buffer(F* f) : buffer(grow, size_t()), file_(f) { + flockfile(f); + file_.init_buffer(); + auto buf = file_.get_write_buffer(); + set(buf.data, buf.size); + } + ~file_print_buffer() { + file_.advance_write_buffer(size()); + bool flush = file_.needs_flush(); + F* f = file_; // Make funlockfile depend on the template parameter F + funlockfile(f); // for the system API detection to work. + if (flush) fflush(file_); + } +}; + +#if !defined(_WIN32) || defined(FMT_USE_WRITE_CONSOLE) +FMT_FUNC auto write_console(int, string_view) -> bool { return false; } #else using dword = conditional_t; extern "C" __declspec(dllimport) int __stdcall WriteConsoleW( // void*, const void*, dword, dword*, void*); -FMT_FUNC bool write_console(std::FILE* f, string_view text) { - auto fd = _fileno(f); - if (!_isatty(fd)) return false; +FMT_FUNC bool write_console(int fd, string_view text) { auto u16 = utf8_to_utf16(text); - auto written = dword(); return WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), u16.c_str(), - static_cast(u16.size()), &written, nullptr) != 0; + static_cast(u16.size()), nullptr, nullptr) != 0; } +#endif +#ifdef _WIN32 // Print assuming legacy (non-Unicode) encoding. -FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args) { +FMT_FUNC void vprint_mojibake(std::FILE* f, string_view fmt, format_args args, + bool newline) { auto buffer = memory_buffer(); - detail::vformat_to(buffer, fmt, - basic_format_args>(args)); - fwrite_fully(buffer.data(), 1, buffer.size(), f); + detail::vformat_to(buffer, fmt, args); + if (newline) buffer.push_back('\n'); + fwrite_all(buffer.data(), buffer.size(), f); } #endif FMT_FUNC void print(std::FILE* f, string_view text) { - if (!write_console(f, text)) fwrite_fully(text.data(), 1, text.size(), f); +#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE) + int fd = _fileno(f); + if (_isatty(fd)) { + std::fflush(f); + if (write_console(fd, text)) return; + } +#endif + fwrite_all(text.data(), text.size(), f); } } // namespace detail -FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) { +FMT_FUNC void vprint_buffered(std::FILE* f, string_view fmt, format_args args) { auto buffer = memory_buffer(); detail::vformat_to(buffer, fmt, args); detail::print(f, {buffer.data(), buffer.size()}); } +FMT_FUNC void vprint(std::FILE* f, string_view fmt, format_args args) { + if (!detail::file_ref(f).is_buffered() || !detail::has_flockfile<>()) + return vprint_buffered(f, fmt, args); + auto&& buffer = detail::file_print_buffer<>(f); + return detail::vformat_to(buffer, fmt, args); +} + +FMT_FUNC void vprintln(std::FILE* f, string_view fmt, format_args args) { + auto buffer = memory_buffer(); + detail::vformat_to(buffer, fmt, args); + buffer.push_back('\n'); + detail::print(f, {buffer.data(), buffer.size()}); +} + FMT_FUNC void vprint(string_view fmt, format_args args) { vprint(stdout, fmt, args); } diff --git a/thirdparty/fmt/include/fmt/format.h b/thirdparty/fmt/include/fmt/format.h index 87a34b972..4466b4f4d 100644 --- a/thirdparty/fmt/include/fmt/format.h +++ b/thirdparty/fmt/include/fmt/format.h @@ -33,20 +33,59 @@ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ -#include // std::signbit -#include // uint32_t -#include // std::memcpy -#include // std::initializer_list -#include // std::numeric_limits -#include // std::uninitialized_copy -#include // std::runtime_error -#include // std::system_error - -#ifdef __cpp_lib_bit_cast -# include // std::bitcast +#ifndef _LIBCPP_REMOVE_TRANSITIVE_INCLUDES +# define _LIBCPP_REMOVE_TRANSITIVE_INCLUDES +# define FMT_REMOVE_TRANSITIVE_INCLUDES #endif -#include "core.h" +#include "base.h" + +#ifndef FMT_MODULE +# include // std::signbit +# include // std::byte +# include // uint32_t +# include // std::memcpy +# include // std::numeric_limits +# include // std::bad_alloc +# if defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI) +// Workaround for pre gcc 5 libstdc++. +# include // std::allocator_traits +# endif +# include // std::runtime_error +# include // std::string +# include // std::system_error + +// Check FMT_CPLUSPLUS to avoid a warning in MSVC. +# if FMT_HAS_INCLUDE() && FMT_CPLUSPLUS > 201703L +# include // std::bit_cast +# endif + +// libc++ supports string_view in pre-c++17. +# if FMT_HAS_INCLUDE() && \ + (FMT_CPLUSPLUS >= 201703L || defined(_LIBCPP_VERSION)) +# include +# define FMT_USE_STRING_VIEW +# endif + +# if FMT_MSC_VERSION +# include // _BitScanReverse[64], _umul128 +# endif +#endif // FMT_MODULE + +#if defined(FMT_USE_NONTYPE_TEMPLATE_ARGS) +// Use the provided definition. +#elif defined(__NVCOMPILER) +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 +#elif FMT_GCC_VERSION >= 903 && FMT_CPLUSPLUS >= 201709L +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +#elif defined(__cpp_nontype_template_args) && \ + __cpp_nontype_template_args >= 201911L +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +#elif FMT_CLANG_VERSION >= 1200 && FMT_CPLUSPLUS >= 202002L +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +#else +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 +#endif #if defined __cpp_inline_variables && __cpp_inline_variables >= 201606L # define FMT_INLINE_VARIABLE inline @@ -54,55 +93,22 @@ # define FMT_INLINE_VARIABLE #endif -#if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) -# define FMT_FALLTHROUGH [[fallthrough]] -#elif defined(__clang__) -# define FMT_FALLTHROUGH [[clang::fallthrough]] -#elif FMT_GCC_VERSION >= 700 && \ - (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) -# define FMT_FALLTHROUGH [[gnu::fallthrough]] +// Check if RTTI is disabled. +#ifdef FMT_USE_RTTI +// Use the provided definition. +#elif defined(__GXX_RTTI) || FMT_HAS_FEATURE(cxx_rtti) || defined(_CPPRTTI) || \ + defined(__INTEL_RTTI__) || defined(__RTTI) +// __RTTI is for EDG compilers. _CPPRTTI is for MSVC. +# define FMT_USE_RTTI 1 #else -# define FMT_FALLTHROUGH +# define FMT_USE_RTTI 0 #endif -#ifndef FMT_DEPRECATED -# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VERSION >= 1900 -# define FMT_DEPRECATED [[deprecated]] -# else -# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__) -# define FMT_DEPRECATED __attribute__((deprecated)) -# elif FMT_MSC_VERSION -# define FMT_DEPRECATED __declspec(deprecated) -# else -# define FMT_DEPRECATED /* deprecated */ -# endif -# endif -#endif - -#ifndef FMT_NO_UNIQUE_ADDRESS -# if FMT_CPLUSPLUS >= 202002L -# if FMT_HAS_CPP_ATTRIBUTE(no_unique_address) -# define FMT_NO_UNIQUE_ADDRESS [[no_unique_address]] -// VS2019 v16.10 and later except clang-cl (https://reviews.llvm.org/D110485) -# elif (FMT_MSC_VERSION >= 1929) && !FMT_CLANG_VERSION -# define FMT_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] -# endif -# endif -#endif -#ifndef FMT_NO_UNIQUE_ADDRESS -# define FMT_NO_UNIQUE_ADDRESS -#endif - -#if FMT_GCC_VERSION || defined(__clang__) -# define FMT_VISIBILITY(value) __attribute__((visibility(value))) +// Visibility when compiled as a shared library/object. +#if defined(FMT_LIB_EXPORT) || defined(FMT_SHARED) +# define FMT_SO_VISIBILITY(value) FMT_VISIBILITY(value) #else -# define FMT_VISIBILITY(value) -#endif - -#ifdef __has_builtin -# define FMT_HAS_BUILTIN(x) __has_builtin(x) -#else -# define FMT_HAS_BUILTIN(x) 0 +# define FMT_SO_VISIBILITY(value) #endif #if FMT_GCC_VERSION || FMT_CLANG_VERSION @@ -111,8 +117,19 @@ # define FMT_NOINLINE #endif +namespace std { +template struct iterator_traits> { + using iterator_category = output_iterator_tag; + using value_type = T; + using difference_type = + decltype(static_cast(nullptr) - static_cast(nullptr)); + using pointer = void; + using reference = void; +}; +} // namespace std + #ifndef FMT_THROW -# if FMT_EXCEPTIONS +# if FMT_USE_EXCEPTIONS # if FMT_MSC_VERSION || defined(__NVCC__) FMT_BEGIN_NAMESPACE namespace detail { @@ -131,35 +148,8 @@ FMT_END_NAMESPACE # else # define FMT_THROW(x) \ ::fmt::detail::assert_fail(__FILE__, __LINE__, (x).what()) -# endif -#endif - -#if FMT_EXCEPTIONS -# define FMT_TRY try -# define FMT_CATCH(x) catch (x) -#else -# define FMT_TRY if (true) -# define FMT_CATCH(x) if (false) -#endif - -#ifndef FMT_MAYBE_UNUSED -# if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused) -# define FMT_MAYBE_UNUSED [[maybe_unused]] -# else -# define FMT_MAYBE_UNUSED -# endif -#endif - -#ifndef FMT_USE_USER_DEFINED_LITERALS -// EDG based compilers (Intel, NVIDIA, Elbrus, etc), GCC and MSVC support UDLs. -# if (FMT_HAS_FEATURE(cxx_user_literals) || FMT_GCC_VERSION >= 407 || \ - FMT_MSC_VERSION >= 1900) && \ - (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= /* UDL feature */ 480) -# define FMT_USE_USER_DEFINED_LITERALS 1 -# else -# define FMT_USE_USER_DEFINED_LITERALS 0 -# endif -#endif +# endif // FMT_USE_EXCEPTIONS +#endif // FMT_THROW // Defining FMT_REDUCE_INT_INSTANTIATIONS to 1, will reduce the number of // integer formatter template instantiations to just one by only using the @@ -169,7 +159,15 @@ FMT_END_NAMESPACE # define FMT_REDUCE_INT_INSTANTIATIONS 0 #endif -// __builtin_clz is broken in clang with Microsoft CodeGen: +FMT_BEGIN_NAMESPACE + +template +struct is_contiguous> + : std::true_type {}; + +namespace detail { + +// __builtin_clz is broken in clang with Microsoft codegen: // https://github.com/fmtlib/fmt/issues/519. #if !FMT_MSC_VERSION # if FMT_HAS_BUILTIN(__builtin_clz) || FMT_GCC_VERSION || FMT_ICC_VERSION @@ -180,53 +178,30 @@ FMT_END_NAMESPACE # endif #endif -// __builtin_ctz is broken in Intel Compiler Classic on Windows: -// https://github.com/fmtlib/fmt/issues/2510. -#ifndef __ICL -# if FMT_HAS_BUILTIN(__builtin_ctz) || FMT_GCC_VERSION || FMT_ICC_VERSION || \ - defined(__NVCOMPILER) -# define FMT_BUILTIN_CTZ(n) __builtin_ctz(n) -# endif -# if FMT_HAS_BUILTIN(__builtin_ctzll) || FMT_GCC_VERSION || \ - FMT_ICC_VERSION || defined(__NVCOMPILER) -# define FMT_BUILTIN_CTZLL(n) __builtin_ctzll(n) -# endif -#endif - -#if FMT_MSC_VERSION -# include // _BitScanReverse[64], _BitScanForward[64], _umul128 -#endif - -// Some compilers masquerade as both MSVC and GCC-likes or otherwise support +// Some compilers masquerade as both MSVC and GCC but otherwise support // __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the // MSVC intrinsics if the clz and clzll builtins are not available. -#if FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) && \ - !defined(FMT_BUILTIN_CTZLL) -FMT_BEGIN_NAMESPACE -namespace detail { +#if FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) // Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. -# if !defined(__clang__) -# pragma intrinsic(_BitScanForward) +# ifndef __clang__ # pragma intrinsic(_BitScanReverse) -# if defined(_WIN64) -# pragma intrinsic(_BitScanForward64) +# ifdef _WIN64 # pragma intrinsic(_BitScanReverse64) # endif # endif inline auto clz(uint32_t x) -> int { + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. unsigned long r = 0; _BitScanReverse(&r, x); - FMT_ASSERT(x != 0, ""); - // Static analysis complains about using uninitialized data - // "r", but the only way that can happen is if "x" is 0, - // which the callers guarantee to not happen. - FMT_MSC_WARNING(suppress : 6102) return 31 ^ static_cast(r); } # define FMT_BUILTIN_CLZ(n) detail::clz(n) inline auto clzll(uint64_t x) -> int { + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. unsigned long r = 0; # ifdef _WIN64 _BitScanReverse64(&r, x); @@ -237,56 +212,10 @@ inline auto clzll(uint64_t x) -> int { // Scan the low 32 bits. _BitScanReverse(&r, static_cast(x)); # endif - FMT_ASSERT(x != 0, ""); - FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. return 63 ^ static_cast(r); } # define FMT_BUILTIN_CLZLL(n) detail::clzll(n) - -inline auto ctz(uint32_t x) -> int { - unsigned long r = 0; - _BitScanForward(&r, x); - FMT_ASSERT(x != 0, ""); - FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. - return static_cast(r); -} -# define FMT_BUILTIN_CTZ(n) detail::ctz(n) - -inline auto ctzll(uint64_t x) -> int { - unsigned long r = 0; - FMT_ASSERT(x != 0, ""); - FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. -# ifdef _WIN64 - _BitScanForward64(&r, x); -# else - // Scan the low 32 bits. - if (_BitScanForward(&r, static_cast(x))) return static_cast(r); - // Scan the high 32 bits. - _BitScanForward(&r, static_cast(x >> 32)); - r += 32; -# endif - return static_cast(r); -} -# define FMT_BUILTIN_CTZLL(n) detail::ctzll(n) -} // namespace detail -FMT_END_NAMESPACE -#endif - -FMT_BEGIN_NAMESPACE - -template struct disjunction : std::false_type {}; -template struct disjunction

: P {}; -template -struct disjunction - : conditional_t> {}; - -template struct conjunction : std::true_type {}; -template struct conjunction

: P {}; -template -struct conjunction - : conditional_t, P1> {}; - -namespace detail { +#endif // FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) { ignore_unused(condition); @@ -295,49 +224,25 @@ FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) { #endif } -template struct string_literal { - static constexpr CharT value[sizeof...(C)] = {C...}; - constexpr operator basic_string_view() const { +#if defined(FMT_USE_STRING_VIEW) +template using std_string_view = std::basic_string_view; +#else +template struct std_string_view { + operator basic_string_view() const; +}; +#endif + +template struct string_literal { + static constexpr Char value[sizeof...(C)] = {C...}; + constexpr operator basic_string_view() const { return {value, sizeof...(C)}; } }; - #if FMT_CPLUSPLUS < 201703L -template -constexpr CharT string_literal::value[sizeof...(C)]; +template +constexpr Char string_literal::value[sizeof...(C)]; #endif -template class formatbuf : public Streambuf { - private: - using char_type = typename Streambuf::char_type; - using streamsize = decltype(std::declval().sputn(nullptr, 0)); - using int_type = typename Streambuf::int_type; - using traits_type = typename Streambuf::traits_type; - - buffer& buffer_; - - public: - explicit formatbuf(buffer& buf) : buffer_(buf) {} - - protected: - // The put area is always empty. This makes the implementation simpler and has - // the advantage that the streambuf and the buffer are always in sync and - // sputc never writes into uninitialized memory. A disadvantage is that each - // call to sputc always results in a (virtual) call to overflow. There is no - // disadvantage here for sputn since this always results in a call to xsputn. - - auto overflow(int_type ch) -> int_type override { - if (!traits_type::eq_int_type(ch, traits_type::eof())) - buffer_.push_back(static_cast(ch)); - return ch; - } - - auto xsputn(const char_type* s, streamsize count) -> streamsize override { - buffer_.append(s, s + count); - return count; - } -}; - // Implementation of std::bit_cast for pre-C++20. template FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To { @@ -373,8 +278,8 @@ class uint128_fallback { constexpr uint128_fallback(uint64_t hi, uint64_t lo) : lo_(lo), hi_(hi) {} constexpr uint128_fallback(uint64_t value = 0) : lo_(value), hi_(0) {} - constexpr uint64_t high() const noexcept { return hi_; } - constexpr uint64_t low() const noexcept { return lo_; } + constexpr auto high() const noexcept -> uint64_t { return hi_; } + constexpr auto low() const noexcept -> uint64_t { return lo_; } template ::value)> constexpr explicit operator T() const { @@ -407,13 +312,14 @@ class uint128_fallback { -> uint128_fallback { return {~n.hi_, ~n.lo_}; } - friend auto operator+(const uint128_fallback& lhs, - const uint128_fallback& rhs) -> uint128_fallback { + friend FMT_CONSTEXPR auto operator+(const uint128_fallback& lhs, + const uint128_fallback& rhs) + -> uint128_fallback { auto result = uint128_fallback(lhs); result += rhs; return result; } - friend auto operator*(const uint128_fallback& lhs, uint32_t rhs) + friend FMT_CONSTEXPR auto operator*(const uint128_fallback& lhs, uint32_t rhs) -> uint128_fallback { FMT_ASSERT(lhs.hi_ == 0, ""); uint64_t hi = (lhs.lo_ >> 32) * rhs; @@ -421,7 +327,7 @@ class uint128_fallback { uint64_t new_lo = (hi << 32) + lo; return {(hi >> 32) + (new_lo < lo ? 1 : 0), new_lo}; } - friend auto operator-(const uint128_fallback& lhs, uint64_t rhs) + friend constexpr auto operator-(const uint128_fallback& lhs, uint64_t rhs) -> uint128_fallback { return {lhs.hi_ - (lhs.lo_ < rhs ? 1 : 0), lhs.lo_ - rhs}; } @@ -450,7 +356,7 @@ class uint128_fallback { hi_ &= n.hi_; } - FMT_CONSTEXPR20 uint128_fallback& operator+=(uint64_t n) noexcept { + FMT_CONSTEXPR20 auto operator+=(uint64_t n) noexcept -> uint128_fallback& { if (is_constant_evaluated()) { lo_ += n; hi_ += (lo_ < n ? 1 : 0); @@ -494,23 +400,24 @@ template constexpr auto num_bits() -> int { } // std::numeric_limits::digits may return 0 for 128-bit ints. template <> constexpr auto num_bits() -> int { return 128; } -template <> constexpr auto num_bits() -> int { return 128; } +template <> constexpr auto num_bits() -> int { return 128; } +template <> constexpr auto num_bits() -> int { return 128; } // A heterogeneous bit_cast used for converting 96-bit long double to uint128_t // and 128-bit pointers to uint128_fallback. template sizeof(From))> inline auto bit_cast(const From& from) -> To { - constexpr auto size = static_cast(sizeof(From) / sizeof(unsigned)); + constexpr auto size = static_cast(sizeof(From) / sizeof(unsigned short)); struct data_t { - unsigned value[static_cast(size)]; + unsigned short value[static_cast(size)]; } data = bit_cast(from); auto result = To(); if (const_check(is_big_endian())) { for (int i = 0; i < size; ++i) - result = (result << num_bits()) | data.value[i]; + result = (result << num_bits()) | data.value[i]; } else { for (int i = size - 1; i >= 0; --i) - result = (result << num_bits()) | data.value[i]; + result = (result << num_bits()) | data.value[i]; } return result; } @@ -546,38 +453,25 @@ FMT_INLINE void assume(bool condition) { #endif } -// An approximation of iterator_t for pre-C++20 systems. -template -using iterator_t = decltype(std::begin(std::declval())); -template using sentinel_t = decltype(std::end(std::declval())); - -// A workaround for std::string not having mutable data() until C++17. -template -inline auto get_data(std::basic_string& s) -> Char* { - return &s[0]; -} -template -inline auto get_data(Container& c) -> typename Container::value_type* { - return c.data(); -} - // Attempts to reserve space for n extra characters in the output range. // Returns a pointer to the reserved range or a reference to it. -template ::value)> +template ::value&& + is_contiguous::value)> #if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION __attribute__((no_sanitize("undefined"))) #endif -inline auto -reserve(std::back_insert_iterator it, size_t n) -> - typename Container::value_type* { - Container& c = get_container(it); +FMT_CONSTEXPR20 inline auto +reserve(OutputIt it, size_t n) -> typename OutputIt::value_type* { + auto& c = get_container(it); size_t size = c.size(); c.resize(size + n); - return get_data(c) + size; + return &c[size]; } template -inline auto reserve(buffer_appender it, size_t n) -> buffer_appender { +FMT_CONSTEXPR20 inline auto reserve(basic_appender it, size_t n) + -> basic_appender { buffer& buf = get_container(it); buf.try_reserve(buf.size() + n); return it; @@ -596,18 +490,22 @@ template constexpr auto to_pointer(OutputIt, size_t) -> T* { return nullptr; } -template auto to_pointer(buffer_appender it, size_t n) -> T* { +template +FMT_CONSTEXPR20 auto to_pointer(basic_appender it, size_t n) -> T* { buffer& buf = get_container(it); + buf.try_reserve(buf.size() + n); auto size = buf.size(); if (buf.capacity() < size + n) return nullptr; buf.try_resize(size + n); return buf.data() + size; } -template ::value)> -inline auto base_iterator(std::back_insert_iterator it, - typename Container::value_type*) - -> std::back_insert_iterator { +template ::value&& + is_contiguous::value)> +inline auto base_iterator(OutputIt it, + typename OutputIt::container_type::value_type*) + -> OutputIt { return it; } @@ -626,23 +524,15 @@ FMT_CONSTEXPR auto fill_n(OutputIt out, Size count, const T& value) } template FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* { - if (is_constant_evaluated()) { - return fill_n(out, count, value); - } + if (is_constant_evaluated()) return fill_n(out, count, value); std::memset(out, value, to_unsigned(count)); return out + count; } -#ifdef __cpp_char8_t -using char8_type = char8_t; -#else -enum char8_type : unsigned char {}; -#endif - template -FMT_CONSTEXPR FMT_NOINLINE auto copy_str_noinline(InputIt begin, InputIt end, - OutputIt out) -> OutputIt { - return copy_str(begin, end, out); +FMT_CONSTEXPR FMT_NOINLINE auto copy_noinline(InputIt begin, InputIt end, + OutputIt out) -> OutputIt { + return copy(begin, end, out); } // A public domain branchless UTF-8 decoder by Christopher Wellons: @@ -713,6 +603,7 @@ FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { string_view(ptr, error ? 1 : to_unsigned(end - buf_ptr))); return result ? (error ? buf_ptr + 1 : end) : nullptr; }; + auto p = s.data(); const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars. if (s.size() >= block_size) { @@ -721,17 +612,20 @@ FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { if (!p) return; } } - if (auto num_chars_left = s.data() + s.size() - p) { - char buf[2 * block_size - 1] = {}; - copy_str(p, p + num_chars_left, buf); - const char* buf_ptr = buf; - do { - auto end = decode(buf_ptr, p); - if (!end) return; - p += end - buf_ptr; - buf_ptr = end; - } while (buf_ptr - buf < num_chars_left); - } + auto num_chars_left = to_unsigned(s.data() + s.size() - p); + if (num_chars_left == 0) return; + + // Suppress bogus -Wstringop-overflow. + if (FMT_GCC_VERSION) num_chars_left &= 3; + char buf[2 * block_size - 1] = {}; + copy(p, p + num_chars_left, buf); + const char* buf_ptr = buf; + do { + auto end = decode(buf_ptr, p); + if (!end) return; + p += end - buf_ptr; + buf_ptr = end; + } while (buf_ptr < buf + num_chars_left); } template @@ -740,13 +634,13 @@ inline auto compute_width(basic_string_view s) -> size_t { } // Computes approximate display width of a UTF-8 string. -FMT_CONSTEXPR inline size_t compute_width(string_view s) { +FMT_CONSTEXPR inline auto compute_width(string_view s) -> size_t { size_t num_code_points = 0; // It is not a lambda for compatibility with C++14. struct count_code_points { size_t* count; FMT_CONSTEXPR auto operator()(uint32_t cp, string_view) const -> bool { - *count += detail::to_unsigned( + *count += to_unsigned( 1 + (cp >= 0x1100 && (cp <= 0x115f || // Hangul Jamo init. consonants @@ -774,31 +668,24 @@ FMT_CONSTEXPR inline size_t compute_width(string_view s) { return num_code_points; } -inline auto compute_width(basic_string_view s) -> size_t { - return compute_width( - string_view(reinterpret_cast(s.data()), s.size())); -} - template inline auto code_point_index(basic_string_view s, size_t n) -> size_t { - size_t size = s.size(); - return n < size ? n : size; + return min_of(n, s.size()); } // Calculates the index of the nth code point in a UTF-8 string. inline auto code_point_index(string_view s, size_t n) -> size_t { - const char* data = s.data(); - size_t num_code_points = 0; - for (size_t i = 0, size = s.size(); i != size; ++i) { - if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) return i; - } - return s.size(); -} - -inline auto code_point_index(basic_string_view s, size_t n) - -> size_t { - return code_point_index( - string_view(reinterpret_cast(s.data()), s.size()), n); + size_t result = s.size(); + const char* begin = s.begin(); + for_each_codepoint(s, [begin, &n, &result](uint32_t, string_view sv) { + if (n != 0) { + --n; + return true; + } + result = to_unsigned(sv.begin() - begin); + return false; + }); + return result; } template struct is_integral : std::is_integral {}; @@ -816,38 +703,22 @@ using is_integer = !std::is_same::value && !std::is_same::value>; -#ifndef FMT_USE_FLOAT -# define FMT_USE_FLOAT 1 +#if defined(FMT_USE_FLOAT128) +// Use the provided definition. +#elif FMT_CLANG_VERSION && FMT_HAS_INCLUDE() +# define FMT_USE_FLOAT128 1 +#elif FMT_GCC_VERSION && defined(_GLIBCXX_USE_FLOAT128) && \ + !defined(__STRICT_ANSI__) +# define FMT_USE_FLOAT128 1 +#else +# define FMT_USE_FLOAT128 0 #endif -#ifndef FMT_USE_DOUBLE -# define FMT_USE_DOUBLE 1 -#endif -#ifndef FMT_USE_LONG_DOUBLE -# define FMT_USE_LONG_DOUBLE 1 -#endif - -#ifndef FMT_USE_FLOAT128 -# ifdef __clang__ -// Clang emulates GCC, so it has to appear early. -# if FMT_HAS_INCLUDE() -# define FMT_USE_FLOAT128 1 -# endif -# elif defined(__GNUC__) -// GNU C++: -# if defined(_GLIBCXX_USE_FLOAT128) && !defined(__STRICT_ANSI__) -# define FMT_USE_FLOAT128 1 -# endif -# endif -# ifndef FMT_USE_FLOAT128 -# define FMT_USE_FLOAT128 0 -# endif -#endif - #if FMT_USE_FLOAT128 using float128 = __float128; #else -using float128 = void; +struct float128 {}; #endif + template using is_float128 = std::is_same; template @@ -866,24 +737,21 @@ using is_double_double = bool_constant::digits == 106>; # define FMT_USE_FULL_CACHE_DRAGONBOX 0 #endif -template -template -void buffer::append(const U* begin, const U* end) { - while (begin != end) { - auto count = to_unsigned(end - begin); - try_reserve(size_ + count); - auto free_cap = capacity_ - size_; - if (free_cap < count) count = free_cap; - std::uninitialized_copy_n(begin, count, ptr_ + size_); - size_ += count; - begin += count; - } -} +// An allocator that uses malloc/free to allow removing dependency on the C++ +// standard libary runtime. +template struct allocator { + using value_type = T; + + T* allocate(size_t n) { + FMT_ASSERT(n <= max_value() / sizeof(T), ""); + T* p = static_cast(malloc(n * sizeof(T))); + if (!p) FMT_THROW(std::bad_alloc()); + return p; + } + + void deallocate(T* p, size_t) { free(p); } +}; -template -struct is_locale : std::false_type {}; -template -struct is_locale> : std::true_type {}; } // namespace detail FMT_BEGIN_EXPORT @@ -893,29 +761,21 @@ FMT_BEGIN_EXPORT enum { inline_buffer_size = 500 }; /** - \rst - A dynamically growing memory buffer for trivially copyable/constructible types - with the first ``SIZE`` elements stored in the object itself. - - You can use the ``memory_buffer`` type alias for ``char`` instead. - - **Example**:: - - auto out = fmt::memory_buffer(); - format_to(std::back_inserter(out), "The answer is {}.", 42); - - This will append the following output to the ``out`` object: - - .. code-block:: none - - The answer is 42. - - The output can be converted to an ``std::string`` with ``to_string(out)``. - \endrst + * A dynamically growing memory buffer for trivially copyable/constructible + * types with the first `SIZE` elements stored in the object itself. Most + * commonly used via the `memory_buffer` alias for `char`. + * + * **Example**: + * + * auto out = fmt::memory_buffer(); + * fmt::format_to(std::back_inserter(out), "The answer is {}.", 42); + * + * This will append "The answer is 42." to `out`. The buffer content can be + * converted to `std::string` with `to_string(out)`. */ template > -class basic_memory_buffer final : public detail::buffer { + typename Allocator = detail::allocator> +class basic_memory_buffer : public detail::buffer { private: T store_[SIZE]; @@ -928,37 +788,37 @@ class basic_memory_buffer final : public detail::buffer { if (data != store_) alloc_.deallocate(data, this->capacity()); } - protected: - FMT_CONSTEXPR20 void grow(size_t size) override { + static FMT_CONSTEXPR20 void grow(detail::buffer& buf, size_t size) { detail::abort_fuzzing_if(size > 5000); - const size_t max_size = std::allocator_traits::max_size(alloc_); - size_t old_capacity = this->capacity(); + auto& self = static_cast(buf); + const size_t max_size = + std::allocator_traits::max_size(self.alloc_); + size_t old_capacity = buf.capacity(); size_t new_capacity = old_capacity + old_capacity / 2; if (size > new_capacity) new_capacity = size; else if (new_capacity > max_size) - new_capacity = size > max_size ? size : max_size; - T* old_data = this->data(); - T* new_data = - std::allocator_traits::allocate(alloc_, new_capacity); + new_capacity = max_of(size, max_size); + T* old_data = buf.data(); + T* new_data = self.alloc_.allocate(new_capacity); // Suppress a bogus -Wstringop-overflow in gcc 13.1 (#3481). - detail::assume(this->size() <= new_capacity); + detail::assume(buf.size() <= new_capacity); // The following code doesn't throw, so the raw pointer above doesn't leak. - std::uninitialized_copy_n(old_data, this->size(), new_data); - this->set(new_data, new_capacity); + memcpy(new_data, old_data, buf.size() * sizeof(T)); + self.set(new_data, new_capacity); // deallocate must not throw according to the standard, but even if it does, // the buffer already uses the new storage and will deallocate it in // destructor. - if (old_data != store_) alloc_.deallocate(old_data, old_capacity); + if (old_data != self.store_) self.alloc_.deallocate(old_data, old_capacity); } public: using value_type = T; using const_reference = const T&; - FMT_CONSTEXPR20 explicit basic_memory_buffer( + FMT_CONSTEXPR explicit basic_memory_buffer( const Allocator& alloc = Allocator()) - : alloc_(alloc) { + : detail::buffer(grow), alloc_(alloc) { this->set(store_, SIZE); if (detail::is_constant_evaluated()) detail::fill_n(store_, SIZE, T()); } @@ -972,7 +832,7 @@ class basic_memory_buffer final : public detail::buffer { size_t size = other.size(), capacity = other.capacity(); if (data == other.store_) { this->set(store_, capacity); - detail::copy_str(other.store_, other.store_ + size, store_); + detail::copy(other.store_, other.store_ + size, store_); } else { this->set(data, capacity); // Set pointer to the inline array so that delete is not called @@ -984,21 +844,14 @@ class basic_memory_buffer final : public detail::buffer { } public: - /** - \rst - Constructs a :class:`fmt::basic_memory_buffer` object moving the content - of the other object to it. - \endrst - */ - FMT_CONSTEXPR20 basic_memory_buffer(basic_memory_buffer&& other) noexcept { + /// Constructs a `basic_memory_buffer` object moving the content of the other + /// object to it. + FMT_CONSTEXPR20 basic_memory_buffer(basic_memory_buffer&& other) noexcept + : detail::buffer(grow) { move(other); } - /** - \rst - Moves the content of the other ``basic_memory_buffer`` object to this one. - \endrst - */ + /// Moves the content of the other `basic_memory_buffer` object to this one. auto operator=(basic_memory_buffer&& other) noexcept -> basic_memory_buffer& { FMT_ASSERT(this != &other, ""); deallocate(); @@ -1009,120 +862,108 @@ class basic_memory_buffer final : public detail::buffer { // Returns a copy of the allocator associated with this buffer. auto get_allocator() const -> Allocator { return alloc_; } - /** - Resizes the buffer to contain *count* elements. If T is a POD type new - elements may not be initialized. - */ - FMT_CONSTEXPR20 void resize(size_t count) { this->try_resize(count); } + /// Resizes the buffer to contain `count` elements. If T is a POD type new + /// elements may not be initialized. + FMT_CONSTEXPR void resize(size_t count) { this->try_resize(count); } - /** Increases the buffer capacity to *new_capacity*. */ + /// Increases the buffer capacity to `new_capacity`. void reserve(size_t new_capacity) { this->try_reserve(new_capacity); } - // Directly append data into the buffer using detail::buffer::append; template - void append(const ContiguousRange& range) { + FMT_CONSTEXPR20 void append(const ContiguousRange& range) { append(range.data(), range.data() + range.size()); } }; using memory_buffer = basic_memory_buffer; +template +FMT_NODISCARD auto to_string(const basic_memory_buffer& buf) + -> std::string { + auto size = buf.size(); + detail::assume(size < std::string().max_size()); + return {buf.data(), size}; +} + +// A writer to a buffered stream. It doesn't own the underlying stream. +class writer { + private: + detail::buffer* buf_; + + // We cannot create a file buffer in advance because any write to a FILE may + // invalidate it. + FILE* file_; + + public: + inline writer(FILE* f) : buf_(nullptr), file_(f) {} + inline writer(detail::buffer& buf) : buf_(&buf) {} + + /// Formats `args` according to specifications in `fmt` and writes the + /// output to the file. + template void print(format_string fmt, T&&... args) { + if (buf_) + fmt::format_to(appender(*buf_), fmt, std::forward(args)...); + else + fmt::print(file_, fmt, std::forward(args)...); + } +}; + +class string_buffer { + private: + std::string str_; + detail::container_buffer buf_; + + public: + inline string_buffer() : buf_(str_) {} + + inline operator writer() { return buf_; } + inline std::string& str() { return str_; } +}; + template struct is_contiguous> : std::true_type { }; -FMT_END_EXPORT -namespace detail { -FMT_API bool write_console(std::FILE* f, string_view text); -FMT_API void print(std::FILE*, string_view); -} // namespace detail - -FMT_BEGIN_EXPORT - // Suppress a misleading warning in older versions of clang. -#if FMT_CLANG_VERSION -# pragma clang diagnostic ignored "-Wweak-vtables" -#endif +FMT_PRAGMA_CLANG(diagnostic ignored "-Wweak-vtables") -/** An error reported from a formatting function. */ -class FMT_VISIBILITY("default") format_error : public std::runtime_error { +/// An error reported from a formatting function. +class FMT_SO_VISIBILITY("default") format_error : public std::runtime_error { public: using std::runtime_error::runtime_error; }; -namespace detail_exported { -#if FMT_USE_NONTYPE_TEMPLATE_ARGS +class loc_value; + +FMT_END_EXPORT +namespace detail { +FMT_API auto write_console(int fd, string_view text) -> bool; +FMT_API void print(FILE*, string_view); +} // namespace detail + +namespace detail { template struct fixed_string { - constexpr fixed_string(const Char (&str)[N]) { - detail::copy_str(static_cast(str), - str + N, data); + FMT_CONSTEXPR20 fixed_string(const Char (&s)[N]) { + detail::copy(static_cast(s), s + N, + data); } Char data[N] = {}; }; -#endif // Converts a compile-time string to basic_string_view. -template +FMT_EXPORT template constexpr auto compile_string_to_view(const Char (&s)[N]) -> basic_string_view { // Remove trailing NUL character if needed. Won't be present if this is used // with a raw character array (i.e. not defined as a string). return {s, N - (std::char_traits::to_int_type(s[N - 1]) == 0 ? 1 : 0)}; } -template -constexpr auto compile_string_to_view(detail::std_string_view s) +FMT_EXPORT template +constexpr auto compile_string_to_view(basic_string_view s) -> basic_string_view { - return {s.data(), s.size()}; + return s; } -} // namespace detail_exported - -class loc_value { - private: - basic_format_arg value_; - - public: - template ::value)> - loc_value(T value) : value_(detail::make_arg(value)) {} - - template ::value)> - loc_value(T) {} - - template auto visit(Visitor&& vis) -> decltype(vis(0)) { - return visit_format_arg(vis, value_); - } -}; - -// A locale facet that formats values in UTF-8. -// It is parameterized on the locale to avoid the heavy include. -template class format_facet : public Locale::facet { - private: - std::string separator_; - std::string grouping_; - std::string decimal_point_; - - protected: - virtual auto do_put(appender out, loc_value val, - const format_specs<>& specs) const -> bool; - - public: - static FMT_API typename Locale::id id; - - explicit format_facet(Locale& loc); - explicit format_facet(string_view sep = "", - std::initializer_list g = {3}, - std::string decimal_point = ".") - : separator_(sep.data(), sep.size()), - grouping_(g.begin(), g.end()), - decimal_point_(decimal_point) {} - - auto put(appender out, loc_value val, const format_specs<>& specs) const - -> bool { - return do_put(out, val, specs); - } -}; - -namespace detail { // Returns true if value is negative, false otherwise. // Same as `value < 0` but doesn't produce warnings if T is an unsigned type. @@ -1135,14 +976,6 @@ constexpr auto is_negative(T) -> bool { return false; } -template -FMT_CONSTEXPR auto is_supported_floating_point(T) -> bool { - if (std::is_same()) return FMT_USE_FLOAT; - if (std::is_same()) return FMT_USE_DOUBLE; - if (std::is_same()) return FMT_USE_LONG_DOUBLE; - return true; -} - // Smallest of uint32_t, uint64_t, uint128_t that is large enough to // represent all values of an integral type T. template @@ -1153,27 +986,28 @@ using uint32_or_64_or_128_t = template using uint64_or_128_t = conditional_t() <= 64, uint64_t, uint128_t>; -#define FMT_POWERS_OF_10(factor) \ - factor * 10, (factor)*100, (factor)*1000, (factor)*10000, (factor)*100000, \ - (factor)*1000000, (factor)*10000000, (factor)*100000000, \ - (factor)*1000000000 +#define FMT_POWERS_OF_10(factor) \ + factor * 10, (factor) * 100, (factor) * 1000, (factor) * 10000, \ + (factor) * 100000, (factor) * 1000000, (factor) * 10000000, \ + (factor) * 100000000, (factor) * 1000000000 // Converts value in the range [0, 100) to a string. -constexpr const char* digits2(size_t value) { - // GCC generates slightly better code when value is pointer-size. - return &"0001020304050607080910111213141516171819" - "2021222324252627282930313233343536373839" - "4041424344454647484950515253545556575859" - "6061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"[value * 2]; +// GCC generates slightly better code when value is pointer-size. +inline auto digits2(size_t value) -> const char* { + // Align data since unaligned access may be slower when crossing a + // hardware-specific boundary. + alignas(2) static const char data[] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + return &data[value * 2]; } -// Sign is a template parameter to workaround a bug in gcc 4.8. -template constexpr Char sign(Sign s) { -#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 604 - static_assert(std::is_same::value, ""); -#endif - return static_cast("\0-+ "[s]); +template constexpr auto getsign(sign s) -> Char { + return static_cast(((' ' << 24) | ('+' << 16) | ('-' << 8)) >> + (static_cast(s) * 8)); } template FMT_CONSTEXPR auto count_digits_fallback(T n) -> int { @@ -1221,9 +1055,7 @@ inline auto do_count_digits(uint64_t n) -> int { // except for n == 0 in which case count_digits returns 1. FMT_CONSTEXPR20 inline auto count_digits(uint64_t n) -> int { #ifdef FMT_BUILTIN_CLZLL - if (!is_constant_evaluated()) { - return do_count_digits(n); - } + if (!is_constant_evaluated() && !FMT_OPTIMIZE_SIZE) return do_count_digits(n); #endif return count_digits_fallback(n); } @@ -1273,9 +1105,7 @@ FMT_INLINE auto do_count_digits(uint32_t n) -> int { // Optional version of count_digits for better performance on 32-bit platforms. FMT_CONSTEXPR20 inline auto count_digits(uint32_t n) -> int { #ifdef FMT_BUILTIN_CLZ - if (!is_constant_evaluated()) { - return do_count_digits(n); - } + if (!is_constant_evaluated() && !FMT_OPTIMIZE_SIZE) return do_count_digits(n); #endif return count_digits_fallback(n); } @@ -1312,6 +1142,17 @@ template <> inline auto decimal_point(locale_ref loc) -> wchar_t { return decimal_point_impl(loc); } +#ifndef FMT_HEADER_ONLY +FMT_BEGIN_EXPORT +extern template FMT_API auto thousands_sep_impl(locale_ref) + -> thousands_sep_result; +extern template FMT_API auto thousands_sep_impl(locale_ref) + -> thousands_sep_result; +extern template FMT_API auto decimal_point_impl(locale_ref) -> char; +extern template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t; +FMT_END_EXPORT +#endif // FMT_HEADER_ONLY + // Compares two characters for equality. template auto equal2(const Char* lhs, const char* rhs) -> bool { return lhs[0] == Char(rhs[0]) && lhs[1] == Char(rhs[1]); @@ -1320,83 +1161,99 @@ inline auto equal2(const char* lhs, const char* rhs) -> bool { return memcmp(lhs, rhs, 2) == 0; } -// Copies two characters from src to dst. +// Writes a two-digit value to out. template -FMT_CONSTEXPR20 FMT_INLINE void copy2(Char* dst, const char* src) { - if (!is_constant_evaluated() && sizeof(Char) == sizeof(char)) { - memcpy(dst, src, 2); +FMT_CONSTEXPR20 FMT_INLINE void write2digits(Char* out, size_t value) { + if (!is_constant_evaluated() && std::is_same::value && + !FMT_OPTIMIZE_SIZE) { + memcpy(out, digits2(value), 2); return; } - *dst++ = static_cast(*src++); - *dst = static_cast(*src); + *out++ = static_cast('0' + value / 10); + *out = static_cast('0' + value % 10); } -template struct format_decimal_result { - Iterator begin; - Iterator end; -}; - -// Formats a decimal unsigned integer value writing into out pointing to a -// buffer of specified size. The caller must ensure that the buffer is large -// enough. +// Formats a decimal unsigned integer value writing to out pointing to a buffer +// of specified size. The caller must ensure that the buffer is large enough. template -FMT_CONSTEXPR20 auto format_decimal(Char* out, UInt value, int size) - -> format_decimal_result { +FMT_CONSTEXPR20 auto do_format_decimal(Char* out, UInt value, int size) + -> Char* { FMT_ASSERT(size >= count_digits(value), "invalid digit count"); - out += size; - Char* end = out; + unsigned n = to_unsigned(size); while (value >= 100) { // Integer division is slow so do it for a group of two digits instead // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. - out -= 2; - copy2(out, digits2(static_cast(value % 100))); + n -= 2; + write2digits(out + n, static_cast(value % 100)); value /= 100; } - if (value < 10) { - *--out = static_cast('0' + value); - return {out, end}; + if (value >= 10) { + n -= 2; + write2digits(out + n, static_cast(value)); + } else { + out[--n] = static_cast('0' + value); } - out -= 2; - copy2(out, digits2(static_cast(value))); - return {out, end}; + return out + n; } -template >::value)> -FMT_CONSTEXPR inline auto format_decimal(Iterator out, UInt value, int size) - -> format_decimal_result { - // Buffer is large enough to hold all digits (digits10 + 1). - Char buffer[digits10() + 1] = {}; - auto end = format_decimal(buffer, value, size).end; - return {out, detail::copy_str_noinline(buffer, end, out)}; +template +FMT_CONSTEXPR FMT_INLINE auto format_decimal(Char* out, UInt value, + int num_digits) -> Char* { + do_format_decimal(out, value, num_digits); + return out + num_digits; } -template -FMT_CONSTEXPR auto format_uint(Char* buffer, UInt value, int num_digits, - bool upper = false) -> Char* { - buffer += num_digits; - Char* end = buffer; - do { - const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; - unsigned digit = static_cast(value & ((1 << BASE_BITS) - 1)); - *--buffer = static_cast(BASE_BITS < 4 ? static_cast('0' + digit) - : digits[digit]); - } while ((value >>= BASE_BITS) != 0); - return end; -} - -template -FMT_CONSTEXPR inline auto format_uint(It out, UInt value, int num_digits, - bool upper = false) -> It { +template >::value)> +FMT_CONSTEXPR auto format_decimal(OutputIt out, UInt value, int num_digits) + -> OutputIt { if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { - format_uint(ptr, value, num_digits, upper); + do_format_decimal(ptr, value, num_digits); return out; } - // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1). - char buffer[num_bits() / BASE_BITS + 1]; - format_uint(buffer, value, num_digits, upper); - return detail::copy_str_noinline(buffer, buffer + num_digits, out); + // Buffer is large enough to hold all digits (digits10 + 1). + char buffer[digits10() + 1]; + if (is_constant_evaluated()) fill_n(buffer, sizeof(buffer), '\0'); + do_format_decimal(buffer, value, num_digits); + return copy_noinline(buffer, buffer + num_digits, out); +} + +template +FMT_CONSTEXPR auto do_format_base2e(int base_bits, Char* out, UInt value, + int size, bool upper = false) -> Char* { + out += size; + do { + const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; + unsigned digit = static_cast(value & ((1 << base_bits) - 1)); + *--out = static_cast(base_bits < 4 ? static_cast('0' + digit) + : digits[digit]); + } while ((value >>= base_bits) != 0); + return out; +} + +// Formats an unsigned integer in the power of two base (binary, octal, hex). +template +FMT_CONSTEXPR auto format_base2e(int base_bits, Char* out, UInt value, + int num_digits, bool upper = false) -> Char* { + do_format_base2e(base_bits, out, value, num_digits, upper); + return out + num_digits; +} + +template ::value)> +FMT_CONSTEXPR inline auto format_base2e(int base_bits, OutputIt out, UInt value, + int num_digits, bool upper = false) + -> OutputIt { + if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { + format_base2e(base_bits, ptr, value, num_digits, upper); + return out; + } + // Make buffer large enough for any base. + char buffer[num_bits()]; + if (is_constant_evaluated()) fill_n(buffer, sizeof(buffer), '\0'); + format_base2e(base_bits, buffer, value, num_digits, upper); + return detail::copy_noinline(buffer, buffer + num_digits, out); } // A converter from UTF-8 to UTF-16. @@ -1406,10 +1263,12 @@ class utf8_to_utf16 { public: FMT_API explicit utf8_to_utf16(string_view s); - operator basic_string_view() const { return {&buffer_[0], size()}; } - auto size() const -> size_t { return buffer_.size() - 1; } - auto c_str() const -> const wchar_t* { return &buffer_[0]; } - auto str() const -> std::wstring { return {&buffer_[0], size()}; } + inline operator basic_string_view() const { + return {&buffer_[0], size()}; + } + inline auto size() const -> size_t { return buffer_.size() - 1; } + inline auto c_str() const -> const wchar_t* { return &buffer_[0]; } + inline auto str() const -> std::wstring { return {&buffer_[0], size()}; } }; enum class to_utf8_error_policy { abort, replace }; @@ -1430,22 +1289,23 @@ template class to_utf8 { : "invalid utf32")); } operator string_view() const { return string_view(&buffer_[0], size()); } - size_t size() const { return buffer_.size() - 1; } - const char* c_str() const { return &buffer_[0]; } - std::string str() const { return std::string(&buffer_[0], size()); } + auto size() const -> size_t { return buffer_.size() - 1; } + auto c_str() const -> const char* { return &buffer_[0]; } + auto str() const -> std::string { return std::string(&buffer_[0], size()); } // Performs conversion returning a bool instead of throwing exception on // conversion error. This method may still throw in case of memory allocation // error. - bool convert(basic_string_view s, - to_utf8_error_policy policy = to_utf8_error_policy::abort) { + auto convert(basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) + -> bool { if (!convert(buffer_, s, policy)) return false; buffer_.push_back(0); return true; } - static bool convert( - Buffer& buf, basic_string_view s, - to_utf8_error_policy policy = to_utf8_error_policy::abort) { + static auto convert(Buffer& buf, basic_string_view s, + to_utf8_error_policy policy = to_utf8_error_policy::abort) + -> bool { for (auto p = s.begin(); p != s.end(); ++p) { uint32_t c = static_cast(*p); if (sizeof(WChar) == 2 && c >= 0xd800 && c <= 0xdfff) { @@ -1455,10 +1315,12 @@ template class to_utf8 { if (policy == to_utf8_error_policy::abort) return false; buf.append(string_view("\xEF\xBF\xBD")); --p; + continue; } else { c = (c << 10) + static_cast(*p) - 0x35fdc00; } - } else if (c < 0x80) { + } + if (c < 0x80) { buf.push_back(static_cast(c)); } else if (c < 0x800) { buf.push_back(static_cast(0xc0 | (c >> 6))); @@ -1481,7 +1343,7 @@ template class to_utf8 { }; // Computes 128-bit result of multiplication of two 64-bit unsigned integers. -inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept { +inline auto umul128(uint64_t x, uint64_t y) noexcept -> uint128_fallback { #if FMT_USE_INT128 auto p = static_cast(x) * static_cast(y); return {static_cast(p >> 64), static_cast(p)}; @@ -1512,19 +1374,19 @@ inline uint128_fallback umul128(uint64_t x, uint64_t y) noexcept { namespace dragonbox { // Computes floor(log10(pow(2, e))) for e in [-2620, 2620] using the method from // https://fmt.dev/papers/Dragonbox.pdf#page=28, section 6.1. -inline int floor_log10_pow2(int e) noexcept { +inline auto floor_log10_pow2(int e) noexcept -> int { FMT_ASSERT(e <= 2620 && e >= -2620, "too large exponent"); static_assert((-1 >> 1) == -1, "right shift is not arithmetic"); return (e * 315653) >> 20; } -inline int floor_log2_pow10(int e) noexcept { +inline auto floor_log2_pow10(int e) noexcept -> int { FMT_ASSERT(e <= 1233 && e >= -1233, "too large exponent"); return (e * 1741647) >> 19; } // Computes upper 64 bits of multiplication of two 64-bit unsigned integers. -inline uint64_t umul128_upper64(uint64_t x, uint64_t y) noexcept { +inline auto umul128_upper64(uint64_t x, uint64_t y) noexcept -> uint64_t { #if FMT_USE_INT128 auto p = static_cast(x) * static_cast(y); return static_cast(p >> 64); @@ -1537,14 +1399,14 @@ inline uint64_t umul128_upper64(uint64_t x, uint64_t y) noexcept { // Computes upper 128 bits of multiplication of a 64-bit unsigned integer and a // 128-bit unsigned integer. -inline uint128_fallback umul192_upper128(uint64_t x, - uint128_fallback y) noexcept { +inline auto umul192_upper128(uint64_t x, uint128_fallback y) noexcept + -> uint128_fallback { uint128_fallback r = umul128(x, y.high()); r += umul128_upper64(x, y.low()); return r; } -FMT_API uint128_fallback get_cached_power(int k) noexcept; +FMT_API auto get_cached_power(int k) noexcept -> uint128_fallback; // Type-specific information that Dragonbox uses. template struct float_info; @@ -1598,14 +1460,14 @@ template FMT_API auto to_decimal(T x) noexcept -> decimal_fp; } // namespace dragonbox // Returns true iff Float has the implicit bit which is not stored. -template constexpr bool has_implicit_bit() { +template constexpr auto has_implicit_bit() -> bool { // An 80-bit FP number has a 64-bit significand an no implicit bit. return std::numeric_limits::digits != 64; } // Returns the number of significand bits stored in Float. The implicit bit is // not counted since it is not stored. -template constexpr int num_significand_bits() { +template constexpr auto num_significand_bits() -> int { // std::numeric_limits may not support __float128. return is_float128() ? 112 : (std::numeric_limits::digits - @@ -1626,25 +1488,30 @@ template constexpr auto exponent_bias() -> int { } // Writes the exponent exp in the form "[+-]d{2,3}" to buffer. -template -FMT_CONSTEXPR auto write_exponent(int exp, It it) -> It { +template +FMT_CONSTEXPR auto write_exponent(int exp, OutputIt out) -> OutputIt { FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range"); if (exp < 0) { - *it++ = static_cast('-'); + *out++ = static_cast('-'); exp = -exp; } else { - *it++ = static_cast('+'); + *out++ = static_cast('+'); } - if (exp >= 100) { - const char* top = digits2(to_unsigned(exp / 100)); - if (exp >= 1000) *it++ = static_cast(top[0]); - *it++ = static_cast(top[1]); - exp %= 100; + auto uexp = static_cast(exp); + if (is_constant_evaluated()) { + if (uexp < 10) *out++ = '0'; + return format_decimal(out, uexp, count_digits(uexp)); } - const char* d = digits2(to_unsigned(exp)); - *it++ = static_cast(d[0]); - *it++ = static_cast(d[1]); - return it; + if (uexp >= 100u) { + const char* top = digits2(uexp / 100); + if (uexp >= 1000u) *out++ = static_cast(top[0]); + *out++ = static_cast(top[1]); + uexp %= 100; + } + const char* d = digits2(uexp); + *out++ = static_cast(d[0]); + *out++ = static_cast(d[1]); + return out; } // A floating-point number f * pow(2, e) where F is an unsigned type. @@ -1698,7 +1565,7 @@ using fp = basic_fp; // Normalizes the value converted from double and multiplied by (1 << SHIFT). template -FMT_CONSTEXPR basic_fp normalize(basic_fp value) { +FMT_CONSTEXPR auto normalize(basic_fp value) -> basic_fp { // Handle subnormals. const auto implicit_bit = F(1) << num_significand_bits(); const auto shifted_implicit_bit = implicit_bit << SHIFT; @@ -1715,7 +1582,7 @@ FMT_CONSTEXPR basic_fp normalize(basic_fp value) { } // Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking. -FMT_CONSTEXPR inline uint64_t multiply(uint64_t lhs, uint64_t rhs) { +FMT_CONSTEXPR inline auto multiply(uint64_t lhs, uint64_t rhs) -> uint64_t { #if FMT_USE_INT128 auto product = static_cast<__uint128_t>(lhs) * rhs; auto f = static_cast(product >> 64); @@ -1732,33 +1599,10 @@ FMT_CONSTEXPR inline uint64_t multiply(uint64_t lhs, uint64_t rhs) { #endif } -FMT_CONSTEXPR inline fp operator*(fp x, fp y) { +FMT_CONSTEXPR inline auto operator*(fp x, fp y) -> fp { return {multiply(x.f, y.f), x.e + y.e + 64}; } -template struct basic_data { - // For checking rounding thresholds. - // The kth entry is chosen to be the smallest integer such that the - // upper 32-bits of 10^(k+1) times it is strictly bigger than 5 * 10^k. - static constexpr uint32_t fractional_part_rounding_thresholds[8] = { - 2576980378U, // ceil(2^31 + 2^32/10^1) - 2190433321U, // ceil(2^31 + 2^32/10^2) - 2151778616U, // ceil(2^31 + 2^32/10^3) - 2147913145U, // ceil(2^31 + 2^32/10^4) - 2147526598U, // ceil(2^31 + 2^32/10^5) - 2147487943U, // ceil(2^31 + 2^32/10^6) - 2147484078U, // ceil(2^31 + 2^32/10^7) - 2147483691U // ceil(2^31 + 2^32/10^8) - }; -}; -// This is a struct rather than an alias to avoid shadowing warnings in gcc. -struct data : basic_data<> {}; - -#if FMT_CPLUSPLUS < 201703L -template -constexpr uint32_t basic_data::fractional_part_rounding_thresholds[]; -#endif - template () == num_bits()> using convert_float_result = conditional_t::value || doublish, double, T>; @@ -1768,67 +1612,69 @@ constexpr auto convert_float(T value) -> convert_float_result { return static_cast>(value); } -template +template FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, - const fill_t& fill) -> OutputIt { - auto fill_size = fill.size(); - if (fill_size == 1) return detail::fill_n(it, n, fill[0]); - auto data = fill.data(); - for (size_t i = 0; i < n; ++i) - it = copy_str(data, data + fill_size, it); + const basic_specs& specs) -> OutputIt { + auto fill_size = specs.fill_size(); + if (fill_size == 1) return detail::fill_n(it, n, specs.fill_unit()); + if (const Char* data = specs.fill()) { + for (size_t i = 0; i < n; ++i) it = copy(data, data + fill_size, it); + } return it; } // Writes the output of f, padded according to format specifications in specs. // size: output size in code units. // width: output display width in (terminal) column positions. -template -FMT_CONSTEXPR auto write_padded(OutputIt out, const format_specs& specs, +FMT_CONSTEXPR auto write_padded(OutputIt out, const format_specs& specs, size_t size, size_t width, F&& f) -> OutputIt { - static_assert(align == align::left || align == align::right, ""); + static_assert(default_align == align::left || default_align == align::right, + ""); unsigned spec_width = to_unsigned(specs.width); size_t padding = spec_width > width ? spec_width - width : 0; // Shifts are encoded as string literals because static constexpr is not // supported in constexpr functions. - auto* shifts = align == align::left ? "\x1f\x1f\x00\x01" : "\x00\x1f\x00\x01"; - size_t left_padding = padding >> shifts[specs.align]; + auto* shifts = + default_align == align::left ? "\x1f\x1f\x00\x01" : "\x00\x1f\x00\x01"; + size_t left_padding = padding >> shifts[static_cast(specs.align())]; size_t right_padding = padding - left_padding; - auto it = reserve(out, size + padding * specs.fill.size()); - if (left_padding != 0) it = fill(it, left_padding, specs.fill); + auto it = reserve(out, size + padding * specs.fill_size()); + if (left_padding != 0) it = fill(it, left_padding, specs); it = f(it); - if (right_padding != 0) it = fill(it, right_padding, specs.fill); + if (right_padding != 0) it = fill(it, right_padding, specs); return base_iterator(out, it); } -template -constexpr auto write_padded(OutputIt out, const format_specs& specs, +constexpr auto write_padded(OutputIt out, const format_specs& specs, size_t size, F&& f) -> OutputIt { - return write_padded(out, specs, size, size, f); + return write_padded(out, specs, size, size, f); } -template +template FMT_CONSTEXPR auto write_bytes(OutputIt out, string_view bytes, - const format_specs& specs) -> OutputIt { - return write_padded( + const format_specs& specs = {}) -> OutputIt { + return write_padded( out, specs, bytes.size(), [bytes](reserve_iterator it) { const char* data = bytes.data(); - return copy_str(data, data + bytes.size(), it); + return copy(data, data + bytes.size(), it); }); } template -auto write_ptr(OutputIt out, UIntPtr value, const format_specs* specs) +auto write_ptr(OutputIt out, UIntPtr value, const format_specs* specs) -> OutputIt { int num_digits = count_digits<4>(value); auto size = to_unsigned(num_digits) + size_t(2); auto write = [=](reserve_iterator it) { *it++ = static_cast('0'); *it++ = static_cast('x'); - return format_uint<4, Char>(it, value, num_digits); + return format_base2e(4, it, value, num_digits); }; - return specs ? write_padded(out, *specs, size, write) + return specs ? write_padded(out, *specs, size, write) : base_iterator(out, write(reserve(out, size))); } @@ -1836,8 +1682,9 @@ auto write_ptr(OutputIt out, UIntPtr value, const format_specs* specs) FMT_API auto is_printable(uint32_t cp) -> bool; inline auto needs_escape(uint32_t cp) -> bool { - return cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\' || - !is_printable(cp); + if (cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\') return true; + if (const_check(FMT_OPTIMIZE_SIZE > 1)) return false; + return !is_printable(cp); } template struct find_escape_result { @@ -1846,17 +1693,11 @@ template struct find_escape_result { uint32_t cp; }; -template -using make_unsigned_char = - typename conditional_t::value, - std::make_unsigned, - type_identity>::type; - template auto find_escape(const Char* begin, const Char* end) -> find_escape_result { for (; begin != end; ++begin) { - uint32_t cp = static_cast>(*begin); + uint32_t cp = static_cast>(*begin); if (const_check(sizeof(Char) == 1) && cp >= 0x80) continue; if (needs_escape(cp)) return {begin, begin + 1, cp}; } @@ -1865,7 +1706,7 @@ auto find_escape(const Char* begin, const Char* end) inline auto find_escape(const char* begin, const char* end) -> find_escape_result { - if (!is_utf8()) return find_escape(begin, end); + if (const_check(!use_utf8)) return find_escape(begin, end); auto result = find_escape_result{end, nullptr, 0}; for_each_codepoint(string_view(begin, to_unsigned(end - begin)), [&](uint32_t cp, string_view sv) { @@ -1878,40 +1719,14 @@ inline auto find_escape(const char* begin, const char* end) return result; } -#define FMT_STRING_IMPL(s, base, explicit) \ - [] { \ - /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \ - /* Use a macro-like name to avoid shadowing warnings. */ \ - struct FMT_VISIBILITY("hidden") FMT_COMPILE_STRING : base { \ - using char_type FMT_MAYBE_UNUSED = fmt::remove_cvref_t; \ - FMT_MAYBE_UNUSED FMT_CONSTEXPR explicit \ - operator fmt::basic_string_view() const { \ - return fmt::detail_exported::compile_string_to_view(s); \ - } \ - }; \ - return FMT_COMPILE_STRING(); \ - }() - -/** - \rst - Constructs a compile-time format string from a string literal *s*. - - **Example**:: - - // A compile-time error because 'd' is an invalid specifier for strings. - std::string s = fmt::format(FMT_STRING("{:d}"), "foo"); - \endrst - */ -#define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string, ) - template auto write_codepoint(OutputIt out, char prefix, uint32_t cp) -> OutputIt { *out++ = static_cast('\\'); *out++ = static_cast(prefix); Char buf[width]; fill_n(buf, width, static_cast('0')); - format_uint<4>(buf, cp, width); - return copy_str(buf, buf + width, out); + format_base2e(4, buf, cp, width); + return copy(buf, buf + width, out); } template @@ -1931,23 +1746,15 @@ auto write_escaped_cp(OutputIt out, const find_escape_result& escape) *out++ = static_cast('\\'); c = static_cast('t'); break; - case '"': - FMT_FALLTHROUGH; - case '\'': - FMT_FALLTHROUGH; - case '\\': - *out++ = static_cast('\\'); - break; + case '"': FMT_FALLTHROUGH; + case '\'': FMT_FALLTHROUGH; + case '\\': *out++ = static_cast('\\'); break; default: - if (escape.cp < 0x100) { - return write_codepoint<2, Char>(out, 'x', escape.cp); - } - if (escape.cp < 0x10000) { + if (escape.cp < 0x100) return write_codepoint<2, Char>(out, 'x', escape.cp); + if (escape.cp < 0x10000) return write_codepoint<4, Char>(out, 'u', escape.cp); - } - if (escape.cp < 0x110000) { + if (escape.cp < 0x110000) return write_codepoint<8, Char>(out, 'U', escape.cp); - } for (Char escape_char : basic_string_view( escape.begin, to_unsigned(escape.end - escape.begin))) { out = write_codepoint<2, Char>(out, 'x', @@ -1966,7 +1773,7 @@ auto write_escaped_string(OutputIt out, basic_string_view str) auto begin = str.begin(), end = str.end(); do { auto escape = find_escape(begin, end); - out = copy_str(begin, escape.begin, out); + out = copy(begin, escape.begin, out); begin = escape.end; if (!begin) break; out = write_escaped_cp(out, escape); @@ -1977,11 +1784,13 @@ auto write_escaped_string(OutputIt out, basic_string_view str) template auto write_escaped_char(OutputIt out, Char v) -> OutputIt { + Char v_array[1] = {v}; *out++ = static_cast('\''); if ((needs_escape(static_cast(v)) && v != static_cast('"')) || v == static_cast('\'')) { - out = write_escaped_cp( - out, find_escape_result{&v, &v + 1, static_cast(v)}); + out = write_escaped_cp(out, + find_escape_result{v_array, v_array + 1, + static_cast(v)}); } else { *out++ = v; } @@ -1991,36 +1800,207 @@ auto write_escaped_char(OutputIt out, Char v) -> OutputIt { template FMT_CONSTEXPR auto write_char(OutputIt out, Char value, - const format_specs& specs) -> OutputIt { - bool is_debug = specs.type == presentation_type::debug; - return write_padded(out, specs, 1, [=](reserve_iterator it) { + const format_specs& specs) -> OutputIt { + bool is_debug = specs.type() == presentation_type::debug; + return write_padded(out, specs, 1, [=](reserve_iterator it) { if (is_debug) return write_escaped_char(it, value); *it++ = value; return it; }); } template -FMT_CONSTEXPR auto write(OutputIt out, Char value, - const format_specs& specs, locale_ref loc = {}) - -> OutputIt { +FMT_CONSTEXPR auto write(OutputIt out, Char value, const format_specs& specs, + locale_ref loc = {}) -> OutputIt { // char is formatted as unsigned char for consistency across platforms. using unsigned_type = conditional_t::value, unsigned char, unsigned>; return check_char_specs(specs) - ? write_char(out, value, specs) - : write(out, static_cast(value), specs, loc); + ? write_char(out, value, specs) + : write(out, static_cast(value), specs, loc); } -// Data for write_int that doesn't depend on output iterator type. It is used to -// avoid template code bloat. -template struct write_int_data { - size_t size; - size_t padding; +template class digit_grouping { + private: + std::string grouping_; + std::basic_string thousands_sep_; - FMT_CONSTEXPR write_int_data(int num_digits, unsigned prefix, - const format_specs& specs) + struct next_state { + std::string::const_iterator group; + int pos; + }; + auto initial_state() const -> next_state { return {grouping_.begin(), 0}; } + + // Returns the next digit group separator position. + auto next(next_state& state) const -> int { + if (thousands_sep_.empty()) return max_value(); + if (state.group == grouping_.end()) return state.pos += grouping_.back(); + if (*state.group <= 0 || *state.group == max_value()) + return max_value(); + state.pos += *state.group++; + return state.pos; + } + + public: + template ::value)> + explicit digit_grouping(Locale loc, bool localized = true) { + if (!localized) return; + auto sep = thousands_sep(loc); + grouping_ = sep.grouping; + if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep); + } + digit_grouping(std::string grouping, std::basic_string sep) + : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {} + + auto has_separator() const -> bool { return !thousands_sep_.empty(); } + + auto count_separators(int num_digits) const -> int { + int count = 0; + auto state = initial_state(); + while (num_digits > next(state)) ++count; + return count; + } + + // Applies grouping to digits and write the output to out. + template + auto apply(Out out, basic_string_view digits) const -> Out { + auto num_digits = static_cast(digits.size()); + auto separators = basic_memory_buffer(); + separators.push_back(0); + auto state = initial_state(); + while (int i = next(state)) { + if (i >= num_digits) break; + separators.push_back(i); + } + for (int i = 0, sep_index = static_cast(separators.size() - 1); + i < num_digits; ++i) { + if (num_digits - i == separators[sep_index]) { + out = copy(thousands_sep_.data(), + thousands_sep_.data() + thousands_sep_.size(), out); + --sep_index; + } + *out++ = static_cast(digits[to_unsigned(i)]); + } + return out; + } +}; + +FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { + prefix |= prefix != 0 ? value << 8 : value; + prefix += (1u + (value > 0xff ? 1 : 0)) << 24; +} + +// Writes a decimal integer with digit grouping. +template +auto write_int(OutputIt out, UInt value, unsigned prefix, + const format_specs& specs, const digit_grouping& grouping) + -> OutputIt { + static_assert(std::is_same, UInt>::value, ""); + int num_digits = 0; + auto buffer = memory_buffer(); + switch (specs.type()) { + default: FMT_ASSERT(false, ""); FMT_FALLTHROUGH; + case presentation_type::none: + case presentation_type::dec: + num_digits = count_digits(value); + format_decimal(appender(buffer), value, num_digits); + break; + case presentation_type::hex: + if (specs.alt()) + prefix_append(prefix, unsigned(specs.upper() ? 'X' : 'x') << 8 | '0'); + num_digits = count_digits<4>(value); + format_base2e(4, appender(buffer), value, num_digits, specs.upper()); + break; + case presentation_type::oct: + num_digits = count_digits<3>(value); + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + if (specs.alt() && specs.precision <= num_digits && value != 0) + prefix_append(prefix, '0'); + format_base2e(3, appender(buffer), value, num_digits); + break; + case presentation_type::bin: + if (specs.alt()) + prefix_append(prefix, unsigned(specs.upper() ? 'B' : 'b') << 8 | '0'); + num_digits = count_digits<1>(value); + format_base2e(1, appender(buffer), value, num_digits); + break; + case presentation_type::chr: + return write_char(out, static_cast(value), specs); + } + + unsigned size = (prefix != 0 ? prefix >> 24 : 0) + to_unsigned(num_digits) + + to_unsigned(grouping.count_separators(num_digits)); + return write_padded( + out, specs, size, size, [&](reserve_iterator it) { + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + return grouping.apply(it, string_view(buffer.data(), buffer.size())); + }); +} + +#if FMT_USE_LOCALE +// Writes a localized value. +FMT_API auto write_loc(appender out, loc_value value, const format_specs& specs, + locale_ref loc) -> bool; +#endif +template +inline auto write_loc(OutputIt, const loc_value&, const format_specs&, + locale_ref) -> bool { + return false; +} + +template struct write_int_arg { + UInt abs_value; + unsigned prefix; +}; + +template +FMT_CONSTEXPR auto make_write_int_arg(T value, sign s) + -> write_int_arg> { + auto prefix = 0u; + auto abs_value = static_cast>(value); + if (is_negative(value)) { + prefix = 0x01000000 | '-'; + abs_value = 0 - abs_value; + } else { + constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+', + 0x1000000u | ' '}; + prefix = prefixes[static_cast(s)]; + } + return {abs_value, prefix}; +} + +template struct loc_writer { + basic_appender out; + const format_specs& specs; + std::basic_string sep; + std::string grouping; + std::basic_string decimal_point; + + template ::value)> + auto operator()(T value) -> bool { + auto arg = make_write_int_arg(value, specs.sign()); + write_int(out, static_cast>(arg.abs_value), arg.prefix, + specs, digit_grouping(grouping, sep)); + return true; + } + + template ::value)> + auto operator()(T) -> bool { + return false; + } +}; + +// Size and padding computation separate from write_int to avoid template bloat. +struct size_padding { + unsigned size; + unsigned padding; + + FMT_CONSTEXPR size_padding(int num_digits, unsigned prefix, + const format_specs& specs) : size((prefix >> 24) + to_unsigned(num_digits)), padding(0) { - if (specs.align == align::numeric) { + if (specs.align() == align::numeric) { auto width = to_unsigned(specs.width); if (width > size) { padding = width - size; @@ -2033,336 +2013,141 @@ template struct write_int_data { } }; -// Writes an integer in the format -// -// where are written by write_digits(it). -// prefix contains chars in three lower bytes and the size in the fourth byte. -template -FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, int num_digits, - unsigned prefix, - const format_specs& specs, - W write_digits) -> OutputIt { +template +FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, + const format_specs& specs) -> OutputIt { + static_assert(std::is_same>::value, ""); + + constexpr int buffer_size = num_bits(); + char buffer[buffer_size]; + if (is_constant_evaluated()) fill_n(buffer, buffer_size, '\0'); + const char* begin = nullptr; + const char* end = buffer + buffer_size; + + auto abs_value = arg.abs_value; + auto prefix = arg.prefix; + switch (specs.type()) { + default: FMT_ASSERT(false, ""); FMT_FALLTHROUGH; + case presentation_type::none: + case presentation_type::dec: + begin = do_format_decimal(buffer, abs_value, buffer_size); + break; + case presentation_type::hex: + begin = do_format_base2e(4, buffer, abs_value, buffer_size, specs.upper()); + if (specs.alt()) + prefix_append(prefix, unsigned(specs.upper() ? 'X' : 'x') << 8 | '0'); + break; + case presentation_type::oct: { + begin = do_format_base2e(3, buffer, abs_value, buffer_size); + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + auto num_digits = end - begin; + if (specs.alt() && specs.precision <= num_digits && abs_value != 0) + prefix_append(prefix, '0'); + break; + } + case presentation_type::bin: + begin = do_format_base2e(1, buffer, abs_value, buffer_size); + if (specs.alt()) + prefix_append(prefix, unsigned(specs.upper() ? 'B' : 'b') << 8 | '0'); + break; + case presentation_type::chr: + return write_char(out, static_cast(abs_value), specs); + } + + // Write an integer in the format + // + // prefix contains chars in three lower bytes and the size in the fourth byte. + int num_digits = static_cast(end - begin); // Slightly faster check for specs.width == 0 && specs.precision == -1. if ((specs.width | (specs.precision + 1)) == 0) { auto it = reserve(out, to_unsigned(num_digits) + (prefix >> 24)); - if (prefix != 0) { - for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) - *it++ = static_cast(p & 0xff); - } - return base_iterator(out, write_digits(it)); + for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) + *it++ = static_cast(p & 0xff); + return base_iterator(out, copy(begin, end, it)); } - auto data = write_int_data(num_digits, prefix, specs); - return write_padded( - out, specs, data.size, [=](reserve_iterator it) { + auto sp = size_padding(num_digits, prefix, specs); + unsigned padding = sp.padding; + return write_padded( + out, specs, sp.size, [=](reserve_iterator it) { for (unsigned p = prefix & 0xffffff; p != 0; p >>= 8) *it++ = static_cast(p & 0xff); - it = detail::fill_n(it, data.padding, static_cast('0')); - return write_digits(it); + it = detail::fill_n(it, padding, static_cast('0')); + return copy(begin, end, it); }); } -template class digit_grouping { - private: - std::string grouping_; - std::basic_string thousands_sep_; - - struct next_state { - std::string::const_iterator group; - int pos; - }; - next_state initial_state() const { return {grouping_.begin(), 0}; } - - // Returns the next digit group separator position. - int next(next_state& state) const { - if (thousands_sep_.empty()) return max_value(); - if (state.group == grouping_.end()) return state.pos += grouping_.back(); - if (*state.group <= 0 || *state.group == max_value()) - return max_value(); - state.pos += *state.group++; - return state.pos; - } - - public: - explicit digit_grouping(locale_ref loc, bool localized = true) { - if (!localized) return; - auto sep = thousands_sep(loc); - grouping_ = sep.grouping; - if (sep.thousands_sep) thousands_sep_.assign(1, sep.thousands_sep); - } - digit_grouping(std::string grouping, std::basic_string sep) - : grouping_(std::move(grouping)), thousands_sep_(std::move(sep)) {} - - bool has_separator() const { return !thousands_sep_.empty(); } - - int count_separators(int num_digits) const { - int count = 0; - auto state = initial_state(); - while (num_digits > next(state)) ++count; - return count; - } - - // Applies grouping to digits and write the output to out. - template - Out apply(Out out, basic_string_view digits) const { - auto num_digits = static_cast(digits.size()); - auto separators = basic_memory_buffer(); - separators.push_back(0); - auto state = initial_state(); - while (int i = next(state)) { - if (i >= num_digits) break; - separators.push_back(i); - } - for (int i = 0, sep_index = static_cast(separators.size() - 1); - i < num_digits; ++i) { - if (num_digits - i == separators[sep_index]) { - out = - copy_str(thousands_sep_.data(), - thousands_sep_.data() + thousands_sep_.size(), out); - --sep_index; - } - *out++ = static_cast(digits[to_unsigned(i)]); - } - return out; - } -}; - -// Writes a decimal integer with digit grouping. -template -auto write_int(OutputIt out, UInt value, unsigned prefix, - const format_specs& specs, - const digit_grouping& grouping) -> OutputIt { - static_assert(std::is_same, UInt>::value, ""); - int num_digits = count_digits(value); - char digits[40]; - format_decimal(digits, value, num_digits); - unsigned size = to_unsigned((prefix != 0 ? 1 : 0) + num_digits + - grouping.count_separators(num_digits)); - return write_padded( - out, specs, size, size, [&](reserve_iterator it) { - if (prefix != 0) { - char sign = static_cast(prefix); - *it++ = static_cast(sign); - } - return grouping.apply(it, string_view(digits, to_unsigned(num_digits))); - }); -} - -// Writes a localized value. -FMT_API auto write_loc(appender out, loc_value value, - const format_specs<>& specs, locale_ref loc) -> bool; -template -inline auto write_loc(OutputIt, loc_value, const format_specs&, - locale_ref) -> bool { - return false; -} - -FMT_CONSTEXPR inline void prefix_append(unsigned& prefix, unsigned value) { - prefix |= prefix != 0 ? value << 8 : value; - prefix += (1u + (value > 0xff ? 1 : 0)) << 24; -} - -template struct write_int_arg { - UInt abs_value; - unsigned prefix; -}; - -template -FMT_CONSTEXPR auto make_write_int_arg(T value, sign_t sign) - -> write_int_arg> { - auto prefix = 0u; - auto abs_value = static_cast>(value); - if (is_negative(value)) { - prefix = 0x01000000 | '-'; - abs_value = 0 - abs_value; - } else { - constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+', - 0x1000000u | ' '}; - prefix = prefixes[sign]; - } - return {abs_value, prefix}; -} - -template struct loc_writer { - buffer_appender out; - const format_specs& specs; - std::basic_string sep; - std::string grouping; - std::basic_string decimal_point; - - template ::value)> - auto operator()(T value) -> bool { - auto arg = make_write_int_arg(value, specs.sign); - write_int(out, static_cast>(arg.abs_value), arg.prefix, - specs, digit_grouping(grouping, sep)); - return true; - } - - template ::value)> - auto operator()(T) -> bool { - return false; - } -}; - template -FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, - const format_specs& specs, - locale_ref) -> OutputIt { - static_assert(std::is_same>::value, ""); - auto abs_value = arg.abs_value; - auto prefix = arg.prefix; - switch (specs.type) { - case presentation_type::none: - case presentation_type::dec: { - auto num_digits = count_digits(abs_value); - return write_int( - out, num_digits, prefix, specs, [=](reserve_iterator it) { - return format_decimal(it, abs_value, num_digits).end; - }); - } - case presentation_type::hex_lower: - case presentation_type::hex_upper: { - bool upper = specs.type == presentation_type::hex_upper; - if (specs.alt) - prefix_append(prefix, unsigned(upper ? 'X' : 'x') << 8 | '0'); - int num_digits = count_digits<4>(abs_value); - return write_int( - out, num_digits, prefix, specs, [=](reserve_iterator it) { - return format_uint<4, Char>(it, abs_value, num_digits, upper); - }); - } - case presentation_type::bin_lower: - case presentation_type::bin_upper: { - bool upper = specs.type == presentation_type::bin_upper; - if (specs.alt) - prefix_append(prefix, unsigned(upper ? 'B' : 'b') << 8 | '0'); - int num_digits = count_digits<1>(abs_value); - return write_int(out, num_digits, prefix, specs, - [=](reserve_iterator it) { - return format_uint<1, Char>(it, abs_value, num_digits); - }); - } - case presentation_type::oct: { - int num_digits = count_digits<3>(abs_value); - // Octal prefix '0' is counted as a digit, so only add it if precision - // is not greater than the number of digits. - if (specs.alt && specs.precision <= num_digits && abs_value != 0) - prefix_append(prefix, '0'); - return write_int(out, num_digits, prefix, specs, - [=](reserve_iterator it) { - return format_uint<3, Char>(it, abs_value, num_digits); - }); - } - case presentation_type::chr: - return write_char(out, static_cast(abs_value), specs); - default: - throw_format_error("invalid format specifier"); - } - return out; +FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline(OutputIt out, + write_int_arg arg, + const format_specs& specs) + -> OutputIt { + return write_int(out, arg, specs); } -template -FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline( - OutputIt out, write_int_arg arg, const format_specs& specs, - locale_ref loc) -> OutputIt { - return write_int(out, arg, specs, loc); -} -template ::value && !std::is_same::value && - std::is_same>::value)> -FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, - const format_specs& specs, - locale_ref loc) -> OutputIt { - if (specs.localized && write_loc(out, value, specs, loc)) return out; - return write_int_noinline(out, make_write_int_arg(value, specs.sign), specs, - loc); + !std::is_same::value)> +FMT_CONSTEXPR FMT_INLINE auto write(basic_appender out, T value, + const format_specs& specs, locale_ref loc) + -> basic_appender { + if (specs.localized() && write_loc(out, value, specs, loc)) return out; + return write_int_noinline(out, make_write_int_arg(value, specs.sign()), + specs); } + // An inlined version of write used in format string compilation. template ::value && !std::is_same::value && - !std::is_same>::value)> + !std::is_same::value && + !std::is_same>::value)> FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, - const format_specs& specs, - locale_ref loc) -> OutputIt { - if (specs.localized && write_loc(out, value, specs, loc)) return out; - return write_int(out, make_write_int_arg(value, specs.sign), specs, loc); + const format_specs& specs, locale_ref loc) + -> OutputIt { + if (specs.localized() && write_loc(out, value, specs, loc)) return out; + return write_int(out, make_write_int_arg(value, specs.sign()), specs); } -// An output iterator that counts the number of objects written to it and -// discards them. -class counting_iterator { - private: - size_t count_; - - public: - using iterator_category = std::output_iterator_tag; - using difference_type = std::ptrdiff_t; - using pointer = void; - using reference = void; - FMT_UNCHECKED_ITERATOR(counting_iterator); - - struct value_type { - template FMT_CONSTEXPR void operator=(const T&) {} - }; - - FMT_CONSTEXPR counting_iterator() : count_(0) {} - - FMT_CONSTEXPR size_t count() const { return count_; } - - FMT_CONSTEXPR counting_iterator& operator++() { - ++count_; - return *this; - } - FMT_CONSTEXPR counting_iterator operator++(int) { - auto it = *this; - ++*this; - return it; - } - - FMT_CONSTEXPR friend counting_iterator operator+(counting_iterator it, - difference_type n) { - it.count_ += static_cast(n); - return it; - } - - FMT_CONSTEXPR value_type operator*() const { return {}; } -}; - template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, - const format_specs& specs) -> OutputIt { + const format_specs& specs) -> OutputIt { auto data = s.data(); auto size = s.size(); if (specs.precision >= 0 && to_unsigned(specs.precision) < size) size = code_point_index(s, to_unsigned(specs.precision)); - bool is_debug = specs.type == presentation_type::debug; + + bool is_debug = specs.type() == presentation_type::debug; + if (is_debug) { + auto buf = counting_buffer(); + write_escaped_string(basic_appender(buf), s); + size = buf.count(); + } + size_t width = 0; if (specs.width != 0) { - if (is_debug) - width = write_escaped_string(counting_iterator{}, s).count(); - else - width = compute_width(basic_string_view(data, size)); + width = + is_debug ? size : compute_width(basic_string_view(data, size)); } - return write_padded(out, specs, size, width, - [=](reserve_iterator it) { - if (is_debug) return write_escaped_string(it, s); - return copy_str(data, data + size, it); - }); + return write_padded( + out, specs, size, width, [=](reserve_iterator it) { + return is_debug ? write_escaped_string(it, s) + : copy(data, data + size, it); + }); } template -FMT_CONSTEXPR auto write(OutputIt out, - basic_string_view> s, - const format_specs& specs, locale_ref) - -> OutputIt { - return write(out, s, specs); +FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, + const format_specs& specs, locale_ref) -> OutputIt { + return write(out, s, specs); } template -FMT_CONSTEXPR auto write(OutputIt out, const Char* s, - const format_specs& specs, locale_ref) - -> OutputIt { - return specs.type != presentation_type::pointer - ? write(out, basic_string_view(s), specs, {}) - : write_ptr(out, bit_cast(s), &specs); +FMT_CONSTEXPR auto write(OutputIt out, const Char* s, const format_specs& specs, + locale_ref) -> OutputIt { + if (specs.type() == presentation_type::pointer) + return write_ptr(out, bit_cast(s), &specs); + if (!s) report_error("string pointer is null"); + return write(out, basic_string_view(s), specs, {}); } template OutputIt { if (negative) abs_value = ~abs_value + 1; int num_digits = count_digits(abs_value); auto size = (negative ? 1 : 0) + static_cast(num_digits); - auto it = reserve(out, size); - if (auto ptr = to_pointer(it, size)) { + if (auto ptr = to_pointer(out, size)) { if (negative) *ptr++ = static_cast('-'); format_decimal(ptr, abs_value, num_digits); return out; } - if (negative) *it++ = static_cast('-'); - it = format_decimal(it, abs_value, num_digits).end; - return base_iterator(out, it); + if (negative) *out++ = static_cast('-'); + return format_decimal(out, abs_value, num_digits); } -// DEPRECATED! template FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, - format_specs& specs) -> const Char* { + format_specs& specs) -> const Char* { FMT_ASSERT(begin != end, ""); - auto align = align::none; + auto alignment = align::none; auto p = begin + code_point_length(begin); if (end - p <= 0) p = begin; for (;;) { switch (to_ascii(*p)) { - case '<': - align = align::left; - break; - case '>': - align = align::right; - break; - case '^': - align = align::center; - break; + case '<': alignment = align::left; break; + case '>': alignment = align::right; break; + case '^': alignment = align::center; break; } - if (align != align::none) { + if (alignment != align::none) { if (p != begin) { auto c = *begin; if (c == '}') return begin; if (c == '{') { - throw_format_error("invalid fill character '{'"); + report_error("invalid fill character '{'"); return begin; } - specs.fill = {begin, to_unsigned(p - begin)}; + specs.set_fill(basic_string_view(begin, to_unsigned(p - begin))); begin = p + 1; } else { ++begin; @@ -2426,89 +2202,27 @@ FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, } p = begin; } - specs.align = align; + specs.set_align(alignment); return begin; } -// A floating-point presentation format. -enum class float_format : unsigned char { - general, // General: exponent notation or fixed point based on magnitude. - exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3. - fixed, // Fixed point with the default precision of 6, e.g. 0.0012. - hex -}; - -struct float_specs { - int precision; - float_format format : 8; - sign_t sign : 8; - bool upper : 1; - bool locale : 1; - bool binary32 : 1; - bool showpoint : 1; -}; - -template -FMT_CONSTEXPR auto parse_float_type_spec(const format_specs& specs, - ErrorHandler&& eh = {}) - -> float_specs { - auto result = float_specs(); - result.showpoint = specs.alt; - result.locale = specs.localized; - switch (specs.type) { - case presentation_type::none: - result.format = float_format::general; - break; - case presentation_type::general_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::general_lower: - result.format = float_format::general; - break; - case presentation_type::exp_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::exp_lower: - result.format = float_format::exp; - result.showpoint |= specs.precision != 0; - break; - case presentation_type::fixed_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::fixed_lower: - result.format = float_format::fixed; - result.showpoint |= specs.precision != 0; - break; - case presentation_type::hexfloat_upper: - result.upper = true; - FMT_FALLTHROUGH; - case presentation_type::hexfloat_lower: - result.format = float_format::hex; - break; - default: - eh.on_error("invalid format specifier"); - break; - } - return result; -} - template FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isnan, - format_specs specs, - const float_specs& fspecs) -> OutputIt { + format_specs specs, sign s) -> OutputIt { auto str = - isnan ? (fspecs.upper ? "NAN" : "nan") : (fspecs.upper ? "INF" : "inf"); + isnan ? (specs.upper() ? "NAN" : "nan") : (specs.upper() ? "INF" : "inf"); constexpr size_t str_size = 3; - auto sign = fspecs.sign; - auto size = str_size + (sign ? 1 : 0); + auto size = str_size + (s != sign::none ? 1 : 0); // Replace '0'-padding with space for non-finite values. const bool is_zero_fill = - specs.fill.size() == 1 && *specs.fill.data() == static_cast('0'); - if (is_zero_fill) specs.fill[0] = static_cast(' '); - return write_padded(out, specs, size, [=](reserve_iterator it) { - if (sign) *it++ = detail::sign(sign); - return copy_str(str, str + str_size, it); - }); + specs.fill_size() == 1 && specs.fill_unit() == '0'; + if (is_zero_fill) specs.set_fill(' '); + return write_padded(out, specs, size, + [=](reserve_iterator it) { + if (s != sign::none) + *it++ = detail::getsign(s); + return copy(str, str + str_size, it); + }); } // A decimal floating-point number significand * pow(10, exp). @@ -2529,12 +2243,12 @@ inline auto get_significand_size(const dragonbox::decimal_fp& f) -> int { template constexpr auto write_significand(OutputIt out, const char* significand, int significand_size) -> OutputIt { - return copy_str(significand, significand + significand_size, out); + return copy(significand, significand + significand_size, out); } template inline auto write_significand(OutputIt out, UInt significand, int significand_size) -> OutputIt { - return format_decimal(out, significand, significand_size).end; + return format_decimal(out, significand, significand_size); } template FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, @@ -2554,14 +2268,13 @@ template ::value)> inline auto write_significand(Char* out, UInt significand, int significand_size, int integral_size, Char decimal_point) -> Char* { - if (!decimal_point) - return format_decimal(out, significand, significand_size).end; + if (!decimal_point) return format_decimal(out, significand, significand_size); out += significand_size + 1; Char* end = out; int floating_size = significand_size - integral_size; for (int i = floating_size / 2; i > 0; --i) { out -= 2; - copy2(out, digits2(static_cast(significand % 100))); + write2digits(out, static_cast(significand % 100)); significand /= 100; } if (floating_size % 2 != 0) { @@ -2582,19 +2295,19 @@ inline auto write_significand(OutputIt out, UInt significand, Char buffer[digits10() + 2]; auto end = write_significand(buffer, significand, significand_size, integral_size, decimal_point); - return detail::copy_str_noinline(buffer, end, out); + return detail::copy_noinline(buffer, end, out); } template FMT_CONSTEXPR auto write_significand(OutputIt out, const char* significand, int significand_size, int integral_size, Char decimal_point) -> OutputIt { - out = detail::copy_str_noinline(significand, - significand + integral_size, out); + out = detail::copy_noinline(significand, significand + integral_size, + out); if (!decimal_point) return out; *out++ = decimal_point; - return detail::copy_str_noinline(significand + integral_size, - significand + significand_size, out); + return detail::copy_noinline(significand + integral_size, + significand + significand_size, out); } template @@ -2607,44 +2320,42 @@ FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, decimal_point); } auto buffer = basic_memory_buffer(); - write_significand(buffer_appender(buffer), significand, - significand_size, integral_size, decimal_point); + write_significand(basic_appender(buffer), significand, significand_size, + integral_size, decimal_point); grouping.apply( out, basic_string_view(buffer.data(), to_unsigned(integral_size))); - return detail::copy_str_noinline(buffer.data() + integral_size, - buffer.end(), out); + return detail::copy_noinline(buffer.data() + integral_size, + buffer.end(), out); } -template > FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, - const format_specs& specs, - float_specs fspecs, locale_ref loc) - -> OutputIt { + const format_specs& specs, sign s, + locale_ref loc) -> OutputIt { auto significand = f.significand; int significand_size = get_significand_size(f); const Char zero = static_cast('0'); - auto sign = fspecs.sign; - size_t size = to_unsigned(significand_size) + (sign ? 1 : 0); + size_t size = to_unsigned(significand_size) + (s != sign::none ? 1 : 0); using iterator = reserve_iterator; - Char decimal_point = - fspecs.locale ? detail::decimal_point(loc) : static_cast('.'); + Char decimal_point = specs.localized() ? detail::decimal_point(loc) + : static_cast('.'); int output_exp = f.exponent + significand_size - 1; auto use_exp_format = [=]() { - if (fspecs.format == float_format::exp) return true; - if (fspecs.format != float_format::general) return false; + if (specs.type() == presentation_type::exp) return true; + if (specs.type() == presentation_type::fixed) return false; // Use the fixed notation if the exponent is in [exp_lower, exp_upper), // e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation. const int exp_lower = -4, exp_upper = 16; return output_exp < exp_lower || - output_exp >= (fspecs.precision > 0 ? fspecs.precision : exp_upper); + output_exp >= (specs.precision > 0 ? specs.precision : exp_upper); }; if (use_exp_format()) { int num_zeros = 0; - if (fspecs.showpoint) { - num_zeros = fspecs.precision - significand_size; + if (specs.alt()) { + num_zeros = specs.precision - significand_size; if (num_zeros < 0) num_zeros = 0; size += to_unsigned(num_zeros); } else if (significand_size == 1) { @@ -2655,9 +2366,9 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3; size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits); - char exp_char = fspecs.upper ? 'E' : 'e'; + char exp_char = specs.upper() ? 'E' : 'e'; auto write = [=](iterator it) { - if (sign) *it++ = detail::sign(sign); + if (s != sign::none) *it++ = detail::getsign(s); // Insert a decimal point after the first digit and add an exponent. it = write_significand(it, significand, significand_size, 1, decimal_point); @@ -2665,39 +2376,41 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, *it++ = static_cast(exp_char); return write_exponent(output_exp, it); }; - return specs.width > 0 ? write_padded(out, specs, size, write) - : base_iterator(out, write(reserve(out, size))); + return specs.width > 0 + ? write_padded(out, specs, size, write) + : base_iterator(out, write(reserve(out, size))); } int exp = f.exponent + significand_size; if (f.exponent >= 0) { // 1234e5 -> 123400000[.0+] size += to_unsigned(f.exponent); - int num_zeros = fspecs.precision - exp; + int num_zeros = specs.precision - exp; abort_fuzzing_if(num_zeros > 5000); - if (fspecs.showpoint) { + if (specs.alt()) { ++size; - if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 0; + if (num_zeros <= 0 && specs.type() != presentation_type::fixed) + num_zeros = 0; if (num_zeros > 0) size += to_unsigned(num_zeros); } - auto grouping = Grouping(loc, fspecs.locale); + auto grouping = Grouping(loc, specs.localized()); size += to_unsigned(grouping.count_separators(exp)); - return write_padded(out, specs, size, [&](iterator it) { - if (sign) *it++ = detail::sign(sign); + return write_padded(out, specs, size, [&](iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); it = write_significand(it, significand, significand_size, f.exponent, grouping); - if (!fspecs.showpoint) return it; + if (!specs.alt()) return it; *it++ = decimal_point; return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; }); } else if (exp > 0) { // 1234e-2 -> 12.34[0+] - int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0; - size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0); - auto grouping = Grouping(loc, fspecs.locale); + int num_zeros = specs.alt() ? specs.precision - significand_size : 0; + size += 1 + static_cast(max_of(num_zeros, 0)); + auto grouping = Grouping(loc, specs.localized()); size += to_unsigned(grouping.count_separators(exp)); - return write_padded(out, specs, size, [&](iterator it) { - if (sign) *it++ = detail::sign(sign); + return write_padded(out, specs, size, [&](iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); it = write_significand(it, significand, significand_size, exp, decimal_point, grouping); return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; @@ -2705,14 +2418,14 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, } // 1234e-6 -> 0.001234 int num_zeros = -exp; - if (significand_size == 0 && fspecs.precision >= 0 && - fspecs.precision < num_zeros) { - num_zeros = fspecs.precision; + if (significand_size == 0 && specs.precision >= 0 && + specs.precision < num_zeros) { + num_zeros = specs.precision; } - bool pointy = num_zeros != 0 || significand_size != 0 || fspecs.showpoint; + bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt(); size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros); - return write_padded(out, specs, size, [&](iterator it) { - if (sign) *it++ = detail::sign(sign); + return write_padded(out, specs, size, [&](iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); *it++ = zero; if (!pointy) return it; *it++ = decimal_point; @@ -2725,32 +2438,30 @@ template class fallback_digit_grouping { public: constexpr fallback_digit_grouping(locale_ref, bool) {} - constexpr bool has_separator() const { return false; } + constexpr auto has_separator() const -> bool { return false; } - constexpr int count_separators(int) const { return 0; } + constexpr auto count_separators(int) const -> int { return 0; } template - constexpr Out apply(Out out, basic_string_view) const { + constexpr auto apply(Out out, basic_string_view) const -> Out { return out; } }; -template +template FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f, - const format_specs& specs, - float_specs fspecs, locale_ref loc) - -> OutputIt { + const format_specs& specs, sign s, + locale_ref loc) -> OutputIt { if (is_constant_evaluated()) { - return do_write_float>(out, f, specs, fspecs, - loc); + return do_write_float>(out, f, specs, s, loc); } else { - return do_write_float(out, f, specs, fspecs, loc); + return do_write_float(out, f, specs, s, loc); } } -template constexpr bool isnan(T value) { - return !(value >= value); // std::isnan doesn't support __float128. +template constexpr auto isnan(T value) -> bool { + return value != value; // std::isnan doesn't support __float128. } template @@ -2762,14 +2473,14 @@ struct has_isfinite> template ::value&& has_isfinite::value)> -FMT_CONSTEXPR20 bool isfinite(T value) { +FMT_CONSTEXPR20 auto isfinite(T value) -> bool { constexpr T inf = T(std::numeric_limits::infinity()); if (is_constant_evaluated()) return !detail::isnan(value) && value < inf && value > -inf; return std::isfinite(value); } template ::value)> -FMT_CONSTEXPR bool isfinite(T value) { +FMT_CONSTEXPR auto isfinite(T value) -> bool { T inf = T(std::numeric_limits::infinity()); // std::isfinite doesn't support __float128. return !detail::isnan(value) && value < inf && value > -inf; @@ -2798,52 +2509,48 @@ inline FMT_CONSTEXPR20 void adjust_precision(int& precision, int exp10) { class bigint { private: - // A bigint is stored as an array of bigits (big digits), with bigit at index - // 0 being the least significant one. - using bigit = uint32_t; + // A bigint is a number in the form bigit_[N - 1] ... bigit_[0] * 32^exp_. + using bigit = uint32_t; // A big digit. using double_bigit = uint64_t; + enum { bigit_bits = num_bits() }; enum { bigits_capacity = 32 }; basic_memory_buffer bigits_; int exp_; - FMT_CONSTEXPR20 bigit operator[](int index) const { - return bigits_[to_unsigned(index)]; - } - FMT_CONSTEXPR20 bigit& operator[](int index) { - return bigits_[to_unsigned(index)]; - } - - static constexpr const int bigit_bits = num_bits(); - friend struct formatter; - FMT_CONSTEXPR20 void subtract_bigits(int index, bigit other, bigit& borrow) { - auto result = static_cast((*this)[index]) - other - borrow; - (*this)[index] = static_cast(result); + FMT_CONSTEXPR auto get_bigit(int i) const -> bigit { + return i >= exp_ && i < num_bigits() ? bigits_[i - exp_] : 0; + } + + FMT_CONSTEXPR void subtract_bigits(int index, bigit other, bigit& borrow) { + auto result = double_bigit(bigits_[index]) - other - borrow; + bigits_[index] = static_cast(result); borrow = static_cast(result >> (bigit_bits * 2 - 1)); } - FMT_CONSTEXPR20 void remove_leading_zeros() { + FMT_CONSTEXPR void remove_leading_zeros() { int num_bigits = static_cast(bigits_.size()) - 1; - while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits; + while (num_bigits > 0 && bigits_[num_bigits] == 0) --num_bigits; bigits_.resize(to_unsigned(num_bigits + 1)); } // Computes *this -= other assuming aligned bigints and *this >= other. - FMT_CONSTEXPR20 void subtract_aligned(const bigint& other) { + FMT_CONSTEXPR void subtract_aligned(const bigint& other) { FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints"); FMT_ASSERT(compare(*this, other) >= 0, ""); bigit borrow = 0; int i = other.exp_ - exp_; for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j) subtract_bigits(i, other.bigits_[j], borrow); - while (borrow > 0) subtract_bigits(i, 0, borrow); + if (borrow != 0) subtract_bigits(i, 0, borrow); + FMT_ASSERT(borrow == 0, ""); remove_leading_zeros(); } - FMT_CONSTEXPR20 void multiply(uint32_t value) { - const double_bigit wide_value = value; + FMT_CONSTEXPR void multiply(uint32_t value) { bigit carry = 0; + const double_bigit wide_value = value; for (size_t i = 0, n = bigits_.size(); i < n; ++i) { double_bigit result = bigits_[i] * wide_value + carry; bigits_[i] = static_cast(result); @@ -2854,7 +2561,7 @@ class bigint { template ::value || std::is_same::value)> - FMT_CONSTEXPR20 void multiply(UInt value) { + FMT_CONSTEXPR void multiply(UInt value) { using half_uint = conditional_t::value, uint64_t, uint32_t>; const int shift = num_bits() - bigit_bits; @@ -2875,7 +2582,7 @@ class bigint { template ::value || std::is_same::value)> - FMT_CONSTEXPR20 void assign(UInt n) { + FMT_CONSTEXPR void assign(UInt n) { size_t num_bigits = 0; do { bigits_[num_bigits++] = static_cast(n); @@ -2886,30 +2593,30 @@ class bigint { } public: - FMT_CONSTEXPR20 bigint() : exp_(0) {} + FMT_CONSTEXPR bigint() : exp_(0) {} explicit bigint(uint64_t n) { assign(n); } bigint(const bigint&) = delete; void operator=(const bigint&) = delete; - FMT_CONSTEXPR20 void assign(const bigint& other) { + FMT_CONSTEXPR void assign(const bigint& other) { auto size = other.bigits_.size(); bigits_.resize(size); auto data = other.bigits_.data(); - copy_str(data, data + size, bigits_.data()); + copy(data, data + size, bigits_.data()); exp_ = other.exp_; } - template FMT_CONSTEXPR20 void operator=(Int n) { + template FMT_CONSTEXPR void operator=(Int n) { FMT_ASSERT(n > 0, ""); assign(uint64_or_128_t(n)); } - FMT_CONSTEXPR20 int num_bigits() const { + FMT_CONSTEXPR auto num_bigits() const -> int { return static_cast(bigits_.size()) + exp_; } - FMT_NOINLINE FMT_CONSTEXPR20 bigint& operator<<=(int shift) { + FMT_CONSTEXPR auto operator<<=(int shift) -> bigint& { FMT_ASSERT(shift >= 0, ""); exp_ += shift / bigit_bits; shift %= bigit_bits; @@ -2924,46 +2631,39 @@ class bigint { return *this; } - template FMT_CONSTEXPR20 bigint& operator*=(Int value) { + template FMT_CONSTEXPR auto operator*=(Int value) -> bigint& { FMT_ASSERT(value > 0, ""); multiply(uint32_or_64_or_128_t(value)); return *this; } - friend FMT_CONSTEXPR20 int compare(const bigint& lhs, const bigint& rhs) { - int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits(); - if (num_lhs_bigits != num_rhs_bigits) - return num_lhs_bigits > num_rhs_bigits ? 1 : -1; - int i = static_cast(lhs.bigits_.size()) - 1; - int j = static_cast(rhs.bigits_.size()) - 1; + friend FMT_CONSTEXPR auto compare(const bigint& b1, const bigint& b2) -> int { + int num_bigits1 = b1.num_bigits(), num_bigits2 = b2.num_bigits(); + if (num_bigits1 != num_bigits2) return num_bigits1 > num_bigits2 ? 1 : -1; + int i = static_cast(b1.bigits_.size()) - 1; + int j = static_cast(b2.bigits_.size()) - 1; int end = i - j; if (end < 0) end = 0; for (; i >= end; --i, --j) { - bigit lhs_bigit = lhs[i], rhs_bigit = rhs[j]; - if (lhs_bigit != rhs_bigit) return lhs_bigit > rhs_bigit ? 1 : -1; + bigit b1_bigit = b1.bigits_[i], b2_bigit = b2.bigits_[j]; + if (b1_bigit != b2_bigit) return b1_bigit > b2_bigit ? 1 : -1; } if (i != j) return i > j ? 1 : -1; return 0; } // Returns compare(lhs1 + lhs2, rhs). - friend FMT_CONSTEXPR20 int add_compare(const bigint& lhs1, const bigint& lhs2, - const bigint& rhs) { - auto minimum = [](int a, int b) { return a < b ? a : b; }; - auto maximum = [](int a, int b) { return a > b ? a : b; }; - int max_lhs_bigits = maximum(lhs1.num_bigits(), lhs2.num_bigits()); + friend FMT_CONSTEXPR auto add_compare(const bigint& lhs1, const bigint& lhs2, + const bigint& rhs) -> int { + int max_lhs_bigits = max_of(lhs1.num_bigits(), lhs2.num_bigits()); int num_rhs_bigits = rhs.num_bigits(); if (max_lhs_bigits + 1 < num_rhs_bigits) return -1; if (max_lhs_bigits > num_rhs_bigits) return 1; - auto get_bigit = [](const bigint& n, int i) -> bigit { - return i >= n.exp_ && i < n.num_bigits() ? n[i - n.exp_] : 0; - }; double_bigit borrow = 0; - int min_exp = minimum(minimum(lhs1.exp_, lhs2.exp_), rhs.exp_); + int min_exp = min_of(min_of(lhs1.exp_, lhs2.exp_), rhs.exp_); for (int i = num_rhs_bigits - 1; i >= min_exp; --i) { - double_bigit sum = - static_cast(get_bigit(lhs1, i)) + get_bigit(lhs2, i); - bigit rhs_bigit = get_bigit(rhs, i); + double_bigit sum = double_bigit(lhs1.get_bigit(i)) + lhs2.get_bigit(i); + bigit rhs_bigit = rhs.get_bigit(i); if (sum > rhs_bigit + borrow) return 1; borrow = rhs_bigit + borrow - sum; if (borrow > 1) return -1; @@ -2976,10 +2676,8 @@ class bigint { FMT_CONSTEXPR20 void assign_pow10(int exp) { FMT_ASSERT(exp >= 0, ""); if (exp == 0) return *this = 1; - // Find the top bit. - int bitmask = 1; - while (exp >= bitmask) bitmask <<= 1; - bitmask >>= 1; + int bitmask = 1 << (num_bits() - + countl_zero(static_cast(exp)) - 1); // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by // repeated squaring and multiplication. *this = 5; @@ -3003,17 +2701,17 @@ class bigint { // cross-product terms n[i] * n[j] such that i + j == bigit_index. for (int i = 0, j = bigit_index; j >= 0; ++i, --j) { // Most terms are multiplied twice which can be optimized in the future. - sum += static_cast(n[i]) * n[j]; + sum += double_bigit(n[i]) * n[j]; } - (*this)[bigit_index] = static_cast(sum); + bigits_[bigit_index] = static_cast(sum); sum >>= num_bits(); // Compute the carry. } // Do the same for the top half. for (int bigit_index = num_bigits; bigit_index < num_result_bigits; ++bigit_index) { for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;) - sum += static_cast(n[i++]) * n[j--]; - (*this)[bigit_index] = static_cast(sum); + sum += double_bigit(n[i++]) * n[j--]; + bigits_[bigit_index] = static_cast(sum); sum >>= num_bits(); } remove_leading_zeros(); @@ -3022,20 +2720,20 @@ class bigint { // If this bigint has a bigger exponent than other, adds trailing zero to make // exponents equal. This simplifies some operations such as subtraction. - FMT_CONSTEXPR20 void align(const bigint& other) { + FMT_CONSTEXPR void align(const bigint& other) { int exp_difference = exp_ - other.exp_; if (exp_difference <= 0) return; int num_bigits = static_cast(bigits_.size()); bigits_.resize(to_unsigned(num_bigits + exp_difference)); for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j) bigits_[j] = bigits_[i]; - std::uninitialized_fill_n(bigits_.data(), exp_difference, 0); + memset(bigits_.data(), 0, to_unsigned(exp_difference) * sizeof(bigit)); exp_ -= exp_difference; } // Divides this bignum by divisor, assigning the remainder to this and // returning the quotient. - FMT_CONSTEXPR20 int divmod_assign(const bigint& divisor) { + FMT_CONSTEXPR auto divmod_assign(const bigint& divisor) -> int { FMT_ASSERT(this != &divisor, ""); if (compare(*this, divisor) < 0) return 0; FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); @@ -3154,8 +2852,11 @@ FMT_CONSTEXPR20 inline void format_dragon(basic_fp value, // Generate the given number of digits. exp10 -= num_digits - 1; if (num_digits <= 0) { - denominator *= 10; - auto digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; + auto digit = '0'; + if (num_digits == 0) { + denominator *= 10; + digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0'; + } buf.push_back(digit); return; } @@ -3178,8 +2879,10 @@ FMT_CONSTEXPR20 inline void format_dragon(basic_fp value, } if (buf[0] == overflow) { buf[0] = '1'; - if ((flags & dragon::fixed) != 0) buf.push_back('0'); - else ++exp10; + if ((flags & dragon::fixed) != 0) + buf.push_back('0'); + else + ++exp10; } return; } @@ -3190,8 +2893,8 @@ FMT_CONSTEXPR20 inline void format_dragon(basic_fp value, // Formats a floating-point number using the hexfloat format. template ::value)> -FMT_CONSTEXPR20 void format_hexfloat(Float value, int precision, - float_specs specs, buffer& buf) { +FMT_CONSTEXPR20 void format_hexfloat(Float value, format_specs specs, + buffer& buf) { // float is passed as double to reduce the number of instantiations and to // simplify implementation. static_assert(!std::is_same::value, ""); @@ -3201,26 +2904,25 @@ FMT_CONSTEXPR20 void format_hexfloat(Float value, int precision, // Assume Float is in the format [sign][exponent][significand]. using carrier_uint = typename info::carrier_uint; - constexpr auto num_float_significand_bits = - detail::num_significand_bits(); + const auto num_float_significand_bits = detail::num_significand_bits(); basic_fp f(value); f.e += num_float_significand_bits; if (!has_implicit_bit()) --f.e; - constexpr auto num_fraction_bits = + const auto num_fraction_bits = num_float_significand_bits + (has_implicit_bit() ? 1 : 0); - constexpr auto num_xdigits = (num_fraction_bits + 3) / 4; + const auto num_xdigits = (num_fraction_bits + 3) / 4; - constexpr auto leading_shift = ((num_xdigits - 1) * 4); + const auto leading_shift = ((num_xdigits - 1) * 4); const auto leading_mask = carrier_uint(0xF) << leading_shift; const auto leading_xdigit = static_cast((f.f & leading_mask) >> leading_shift); if (leading_xdigit > 1) f.e -= (32 - countl_zero(leading_xdigit) - 1); int print_xdigits = num_xdigits - 1; - if (precision >= 0 && print_xdigits > precision) { - const int shift = ((print_xdigits - precision - 1) * 4); + if (specs.precision >= 0 && print_xdigits > specs.precision) { + const int shift = ((print_xdigits - specs.precision - 1) * 4); const auto mask = carrier_uint(0xF) << shift; const auto v = static_cast((f.f & mask) >> shift); @@ -3239,25 +2941,25 @@ FMT_CONSTEXPR20 void format_hexfloat(Float value, int precision, } } - print_xdigits = precision; + print_xdigits = specs.precision; } char xdigits[num_bits() / 4]; detail::fill_n(xdigits, sizeof(xdigits), '0'); - format_uint<4>(xdigits, f.f, num_xdigits, specs.upper); + format_base2e(4, xdigits, f.f, num_xdigits, specs.upper()); // Remove zero tail while (print_xdigits > 0 && xdigits[print_xdigits] == '0') --print_xdigits; buf.push_back('0'); - buf.push_back(specs.upper ? 'X' : 'x'); + buf.push_back(specs.upper() ? 'X' : 'x'); buf.push_back(xdigits[0]); - if (specs.showpoint || print_xdigits > 0 || print_xdigits < precision) + if (specs.alt() || print_xdigits > 0 || print_xdigits < specs.precision) buf.push_back('.'); buf.append(xdigits + 1, xdigits + 1 + print_xdigits); - for (; print_xdigits < precision; ++print_xdigits) buf.push_back('0'); + for (; print_xdigits < specs.precision; ++print_xdigits) buf.push_back('0'); - buf.push_back(specs.upper ? 'P' : 'p'); + buf.push_back(specs.upper() ? 'P' : 'p'); uint32_t abs_e; if (f.e < 0) { @@ -3271,21 +2973,32 @@ FMT_CONSTEXPR20 void format_hexfloat(Float value, int precision, } template ::value)> -FMT_CONSTEXPR20 void format_hexfloat(Float value, int precision, - float_specs specs, buffer& buf) { - format_hexfloat(static_cast(value), precision, specs, buf); +FMT_CONSTEXPR20 void format_hexfloat(Float value, format_specs specs, + buffer& buf) { + format_hexfloat(static_cast(value), specs, buf); +} + +constexpr auto fractional_part_rounding_thresholds(int index) -> uint32_t { + // For checking rounding thresholds. + // The kth entry is chosen to be the smallest integer such that the + // upper 32-bits of 10^(k+1) times it is strictly bigger than 5 * 10^k. + // It is equal to ceil(2^31 + 2^32/10^(k + 1)). + // These are stored in a string literal because we cannot have static arrays + // in constexpr functions and non-static ones are poorly optimized. + return U"\x9999999a\x828f5c29\x80418938\x80068db9\x8000a7c6\x800010c7" + U"\x800001ae\x8000002b"[index]; } template -FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, +FMT_CONSTEXPR20 auto format_float(Float value, int precision, + const format_specs& specs, bool binary32, buffer& buf) -> int { // float is passed as double to reduce the number of instantiations. static_assert(!std::is_same::value, ""); - FMT_ASSERT(value >= 0, "value is negative"); auto converted_value = convert_float(value); - const bool fixed = specs.format == float_format::fixed; - if (value <= 0) { // <= instead of == to silence a warning. + const bool fixed = specs.type() == presentation_type::fixed; + if (value == 0) { if (precision <= 0 || !fixed) { buf.push_back('0'); return 0; @@ -3310,16 +3023,6 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, exp = static_cast(e); if (e > exp) ++exp; // Compute ceil. dragon_flags = dragon::fixup; - } else if (precision < 0) { - // Use Dragonbox for the shortest format. - if (specs.binary32) { - auto dec = dragonbox::to_decimal(static_cast(value)); - write(buffer_appender(buf), dec.significand); - return dec.exponent; - } - auto dec = dragonbox::to_decimal(static_cast(value)); - write(buffer_appender(buf), dec.significand); - return dec.exponent; } else { // Extract significand bits and exponent bits. using info = dragonbox::float_info; @@ -3418,7 +3121,7 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, uint64_t prod; uint32_t digits; bool should_round_up; - int number_of_digits_to_print = precision > 9 ? 9 : precision; + int number_of_digits_to_print = min_of(precision, 9); // Print a 9-digits subsegment, either the first or the second. auto print_subsegment = [&](uint32_t subsegment, char* buffer) { @@ -3446,7 +3149,7 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, // for details. prod = ((subsegment * static_cast(450359963)) >> 20) + 1; digits = static_cast(prod >> 32); - copy2(buffer, digits2(digits)); + write2digits(buffer, digits); number_of_digits_printed += 2; } @@ -3454,7 +3157,7 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, while (number_of_digits_printed < number_of_digits_to_print) { prod = static_cast(prod) * static_cast(100); digits = static_cast(prod >> 32); - copy2(buffer + number_of_digits_printed, digits2(digits)); + write2digits(buffer + number_of_digits_printed, digits); number_of_digits_printed += 2; } }; @@ -3480,12 +3183,12 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, // fractional part is strictly larger than 1/2. if (precision < 9) { uint32_t fractional_part = static_cast(prod); - should_round_up = fractional_part >= - data::fractional_part_rounding_thresholds - [8 - number_of_digits_to_print] || - ((fractional_part >> 31) & - ((digits & 1) | (second_third_subsegments != 0) | - has_more_segments)) != 0; + should_round_up = + fractional_part >= fractional_part_rounding_thresholds( + 8 - number_of_digits_to_print) || + ((fractional_part >> 31) & + ((digits & 1) | (second_third_subsegments != 0) | + has_more_segments)) != 0; } // Rounding at the subsegment boundary. // In this case, the fractional part is at least 1/2 if and only if @@ -3520,12 +3223,12 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, // of 19 digits, so in this case the third segment should be // consisting of a genuine digit from the input. uint32_t fractional_part = static_cast(prod); - should_round_up = fractional_part >= - data::fractional_part_rounding_thresholds - [8 - number_of_digits_to_print] || - ((fractional_part >> 31) & - ((digits & 1) | (third_subsegment != 0) | - has_more_segments)) != 0; + should_round_up = + fractional_part >= fractional_part_rounding_thresholds( + 8 - number_of_digits_to_print) || + ((fractional_part >> 31) & + ((digits & 1) | (third_subsegment != 0) | + has_more_segments)) != 0; } // Rounding at the subsegment boundary. else { @@ -3563,9 +3266,8 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, } if (use_dragon) { auto f = basic_fp(); - bool is_predecessor_closer = specs.binary32 - ? f.assign(static_cast(value)) - : f.assign(converted_value); + bool is_predecessor_closer = binary32 ? f.assign(static_cast(value)) + : f.assign(converted_value); if (is_predecessor_closer) dragon_flags |= dragon::predecessor_closer; if (fixed) dragon_flags |= dragon::fixed; // Limit precision to the maximum possible number of significant digits in @@ -3574,7 +3276,7 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, if (precision > max_double_digits) precision = max_double_digits; format_dragon(f, dragon_flags, precision, buf, exp); } - if (!fixed && !specs.showpoint) { + if (!fixed && !specs.alt()) { // Remove trailing zeros. auto num_digits = buf.size(); while (num_digits > 0 && buf[num_digits - 1] == '0') { @@ -3585,97 +3287,97 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, float_specs specs, } return exp; } + template -FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, - format_specs specs, locale_ref loc) - -> OutputIt { - float_specs fspecs = parse_float_type_spec(specs); - fspecs.sign = specs.sign; - if (detail::signbit(value)) { // value < 0 is false for NaN so use signbit. - fspecs.sign = sign::minus; - value = -value; - } else if (fspecs.sign == sign::minus) { - fspecs.sign = sign::none; - } +FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs, + locale_ref loc) -> OutputIt { + // Use signbit because value < 0 is false for NaN. + sign s = detail::signbit(value) ? sign::minus : specs.sign(); if (!detail::isfinite(value)) - return write_nonfinite(out, detail::isnan(value), specs, fspecs); + return write_nonfinite(out, detail::isnan(value), specs, s); - if (specs.align == align::numeric && fspecs.sign) { - auto it = reserve(out, 1); - *it++ = detail::sign(fspecs.sign); - out = base_iterator(out, it); - fspecs.sign = sign::none; + if (specs.align() == align::numeric && s != sign::none) { + *out++ = detail::getsign(s); + s = sign::none; if (specs.width != 0) --specs.width; } - memory_buffer buffer; - if (fspecs.format == float_format::hex) { - if (fspecs.sign) buffer.push_back(detail::sign(fspecs.sign)); - format_hexfloat(convert_float(value), specs.precision, fspecs, buffer); - return write_bytes(out, {buffer.data(), buffer.size()}, - specs); + int precision = specs.precision; + if (precision < 0) { + if (specs.type() != presentation_type::none) { + precision = 6; + } else if (is_fast_float::value && !is_constant_evaluated()) { + // Use Dragonbox for the shortest format. + using floaty = conditional_t= sizeof(double), double, float>; + auto dec = dragonbox::to_decimal(static_cast(value)); + return write_float(out, dec, specs, s, loc); + } } - int precision = specs.precision >= 0 || specs.type == presentation_type::none - ? specs.precision - : 6; - if (fspecs.format == float_format::exp) { + + memory_buffer buffer; + if (specs.type() == presentation_type::hexfloat) { + if (s != sign::none) buffer.push_back(detail::getsign(s)); + format_hexfloat(convert_float(value), specs, buffer); + return write_bytes(out, {buffer.data(), buffer.size()}, + specs); + } + + if (specs.type() == presentation_type::exp) { if (precision == max_value()) - throw_format_error("number is too big"); + report_error("number is too big"); else ++precision; - } else if (fspecs.format != float_format::fixed && precision == 0) { + if (specs.precision != 0) specs.set_alt(); + } else if (specs.type() == presentation_type::fixed) { + if (specs.precision != 0) specs.set_alt(); + } else if (precision == 0) { precision = 1; } - if (const_check(std::is_same())) fspecs.binary32 = true; - int exp = format_float(convert_float(value), precision, fspecs, buffer); - fspecs.precision = precision; + int exp = format_float(convert_float(value), precision, specs, + std::is_same(), buffer); + + specs.precision = precision; auto f = big_decimal_fp{buffer.data(), static_cast(buffer.size()), exp}; - return write_float(out, f, specs, fspecs, loc); + return write_float(out, f, specs, s, loc); } template ::value)> -FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs, +FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs, locale_ref loc = {}) -> OutputIt { - if (const_check(!is_supported_floating_point(value))) return out; - return specs.localized && write_loc(out, value, specs, loc) + return specs.localized() && write_loc(out, value, specs, loc) ? out - : write_float(out, value, specs, loc); + : write_float(out, value, specs, loc); } template ::value)> FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt { - if (is_constant_evaluated()) return write(out, value, format_specs()); - if (const_check(!is_supported_floating_point(value))) return out; + if (is_constant_evaluated()) return write(out, value, format_specs()); - auto fspecs = float_specs(); - if (detail::signbit(value)) { - fspecs.sign = sign::minus; - value = -value; - } + auto s = detail::signbit(value) ? sign::minus : sign::none; - constexpr auto specs = format_specs(); - using floaty = conditional_t::value, double, T>; + constexpr auto specs = format_specs(); + using floaty = conditional_t= sizeof(double), double, float>; using floaty_uint = typename dragonbox::float_info::carrier_uint; floaty_uint mask = exponent_mask(); if ((bit_cast(value) & mask) == mask) - return write_nonfinite(out, std::isnan(value), specs, fspecs); + return write_nonfinite(out, std::isnan(value), specs, s); auto dec = dragonbox::to_decimal(static_cast(value)); - return write_float(out, dec, specs, fspecs, {}); + return write_float(out, dec, specs, s, {}); } template ::value && !is_fast_float::value)> inline auto write(OutputIt out, T value) -> OutputIt { - return write(out, value, format_specs()); + return write(out, value, format_specs()); } template -auto write(OutputIt out, monostate, format_specs = {}, locale_ref = {}) +auto write(OutputIt out, monostate, format_specs = {}, locale_ref = {}) -> OutputIt { FMT_ASSERT(false, ""); return out; @@ -3684,13 +3386,11 @@ auto write(OutputIt out, monostate, format_specs = {}, locale_ref = {}) template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view value) -> OutputIt { - auto it = reserve(out, value.size()); - it = copy_str_noinline(value.begin(), value.end(), it); - return base_iterator(out, it); + return copy_noinline(value.begin(), value.end(), out); } template ::value)> + FMT_ENABLE_IF(has_to_string_view::value)> constexpr auto write(OutputIt out, const T& value) -> OutputIt { return write(out, to_string_view(value)); } @@ -3698,10 +3398,8 @@ constexpr auto write(OutputIt out, const T& value) -> OutputIt { // FMT_ENABLE_IF() condition separated to workaround an MSVC bug. template < typename Char, typename OutputIt, typename T, - bool check = - std::is_enum::value && !std::is_same::value && - mapped_type_constant>::value != - type::custom_type, + bool check = std::is_enum::value && !std::is_same::value && + mapped_type_constant::value != type::custom_type, FMT_ENABLE_IF(check)> FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { return write(out, static_cast>(value)); @@ -3709,13 +3407,12 @@ FMT_CONSTEXPR auto write(OutputIt out, T value) -> OutputIt { template ::value)> -FMT_CONSTEXPR auto write(OutputIt out, T value, - const format_specs& specs = {}, locale_ref = {}) - -> OutputIt { - return specs.type != presentation_type::none && - specs.type != presentation_type::string - ? write(out, value ? 1 : 0, specs, {}) - : write_bytes(out, value ? "true" : "false", specs); +FMT_CONSTEXPR auto write(OutputIt out, T value, const format_specs& specs = {}, + locale_ref = {}) -> OutputIt { + return specs.type() != presentation_type::none && + specs.type() != presentation_type::string + ? write(out, value ? 1 : 0, specs, {}) + : write_bytes(out, value ? "true" : "false", specs); } template @@ -3726,193 +3423,150 @@ FMT_CONSTEXPR auto write(OutputIt out, Char value) -> OutputIt { } template -FMT_CONSTEXPR_CHAR_TRAITS auto write(OutputIt out, const Char* value) - -> OutputIt { +FMT_CONSTEXPR20 auto write(OutputIt out, const Char* value) -> OutputIt { if (value) return write(out, basic_string_view(value)); - throw_format_error("string pointer is null"); + report_error("string pointer is null"); return out; } template ::value)> -auto write(OutputIt out, const T* value, const format_specs& specs = {}, +auto write(OutputIt out, const T* value, const format_specs& specs = {}, locale_ref = {}) -> OutputIt { return write_ptr(out, bit_cast(value), &specs); } -// A write overload that handles implicit conversions. template > -FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> enable_if_t< - std::is_class::value && !is_string::value && - !is_floating_point::value && !std::is_same::value && - !std::is_same().map( - value))>>::value, - OutputIt> { - return write(out, arg_mapper().map(value)); + FMT_ENABLE_IF(mapped_type_constant::value == + type::custom_type && + !std::is_fundamental::value)> +FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> OutputIt { + auto f = formatter(); + auto parse_ctx = parse_context({}); + f.parse(parse_ctx); + auto ctx = basic_format_context(out, {}, {}); + return f.format(value, ctx); } -template > -FMT_CONSTEXPR auto write(OutputIt out, const T& value) - -> enable_if_t::value == type::custom_type, - OutputIt> { - auto ctx = Context(out, {}, {}); - return typename Context::template formatter_type().format(value, ctx); -} +template +using is_builtin = + bool_constant::value || FMT_BUILTIN_TYPES>; // An argument visitor that formats the argument and writes it via the output // iterator. It's a class and not a generic lambda for compatibility with C++11. template struct default_arg_formatter { - using iterator = buffer_appender; - using context = buffer_context; + using context = buffered_context; - iterator out; - basic_format_args args; - locale_ref loc; + basic_appender out; - template auto operator()(T value) -> iterator { - return write(out, value); + void operator()(monostate) { report_error("argument not found"); } + + template ::value)> + void operator()(T value) { + write(out, value); } - auto operator()(typename basic_format_arg::handle h) -> iterator { - basic_format_parse_context parse_ctx({}); - context format_ctx(out, args, loc); + + template ::value)> + void operator()(T) { + FMT_ASSERT(false, ""); + } + + void operator()(typename basic_format_arg::handle h) { + // Use a null locale since the default format must be unlocalized. + auto parse_ctx = parse_context({}); + auto format_ctx = context(out, {}, {}); h.format(parse_ctx, format_ctx); - return format_ctx.out(); } }; template struct arg_formatter { - using iterator = buffer_appender; - using context = buffer_context; + basic_appender out; + const format_specs& specs; + FMT_NO_UNIQUE_ADDRESS locale_ref locale; - iterator out; - const format_specs& specs; - locale_ref locale; - - template - FMT_CONSTEXPR FMT_INLINE auto operator()(T value) -> iterator { - return detail::write(out, value, specs, locale); + template ::value)> + FMT_CONSTEXPR FMT_INLINE void operator()(T value) { + detail::write(out, value, specs, locale); } - auto operator()(typename basic_format_arg::handle) -> iterator { + + template ::value)> + void operator()(T) { + FMT_ASSERT(false, ""); + } + + void operator()(typename basic_format_arg>::handle) { // User-defined types are handled separately because they require access // to the parse context. - return out; } }; -template struct custom_formatter { - basic_format_parse_context& parse_ctx; - buffer_context& ctx; - - void operator()( - typename basic_format_arg>::handle h) const { - h.format(parse_ctx, ctx); - } - template void operator()(T) const {} -}; - -template class width_checker { - public: - explicit FMT_CONSTEXPR width_checker(ErrorHandler& eh) : handler_(eh) {} - +struct dynamic_spec_getter { template ::value)> FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { - if (is_negative(value)) handler_.on_error("negative width"); - return static_cast(value); + return is_negative(value) ? ~0ull : static_cast(value); } template ::value)> FMT_CONSTEXPR auto operator()(T) -> unsigned long long { - handler_.on_error("width is not integer"); + report_error("width/precision is not integer"); return 0; } - - private: - ErrorHandler& handler_; }; -template class precision_checker { - public: - explicit FMT_CONSTEXPR precision_checker(ErrorHandler& eh) : handler_(eh) {} - - template ::value)> - FMT_CONSTEXPR auto operator()(T value) -> unsigned long long { - if (is_negative(value)) handler_.on_error("negative precision"); - return static_cast(value); - } - - template ::value)> - FMT_CONSTEXPR auto operator()(T) -> unsigned long long { - handler_.on_error("precision is not integer"); - return 0; - } - - private: - ErrorHandler& handler_; -}; - -template